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: "&lt;Keyboard&gt;/space"); 59 /// map.AddAction("action2", binding: "&lt;Gamepad&gt;/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: "&lt;Gamepad&gt;/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: "&lt;Gamepad&gt;/buttonSouth"); 491 /// actionMap.AddAction("action2", binding: "&lt;XRController{LeftHand}&gt;/{PrimaryAction}"); 492 /// 493 /// // 494 /// var gamepad = InputSystem.AddDevice&lt;Gamepad&gt;(); 495 /// var xrController = InputSystem.AddDevice&lt;XRController&gt;(); 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>"&lt;Unnamed Action Map&gt;"</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 = "&lt;Gamepad&gt;/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"" : ""&lt;Gamepad&gt;/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}