A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using Unity.Collections;
6using UnityEngine.InputSystem.Utilities;
7
8////REVIEW: given we have the global ActionPerformed callback, do we really need the per-map callback?
9
10////TODO: remove constraint of not being able to modify bindings while enabled from both actions and maps
11//// (because of the sharing of state between multiple maps in an asset, we'd have to extend that constraint
12//// to all maps in an asset in order to uphold it properly)
13
14namespace UnityEngine.InputSystem
15{
16 /// <summary>
17 /// A mechanism for collecting a series of input actions (see <see cref="InputAction"/>)
18 /// and treating them as a group.
19 /// </summary>
20 /// <remarks>
21 /// Each action map is a named collection of bindings and actions. Both are stored
22 /// as a flat list. The bindings are available through the <see cref="bindings"/>
23 /// property and the actions are available through the <see cref="actions"/> property.
24 ///
25 /// The actions in a map are owned by the map. No action can appear in two maps
26 /// at the same time. To find the action map an action belongs to, use the
27 /// <see cref="InputAction.actionMap"/> property. Note that actions can also stand
28 /// on their own and thus do not necessarily need to belong to a map (in which case
29 /// the <see cref="InputAction.actionMap"/> property is <c>null</c>).
30 ///
31 /// Within a map, all actions have to have names and each action name must
32 /// be unique. The <see cref="InputBinding.action"/> property of bindings in a map
33 /// are resolved within the <see cref="actions"/> in the map. Looking up actions
34 /// by name can be done through <see cref="FindAction(string,bool)"/>.
35 ///
36 /// The <see cref="name"/> of the map itself can be empty, except if the map is part of
37 /// an <see cref="InputActionAsset"/> in which case it is required to have a name
38 /// which also must be unique within the asset.
39 ///
40 /// Action maps are most useful for grouping actions that contextually
41 /// belong together. For example, one common usage is to separate the actions
42 /// that can be performed in the UI or in the main menu from those that can
43 /// be performed during gameplay. However, even within gameplay, multiple action
44 /// maps can be employed. For example, one could have different action maps for
45 /// driving and for walking plus one more map for the actions shared between
46 /// the two modes.
47 ///
48 /// Action maps are usually created in the <a href="../manual/ActionAssets.html">action
49 /// editor</a> as part of <see cref="InputActionAsset"/>s. However, they can also be
50 /// created standing on their own directly in code or from JSON (see <see cref="FromJson"/>).
51 ///
52 /// <example>
53 /// <code>
54 /// // Create a free-standing action map.
55 /// var map = new InputActionMap();
56 ///
57 /// // Add some actions and bindings to it.
58 /// map.AddAction("action1", binding: "<Keyboard>/space");
59 /// map.AddAction("action2", binding: "<Gamepad>/buttonSouth");
60 /// </code>
61 /// </example>
62 ///
63 /// Actions in action maps, like actions existing by themselves outside of action
64 /// maps, do not actively process input except if enabled. Actions can either
65 /// be enabled individually (see <see cref="InputAction.Enable"/> and <see
66 /// cref="InputAction.Disable"/>) or in bulk by enabling and disabling the
67 /// entire map (see <see cref="Enable"/> and <see cref="Disable"/>).
68 /// </remarks>
69 /// <seealso cref="InputActionAsset"/>
70 /// <seealso cref="InputAction"/>
71 [Serializable]
72 public sealed class InputActionMap : ICloneable, ISerializationCallbackReceiver, IInputActionCollection2, IDisposable
73 {
74 /// <summary>
75 /// Name of the action map.
76 /// </summary>
77 /// <value>Name of the action map.</value>
78 /// <remarks>
79 /// For action maps that are part of <see cref="InputActionAsset"/>s, this will always be
80 /// a non-null, non-empty string that is unique within the maps in the asset. For action maps
81 /// that are standing on their own, this can be null or empty.
82 /// </remarks>
83 public string name => m_Name;
84
85 /// <summary>
86 /// If the action map is part of an asset, this refers to the asset. Otherwise it is <c>null</c>.
87 /// </summary>
88 /// <value>Asset to which the action map belongs.</value>
89 public InputActionAsset asset => m_Asset;
90
91 /// <summary>
92 /// A stable, unique identifier for the map.
93 /// </summary>
94 /// <value>Unique ID for the action map.</value>
95 /// <remarks>
96 /// This can be used instead of the name to refer to the action map. Doing so allows referring to the
97 /// map such that renaming it does not break references.
98 /// </remarks>
99 /// <seealso cref="InputAction.id"/>
100 public Guid id
101 {
102 get
103 {
104 if (string.IsNullOrEmpty(m_Id))
105 GenerateId();
106 return new Guid(m_Id);
107 }
108 }
109
110 internal Guid idDontGenerate
111 {
112 get
113 {
114 if (string.IsNullOrEmpty(m_Id))
115 return default;
116 return new Guid(m_Id);
117 }
118 }
119
120 /// <summary>
121 /// Whether any action in the map is currently enabled.
122 /// </summary>
123 /// <value>True if any action in <see cref="actions"/> is currently enabled.</value>
124 /// <seealso cref="InputAction.enabled"/>
125 /// <seealso cref="Enable"/>
126 /// <seealso cref="InputAction.Enable"/>
127 public bool enabled => m_EnabledActionsCount > 0;
128
129 /// <summary>
130 /// List of actions contained in the map.
131 /// </summary>
132 /// <value>Collection of actions belonging to the map.</value>
133 /// <remarks>
134 /// Actions are owned by their map. The same action cannot appear in multiple maps.
135 ///
136 /// Accessing this property. Note that values returned by the property become invalid if
137 /// the setup of actions in a map is changed.
138 /// </remarks>
139 /// <seealso cref="InputAction.actionMap"/>
140 public ReadOnlyArray<InputAction> actions => new ReadOnlyArray<InputAction>(m_Actions);
141
142 /// <summary>
143 /// List of bindings contained in the map.
144 /// </summary>
145 /// <value>Collection of bindings in the map.</value>
146 /// <remarks>
147 /// <see cref="InputBinding"/>s are owned by action maps and not by individual actions.
148 ///
149 /// Bindings that trigger actions refer to the action by <see cref="InputAction.name"/>
150 /// or <see cref="InputAction.id"/>.
151 ///
152 /// Accessing this property does not allocate. Note that values returned by the property
153 /// become invalid if the setup of bindings in a map is changed.
154 /// </remarks>
155 /// <seealso cref="InputAction.bindings"/>
156 public ReadOnlyArray<InputBinding> bindings => new ReadOnlyArray<InputBinding>(m_Bindings);
157
158 IEnumerable<InputBinding> IInputActionCollection2.bindings => bindings;
159
160 /// <summary>
161 /// Control schemes defined for the action map.
162 /// </summary>
163 /// <value>List of available control schemes.</value>
164 /// <remarks>
165 /// Control schemes can only be defined at the level of <see cref="InputActionAsset"/>s.
166 /// For action maps that are part of assets, this property will return the control schemes
167 /// from the asset. For free-standing action maps, this will return an empty list.
168 /// </remarks>
169 /// <seealso cref="InputActionAsset.controlSchemes"/>
170 public ReadOnlyArray<InputControlScheme> controlSchemes
171 {
172 get
173 {
174 if (m_Asset == null)
175 return new ReadOnlyArray<InputControlScheme>();
176 return m_Asset.controlSchemes;
177 }
178 }
179
180 /// <summary>
181 /// Binding mask to apply to all actions in the asset.
182 /// </summary>
183 /// <value>Optional mask that determines which bindings in the action map to enable.</value>
184 /// <remarks>
185 /// Binding masks can be applied at three different levels: for an entire asset through
186 /// <see cref="InputActionAsset.bindingMask"/>, for a specific map through this property,
187 /// and for single actions through <see cref="InputAction.bindingMask"/>. By default,
188 /// none of the masks will be set (that is, they will be <c>null</c>).
189 ///
190 /// When an action is enabled, all the binding masks that apply to it are taken into
191 /// account. Specifically, this means that any given binding on the action will be
192 /// enabled only if it matches the mask applied to the asset, the mask applied
193 /// to the map that contains the action, and the mask applied to the action itself.
194 /// All the masks are individually optional.
195 ///
196 /// Masks are matched against bindings using <see cref="InputBinding.Matches"/>.
197 ///
198 /// Note that if you modify the masks applicable to an action while it is
199 /// enabled, the action's <see cref="InputAction.controls"/> will get updated immediately to
200 /// respect the mask. To avoid repeated binding resolution, it is most efficient
201 /// to apply binding masks before enabling actions.
202 ///
203 /// Binding masks are non-destructive. All the bindings on the action are left
204 /// in place. Setting a mask will not affect the value of the <see cref="InputAction.bindings"/>
205 /// and <see cref="bindings"/> properties.
206 /// </remarks>
207 /// <seealso cref="InputBinding.MaskByGroup"/>
208 /// <seealso cref="InputAction.bindingMask"/>
209 /// <seealso cref="InputActionAsset.bindingMask"/>
210 public InputBinding? bindingMask
211 {
212 get => m_BindingMask;
213 set
214 {
215 if (m_BindingMask == value)
216 return;
217
218 m_BindingMask = value;
219 LazyResolveBindings(fullResolve: true);
220 }
221 }
222
223 /// <summary>
224 /// Set of devices that bindings in the action map can bind to.
225 /// </summary>
226 /// <value>Optional set of devices to use by bindings in the map.</value>
227 /// <remarks>
228 /// By default (with this property being <c>null</c>), bindings will bind to any of the
229 /// controls available through <see cref="InputSystem.devices"/>, that is, controls from all
230 /// devices in the system will be used.
231 ///
232 /// By setting this property, binding resolution can instead be restricted to just specific
233 /// devices. This restriction can either be applied to an entire asset using <see
234 /// cref="InputActionMap.devices"/> or to specific action maps by using this property. Note that
235 /// if both this property and <see cref="InputActionAsset.devices"/> is set for a specific action
236 /// map, the list of devices on the action map will take precedence and the list on the
237 /// asset will be ignored for bindings in that action map.
238 ///
239 /// <example>
240 /// <code>
241 /// // Create an action map containing a single action with a gamepad binding.
242 /// var actionMap = new InputActionMap();
243 /// var fireAction = actionMap.AddAction("Fire", binding: "<Gamepad>/buttonSouth");
244 /// asset.AddActionMap(actionMap);
245 ///
246 /// // Let's assume we have two gamepads connected. If we enable the
247 /// // action map now, the 'Fire' action will bind to both.
248 /// actionMap.Enable();
249 ///
250 /// // This will print two controls.
251 /// Debug.Log(string.Join("\n", fireAction.controls));
252 ///
253 /// // To restrict the setup to just the first gamepad, we can assign
254 /// // to the 'devices' property.
255 /// actionMap.devices = new InputDevice[] { Gamepad.all[0] };
256 ///
257 /// // Now this will print only one control.
258 /// Debug.Log(string.Join("\n", fireAction.controls));
259 /// </code>
260 /// </example>
261 /// </remarks>
262 /// <seealso cref="InputActionAsset.devices"/>
263 public ReadOnlyArray<InputDevice>? devices
264 {
265 // Return asset's device list if we have none (only if we're part of an asset).
266 get => m_Devices.Get() ?? m_Asset?.devices;
267 set
268 {
269 if (m_Devices.Set(value))
270 LazyResolveBindings(fullResolve: false);
271 }
272 }
273
274 /// <summary>
275 /// Look up an action by name or ID.
276 /// </summary>
277 /// <param name="actionNameOrId">Name (as in <see cref="InputAction.name"/>) or ID (as in <see cref="InputAction.id"/>)
278 /// of the action. Note that matching of names is case-insensitive.</param>
279 /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
280 /// <exception cref="KeyNotFoundException">No action with the name or ID of <paramref name="actionNameOrId"/>
281 /// was found in the action map.</exception>
282 /// <remarks>
283 /// This method is equivalent to <see cref="FindAction(string,bool)"/> except it throws <c>KeyNotFoundException</c>
284 /// if no action with the given name or ID can be found.
285 /// </remarks>
286 /// <seealso cref="FindAction(string,bool)"/>
287 /// <seealso cref="FindAction(Guid)"/>
288 /// <see cref="actions"/>
289 public InputAction this[string actionNameOrId]
290 {
291 get
292 {
293 if (actionNameOrId == null)
294 throw new ArgumentNullException(nameof(actionNameOrId));
295 var action = FindAction(actionNameOrId);
296 if (action == null)
297 throw new KeyNotFoundException($"Cannot find action '{actionNameOrId}'");
298 return action;
299 }
300 }
301
302 ////REVIEW: inconsistent naming; elsewhere we use "onActionTriggered" (which in turn is inconsistent with InputAction.started etc)
303 /// <summary>
304 /// Add or remove a callback that is triggered when an action in the map changes its <see cref="InputActionPhase">
305 /// phase</see>.
306 /// </summary>
307 /// <seealso cref="InputAction.started"/>
308 /// <seealso cref="InputAction.performed"/>
309 /// <seealso cref="InputAction.canceled"/>
310 public event Action<InputAction.CallbackContext> actionTriggered
311 {
312 add => m_ActionCallbacks.AddCallback(value);
313 remove => m_ActionCallbacks.RemoveCallback(value);
314 }
315
316 /// <summary>
317 /// Construct an action map with default values.
318 /// </summary>
319 public InputActionMap()
320 {
321 s_NeedToResolveBindings = true;
322 }
323
324 /// <summary>
325 /// Construct an action map with the given name.
326 /// </summary>
327 /// <param name="name">Name to give to the action map. By default <c>null</c>, i.e. does
328 /// not assign a name to the map.</param>
329 public InputActionMap(string name)
330 : this()
331 {
332 m_Name = name;
333 }
334
335 /// <summary>
336 /// Release internal state held on to by the action map.
337 /// </summary>
338 /// <remarks>
339 /// Once actions in a map are enabled, the map will allocate a block of state internally that
340 /// it will hold on to until disposed of. All actions in the map will share the same internal
341 /// state. Also, if the map is part of an <see cref="InputActionAsset"/> all maps and actions
342 /// in the same asset will share the same internal state.
343 ///
344 /// Note that the internal state holds on to GC heap memory as well as memory from the
345 /// unmanaged, C++ heap.
346 /// </remarks>
347 public void Dispose()
348 {
349 m_State?.Dispose();
350 }
351
352 internal int FindActionIndex(string nameOrId)
353 {
354 ////REVIEW: have transient lookup table? worth optimizing this?
355 //// Ideally, this should at least be an InternedString comparison but due to serialization,
356 //// that's quite tricky.
357
358 if (string.IsNullOrEmpty(nameOrId))
359 return -1;
360 if (m_Actions == null)
361 return -1;
362
363 // First time we hit this method, we populate the lookup table.
364 SetUpActionLookupTable();
365
366 var actionCount = m_Actions.Length;
367
368 var isOldBracedFormat = nameOrId.StartsWith("{") && nameOrId.EndsWith("}");
369 if (isOldBracedFormat)
370 {
371 var length = nameOrId.Length - 2;
372 for (var i = 0; i < actionCount; ++i)
373 {
374 if (string.Compare(m_Actions[i].m_Id, 0, nameOrId, 1, length) == 0)
375 return i;
376 }
377 }
378
379 if (m_ActionIndexByNameOrId.TryGetValue(nameOrId, out var actionIndex))
380 return actionIndex;
381
382 for (var i = 0; i < actionCount; ++i)
383 {
384 var action = m_Actions[i];
385 if (action.m_Id == nameOrId || string.Compare(m_Actions[i].m_Name, nameOrId, StringComparison.InvariantCultureIgnoreCase) == 0)
386 return i;
387 }
388
389 return InputActionState.kInvalidIndex;
390 }
391
392 private void SetUpActionLookupTable()
393 {
394 if (m_ActionIndexByNameOrId != null || m_Actions == null)
395 return;
396
397 m_ActionIndexByNameOrId = new Dictionary<string, int>();
398
399 var actionCount = m_Actions.Length;
400 for (var i = 0; i < actionCount; ++i)
401 {
402 var action = m_Actions[i];
403
404 // We want to make sure an action ID cannot change *after* we have created the table.
405 // NOTE: The *name* of an action, however, *may* change.
406 action.MakeSureIdIsInPlace();
407
408 // We create two lookup paths for each action:
409 // (1) By case-sensitive name.
410 // (2) By GUID string.
411 m_ActionIndexByNameOrId[action.name] = i;
412 m_ActionIndexByNameOrId[action.m_Id] = i;
413 }
414 }
415
416 internal void ClearActionLookupTable()
417 {
418 m_ActionIndexByNameOrId?.Clear();
419 }
420
421 private int FindActionIndex(Guid id)
422 {
423 if (m_Actions == null)
424 return InputActionState.kInvalidIndex;
425 var actionCount = m_Actions.Length;
426 for (var i = 0; i < actionCount; ++i)
427 if (m_Actions[i].idDontGenerate == id)
428 return i;
429
430 return InputActionState.kInvalidIndex;
431 }
432
433 /// <summary>
434 /// Find an action in the map by name or ID.
435 /// </summary>
436 /// <param name="actionNameOrId">Name (as in <see cref="InputAction.name"/>) or ID (as in <see cref="InputAction.id"/>)
437 /// of the action. Note that matching of names is case-insensitive.</param>
438 /// <param name="throwIfNotFound">If set to <see langword="true"/> will cause an exception to be thrown when the action was not found.</param>
439 /// <returns>The action with the given name or ID or <c>null</c> if no matching action
440 /// was found.</returns>
441 /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
442 /// <seealso cref="FindAction(Guid)"/>
443 public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
444 {
445 if (actionNameOrId == null)
446 throw new ArgumentNullException(nameof(actionNameOrId));
447 var index = FindActionIndex(actionNameOrId);
448 if (index == -1)
449 {
450 if (throwIfNotFound)
451 throw new ArgumentException($"No action '{actionNameOrId}' in '{this}'", nameof(actionNameOrId));
452 return null;
453 }
454 return m_Actions[index];
455 }
456
457 /// <summary>
458 /// Find an action by ID.
459 /// </summary>
460 /// <param name="id">ID (as in <see cref="InputAction.id"/>) of the action.</param>
461 /// <returns>The action with the given ID or null if no action in the map has
462 /// the given ID.</returns>
463 /// <seealso cref="FindAction(string,bool)"/>
464 public InputAction FindAction(Guid id)
465 {
466 var index = FindActionIndex(id);
467 if (index == -1)
468 return null;
469 return m_Actions[index];
470 }
471
472 /// <summary>
473 /// Check whether there are any bindings in the action map that can bind to
474 /// controls on the given device.
475 /// </summary>
476 /// <param name="device">An input device.</param>
477 /// <returns>True if any of the bindings in the map can resolve to controls on the device, false otherwise.</returns>
478 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception>
479 /// <remarks>
480 /// The logic is entirely based on the contents of <see cref="bindings"/> and, more specifically,
481 /// <see cref="InputBinding.effectivePath"/> of each binding. Each path is checked using <see
482 /// cref="InputControlPath.Matches"/>. If any path matches, the method returns <c>true</c>.
483 ///
484 /// Properties such as <see cref="devices"/> and <see cref="bindingMask"/> are ignored.
485 ///
486 /// <example>
487 /// <code>
488 /// // Create action map with two actions and bindings.
489 /// var actionMap = new InputActionMap();
490 /// actionMap.AddAction("action1", binding: "<Gamepad>/buttonSouth");
491 /// actionMap.AddAction("action2", binding: "<XRController{LeftHand}>/{PrimaryAction}");
492 ///
493 /// //
494 /// var gamepad = InputSystem.AddDevice<Gamepad>();
495 /// var xrController = InputSystem.AddDevice<XRController>();
496 ///
497 /// // Returns true:
498 /// actionMap.IsUsableWith(gamepad);
499 ///
500 /// // Returns false: (the XRController does not have the LeftHand usage assigned to it)
501 /// actionMap.IsUsableWith(xrController);
502 /// </code>
503 /// </example>
504 /// </remarks>
505 public bool IsUsableWithDevice(InputDevice device)
506 {
507 if (device == null)
508 throw new ArgumentNullException(nameof(device));
509
510 if (m_Bindings == null)
511 return false;
512
513 foreach (var binding in m_Bindings)
514 {
515 var path = binding.effectivePath;
516 if (string.IsNullOrEmpty(path))
517 continue;
518
519 if (InputControlPath.Matches(path, device))
520 return true;
521 }
522
523 return false;
524 }
525
526 /// <summary>
527 /// Enable all the actions in the map.
528 /// </summary>
529 /// <remarks>
530 /// This is equivalent to calling <see cref="InputAction.Enable"/> on each
531 /// action in <see cref="actions"/>, but is more efficient as the actions
532 /// will get enabled in bulk.
533 /// </remarks>
534 /// <seealso cref="Disable"/>
535 /// <seealso cref="enabled"/>
536 public void Enable()
537 {
538 if (m_Actions == null || m_EnabledActionsCount == m_Actions.Length)
539 return;
540
541 ResolveBindingsIfNecessary();
542 m_State.EnableAllActions(this);
543 }
544
545 /// <summary>
546 /// Disable all the actions in the map.
547 /// </summary>
548 /// <remarks>
549 /// This is equivalent to calling <see cref="InputAction.Disable"/> on each
550 /// action in <see cref="actions"/>, but is more efficient as the actions
551 /// will get disabled in bulk.
552 /// </remarks>
553 /// <seealso cref="Enable"/>
554 /// <seealso cref="enabled"/>
555 public void Disable()
556 {
557 if (!enabled)
558 return;
559
560 m_State.DisableAllActions(this);
561 }
562
563 /// <summary>
564 /// Produce an identical copy of the action map with its actions and bindings.
565 /// </summary>
566 /// <returns>A copy of the action map.</returns>
567 /// <remarks>
568 /// If the action map is part of an <see cref="InputActionAsset"/>, the clone will <em>not</em>
569 /// be. It will be a free-standing action map and <see cref="asset"/> will be <c>null</c>.
570 ///
571 /// Note that the IDs for the map itself as well as for its <see cref="actions"/> and
572 /// <see cref="bindings"/> are not copied. Instead, new IDs will be assigned. Also, callbacks
573 /// installed on actions or on the map itself will not be copied over.
574 /// </remarks>
575 public InputActionMap Clone()
576 {
577 Debug.Assert(m_SingletonAction == null, "Internal (hidden) action maps of singleton actions should not be cloned");
578
579 var clone = new InputActionMap
580 {
581 m_Name = m_Name
582 };
583
584 // Clone actions.
585 if (m_Actions != null)
586 {
587 var actionCount = m_Actions.Length;
588 var actions = new InputAction[actionCount];
589 for (var i = 0; i < actionCount; ++i)
590 {
591 var original = m_Actions[i];
592 actions[i] = new InputAction
593 {
594 m_Name = original.m_Name,
595 m_ActionMap = clone,
596 m_Type = original.m_Type,
597 m_Interactions = original.m_Interactions,
598 m_Processors = original.m_Processors,
599 m_ExpectedControlType = original.m_ExpectedControlType,
600 m_Flags = original.m_Flags,
601 };
602 }
603 clone.m_Actions = actions;
604 }
605
606 // Clone bindings.
607 if (m_Bindings != null)
608 {
609 var bindingCount = m_Bindings.Length;
610 var bindings = new InputBinding[bindingCount];
611 Array.Copy(m_Bindings, 0, bindings, 0, bindingCount);
612 for (var i = 0; i < bindingCount; ++i)
613 bindings[i].m_Id = default;
614 clone.m_Bindings = bindings;
615 }
616
617 return clone;
618 }
619
620 /// <summary>
621 /// Return an boxed instance of the action map.
622 /// </summary>
623 /// <returns>An boxed clone of the action map</returns>
624 /// <seealso cref="Clone"/>
625 object ICloneable.Clone()
626 {
627 return Clone();
628 }
629
630 /// <summary>
631 /// Return <c>true</c> if the action map contains the given action.
632 /// </summary>
633 /// <param name="action">An input action. Can be <c>null</c>.</param>
634 /// <returns>True if the action map contains <paramref name="action"/>, false otherwise.</returns>
635 public bool Contains(InputAction action)
636 {
637 if (action == null)
638 return false;
639
640 return action.actionMap == this;
641 }
642
643 /// <summary>
644 /// Return a string representation of the action map useful for debugging.
645 /// </summary>
646 /// <returns>A string representation of the action map.</returns>
647 /// <remarks>
648 /// For unnamed action maps, this will always be <c>"<Unnamed Action Map>"</c>.
649 /// </remarks>
650 public override string ToString()
651 {
652 if (m_Asset != null)
653 return $"{m_Asset}:{m_Name}";
654 if (!string.IsNullOrEmpty(m_Name))
655 return m_Name;
656 return "<Unnamed Action Map>";
657 }
658
659 /// <summary>
660 /// Enumerate the actions in the map.
661 /// </summary>
662 /// <returns>An enumerator going over the actions in the map.</returns>
663 /// <remarks>
664 /// This method supports to generically iterate over the actions in a map. However, it will usually
665 /// lead to GC allocation. Iterating directly over <see cref="actions"/> avoids allocating GC memory.
666 /// </remarks>
667 public IEnumerator<InputAction> GetEnumerator()
668 {
669 return actions.GetEnumerator();
670 }
671
672 /// <summary>
673 /// Enumerate the actions in the map.
674 /// </summary>
675 /// <returns>An enumerator going over the actions in the map.</returns>
676 /// <seealso cref="GetEnumerator"/>
677 IEnumerator IEnumerable.GetEnumerator()
678 {
679 return GetEnumerator();
680 }
681
682 // The state we persist is pretty much just a name, a flat list of actions, and a flat
683 // list of bindings. The rest is state we keep at runtime when a map is in use.
684
685 [SerializeField] internal string m_Name;
686 [SerializeField] internal string m_Id; // Can't serialize System.Guid and Unity's GUID is editor only.
687 [SerializeField] internal InputActionAsset m_Asset;
688
689 /// <summary>
690 /// List of actions in this map.
691 /// </summary>
692 [SerializeField] internal InputAction[] m_Actions;
693
694 /// <summary>
695 /// List of bindings in this map.
696 /// </summary>
697 /// <remarks>
698 /// For singleton actions, we ensure this is always the same as <see cref="InputAction.m_SingletonActionBindings"/>.
699 /// </remarks>
700 [SerializeField] internal InputBinding[] m_Bindings;
701
702 // These fields are caches. If m_Bindings is modified, these are thrown away
703 // and re-computed only if needed.
704 // NOTE: Because InputBindings are structs, m_BindingsForEachAction actually duplicates each binding
705 // (only in the case where m_Bindings has scattered references to actions).
706 ////REVIEW: this will lead to problems when overrides are thrown into the mix
707
708 /// <summary>
709 /// For each entry in <see cref="m_Actions"/>, a slice of this array corresponds to the
710 /// action's bindings.
711 /// </summary>
712 /// <remarks>
713 /// Ideally, this array is the same as <see cref="m_Bindings"/> (the same as in literally reusing the
714 /// same array). However, we have no guarantee that <see cref="m_Bindings"/> is sorted by actions. In case it
715 /// isn't, we create a separate array with the bindings sorted by action and have each action reference
716 /// a slice through <see cref="InputAction.m_BindingsStartIndex"/> and <see cref="InputAction.m_BindingsCount"/>.
717 /// </remarks>
718 /// <seealso cref="SetUpPerActionControlAndBindingArrays"/>
719 [NonSerialized] private InputBinding[] m_BindingsForEachAction;
720
721 [NonSerialized] private InputControl[] m_ControlsForEachAction;
722
723 /// <summary>
724 /// Number of actions currently enabled in the map.
725 /// </summary>
726 /// <remarks>
727 /// This should only be written to by <see cref="InputActionState"/>.
728 /// </remarks>
729 [NonSerialized] internal int m_EnabledActionsCount;
730
731 // Action maps that are created internally by singleton actions to hold their data
732 // are never exposed and never serialized so there is no point allocating an m_Actions
733 // array.
734 [NonSerialized] internal InputAction m_SingletonAction;
735
736 [NonSerialized] internal int m_MapIndexInState = InputActionState.kInvalidIndex;
737
738 /// <summary>
739 /// Current execution state.
740 /// </summary>
741 /// <remarks>
742 /// Initialized when map (or any action in it) is first enabled.
743 /// </remarks>
744 [NonSerialized] internal InputActionState m_State;
745 [NonSerialized] internal InputBinding? m_BindingMask;
746 [NonSerialized] private Flags m_Flags;
747 [NonSerialized] internal int m_ParameterOverridesCount;
748 [NonSerialized] internal InputActionRebindingExtensions.ParameterOverride[] m_ParameterOverrides;
749
750 [NonSerialized] internal DeviceArray m_Devices;
751
752 [NonSerialized] internal CallbackArray<Action<InputAction.CallbackContext>> m_ActionCallbacks;
753
754 [NonSerialized] internal Dictionary<string, int> m_ActionIndexByNameOrId;
755
756 private bool needToResolveBindings
757 {
758 get => (m_Flags & Flags.NeedToResolveBindings) != 0;
759 set
760 {
761 if (value)
762 m_Flags |= Flags.NeedToResolveBindings;
763 else
764 m_Flags &= ~Flags.NeedToResolveBindings;
765 }
766 }
767
768 private bool bindingResolutionNeedsFullReResolve
769 {
770 get => (m_Flags & Flags.BindingResolutionNeedsFullReResolve) != 0;
771 set
772 {
773 if (value)
774 m_Flags |= Flags.BindingResolutionNeedsFullReResolve;
775 else
776 m_Flags &= ~Flags.BindingResolutionNeedsFullReResolve;
777 }
778 }
779
780 private bool controlsForEachActionInitialized
781 {
782 get => (m_Flags & Flags.ControlsForEachActionInitialized) != 0;
783 set
784 {
785 if (value)
786 m_Flags |= Flags.ControlsForEachActionInitialized;
787 else
788 m_Flags &= ~Flags.ControlsForEachActionInitialized;
789 }
790 }
791
792 private bool bindingsForEachActionInitialized
793 {
794 get => (m_Flags & Flags.BindingsForEachActionInitialized) != 0;
795 set
796 {
797 if (value)
798 m_Flags |= Flags.BindingsForEachActionInitialized;
799 else
800 m_Flags &= ~Flags.BindingsForEachActionInitialized;
801 }
802 }
803
804 [Flags]
805 private enum Flags
806 {
807 NeedToResolveBindings = 1 << 0,
808 BindingResolutionNeedsFullReResolve = 1 << 1,
809 ControlsForEachActionInitialized = 1 << 2,
810 BindingsForEachActionInitialized = 1 << 3,
811 }
812
813 internal static int s_DeferBindingResolution;
814 internal static bool s_NeedToResolveBindings;
815
816 internal struct DeviceArray
817 {
818 private bool m_HaveValue;
819 private int m_DeviceCount;
820 private InputDevice[] m_DeviceArray; // May have extra capacity; we won't let go once allocated.
821
822 public int IndexOf(InputDevice device)
823 {
824 return m_DeviceArray.IndexOfReference(device, m_DeviceCount);
825 }
826
827 public bool Remove(InputDevice device)
828 {
829 var index = IndexOf(device);
830 if (index < 0)
831 return false;
832 m_DeviceArray.EraseAtWithCapacity(ref m_DeviceCount, index);
833 return true;
834 }
835
836 public ReadOnlyArray<InputDevice>? Get()
837 {
838 if (!m_HaveValue)
839 return null;
840 return new ReadOnlyArray<InputDevice>(m_DeviceArray, 0, m_DeviceCount);
841 }
842
843 public bool Set(ReadOnlyArray<InputDevice>? devices)
844 {
845 if (!devices.HasValue)
846 {
847 if (!m_HaveValue)
848 return false; // No change.
849 if (m_DeviceCount > 0)
850 Array.Clear(m_DeviceArray, 0, m_DeviceCount);
851 m_DeviceCount = 0;
852 m_HaveValue = false;
853 }
854 else
855 {
856 // See if the array actually changes content. Avoids re-resolving when there
857 // is no need to.
858 var array = devices.Value;
859 if (m_HaveValue && array.Count == m_DeviceCount && array.HaveEqualReferences(m_DeviceArray, m_DeviceCount))
860 return false;
861
862 if (m_DeviceCount > 0)
863 m_DeviceArray.Clear(ref m_DeviceCount);
864 m_HaveValue = true;
865 m_DeviceCount = 0;
866 ArrayHelpers.AppendListWithCapacity(ref m_DeviceArray, ref m_DeviceCount, array);
867 }
868
869 return true;
870 }
871 }
872
873 /// <summary>
874 /// Return the list of bindings for just the given actions.
875 /// </summary>
876 /// <param name="action"></param>
877 /// <returns></returns>
878 /// <remarks>
879 /// The bindings for a single action may be contiguous in <see cref="m_Bindings"/> or may be scattered
880 /// around. We don't keep persistent storage for these and instead set up a transient
881 /// array if and when bindings are queried directly from an action. In the simple case,
882 /// we don't even need a separate array but rather just need to find out which slice in the
883 /// bindings array corresponds to which action.
884 ///
885 /// NOTE: Bindings for individual actions aren't queried by the system itself during normal
886 /// runtime operation so we only do this for cases where the user asks for the
887 /// information. If the user never asks for bindings or controls on a per-action basis,
888 /// none of this data gets initialized.
889 /// </remarks>
890 internal ReadOnlyArray<InputBinding> GetBindingsForSingleAction(InputAction action)
891 {
892 Debug.Assert(action != null, "Action cannot be null");
893 Debug.Assert(action.m_ActionMap == this, "Action must be in action map");
894 Debug.Assert(!action.isSingletonAction || m_SingletonAction == action, "Action is not a singleton action");
895
896 // See if we need to refresh.
897 if (!bindingsForEachActionInitialized)
898 SetUpPerActionControlAndBindingArrays();
899
900 return new ReadOnlyArray<InputBinding>(m_BindingsForEachAction, action.m_BindingsStartIndex,
901 action.m_BindingsCount);
902 }
903
904 internal ReadOnlyArray<InputControl> GetControlsForSingleAction(InputAction action)
905 {
906 Debug.Assert(m_State != null);
907 Debug.Assert(m_MapIndexInState != InputActionState.kInvalidIndex);
908 Debug.Assert(m_Actions != null);
909 Debug.Assert(action != null);
910 Debug.Assert(action.m_ActionMap == this);
911 Debug.Assert(!action.isSingletonAction || m_SingletonAction == action);
912
913 if (!controlsForEachActionInitialized)
914 SetUpPerActionControlAndBindingArrays();
915
916 return new ReadOnlyArray<InputControl>(m_ControlsForEachAction, action.m_ControlStartIndex,
917 action.m_ControlCount);
918 }
919
920 /// <summary>
921 /// Collect data from <see cref="m_Bindings"/> and <see cref="m_Actions"/> such that we can
922 /// we can cleanly expose it from <see cref="InputAction.bindings"/> and <see cref="InputAction.controls"/>.
923 /// </summary>
924 /// <remarks>
925 /// We set up per-action caches the first time their information is requested. Internally, we do not
926 /// use those arrays and thus they will not get set up by default.
927 ///
928 /// Note that it is important to allow to call this method at a point where we have not resolved
929 /// controls yet (i.e. <see cref="m_State"/> is <c>null</c>). Otherwise, using <see cref="InputAction.bindings"/>
930 /// may trigger a control resolution which would be surprising.
931 /// </remarks>
932 private unsafe void SetUpPerActionControlAndBindingArrays()
933 {
934 // Handle case where we don't have any bindings.
935 if (m_Bindings == null)
936 {
937 m_ControlsForEachAction = null;
938 m_BindingsForEachAction = null;
939 controlsForEachActionInitialized = true;
940 bindingsForEachActionInitialized = true;
941 return;
942 }
943
944 if (m_SingletonAction != null)
945 {
946 // Dead simple case: map is internally owned by action. The entire
947 // list of bindings is specific to the action.
948
949 Debug.Assert(m_Bindings == m_SingletonAction.m_SingletonActionBindings,
950 "For singleton action, bindings array must match that of the action");
951
952 m_BindingsForEachAction = m_Bindings;
953 m_ControlsForEachAction = m_State?.controls;
954
955 m_SingletonAction.m_BindingsStartIndex = 0;
956 m_SingletonAction.m_BindingsCount = m_Bindings.Length;
957 m_SingletonAction.m_ControlStartIndex = 0;
958 m_SingletonAction.m_ControlCount = m_State?.totalControlCount ?? 0;
959
960 // Only complication, InputActionState allows a control to appear multiple times
961 // on the same action and InputAction.controls[] doesn't.
962 if (m_ControlsForEachAction.HaveDuplicateReferences(0, m_SingletonAction.m_ControlCount))
963 {
964 var numControls = 0;
965 var controls = new InputControl[m_SingletonAction.m_ControlCount];
966 for (var i = 0; i < m_SingletonAction.m_ControlCount; ++i)
967 {
968 if (!controls.ContainsReference(m_ControlsForEachAction[i]))
969 {
970 controls[numControls] = m_ControlsForEachAction[i];
971 ++numControls;
972 }
973 }
974
975 m_ControlsForEachAction = controls;
976 m_SingletonAction.m_ControlCount = numControls;
977 }
978 }
979 else
980 {
981 ////REVIEW: now that we have per-action binding information in UnmanagedMemory, this here can likely be done more easily
982
983 // Go through all bindings and slice them out to individual actions.
984
985 Debug.Assert(m_Actions != null, "Action map is associated with action but action map has no array of actions"); // Action isn't a singleton so this has to be true.
986 var mapIndices = m_State?.FetchMapIndices(this) ?? new InputActionState.ActionMapIndices();
987
988 // Reset state on each action. Important if we have actions that are no longer
989 // referred to by bindings.
990 for (var i = 0; i < m_Actions.Length; ++i)
991 {
992 var action = m_Actions[i];
993 action.m_BindingsCount = 0;
994 action.m_BindingsStartIndex = -1;
995 action.m_ControlCount = 0;
996 action.m_ControlStartIndex = -1;
997 }
998
999 // Count bindings on each action.
1000 // After this loop, we can have one of two situations:
1001 // 1) The bindings for any action X start at some index N and occupy the next m_BindingsCount slots.
1002 // 2) The bindings for some or all actions are scattered across non-contiguous chunks of the array.
1003 var bindingCount = m_Bindings.Length;
1004 for (var i = 0; i < bindingCount; ++i)
1005 {
1006 var action = FindAction(m_Bindings[i].action);
1007 if (action != null)
1008 ++action.m_BindingsCount;
1009 }
1010
1011 // Collect the bindings and controls and bundle them into chunks.
1012 var newBindingsArrayIndex = 0;
1013 if (m_State != null && (m_ControlsForEachAction == null || m_ControlsForEachAction.Length != mapIndices.controlCount))
1014 {
1015 if (mapIndices.controlCount == 0)
1016 m_ControlsForEachAction = null;
1017 else
1018 m_ControlsForEachAction = new InputControl[mapIndices.controlCount];
1019 }
1020 InputBinding[] newBindingsArray = null;
1021 var currentControlIndex = 0;
1022 for (var currentBindingIndex = 0; currentBindingIndex < m_Bindings.Length;)
1023 {
1024 var currentAction = FindAction(m_Bindings[currentBindingIndex].action);
1025 if (currentAction == null || currentAction.m_BindingsStartIndex != -1)
1026 {
1027 // Skip bindings not targeting an action or bindings we have already processed
1028 // (when gathering bindings for a single actions scattered across the array we may have
1029 // skipping ahead).
1030 ++currentBindingIndex;
1031 continue;
1032 }
1033
1034 // Bindings for current action start at current index.
1035 currentAction.m_BindingsStartIndex = newBindingsArray != null
1036 ? newBindingsArrayIndex
1037 : currentBindingIndex;
1038 currentAction.m_ControlStartIndex = currentControlIndex;
1039
1040 // Collect all bindings for the action. As part of that, also copy the controls
1041 // for each binding over to m_ControlsForEachAction.
1042 var bindingCountForCurrentAction = currentAction.m_BindingsCount;
1043 Debug.Assert(bindingCountForCurrentAction > 0);
1044 var sourceBindingToCopy = currentBindingIndex;
1045 for (var i = 0; i < bindingCountForCurrentAction; ++i)
1046 {
1047 // See if we've come across a binding that doesn't belong to our currently looked at action.
1048 if (FindAction(m_Bindings[sourceBindingToCopy].action) != currentAction)
1049 {
1050 // Yes, we have. Means the bindings for our actions are scattered in m_Bindings and
1051 // we need to collect them.
1052
1053 // If this is the first action that has its bindings scattered around, switch to
1054 // having a separate bindings array and copy whatever bindings we already processed
1055 // over to it.
1056 if (newBindingsArray == null)
1057 {
1058 newBindingsArray = new InputBinding[m_Bindings.Length];
1059 newBindingsArrayIndex = sourceBindingToCopy;
1060 Array.Copy(m_Bindings, 0, newBindingsArray, 0, sourceBindingToCopy);
1061 }
1062
1063 // Find the next binding belonging to the action. We've counted bindings for
1064 // the action in the previous pass so we know exactly how many bindings we
1065 // can expect.
1066 do
1067 {
1068 ++sourceBindingToCopy;
1069 Debug.Assert(sourceBindingToCopy < m_Bindings.Length);
1070 }
1071 while (FindAction(m_Bindings[sourceBindingToCopy].action) != currentAction);
1072 }
1073 else if (currentBindingIndex == sourceBindingToCopy)
1074 ++currentBindingIndex;
1075
1076 // Copy binding over to new bindings array, if need be.
1077 if (newBindingsArray != null)
1078 newBindingsArray[newBindingsArrayIndex++] = m_Bindings[sourceBindingToCopy];
1079
1080 // Copy controls for binding, if we have resolved controls already and if the
1081 // binding isn't a composite (they refer to the controls from all of their part bindings
1082 // but do not really resolve to controls themselves).
1083 if (m_State != null && !m_Bindings[sourceBindingToCopy].isComposite)
1084 {
1085 ref var bindingState = ref m_State.bindingStates[mapIndices.bindingStartIndex + sourceBindingToCopy];
1086
1087 var controlCountForBinding = bindingState.controlCount;
1088 if (controlCountForBinding > 0)
1089 {
1090 // Internally, we allow several bindings on a given action to resolve to the same control.
1091 // Externally, however, InputAction.controls[] is a set and thus should not contain duplicates.
1092 // So, instead of just doing a straight copy here, we copy controls one by one.
1093
1094 var controlStartIndexForBinding = bindingState.controlStartIndex;
1095 for (var n = 0; n < controlCountForBinding; ++n)
1096 {
1097 var control = m_State.controls[controlStartIndexForBinding + n];
1098 if (!m_ControlsForEachAction.ContainsReference(currentAction.m_ControlStartIndex,
1099 currentAction.m_ControlCount, control))
1100 {
1101 m_ControlsForEachAction[currentControlIndex] = control;
1102 ++currentControlIndex;
1103 ++currentAction.m_ControlCount;
1104 }
1105 }
1106 }
1107 }
1108
1109 ++sourceBindingToCopy;
1110 }
1111 }
1112
1113 if (newBindingsArray == null)
1114 {
1115 // Bindings are already clustered by action in m_Bindings
1116 // so we can just stick to having one array only.
1117 m_BindingsForEachAction = m_Bindings;
1118 }
1119 else
1120 {
1121 // Bindings are not clustered by action in m_Bindings so
1122 // we had to allocate a separate array where the bindings are sorted.
1123 m_BindingsForEachAction = newBindingsArray;
1124 }
1125 }
1126
1127 controlsForEachActionInitialized = true;
1128 bindingsForEachActionInitialized = true;
1129 }
1130
1131 internal void OnWantToChangeSetup()
1132 {
1133 if (asset != null)
1134 {
1135 foreach (var assetMap in asset.actionMaps)
1136 if (assetMap.enabled)
1137 throw new InvalidOperationException(
1138 $"Cannot add, remove, or change elements of InputActionAsset {asset} while one or more of its actions are enabled");
1139 }
1140 else if (enabled)
1141 {
1142 throw new InvalidOperationException(
1143 $"Cannot add, remove, or change elements of InputActionMap {this} while one or more of its actions are enabled");
1144 }
1145 }
1146
1147 internal void OnSetupChanged()
1148 {
1149 if (m_Asset != null)
1150 {
1151 m_Asset.MarkAsDirty();
1152 foreach (var map in m_Asset.actionMaps)
1153 map.m_State = default;
1154 }
1155 else
1156 {
1157 m_State = default;
1158 }
1159 ClearCachedActionData();
1160 LazyResolveBindings(fullResolve: true);
1161 }
1162
1163 internal void OnBindingModified()
1164 {
1165 ClearCachedActionData();
1166 LazyResolveBindings(fullResolve: true);
1167 }
1168
1169 ////TODO: re-use allocations such that only grow the arrays and hit zero GC allocs when we already have enough memory
1170 internal void ClearCachedActionData(bool onlyControls = false)
1171 {
1172 if (!onlyControls)
1173 {
1174 bindingsForEachActionInitialized = false;
1175 m_BindingsForEachAction = default;
1176 m_ActionIndexByNameOrId = default;
1177 }
1178
1179 controlsForEachActionInitialized = false;
1180 m_ControlsForEachAction = default;
1181 }
1182
1183 internal void GenerateId()
1184 {
1185 m_Id = Guid.NewGuid().ToString();
1186 }
1187
1188 /// <summary>
1189 /// Resolve bindings right away if we have to. Otherwise defer it to when we next need
1190 /// the bindings.
1191 /// </summary>
1192 internal bool LazyResolveBindings(bool fullResolve)
1193 {
1194 // Clear cached controls for actions. Don't need to necessarily clear m_BindingsForEachAction.
1195 m_ControlsForEachAction = null;
1196 controlsForEachActionInitialized = false;
1197
1198 // Indicate that there is at least one action map that has a change
1199 s_NeedToResolveBindings = true;
1200
1201 // If we haven't had to resolve bindings yet, we can wait until when we
1202 // actually have to.
1203 if (m_State == null)
1204 return false;
1205
1206 // We used to defer binding resolution here in case the map had no enabled actions. That behavior,
1207 // however, leads to rather unpredictable BoundControlsChanged notifications (especially for
1208 // rebinding UIs), so now we just always re-resolve anything that ever had an InputActionState
1209 // created. Unfortunately, this can lead to some unnecessary re-resolving.
1210
1211 needToResolveBindings = true;
1212 bindingResolutionNeedsFullReResolve |= fullResolve;
1213
1214 if (s_DeferBindingResolution > 0)
1215 return false;
1216
1217 // Have to do it straight away.
1218 ResolveBindings();
1219 return true;
1220 }
1221
1222 internal bool ResolveBindingsIfNecessary()
1223 {
1224 // NOTE: We only check locally for the current map here. When there are multiple maps
1225 // in an asset, we may have maps that require re-resolution while others don't.
1226 // We only resolve if a map is used that needs resolution to happen. Note that
1227 // this will still resolve bindings for *all* maps in the asset.
1228
1229 if (m_State == null || needToResolveBindings)
1230 {
1231 if (m_State != null && m_State.isProcessingControlStateChange)
1232 {
1233 Debug.Assert(s_DeferBindingResolution > 0, "While processing control state changes, binding resolution should be suppressed");
1234 return false;
1235 }
1236
1237 ResolveBindings();
1238 return true;
1239 }
1240
1241 return false;
1242 }
1243
1244 // We have three different starting scenarios for binding resolution:
1245 //
1246 // (1) From scratch.
1247 // There is no InputActionState and we resolve everything from a completely fresh start. This happens when
1248 // we either have not resolved bindings at all yet or when something touches the action setup (e.g. adds
1249 // or removes an action or binding) and we thus throw away the existing InputActionState.
1250 // NOTE:
1251 // * Actions can be in enabled state.
1252 // * No action can be in an in-progress state (since binding resolution is needed for actions to
1253 // be processed, no action processing can have happened yet)
1254 //
1255 // (2) From an existing InputActionState when a device has been added or removed.
1256 // There is an InputActionState and the action setup (maps, actions, bindings, binding masks) has not changed. However,
1257 // the set of devices usable with the action has changed (either the per-asset/map device list or the global
1258 // list, if we're using it).
1259 // NOTE:
1260 // * Actions can be in enabled state.
1261 // * Actions *can* be in an in-progress state.
1262 // IF the control currently driving the action is on a device that is no longer usable with the action, the
1263 // action is CANCELLED. OTHERWISE, the action will be left as is and keep being in progress from its active control.
1264 // * A device CONFIGURATION change will NOT go down this path (e.g. changing the Keyboard layout). This is because
1265 // any binding path involving display names may now resolve to different controls -- which may impact currently
1266 // active controls of in-progress actions.
1267 // * A change in the USAGES of a device will NOT go down this path either. This is for the same reason -- i.e. an
1268 // active control may no longer match the binding path it matched before. If, for example, we switch the left-hand
1269 // and right-hand roles of two controllers, will will go down path (3) and not (2).
1270 //
1271 // (3) From an existing InputActionState on any other change not covered before.
1272 // There is an InputActionState and the action setup (maps, actions, bindings, binding masks) may have changed. Also,
1273 // any change may have happened in the set of usable devices and targeted controls. This includes binding overrides
1274 // having been applied.
1275 // NOTE:
1276 // * Action can be in enabled state.
1277 // * Actions *can* be in an in-progress state.
1278 // Any such action will be CANCELLED as part of the re-resolution process.
1279 //
1280 // Both (1) and (3) are considered a "full resolve". (2) is not.
1281
1282 /// <summary>
1283 /// Resolve all bindings to their controls and also add any action interactions
1284 /// from the bindings.
1285 /// </summary>
1286 /// <remarks>
1287 /// This is the core method of action binding resolution. All binding resolution goes through here.
1288 ///
1289 /// The best way is for binding resolution to happen once for each action map at the beginning of the game
1290 /// and to then enable and disable the maps as needed. However, the system will also re-resolve
1291 /// bindings if the control setup in the system changes (i.e. if devices are added or removed
1292 /// or if layouts in the system are changed).
1293 ///
1294 /// Bindings can be re-resolved while actions are enabled. This happens changing device or binding
1295 /// masks on action maps or assets (<see cref="devices"/>, <see cref="bindingMask"/>, <see cref="InputAction.bindingMask"/>,
1296 /// <see cref="InputActionAsset.devices"/>, <see cref="InputActionAsset.bindingMask"/>). Doing so will
1297 /// not affect the enable state of actions and, as much as possible, will try to take current
1298 /// action states across.
1299 /// </remarks>
1300 internal void ResolveBindings()
1301 {
1302 // Make sure that if we trigger callbacks as part of disabling and re-enabling actions,
1303 // we don't trigger a re-resolve while we're already resolving bindings.
1304 using (InputActionRebindingExtensions.DeferBindingResolution())
1305 {
1306 // In case we have actions that are currently enabled, we temporarily retain the
1307 // UnmanagedMemory of our InputActionState so that we can sync action states after
1308 // we have re-resolved bindings.
1309 var oldMemory = new InputActionState.UnmanagedMemory();
1310 try
1311 {
1312 OneOrMore<InputActionMap, ReadOnlyArray<InputActionMap>> actionMaps;
1313
1314 // Start resolving.
1315 var resolver = new InputBindingResolver();
1316
1317 // If we're part of an asset, we share state and thus binding resolution with
1318 // all maps in the asset.
1319 var needFullResolve = m_State == null;
1320 if (m_Asset != null)
1321 {
1322 actionMaps = m_Asset.actionMaps;
1323 Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps");
1324
1325 // If there's a binding mask set on the asset, apply it.
1326 resolver.bindingMask = m_Asset.m_BindingMask;
1327
1328 foreach (var map in actionMaps)
1329 {
1330 needFullResolve |= map.bindingResolutionNeedsFullReResolve;
1331 map.needToResolveBindings = false;
1332 map.bindingResolutionNeedsFullReResolve = false;
1333 map.controlsForEachActionInitialized = false;
1334 }
1335 }
1336 else
1337 {
1338 // Standalone action map (possibly a hidden one created for a singleton action).
1339 // Gets its own private state.
1340
1341 actionMaps = this;
1342 needFullResolve |= bindingResolutionNeedsFullReResolve;
1343 needToResolveBindings = false;
1344 bindingResolutionNeedsFullReResolve = false;
1345 controlsForEachActionInitialized = false;
1346 }
1347
1348 // If we already have a state, re-use the arrays we have already allocated.
1349 // NOTE: We will install the arrays on the very same InputActionState instance below. In the
1350 // case where we didn't have to grow the arrays, we should end up with zero GC allocations
1351 // here.
1352 var hasEnabledActions = false;
1353 InputControlList<InputControl> activeControls = default;
1354 if (m_State != null)
1355 {
1356 // Grab a clone of the current memory. We clone because disabling all the actions
1357 // in the map will alter the memory state and we want the state before we start
1358 // touching it.
1359 oldMemory = m_State.memory.Clone();
1360
1361 m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions);
1362
1363 // Reuse the arrays we have so that we can avoid managed memory allocations, if possible.
1364 resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve);
1365
1366 // Throw away old memory.
1367 m_State.memory.Dispose();
1368 }
1369
1370 // Resolve all maps in the asset.
1371 foreach (var map in actionMaps)
1372 resolver.AddActionMap(map);
1373
1374 // Install state.
1375 if (m_State == null)
1376 {
1377 m_State = new InputActionState();
1378 m_State.Initialize(resolver);
1379 }
1380 else
1381 {
1382 m_State.ClaimDataFrom(resolver);
1383 }
1384 if (m_Asset != null)
1385 {
1386 foreach (var map in actionMaps)
1387 map.m_State = m_State;
1388 m_Asset.m_SharedStateForAllMaps = m_State;
1389 }
1390
1391 m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve);
1392 }
1393 finally
1394 {
1395 oldMemory.Dispose();
1396 }
1397 }
1398 }
1399
1400 /// <inheritdoc/>
1401 public int FindBinding(InputBinding mask, out InputAction action)
1402 {
1403 var index = FindBindingRelativeToMap(mask);
1404 if (index == -1)
1405 {
1406 action = null;
1407 return -1;
1408 }
1409
1410 action = m_SingletonAction ?? FindAction(bindings[index].action);
1411 return action.BindingIndexOnMapToBindingIndexOnAction(index);
1412 }
1413
1414 /// <summary>
1415 /// Find the index of the first binding that matches the given mask.
1416 /// </summary>
1417 /// <param name="mask">A binding. See <see cref="InputBinding.Matches"/> for details.</param>
1418 /// <returns>Index into <see cref="InputAction.bindings"/> of <paramref name="action"/> of the binding
1419 /// that matches <paramref name="mask"/>. If no binding matches, will return -1.</returns>
1420 /// <remarks>
1421 /// For details about matching bindings by a mask, see <see cref="InputBinding.Matches"/>.
1422 ///
1423 /// <example>
1424 /// <code>
1425 /// var index = playerInput.actions.FindBindingRelativeToMap(
1426 /// new InputBinding { path = "<Gamepad>/buttonSouth" });
1427 ///
1428 /// if (index != -1)
1429 /// Debug.Log($"Found binding with index {index}");
1430 /// </code>
1431 /// </example>
1432 /// </remarks>
1433 /// <seealso cref="InputBinding.Matches"/>
1434 /// <seealso cref="bindings"/>
1435 internal int FindBindingRelativeToMap(InputBinding mask)
1436 {
1437 var bindings = m_Bindings;
1438 var bindingsCount = bindings.LengthSafe();
1439
1440 for (var i = 0; i < bindingsCount; ++i)
1441 {
1442 ref var binding = ref bindings[i];
1443 if (mask.Matches(ref binding))
1444 return i;
1445 }
1446
1447 return -1;
1448 }
1449
1450 #region Serialization
1451
1452 ////REVIEW: when GetParameter/SetParameter is coming, should these also be considered part of binding override data?
1453
1454 [Serializable]
1455 internal struct BindingOverrideListJson
1456 {
1457 public List<BindingOverrideJson> bindings;
1458 }
1459
1460 [Serializable]
1461 internal struct BindingOverrideJson
1462 {
1463 // We save both the "map/action" path of the action as well as the binding ID.
1464 // This gives us two avenues into finding our target binding to apply the override
1465 // to.
1466 public string action;
1467 public string id;
1468 public string path;
1469 public string interactions;
1470 public string processors;
1471
1472 public static BindingOverrideJson FromBinding(InputBinding binding, string actionName)
1473 {
1474 return new BindingOverrideJson
1475 {
1476 action = actionName,
1477 id = binding.id.ToString() ,
1478 path = binding.overridePath ?? "null",
1479 interactions = binding.overrideInteractions ?? "null",
1480 processors = binding.overrideProcessors ?? "null"
1481 };
1482 }
1483
1484 public static BindingOverrideJson FromBinding(InputBinding binding)
1485 {
1486 return FromBinding(binding, binding.action);
1487 }
1488
1489 public static InputBinding ToBinding(BindingOverrideJson bindingOverride)
1490 {
1491 return new InputBinding
1492 {
1493 overridePath = bindingOverride.path != "null" ? bindingOverride.path : null,
1494 overrideInteractions = bindingOverride.interactions != "null" ? bindingOverride.interactions : null,
1495 overrideProcessors = bindingOverride.processors != "null" ? bindingOverride.processors : null,
1496 };
1497 }
1498 }
1499
1500 // Action maps are serialized in two different ways. For storage as imported assets in Unity's Library/ folder
1501 // and in player data and asset bundles as well as for surviving domain reloads, InputActionMaps are serialized
1502 // directly by Unity. For storage as source data in user projects, InputActionMaps are serialized indirectly
1503 // as JSON by setting up a separate set of structs that are then read and written using Unity's JSON serializer.
1504
1505 [Serializable]
1506 internal struct BindingJson
1507 {
1508 public string name;
1509 public string id;
1510 public string path;
1511 public string interactions;
1512 public string processors;
1513 public string groups;
1514 public string action;
1515 public bool isComposite;
1516 public bool isPartOfComposite;
1517
1518 public InputBinding ToBinding()
1519 {
1520 return new InputBinding
1521 {
1522 name = string.IsNullOrEmpty(name) ? null : name,
1523 m_Id = string.IsNullOrEmpty(id) ? null : id,
1524 path = path,
1525 action = string.IsNullOrEmpty(action) ? null : action,
1526 interactions = string.IsNullOrEmpty(interactions) ? null : interactions,
1527 processors = string.IsNullOrEmpty(processors) ? null : processors,
1528 groups = string.IsNullOrEmpty(groups) ? null : groups,
1529 isComposite = isComposite,
1530 isPartOfComposite = isPartOfComposite,
1531 };
1532 }
1533
1534 public static BindingJson FromBinding(ref InputBinding binding)
1535 {
1536 return new BindingJson
1537 {
1538 name = binding.name,
1539 id = binding.m_Id,
1540 path = binding.path,
1541 action = binding.action,
1542 interactions = binding.interactions,
1543 processors = binding.processors,
1544 groups = binding.groups,
1545 isComposite = binding.isComposite,
1546 isPartOfComposite = binding.isPartOfComposite,
1547 };
1548 }
1549 }
1550
1551 // Backwards-compatible read format.
1552 [Serializable]
1553 internal struct ReadActionJson
1554 {
1555 public string name;
1556 public string type;
1557 public string id;
1558 public string expectedControlType;
1559 public string expectedControlLayout;
1560 public string processors;
1561 public string interactions;
1562 public bool passThrough;
1563 public bool initialStateCheck;
1564
1565 // Bindings can either be on the action itself (in which case the action name
1566 // for each binding is implied) or listed separately in the action file.
1567 public BindingJson[] bindings;
1568
1569 public InputAction ToAction(string actionName = null)
1570 {
1571 // FormerlySerializedAs doesn't seem to work as expected so manually
1572 // handling the rename here.
1573 if (!string.IsNullOrEmpty(expectedControlLayout))
1574 expectedControlType = expectedControlLayout;
1575
1576 // Determine type.
1577 InputActionType actionType = default;
1578 if (!string.IsNullOrEmpty(type))
1579 actionType = (InputActionType)Enum.Parse(typeof(InputActionType), type, true);
1580 else
1581 {
1582 // Old format that doesn't have type. Try to infer from settings.
1583
1584 if (passThrough)
1585 actionType = InputActionType.PassThrough;
1586 else if (initialStateCheck)
1587 actionType = InputActionType.Value;
1588 else if (!string.IsNullOrEmpty(expectedControlType) &&
1589 (expectedControlType == "Button" || expectedControlType == "Key"))
1590 actionType = InputActionType.Button;
1591 }
1592
1593 return new InputAction(actionName ?? name, actionType)
1594 {
1595 m_Id = string.IsNullOrEmpty(id) ? null : id,
1596 m_ExpectedControlType = !string.IsNullOrEmpty(expectedControlType)
1597 ? expectedControlType
1598 : null,
1599 m_Processors = processors,
1600 m_Interactions = interactions,
1601 wantsInitialStateCheck = initialStateCheck,
1602 };
1603 }
1604 }
1605
1606 [Serializable]
1607 internal struct WriteActionJson
1608 {
1609 public string name;
1610 public string type;
1611 public string id;
1612 public string expectedControlType;
1613 public string processors;
1614 public string interactions;
1615 public bool initialStateCheck;
1616
1617 public static WriteActionJson FromAction(InputAction action)
1618 {
1619 return new WriteActionJson
1620 {
1621 name = action.m_Name,
1622 type = action.m_Type.ToString(),
1623 id = action.m_Id,
1624 expectedControlType = action.m_ExpectedControlType,
1625 processors = action.processors,
1626 interactions = action.interactions,
1627 initialStateCheck = action.wantsInitialStateCheck,
1628 };
1629 }
1630 }
1631
1632 [Serializable]
1633 internal struct ReadMapJson
1634 {
1635 public string name;
1636 public string id;
1637 public ReadActionJson[] actions;
1638 public BindingJson[] bindings;
1639 }
1640
1641 [Serializable]
1642 internal struct WriteMapJson
1643 {
1644 public string name;
1645 public string id;
1646 public WriteActionJson[] actions;
1647 public BindingJson[] bindings;
1648
1649 public static WriteMapJson FromMap(InputActionMap map)
1650 {
1651 WriteActionJson[] jsonActions = null;
1652 BindingJson[] jsonBindings = null;
1653
1654 var actions = map.m_Actions;
1655 if (actions != null)
1656 {
1657 var actionCount = actions.Length;
1658 jsonActions = new WriteActionJson[actionCount];
1659
1660 for (var i = 0; i < actionCount; ++i)
1661 jsonActions[i] = WriteActionJson.FromAction(actions[i]);
1662 }
1663
1664 var bindings = map.m_Bindings;
1665 if (bindings != null)
1666 {
1667 var bindingCount = bindings.Length;
1668 jsonBindings = new BindingJson[bindingCount];
1669
1670 for (var i = 0; i < bindingCount; ++i)
1671 jsonBindings[i] = BindingJson.FromBinding(ref bindings[i]);
1672 }
1673
1674 return new WriteMapJson
1675 {
1676 name = map.name,
1677 id = map.id.ToString(),
1678 actions = jsonActions,
1679 bindings = jsonBindings,
1680 };
1681 }
1682 }
1683
1684 // We write JSON in a less flexible format than we allow to be read. JSON files
1685 // we read can just be flat lists of actions with the map name being contained in
1686 // the action name and containing their own bindings directly. JSON files we write
1687 // go map by map and separate bindings and actions.
1688 [Serializable]
1689 internal struct WriteFileJson
1690 {
1691 public WriteMapJson[] maps;
1692
1693 public static WriteFileJson FromMap(InputActionMap map)
1694 {
1695 return new WriteFileJson
1696 {
1697 maps = new[] {WriteMapJson.FromMap(map)}
1698 };
1699 }
1700
1701 public static WriteFileJson FromMaps(IEnumerable<InputActionMap> maps)
1702 {
1703 var mapCount = maps.Count();
1704 if (mapCount == 0)
1705 return new WriteFileJson();
1706
1707 var mapsJson = new WriteMapJson[mapCount];
1708 var index = 0;
1709 foreach (var map in maps)
1710 mapsJson[index++] = WriteMapJson.FromMap(map);
1711
1712 return new WriteFileJson {maps = mapsJson};
1713 }
1714 }
1715
1716 // A JSON representation of one or more sets of actions.
1717 // Contains a list of actions. Each action may specify the set it belongs to
1718 // as part of its name ("set/action").
1719 [Serializable]
1720 internal struct ReadFileJson
1721 {
1722 public ReadActionJson[] actions;
1723 public ReadMapJson[] maps;
1724
1725 public InputActionMap[] ToMaps()
1726 {
1727 var mapList = new List<InputActionMap>();
1728 var actionLists = new List<List<InputAction>>();
1729 var bindingLists = new List<List<InputBinding>>();
1730
1731 // Process actions listed at toplevel.
1732 var actionCount = actions?.Length ?? 0;
1733 for (var i = 0; i < actionCount; ++i)
1734 {
1735 var jsonAction = actions[i];
1736
1737 if (string.IsNullOrEmpty(jsonAction.name))
1738 throw new InvalidOperationException($"Action number {i + 1} has no name");
1739
1740 ////REVIEW: make sure all action names are unique?
1741
1742 // Determine name of action map.
1743 string mapName = null;
1744 var actionName = jsonAction.name;
1745 var indexOfFirstSlash = actionName.IndexOf('/');
1746 if (indexOfFirstSlash != -1)
1747 {
1748 mapName = actionName.Substring(0, indexOfFirstSlash);
1749 actionName = actionName.Substring(indexOfFirstSlash + 1);
1750
1751 if (string.IsNullOrEmpty(actionName))
1752 throw new InvalidOperationException(
1753 $"Invalid action name '{jsonAction.name}' (missing action name after '/')");
1754 }
1755
1756 // Try to find existing map.
1757 InputActionMap map = null;
1758 var mapIndex = 0;
1759 for (; mapIndex < mapList.Count; ++mapIndex)
1760 {
1761 if (string.Compare(mapList[mapIndex].name, mapName, StringComparison.InvariantCultureIgnoreCase) == 0)
1762 {
1763 map = mapList[mapIndex];
1764 break;
1765 }
1766 }
1767
1768 // Create new map if it's the first action in the map.
1769 if (map == null)
1770 {
1771 // NOTE: No map IDs supported on this path.
1772 map = new InputActionMap(mapName);
1773 mapIndex = mapList.Count;
1774 mapList.Add(map);
1775 actionLists.Add(new List<InputAction>());
1776 bindingLists.Add(new List<InputBinding>());
1777 }
1778
1779 // Create action.
1780 var action = jsonAction.ToAction(actionName);
1781 actionLists[mapIndex].Add(action);
1782
1783 // Add bindings.
1784 if (jsonAction.bindings != null)
1785 {
1786 var bindingsForMap = bindingLists[mapIndex];
1787 for (var n = 0; n < jsonAction.bindings.Length; ++n)
1788 {
1789 var jsonBinding = jsonAction.bindings[n];
1790 var binding = jsonBinding.ToBinding();
1791 binding.action = action.m_Name;
1792 bindingsForMap.Add(binding);
1793 }
1794 }
1795 }
1796
1797 // Process maps.
1798 var mapCount = maps?.Length ?? 0;
1799 for (var i = 0; i < mapCount; ++i)
1800 {
1801 var jsonMap = maps[i];
1802
1803 var mapName = jsonMap.name;
1804 if (string.IsNullOrEmpty(mapName))
1805 throw new InvalidOperationException($"Map number {i + 1} has no name");
1806
1807 // Try to find existing map.
1808 InputActionMap map = null;
1809 var mapIndex = 0;
1810 for (; mapIndex < mapList.Count; ++mapIndex)
1811 {
1812 if (string.Compare(mapList[mapIndex].name, mapName, StringComparison.InvariantCultureIgnoreCase) == 0)
1813 {
1814 map = mapList[mapIndex];
1815 break;
1816 }
1817 }
1818
1819 // Create new map if we haven't seen it before.
1820 if (map == null)
1821 {
1822 map = new InputActionMap(mapName)
1823 {
1824 m_Id = string.IsNullOrEmpty(jsonMap.id) ? null : jsonMap.id
1825 };
1826 mapIndex = mapList.Count;
1827 mapList.Add(map);
1828 actionLists.Add(new List<InputAction>());
1829 bindingLists.Add(new List<InputBinding>());
1830 }
1831
1832 // Process actions in map.
1833 var actionCountInMap = jsonMap.actions?.Length ?? 0;
1834 for (var n = 0; n < actionCountInMap; ++n)
1835 {
1836 var jsonAction = jsonMap.actions[n];
1837
1838 if (string.IsNullOrEmpty(jsonAction.name))
1839 throw new InvalidOperationException($"Action number {i + 1} in map '{mapName}' has no name");
1840
1841 // Create action.
1842 var action = jsonAction.ToAction();
1843 actionLists[mapIndex].Add(action);
1844
1845 // Add bindings.
1846 if (jsonAction.bindings != null)
1847 {
1848 var bindingList = bindingLists[mapIndex];
1849 for (var k = 0; k < jsonAction.bindings.Length; ++k)
1850 {
1851 var jsonBinding = jsonAction.bindings[k];
1852 var binding = jsonBinding.ToBinding();
1853 binding.action = action.m_Name;
1854 bindingList.Add(binding);
1855 }
1856 }
1857 }
1858
1859 // Process bindings in map.
1860 var bindingCountInMap = jsonMap.bindings?.Length ?? 0;
1861 var bindingsForMap = bindingLists[mapIndex];
1862 for (var n = 0; n < bindingCountInMap; ++n)
1863 {
1864 var jsonBinding = jsonMap.bindings[n];
1865 var binding = jsonBinding.ToBinding();
1866 bindingsForMap.Add(binding);
1867 }
1868 }
1869
1870 // Finalize arrays.
1871 for (var i = 0; i < mapList.Count; ++i)
1872 {
1873 var map = mapList[i];
1874
1875 var actionArray = actionLists[i].ToArray();
1876 var bindingArray = bindingLists[i].ToArray();
1877
1878 map.m_Actions = actionArray;
1879 map.m_Bindings = bindingArray;
1880
1881 for (var n = 0; n < actionArray.Length; ++n)
1882 {
1883 var action = actionArray[n];
1884 action.m_ActionMap = map;
1885 }
1886 }
1887
1888 return mapList.ToArray();
1889 }
1890 }
1891
1892 /// <summary>
1893 /// Load one or more action maps from JSON.
1894 /// </summary>
1895 /// <param name="json">JSON representation of the action maps. Can be empty.</param>
1896 /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c>.</exception>
1897 /// <returns>The array of action maps (may be empty) read from the given JSON string. Will not be
1898 /// <c>null</c>.</returns>
1899 /// <remarks>
1900 /// Note that the format used by this method is different than what you
1901 /// get if you call <c>JsonUtility.ToJson</c> on an InputActionMap instance. In other
1902 /// words, the JSON format is not identical to the Unity serialized object representation
1903 /// of the asset.
1904 ///
1905 /// <example>
1906 /// <code>
1907 /// var maps = InputActionMap.FromJson(@"
1908 /// {
1909 /// ""maps"" : [
1910 /// {
1911 /// ""name"" : ""Gameplay"",
1912 /// ""actions"" : [
1913 /// { ""name"" : ""fire"", ""type"" : ""button"" }
1914 /// ],
1915 /// ""bindings"" : [
1916 /// { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"" }
1917 /// ],
1918 /// }
1919 /// ]
1920 /// }
1921 /// ");
1922 /// </code>
1923 /// </example>
1924 /// </remarks>
1925 /// <seealso cref="InputActionAsset.FromJson"/>
1926 /// <seealso cref="ToJson(IEnumerable{InputActionMap})"/>
1927 public static InputActionMap[] FromJson(string json)
1928 {
1929 if (json == null)
1930 throw new ArgumentNullException(nameof(json));
1931 var fileJson = JsonUtility.FromJson<ReadFileJson>(json);
1932 return fileJson.ToMaps();
1933 }
1934
1935 /// <summary>
1936 /// Convert a set of action maps to JSON format.
1937 /// </summary>
1938 /// <param name="maps">List of action maps to serialize.</param>
1939 /// <exception cref="ArgumentNullException"><paramref name="maps"/> is <c>null</c>.</exception>
1940 /// <returns>JSON representation of the given action maps.</returns>
1941 /// <remarks>
1942 /// The result of this method can be loaded with <see cref="FromJson"/>.
1943 ///
1944 /// Note that the format used by this method is different than what you
1945 /// get if you call <c>JsonUtility.ToJson</c> on an InputActionMap instance. In other
1946 /// words, the JSON format is not identical to the Unity serialized object representation
1947 /// of the asset.
1948 /// </remarks>
1949 /// <seealso cref="FromJson"/>
1950 public static string ToJson(IEnumerable<InputActionMap> maps)
1951 {
1952 if (maps == null)
1953 throw new ArgumentNullException(nameof(maps));
1954 var fileJson = WriteFileJson.FromMaps(maps);
1955 return JsonUtility.ToJson(fileJson, true);
1956 }
1957
1958 /// <summary>
1959 /// Convert the action map to JSON format.
1960 /// </summary>
1961 /// <returns>A JSON representation of the action map.</returns>
1962 /// <remarks>
1963 /// The result of this method can be loaded with <see cref="FromJson"/>.
1964 ///
1965 /// Note that the format used by this method is different than what you
1966 /// get if you call <c>JsonUtility.ToJson</c> on an InputActionMap instance. In other
1967 /// words, the JSON format is not identical to the Unity serialized object representation
1968 /// of the asset.
1969 /// </remarks>
1970 public string ToJson()
1971 {
1972 var fileJson = WriteFileJson.FromMap(this);
1973 return JsonUtility.ToJson(fileJson, true);
1974 }
1975
1976 /// <summary>
1977 /// Called by Unity before the action map is serialized using Unity's
1978 /// serialization system.
1979 /// </summary>
1980 public void OnBeforeSerialize()
1981 {
1982 }
1983
1984 /// <summary>
1985 /// Called by Unity after the action map has been deserialized using Unity's
1986 /// serialization system.
1987 /// </summary>
1988 public void OnAfterDeserialize()
1989 {
1990 // Indicate that there is at least one action map that has a change
1991 s_NeedToResolveBindings = true;
1992
1993 m_State = null;
1994 m_MapIndexInState = InputActionState.kInvalidIndex;
1995 m_EnabledActionsCount = 0;
1996
1997 // Restore references of actions linking back to us.
1998 if (m_Actions != null)
1999 {
2000 var actionCount = m_Actions.Length;
2001 for (var i = 0; i < actionCount; ++i)
2002 m_Actions[i].m_ActionMap = this;
2003 }
2004
2005 // Make sure we don't retain any cached per-action data when using serialization
2006 // to doctor around in action map configurations in the editor.
2007 ClearCachedActionData();
2008 ClearActionLookupTable();
2009 }
2010
2011 #endregion
2012 }
2013}