File size: 9,790 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
using System;
using System.Linq;

////REVIEW: Can we somehow make this a simple struct? The one problem we have is that we can't put struct instances as sub-assets into
////        the import (i.e. InputActionImporter can't do AddObjectToAsset with them). However, maybe there's a way around that. The thing
////        is that we really want to store the asset reference plus the action GUID on the *user* side, i.e. the referencing side. Right
////        now, what happens is that InputActionImporter puts these objects along with the reference and GUID they contain in the
////        *imported* object, i.e. right with the asset. This partially defeats the whole purpose of having these objects and it means
////        that now the GUID doesn't really matter anymore. Rather, it's the file ID that now has to be stable.
////
////        If we always store the GUID and asset reference on the user side, we can put the serialized data *anywhere* and it'll remain
////        save and proper no matter what we do in InputActionImporter.

////REVIEW: should this throw if you try to assign an action that is not a singleton?

////REVIEW: akin to this, also have an InputActionMapReference?

namespace UnityEngine.InputSystem
{
    /// <summary>
    /// References a specific <see cref="InputAction"/> in an <see cref="InputActionMap"/>
    /// stored inside an <see cref="InputActionAsset"/>.
    /// </summary>
    /// <remarks>
    /// The difference to a plain reference directly to an <see cref="InputAction"/> object is
    /// that an InputActionReference can be serialized without causing the referenced <see cref="InputAction"/>
    /// to be serialized as well. The reference will remain intact even if the action or the map
    /// that contains the action is renamed.
    ///
    /// References can be set up graphically in the editor by dropping individual actions from the project
    /// browser onto a reference field.
    /// </remarks>
    /// <seealso cref="InputActionProperty"/>
    /// <seealso cref="InputAction"/>
    /// <seealso cref="InputActionAsset"/>
    public class InputActionReference : ScriptableObject
    {
        /// <summary>
        /// The asset that the referenced action is part of. Null if the reference
        /// is not initialized or if the asset has been deleted.
        /// </summary>
        /// <value>InputActionAsset of the referenced action.</value>
        public InputActionAsset asset => m_Asset;

        /// <summary>
        /// The action that the reference resolves to. Null if the action
        /// cannot be found.
        /// </summary>
        /// <value>The action that reference points to.</value>
        /// <remarks>
        /// Actions are resolved on demand based on their internally stored IDs.
        /// </remarks>
        public InputAction action
        {
            get
            {
                if (m_Action == null)
                {
                    if (m_Asset == null)
                        return null;

                    m_Action = m_Asset.FindAction(new Guid(m_ActionId));
                }

                return m_Action;
            }
        }

        /// <summary>
        /// Initialize the reference to refer to the given action.
        /// </summary>
        /// <param name="action">An input action. Must be contained in an <see cref="InputActionMap"/>
        /// that is itself contained in an <see cref="InputActionAsset"/>. Can be <c>null</c> in which
        /// case the reference is reset to its default state which does not reference an action.</param>
        /// <exception cref="InvalidOperationException"><paramref name="action"/> is not contained in an
        /// <see cref="InputActionMap"/> that is itself contained in an <see cref="InputActionAsset"/>.</exception>
        public void Set(InputAction action)
        {
            if (action == null)
            {
                m_Asset = default;
                m_ActionId = default;
                return;
            }

            var map = action.actionMap;
            if (map == null || map.asset == null)
                throw new InvalidOperationException(
                    $"Action '{action}' must be part of an InputActionAsset in order to be able to create an InputActionReference for it");

            SetInternal(map.asset, action);
        }

        /// <summary>
        /// Look up an action in the given asset and initialize the reference to
        /// point to it.
        /// </summary>
        /// <param name="asset">An .inputactions asset.</param>
        /// <param name="mapName">Name of the <see cref="InputActionMap"/> in <paramref name="asset"/>
        /// (see <see cref="InputActionAsset.actionMaps"/>). Case-insensitive.</param>
        /// <param name="actionName">Name of the action in <paramref name="mapName"/>. Case-insensitive.</param>
        /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or-
        /// <paramref name="mapName"/> is <c>null</c> or empty -or- <paramref name="actionName"/>
        /// is <c>null</c> or empty.</exception>
        /// <exception cref="ArgumentException">No action map called <paramref name="mapName"/> could
        /// be found in <paramref name="asset"/> -or- no action called <paramref name="actionName"/>
        /// could be found in the action map called <paramref name="mapName"/> in <paramref name="asset"/>.</exception>
        public void Set(InputActionAsset asset, string mapName, string actionName)
        {
            if (asset == null)
                throw new ArgumentNullException(nameof(asset));
            if (string.IsNullOrEmpty(mapName))
                throw new ArgumentNullException(nameof(mapName));
            if (string.IsNullOrEmpty(actionName))
                throw new ArgumentNullException(nameof(actionName));

            var actionMap = asset.FindActionMap(mapName);
            if (actionMap == null)
                throw new ArgumentException($"No action map '{mapName}' in '{asset}'", nameof(mapName));

            var action = actionMap.FindAction(actionName);
            if (action == null)
                throw new ArgumentException($"No action '{actionName}' in map '{mapName}' of asset '{asset}'",
                    nameof(actionName));

            SetInternal(asset, action);
        }

        private void SetInternal(InputActionAsset asset, InputAction action)
        {
            var actionMap = action.actionMap;
            if (!asset.actionMaps.Contains(actionMap))
                throw new ArgumentException(
                    $"Action '{action}' is not contained in asset '{asset}'", nameof(action));

            m_Asset = asset;
            m_ActionId = action.id.ToString();
            name = GetDisplayName(action);

            ////REVIEW: should this dirty the asset if IDs had not been generated yet?
        }

        /// <summary>
        /// Return a string representation of the reference useful for debugging.
        /// </summary>
        /// <returns>A string representation of the reference.</returns>
        public override string ToString()
        {
            try
            {
                var action = this.action;
                return $"{m_Asset.name}:{action.actionMap.name}/{action.name}";
            }
            catch
            {
                if (m_Asset != null)
                    return $"{m_Asset.name}:{m_ActionId}";
            }

            return base.ToString();
        }

        private static string GetDisplayName(InputAction action)
        {
            return !string.IsNullOrEmpty(action?.actionMap?.name) ? $"{action.actionMap?.name}/{action.name}" : action?.name;
        }

        /// <summary>
        /// Return a string representation useful for showing in UI.
        /// </summary>
        internal string ToDisplayName()
        {
            return string.IsNullOrEmpty(name) ? GetDisplayName(action) : name;
        }

        /// <summary>
        /// Convert an InputActionReference to the InputAction it points to.
        /// </summary>
        /// <param name="reference">An InputActionReference object. Can be null.</param>
        /// <returns>The value of <see cref="action"/> from <paramref name="reference"/>. Can be null.</returns>
        public static implicit operator InputAction(InputActionReference reference)
        {
            return reference?.action;
        }

        /// <summary>
        /// Create a new InputActionReference object that references the given action.
        /// </summary>
        /// <param name="action">An input action. Must be contained in an <see cref="InputActionMap"/>
        /// that is itself contained in an <see cref="InputActionAsset"/>. Can be <c>null</c> in which
        /// case the reference is reset to its default state which does not reference an action.</param>
        /// <returns>A new InputActionReference referencing <paramref name="action"/>.</returns>
        public static InputActionReference Create(InputAction action)
        {
            if (action == null)
                return null;
            var reference = CreateInstance<InputActionReference>();
            reference.Set(action);
            return reference;
        }

        [SerializeField] internal InputActionAsset m_Asset;
        // Can't serialize System.Guid and Unity's GUID is editor only so these
        // go out as strings.
        [SerializeField] internal string m_ActionId;

        /// <summary>
        /// The resolved, cached input action.
        /// </summary>
        [NonSerialized] private InputAction m_Action;

        // Make annoying Microsoft code analyzer happy.
        public InputAction ToInputAction()
        {
            return action;
        }
    }
}