A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Runtime.InteropServices; 5using Unity.Collections; 6using Unity.Collections.LowLevel.Unsafe; 7using UnityEngine.InputSystem.Controls; 8using UnityEngine.InputSystem.LowLevel; 9using Unity.Profiling; 10using UnityEngine.InputSystem.Utilities; 11 12using ProfilerMarker = Unity.Profiling.ProfilerMarker; 13 14////TODO: now that we can bind to controls by display name, we need to re-resolve controls when those change (e.g. when the keyboard layout changes) 15 16////TODO: remove direct references to InputManager 17 18////TODO: make sure controls in per-action and per-map control arrays are unique (the internal arrays are probably okay to have duplicates) 19 20////REVIEW: should the default interaction be an *explicit* interaction? 21 22////REVIEW: should "pass-through" be an interaction instead of a setting on actions? 23 24////REVIEW: allow setup where state monitor is enabled but action is disabled? 25 26namespace UnityEngine.InputSystem 27{ 28 using InputActionListener = Action<InputAction.CallbackContext>; 29 30 /// <summary> 31 /// Dynamic execution state of one or more <see cref="InputActionMap">action maps</see> and 32 /// all the actions they contain. 33 /// </summary> 34 /// <remarks> 35 /// The aim of this class is to both put all the dynamic execution state into one place as well 36 /// as to organize state in tight, GC-optimized arrays. Also, by moving state out of individual 37 /// <see cref="InputActionMap">action maps</see>, we can combine the state of several maps 38 /// into one single object with a single set of arrays. Ideally, if you have a single action 39 /// asset in the game, you get a single InputActionState that contains the entire dynamic 40 /// execution state for your game's actions. 41 /// 42 /// Note that this class allocates unmanaged memory. It has to be disposed of or it will leak 43 /// memory! 44 /// 45 /// An instance of this class is also used for singleton actions by means of the hidden action 46 /// map we create for those actions. In that case, there will be both a hidden map instance 47 /// as well as an action state for every separate singleton action. This makes singleton actions 48 /// relatively expensive. 49 /// </remarks> 50 internal unsafe class InputActionState : IInputStateChangeMonitor, ICloneable, IDisposable 51 { 52 public const int kInvalidIndex = -1; 53 54 /// <summary> 55 /// Array of all maps added to the state. 56 /// </summary> 57 public InputActionMap[] maps; 58 59 /// <summary> 60 /// List of all resolved controls. 61 /// </summary> 62 /// <remarks> 63 /// As we don't know in advance how many controls a binding may match (if any), we bump the size of 64 /// this array in increments during resolution. This means it may be end up being larger than the total 65 /// number of used controls and have empty entries at the end. Use <see cref="UnmanagedMemory.controlCount"/> and not 66 /// <c>.Length</c> to find the actual number of controls. 67 /// 68 /// All bound controls are included in the array regardless of whether only a partial set of actions 69 /// is currently enabled. What ultimately decides whether controls get triggered or not is whether we 70 /// have installed state monitors for them or not. 71 /// </remarks> 72 public InputControl[] controls; 73 74 /// <summary> 75 /// Array of instantiated interaction objects. 76 /// </summary> 77 /// <remarks> 78 /// Every binding that has interactions corresponds to a slice of this array. 79 /// 80 /// Indices match between this and interaction states in <see cref="memory"/>. 81 /// </remarks> 82 public IInputInteraction[] interactions; 83 84 /// <summary> 85 /// Processor objects instantiated for the bindings in the state. 86 /// </summary> 87 public InputProcessor[] processors; 88 89 /// <summary> 90 /// Array of instantiated composite objects. 91 /// </summary> 92 public InputBindingComposite[] composites; 93 94 public int totalProcessorCount; 95 public int totalCompositeCount => memory.compositeCount; 96 public int totalMapCount => memory.mapCount; 97 public int totalActionCount => memory.actionCount; 98 public int totalBindingCount => memory.bindingCount; 99 public int totalInteractionCount => memory.interactionCount; 100 public int totalControlCount => memory.controlCount; 101 102 /// <summary> 103 /// Block of unmanaged memory that holds the dynamic execution state of the actions and their controls. 104 /// </summary> 105 /// <remarks> 106 /// We keep several arrays of structured data in a single block of unmanaged memory. 107 /// </remarks> 108 public UnmanagedMemory memory; 109 110 public ActionMapIndices* mapIndices => memory.mapIndices; 111 public TriggerState* actionStates => memory.actionStates; 112 public BindingState* bindingStates => memory.bindingStates; 113 public InteractionState* interactionStates => memory.interactionStates; 114 public int* controlIndexToBindingIndex => memory.controlIndexToBindingIndex; 115 public ushort* controlGroupingAndComplexity => memory.controlGroupingAndComplexity; 116 public float* controlMagnitudes => memory.controlMagnitudes; 117 public uint* enabledControls => (uint*)memory.enabledControls; 118 119 public bool isProcessingControlStateChange => m_InProcessControlStateChange; 120 121 private bool m_OnBeforeUpdateHooked; 122 private bool m_OnAfterUpdateHooked; 123 private bool m_InProcessControlStateChange; 124 private InputEventPtr m_CurrentlyProcessingThisEvent; 125 private Action m_OnBeforeUpdateDelegate; 126 private Action m_OnAfterUpdateDelegate; 127 private static readonly ProfilerMarker k_InputInitialActionStateCheckMarker = new ProfilerMarker("InitialActionStateCheck"); 128 private static readonly ProfilerMarker k_InputActionResolveConflictMarker = new ProfilerMarker("InputActionResolveConflict"); 129 private static readonly ProfilerMarker k_InputActionCallbackMarker = new ProfilerMarker("InputActionCallback"); 130 private static readonly ProfilerMarker k_InputOnActionChangeMarker = new ProfilerMarker("InpustSystem.onActionChange"); 131 private static readonly ProfilerMarker k_InputOnDeviceChangeMarker = new ProfilerMarker("InpustSystem.onDeviceChange"); 132 133 /// <summary> 134 /// Initialize execution state with given resolved binding information. 135 /// </summary> 136 /// <param name="resolver"></param> 137 public void Initialize(InputBindingResolver resolver) 138 { 139 ClaimDataFrom(resolver); 140 AddToGlobalList(); 141 } 142 143 private void ComputeControlGroupingIfNecessary() 144 { 145 if (memory.controlGroupingInitialized) 146 return; 147 148 // If shortcut support is disabled, we simply put put all bindings at complexity=1 and 149 // in their own group. 150 var disableControlGrouping = !InputSystem.settings.shortcutKeysConsumeInput; 151 152 var currentGroup = 1u; 153 for (var i = 0; i < totalControlCount; ++i) 154 { 155 var control = controls[i]; 156 var bindingIndex = controlIndexToBindingIndex[i]; 157 ref var binding = ref bindingStates[bindingIndex]; 158 159 ////REVIEW: take processors and interactions into account?? 160 161 // Compute complexity. 162 var complexity = 1; 163 if (binding.isPartOfComposite && !disableControlGrouping) 164 { 165 var compositeBindingIndex = binding.compositeOrCompositeBindingIndex; 166 167 for (var n = compositeBindingIndex + 1; n < totalBindingCount; ++n) 168 { 169 ref var partBinding = ref bindingStates[n]; 170 if (!partBinding.isPartOfComposite || partBinding.compositeOrCompositeBindingIndex != compositeBindingIndex) 171 break; 172 ++complexity; 173 } 174 } 175 controlGroupingAndComplexity[i * 2 + 1] = (ushort)complexity; 176 177 // Compute grouping. If already set, skip. 178 if (controlGroupingAndComplexity[i * 2] == 0) 179 { 180 if (!disableControlGrouping) 181 { 182 for (var n = 0; n < totalControlCount; ++n) 183 { 184 // NOTE: We could compute group numbers based on device index + control offsets 185 // and thus make them work globally in a stable way. But we'd need a mechanism 186 // to then determine ordering of actions globally such that it is clear which 187 // action gets a first shot at an input. 188 189 var otherControl = controls[n]; 190 if (control != otherControl) 191 continue; 192 193 controlGroupingAndComplexity[n * 2] = (ushort)currentGroup; 194 } 195 } 196 197 controlGroupingAndComplexity[i * 2] = (ushort)currentGroup; 198 199 ++currentGroup; 200 } 201 } 202 203 memory.controlGroupingInitialized = true; 204 } 205 206 public void ClaimDataFrom(InputBindingResolver resolver) 207 { 208 totalProcessorCount = resolver.totalProcessorCount; 209 210 maps = resolver.maps; 211 interactions = resolver.interactions; 212 processors = resolver.processors; 213 composites = resolver.composites; 214 controls = resolver.controls; 215 216 memory = resolver.memory; 217 resolver.memory = new UnmanagedMemory(); 218 219 ComputeControlGroupingIfNecessary(); 220 } 221 222 ~InputActionState() 223 { 224 Destroy(isFinalizing: true); 225 } 226 227 public void Dispose() 228 { 229 Destroy(); 230 } 231 232 private void Destroy(bool isFinalizing = false) 233 { 234 Debug.Assert(!isProcessingControlStateChange, "Must not destroy InputActionState while executing an action callback within it"); 235 236 if (!isFinalizing) 237 { 238 for (var i = 0; i < totalMapCount; ++i) 239 { 240 var map = maps[i]; 241 242 // Remove state change monitors. 243 if (map.enabled) 244 DisableControls(i, mapIndices[i].controlStartIndex, mapIndices[i].controlCount); 245 246 if (map.m_Asset != null) 247 map.m_Asset.m_SharedStateForAllMaps = null; 248 249 map.m_State = null; 250 map.m_MapIndexInState = kInvalidIndex; 251 map.m_EnabledActionsCount = 0; 252 253 // Reset action indices on the map's actions. 254 var actions = map.m_Actions; 255 if (actions != null) 256 { 257 for (var n = 0; n < actions.Length; ++n) 258 actions[n].m_ActionIndexInState = kInvalidIndex; 259 } 260 } 261 262 RemoveMapFromGlobalList(); 263 } 264 memory.Dispose(); 265 } 266 267 /// <summary> 268 /// Create a copy of the state. 269 /// </summary> 270 /// <returns></returns> 271 /// <remarks> 272 /// The copy is non-functional in so far as it cannot be used to keep track of changes made to 273 /// any associated actions. However, it can be used to freeze the binding resolution state of 274 /// a particular set of enabled actions. This is used by <see cref="InputActionTrace"/>. 275 /// </remarks> 276 public InputActionState Clone() 277 { 278 return new InputActionState 279 { 280 maps = ArrayHelpers.Copy(maps), 281 controls = ArrayHelpers.Copy(controls), 282 interactions = ArrayHelpers.Copy(interactions), 283 processors = ArrayHelpers.Copy(processors), 284 composites = ArrayHelpers.Copy(composites), 285 totalProcessorCount = totalProcessorCount, 286 memory = memory.Clone(), 287 }; 288 } 289 290 object ICloneable.Clone() 291 { 292 return Clone(); 293 } 294 295 /// <summary> 296 /// Check if the state is currently using a control from the given device. 297 /// </summary> 298 /// <param name="device">Any input device.</param> 299 /// <returns>True if any of the maps in the state has the device in its <see cref="InputActionMap.devices"/> 300 /// list or if any of the device's controls are contained in <see cref="controls"/>.</returns> 301 private bool IsUsingDevice(InputDevice device) 302 { 303 Debug.Assert(device != null, "Device is null"); 304 305 // If all maps have device restrictions, the device must be in it 306 // or we're not using it. 307 var haveMapsWithoutDeviceRestrictions = false; 308 for (var i = 0; i < totalMapCount; ++i) 309 { 310 var map = maps[i]; 311 var devicesForMap = map.devices; 312 313 if (devicesForMap == null) 314 haveMapsWithoutDeviceRestrictions = true; 315 else if (devicesForMap.Value.Contains(device)) 316 return true; 317 } 318 319 if (!haveMapsWithoutDeviceRestrictions) 320 return false; 321 322 // Check all our controls one by one. 323 for (var i = 0; i < totalControlCount; ++i) 324 if (controls[i].device == device) 325 return true; 326 327 return false; 328 } 329 330 // Check if the state would use a control from the given device. 331 private bool CanUseDevice(InputDevice device) 332 { 333 Debug.Assert(device != null, "Device is null"); 334 335 // If all maps have device restrictions and the device isn't in them, we can't use 336 // the device. 337 var haveMapWithoutDeviceRestrictions = false; 338 for (var i = 0; i < totalMapCount; ++i) 339 { 340 var map = maps[i]; 341 var devicesForMap = map.devices; 342 343 if (devicesForMap == null) 344 haveMapWithoutDeviceRestrictions = true; 345 else if (devicesForMap.Value.Contains(device)) 346 return true; 347 } 348 349 if (!haveMapWithoutDeviceRestrictions) 350 return false; 351 352 for (var i = 0; i < totalMapCount; ++i) 353 { 354 var map = maps[i]; 355 var bindings = map.m_Bindings; 356 if (bindings == null) 357 continue; 358 359 var bindingCount = bindings.Length; 360 for (var n = 0; n < bindingCount; ++n) 361 { 362 if (InputControlPath.TryFindControl(device, bindings[n].effectivePath) != null) 363 return true; 364 } 365 } 366 367 return false; 368 } 369 370 /// <summary> 371 /// Check whether the state has any actions that are currently enabled. 372 /// </summary> 373 /// <returns></returns> 374 public bool HasEnabledActions() 375 { 376 for (var i = 0; i < totalMapCount; ++i) 377 { 378 var map = maps[i]; 379 if (map.enabled) 380 return true; 381 } 382 383 return false; 384 } 385 386 private void FinishBindingCompositeSetups() 387 { 388 for (var i = 0; i < totalBindingCount; ++i) 389 { 390 ref var binding = ref bindingStates[i]; 391 if (!binding.isComposite || binding.compositeOrCompositeBindingIndex == -1) 392 continue; 393 394 var composite = composites[binding.compositeOrCompositeBindingIndex]; 395 var context = new InputBindingCompositeContext { m_State = this, m_BindingIndex = i }; 396 composite.CallFinishSetup(ref context); 397 } 398 } 399 400 internal void PrepareForBindingReResolution(bool needFullResolve, 401 ref InputControlList<InputControl> activeControls, ref bool hasEnabledActions) 402 { 403 // Let listeners know we're about to modify bindings. 404 var needToCloneActiveControls = false; 405 for (var i = 0; i < totalMapCount; ++i) 406 { 407 var map = maps[i]; 408 409 if (map.enabled) 410 { 411 hasEnabledActions = true; 412 413 if (needFullResolve) 414 { 415 // For a full-resolve, we temporarily disable all actions and then re-enable 416 // all that were enabled after bindings have been resolved (plus we also flip on 417 // initial state checks for those actions to make sure they react right away 418 // to whatever state controls are in). 419 DisableAllActions(map); 420 } 421 else 422 { 423 // Cancel any action that is driven from a control we will lose when we re-resolve. 424 // For any other on-going action, save active controls. 425 foreach (var action in map.actions) 426 { 427 if (!action.phase.IsInProgress()) 428 continue; 429 430 // Skip action's that are in progress but whose active control is not affected 431 // by the changes that lead to re-resolution. 432 if (action.ActiveControlIsValid(action.activeControl)) 433 { 434 // As part of re-resolving, we're losing m_State.controls. So, while we retain 435 // the current execution state of the method including the index of the currently 436 // active control, we lose the actual references to the control. 437 // Thus, we retain an explicit list of active controls into which we *only* copy 438 // those few controls that are currently active. Also, this list is kept in unmanaged 439 // memory so we don't add an additional GC allocation here. 440 if (needToCloneActiveControls == false) 441 { 442 activeControls = new InputControlList<InputControl>(Allocator.Temp); 443 activeControls.Resize(totalControlCount); 444 needToCloneActiveControls = true; 445 } 446 447 ref var actionState = ref actionStates[action.m_ActionIndexInState]; 448 var activeControlIndex = actionState.controlIndex; 449 activeControls[activeControlIndex] = controls[activeControlIndex]; 450 451 // Also save active controls for other ongoing interactions. 452 var bindingState = bindingStates[actionState.bindingIndex]; 453 for (var n = 0; n < bindingState.interactionCount; ++n) 454 { 455 var interactionIndex = bindingState.interactionStartIndex + n; 456 if (!interactionStates[interactionIndex].phase.IsInProgress()) 457 continue; 458 459 activeControlIndex = interactionStates[interactionIndex] 460 .triggerControlIndex; 461 if (action.ActiveControlIsValid(controls[activeControlIndex])) 462 activeControls[activeControlIndex] = controls[activeControlIndex]; 463 else 464 ResetInteractionState(interactionIndex); 465 } 466 } 467 else 468 { 469 ResetActionState(action.m_ActionIndexInState); 470 } 471 } 472 473 // NOTE: Removing state monitors here also means we're terminating any pending 474 // timeouts. However, we have information in the action state about how much 475 // is time is remaining on each of them so we can resume them later. 476 477 DisableControls(map); 478 } 479 } 480 481 map.ClearCachedActionData(onlyControls: !needFullResolve); 482 } 483 484 NotifyListenersOfActionChange(InputActionChange.BoundControlsAboutToChange); 485 } 486 487 public void FinishBindingResolution(bool hasEnabledActions, UnmanagedMemory oldMemory, InputControlList<InputControl> activeControls, bool isFullResolve) 488 { 489 // Fire InputBindingComposite.FinishSetup() calls. 490 FinishBindingCompositeSetups(); 491 492 // Sync action states between the old and the new state. This also ensures 493 // that any action that was already in progress just keeps going -- except 494 // if we actually lost the control that was driving it. 495 if (hasEnabledActions) 496 RestoreActionStatesAfterReResolvingBindings(oldMemory, activeControls, isFullResolve); 497 else 498 NotifyListenersOfActionChange(InputActionChange.BoundControlsChanged); 499 } 500 501 /// <summary> 502 /// Synchronize the current action states based on what they were before. 503 /// </summary> 504 /// <param name="oldState"></param> 505 /// <remarks> 506 /// We do this when we have to temporarily disable actions in order to re-resolve bindings. 507 /// 508 /// Note that we do NOT restore action states perfectly. I.e. will we will not preserve trigger 509 /// and interaction states exactly to what they were before. Given that the bound controls may change, 510 /// it would be non-trivial to reliably correlate the old and the new state. Instead, we simply 511 /// reenable all the actions and controls that were enabled before and then let the next update 512 /// take it from there. 513 /// </remarks> 514 private void RestoreActionStatesAfterReResolvingBindings(UnmanagedMemory oldState, InputControlList<InputControl> activeControls, bool isFullResolve) 515 { 516 Debug.Assert(oldState.isAllocated, "Old state contains no memory"); 517 518 // No maps and/or actions must have been added, replaced, or removed. 519 // 520 // IF 521 // isFullResolve==true: 522 // - No bindings must have been added, replaced, or removed or touched in any other way. 523 // - The only thing that is allowed to have changed is the list of controls used by the actions. 524 // - Binding masks must not have changed. 525 // 526 // isFullResolve==false: 527 // - Bindings may have been added, replaced, modified, and/or removed. 528 // - Also, the list of controls may have changed. 529 // - Binding masks may have changed. 530 // 531 // This means that when we compare UnmanagedMemory from before and after: 532 // - Map indices are identical. 533 // - Action indices are identical. 534 // - Binding indices may have changed arbitrarily. 535 // - Control indices may have changed arbitrarily (controls[] before and after need not relate at all). 536 // - Processor indices may have changed arbitrarily. 537 // - Interaction indices may have changed arbitrarily. 538 // 539 // HOWEVER, if isFullResolve==false, then ONLY control indices may have changed. All other 540 // indices must have remained unchanged. 541 Debug.Assert(oldState.actionCount == memory.actionCount, "Action count in old and new state must be the same"); 542 Debug.Assert(oldState.mapCount == memory.mapCount, "Map count in old and new state must be the same"); 543 if (!isFullResolve) 544 { 545 Debug.Assert(oldState.bindingCount == memory.bindingCount, "Binding count in old and new state must be the same"); 546 Debug.Assert(oldState.interactionCount == memory.interactionCount, "Interaction count in old and new state must be the same"); 547 Debug.Assert(oldState.compositeCount == memory.compositeCount, "Composite count in old and new state must be the same"); 548 } 549 550 // Restore action states. 551 for (var actionIndex = 0; actionIndex < totalActionCount; ++actionIndex) 552 { 553 ref var oldActionState = ref oldState.actionStates[actionIndex]; 554 ref var newActionState = ref actionStates[actionIndex]; 555 556 newActionState.lastCanceledInUpdate = oldActionState.lastCanceledInUpdate; 557 newActionState.lastPerformedInUpdate = oldActionState.lastPerformedInUpdate; 558 newActionState.lastCompletedInUpdate = oldActionState.lastCompletedInUpdate; 559 newActionState.pressedInUpdate = oldActionState.pressedInUpdate; 560 newActionState.releasedInUpdate = oldActionState.releasedInUpdate; 561 newActionState.startTime = oldActionState.startTime; 562 newActionState.bindingIndex = oldActionState.bindingIndex; 563 newActionState.frame = oldActionState.frame; 564 565 if (oldActionState.phase != InputActionPhase.Disabled) 566 { 567 // In this step, we only put enabled actions into Waiting phase. 568 // When isFullResolve==false, we will restore the actual phase from 569 // before when we look at bindings further down in the code. 570 newActionState.phase = InputActionPhase.Waiting; 571 572 // In a full resolve, we actually disable any action we find enabled. 573 // So count any action we reenable here. 574 if (isFullResolve) 575 ++maps[newActionState.mapIndex].m_EnabledActionsCount; 576 } 577 } 578 579 // Restore binding (and interaction) states. 580 for (var bindingIndex = 0; bindingIndex < totalBindingCount; ++bindingIndex) 581 { 582 ref var newBindingState = ref memory.bindingStates[bindingIndex]; 583 if (newBindingState.isPartOfComposite) 584 { 585 // Bindings that are part of composites get enabled through the composite itself. 586 continue; 587 } 588 589 // For composites, bring magnitudes along. 590 if (newBindingState.isComposite) 591 { 592 var compositeIndex = newBindingState.compositeOrCompositeBindingIndex; 593 memory.compositeMagnitudes[compositeIndex] = oldState.compositeMagnitudes[compositeIndex]; 594 } 595 596 var actionIndex = newBindingState.actionIndex; 597 if (actionIndex == kInvalidIndex) 598 { 599 // Binding is not targeting an action. 600 continue; 601 } 602 603 // Skip if action is disabled. 604 ref var newActionState = ref actionStates[actionIndex]; 605 if (newActionState.isDisabled) 606 continue; 607 608 // For all bindings to actions that are enabled, we flip on initial state checks to make sure 609 // we're checking the action's current state against the most up-to-date actuation state of controls. 610 // NOTE: We're only restore execution state for currently active controls. So, if there were multiple 611 // concurrent actuations on an action that was in progress, we let initial state checks restore 612 // relevant state. 613 newBindingState.initialStateCheckPending = newBindingState.wantsInitialStateCheck; 614 615 // Enable all controls on the binding. 616 EnableControls(newBindingState.mapIndex, newBindingState.controlStartIndex, 617 newBindingState.controlCount); 618 619 // For the remainder of what we do, we need binding indices to be stable. 620 if (isFullResolve) 621 continue; 622 623 ref var oldBindingState = ref memory.bindingStates[bindingIndex]; 624 newBindingState.triggerEventIdForComposite = oldBindingState.triggerEventIdForComposite; 625 626 // If we only re-resolved controls and the action was in progress from the binding we're currently 627 // looking at and we still have the control that was driving the action, we can simply keep the 628 // action going from its previous state. However, control indices may have shifted (devices may have been added 629 // or removed) so we need to be careful to update those. Other indices (bindings, actions, maps, etc.) 630 // are guaranteed to still match. 631 ref var oldActionState = ref oldState.actionStates[actionIndex]; 632 if (bindingIndex == oldActionState.bindingIndex && oldActionState.phase.IsInProgress() && 633 activeControls.Count > 0 && activeControls[oldActionState.controlIndex] != null) 634 { 635 var control = activeControls[oldActionState.controlIndex]; 636 637 // Find the new control index. Binding index is guaranteed to be the same, 638 // so we can simply look on the binding for where the control is now. 639 var newControlIndex = FindControlIndexOnBinding(bindingIndex, control); 640 641 // This assert is used by test: Actions_ActiveBindingsHaveCorrectBindingIndicesAfterBindingResolution 642 Debug.Assert(newControlIndex != kInvalidIndex, "Could not find active control after binding resolution"); 643 if (newControlIndex != kInvalidIndex) 644 { 645 newActionState.phase = oldActionState.phase; 646 newActionState.controlIndex = newControlIndex; 647 newActionState.magnitude = oldActionState.magnitude; 648 newActionState.interactionIndex = oldActionState.interactionIndex; 649 650 memory.controlMagnitudes[newControlIndex] = oldActionState.magnitude; 651 } 652 653 // Also bring over interaction states. 654 Debug.Assert(newBindingState.interactionCount == oldBindingState.interactionCount, 655 "Interaction count on binding must not have changed when doing a control-only resolve"); 656 for (var n = 0; n < newBindingState.interactionCount; ++n) 657 { 658 ref var oldInteractionState = ref oldState.interactionStates[oldBindingState.interactionStartIndex + n]; 659 if (!oldInteractionState.phase.IsInProgress()) 660 continue; 661 662 control = activeControls[oldInteractionState.triggerControlIndex]; 663 if (control == null) 664 continue; 665 666 newControlIndex = FindControlIndexOnBinding(bindingIndex, control); 667 Debug.Assert(newControlIndex != kInvalidIndex, "Could not find active control on interaction after binding resolution"); 668 669 ref var newInteractionState = ref interactionStates[newBindingState.interactionStartIndex + n]; 670 newInteractionState.phase = oldInteractionState.phase; 671 newInteractionState.performedTime = oldInteractionState.performedTime; 672 newInteractionState.startTime = oldInteractionState.startTime; 673 newInteractionState.triggerControlIndex = newControlIndex; 674 675 // If there was a running timeout on the interaction, resume it now. 676 if (oldInteractionState.isTimerRunning) 677 { 678 var trigger = new TriggerState 679 { 680 mapIndex = newBindingState.mapIndex, 681 controlIndex = newControlIndex, 682 bindingIndex = bindingIndex, 683 time = oldInteractionState.timerStartTime, 684 interactionIndex = newBindingState.interactionStartIndex + n 685 }; 686 StartTimeout(oldInteractionState.timerDuration, ref trigger); 687 688 newInteractionState.totalTimeoutCompletionDone = oldInteractionState.totalTimeoutCompletionDone; 689 newInteractionState.totalTimeoutCompletionTimeRemaining = oldInteractionState.totalTimeoutCompletionTimeRemaining; 690 } 691 } 692 } 693 } 694 695 // Make sure we get an initial state check. 696 HookOnBeforeUpdate(); 697 698 // Let listeners know we have changed controls. 699 NotifyListenersOfActionChange(InputActionChange.BoundControlsChanged); 700 701 // For a full resolve, we will have temporarily disabled actions and reenabled them now. 702 // Let listeners now. 703 if (isFullResolve && s_GlobalState.onActionChange.length > 0) 704 { 705 for (var i = 0; i < totalMapCount; ++i) 706 { 707 var map = maps[i]; 708 if (map.m_SingletonAction == null && map.m_EnabledActionsCount == map.m_Actions.LengthSafe()) 709 { 710 NotifyListenersOfActionChange(InputActionChange.ActionMapEnabled, map); 711 } 712 else 713 { 714 var actions = map.actions; 715 foreach (var action in actions) 716 if (action.enabled) 717 NotifyListenersOfActionChange(InputActionChange.ActionEnabled, action); 718 } 719 } 720 } 721 } 722 723 // Return true if the action that bindingIndex is bound to is currently driven from the given control 724 // -OR- if any of the interactions on the binding are currently driven from the control. 725 private bool IsActiveControl(int bindingIndex, int controlIndex) 726 { 727 ref var bindingState = ref bindingStates[bindingIndex]; 728 var actionIndex = bindingState.actionIndex; 729 if (actionIndex == kInvalidIndex) 730 return false; 731 if (actionStates[actionIndex].controlIndex == controlIndex) 732 return true; 733 for (var i = 0; i < bindingState.interactionCount; ++i) 734 if (interactionStates[bindingStates->interactionStartIndex + i].triggerControlIndex == controlIndex) 735 return true; 736 return false; 737 } 738 739 private int FindControlIndexOnBinding(int bindingIndex, InputControl control) 740 { 741 var controlStartIndex = bindingStates[bindingIndex].controlStartIndex; 742 var controlCount = bindingStates[bindingIndex].controlCount; 743 744 for (var n = 0; n < controlCount; ++n) 745 { 746 if (control == controls[controlStartIndex + n]) 747 return controlStartIndex + n; 748 } 749 750 return kInvalidIndex; 751 } 752 753 private void ResetActionStatesDrivenBy(InputDevice device) 754 { 755 using (InputActionRebindingExtensions.DeferBindingResolution()) 756 { 757 for (var actionIndex = 0; actionIndex < totalActionCount; ++actionIndex) 758 { 759 var actionState = &actionStates[actionIndex]; 760 761 // Skip actions that aren't in progress. 762 if (actionState->phase == InputActionPhase.Waiting || actionState->phase == InputActionPhase.Disabled) 763 continue; 764 765 // Skip actions not driven from this device. 766 if (actionState->isPassThrough) 767 { 768 // Pass-through actions are not driven from specific controls yet still benefit 769 // from being able to observe resets. So for these, we need to check all bound controls, 770 // not just the one that happen to trigger last. 771 if (!IsActionBoundToControlFromDevice(device, actionIndex)) 772 continue; 773 } 774 else 775 { 776 // For button and value actions, we go by whatever is currently driving the action. 777 778 var controlIndex = actionState->controlIndex; 779 if (controlIndex == -1) 780 continue; 781 var control = controls[controlIndex]; 782 if (control.device != device) 783 continue; 784 } 785 786 // Reset. 787 ResetActionState(actionIndex); 788 } 789 } 790 } 791 792 private bool IsActionBoundToControlFromDevice(InputDevice device, int actionIndex) 793 { 794 var usesControlFromDevice = false; 795 var bindingStartIndex = GetActionBindingStartIndexAndCount(actionIndex, out var bindingCount); 796 for (var i = 0; i < bindingCount; ++i) 797 { 798 var bindingIndex = memory.actionBindingIndices[bindingStartIndex + i]; 799 var controlCount = bindingStates[bindingIndex].controlCount; 800 var controlStartIndex = bindingStates[bindingIndex].controlStartIndex; 801 802 for (var n = 0; n < controlCount; ++n) 803 { 804 var control = controls[controlStartIndex + n]; 805 if (control.device == device) 806 { 807 usesControlFromDevice = true; 808 break; 809 } 810 } 811 } 812 813 return usesControlFromDevice; 814 } 815 816 /// <summary> 817 /// Reset the trigger state of the given action such that the action has no record of being triggered. 818 /// </summary> 819 /// <param name="actionIndex">Action whose state to reset.</param> 820 /// <param name="toPhase">Phase to reset the action to. Must be either <see cref="InputActionPhase.Waiting"/> 821 /// or <see cref="InputActionPhase.Disabled"/>. Other phases cannot be transitioned to through resets.</param> 822 /// <param name="hardReset">If true, also wipe state such as for <see cref="InputAction.WasPressedThisFrame"/> which normally 823 /// persists even if an action is disabled.</param> 824 public void ResetActionState(int actionIndex, InputActionPhase toPhase = InputActionPhase.Waiting, bool hardReset = false) 825 { 826 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, "Action index out of range when resetting action"); 827 Debug.Assert(toPhase == InputActionPhase.Waiting || toPhase == InputActionPhase.Disabled, 828 "Phase must be Waiting or Disabled"); 829 830 // If the action in started or performed phase, cancel it first. 831 var actionState = &actionStates[actionIndex]; 832 if (actionState->phase != InputActionPhase.Waiting && actionState->phase != InputActionPhase.Disabled) 833 { 834 // Cancellation calls should receive current time. 835 actionState->time = InputState.currentTime; 836 837 // If the action got triggered from an interaction, go and reset all interactions on the binding 838 // that got triggered. 839 if (actionState->interactionIndex != kInvalidIndex) 840 { 841 var bindingIndex = actionState->bindingIndex; 842 if (bindingIndex != kInvalidIndex) 843 { 844 var mapIndex = actionState->mapIndex; 845 var interactionCount = bindingStates[bindingIndex].interactionCount; 846 var interactionStartIndex = bindingStates[bindingIndex].interactionStartIndex; 847 848 for (var i = 0; i < interactionCount; ++i) 849 { 850 var interactionIndex = interactionStartIndex + i; 851 ResetInteractionStateAndCancelIfNecessary(mapIndex, bindingIndex, interactionIndex, phaseAfterCanceled: toPhase); 852 } 853 } 854 } 855 else 856 { 857 // No interactions. Cancel the action directly. 858 859 Debug.Assert(actionState->bindingIndex != kInvalidIndex, "Binding index on trigger state is invalid"); 860 Debug.Assert(bindingStates[actionState->bindingIndex].interactionCount == 0, 861 "Action has been triggered but apparently not from an interaction yet there's interactions on the binding that got triggered?!?"); 862 863 if (actionState->phase != InputActionPhase.Canceled) 864 ChangePhaseOfAction(InputActionPhase.Canceled, ref actionStates[actionIndex], 865 phaseAfterPerformedOrCanceled: toPhase); 866 } 867 } 868 869 // Wipe state. 870 actionState->phase = toPhase; 871 actionState->controlIndex = kInvalidIndex; 872 actionState->bindingIndex = memory.actionBindingIndices[memory.actionBindingIndicesAndCounts[actionIndex]]; 873 actionState->interactionIndex = kInvalidIndex; 874 actionState->startTime = 0; 875 actionState->time = 0; 876 actionState->hasMultipleConcurrentActuations = false; 877 actionState->inProcessing = false; 878 actionState->isPressed = false; 879 880 // For "hard resets", wipe state we don't normally wipe. This resets things such as WasPressedThisFrame(). 881 if (hardReset) 882 { 883 actionState->lastCanceledInUpdate = default; 884 actionState->lastPerformedInUpdate = default; 885 actionState->lastCompletedInUpdate = default; 886 actionState->pressedInUpdate = default; 887 actionState->releasedInUpdate = default; 888 actionState->frame = default; 889 } 890 891 Debug.Assert(!actionState->isStarted, "Cannot reset an action to started phase"); 892 Debug.Assert(!actionState->isPerformed, "Cannot reset an action to performed phase"); 893 Debug.Assert(!actionState->isCanceled, "Cannot reset an action to canceled phase"); 894 } 895 896 public ref TriggerState FetchActionState(InputAction action) 897 { 898 Debug.Assert(action != null, "Action must not be null"); 899 Debug.Assert(action.m_ActionMap != null, "Action must have an action map"); 900 Debug.Assert(action.m_ActionMap.m_MapIndexInState != kInvalidIndex, "Action must have index set"); 901 Debug.Assert(maps.Contains(action.m_ActionMap), "Action map must be contained in state"); 902 Debug.Assert(action.m_ActionIndexInState >= 0 && action.m_ActionIndexInState < totalActionCount, "Action index is out of range"); 903 904 return ref actionStates[action.m_ActionIndexInState]; 905 } 906 907 public ActionMapIndices FetchMapIndices(InputActionMap map) 908 { 909 Debug.Assert(map != null, "Must must not be null"); 910 Debug.Assert(maps.Contains(map), "Map must be contained in state"); 911 return mapIndices[map.m_MapIndexInState]; 912 } 913 914 public void EnableAllActions(InputActionMap map) 915 { 916 Debug.Assert(map != null, "Map must not be null"); 917 Debug.Assert(map.m_Actions != null, "Map must have actions"); 918 Debug.Assert(maps.Contains(map), "Map must be contained in state"); 919 920 // Enable all controls in map that aren't already enabled. 921 EnableControls(map); 922 923 // Put all actions that aren't already enabled into waiting state. 924 var mapIndex = map.m_MapIndexInState; 925 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index on InputActionMap is out of range"); 926 var actionCount = mapIndices[mapIndex].actionCount; 927 var actionStartIndex = mapIndices[mapIndex].actionStartIndex; 928 for (var i = 0; i < actionCount; ++i) 929 { 930 var actionIndex = actionStartIndex + i; 931 var actionState = &actionStates[actionIndex]; 932 if (actionState->isDisabled) 933 actionState->phase = InputActionPhase.Waiting; 934 actionState->inProcessing = false; 935 } 936 map.m_EnabledActionsCount = actionCount; 937 938 HookOnBeforeUpdate(); 939 940 // Make sure that if we happen to get here with one of the hidden action maps we create for singleton 941 // action, we notify on the action, not the hidden map. 942 if (map.m_SingletonAction != null) 943 NotifyListenersOfActionChange(InputActionChange.ActionEnabled, map.m_SingletonAction); 944 else 945 NotifyListenersOfActionChange(InputActionChange.ActionMapEnabled, map); 946 } 947 948 private void EnableControls(InputActionMap map) 949 { 950 Debug.Assert(map != null, "Map must not be null"); 951 Debug.Assert(map.m_Actions != null, "Map must have actions"); 952 Debug.Assert(maps.Contains(map), "Map must be contained in state"); 953 954 var mapIndex = map.m_MapIndexInState; 955 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index on InputActionMap is out of range"); 956 957 // Install state monitors for all controls. 958 var controlCount = mapIndices[mapIndex].controlCount; 959 var controlStartIndex = mapIndices[mapIndex].controlStartIndex; 960 if (controlCount > 0) 961 EnableControls(mapIndex, controlStartIndex, controlCount); 962 } 963 964 public void EnableSingleAction(InputAction action) 965 { 966 Debug.Assert(action != null, "Action must not be null"); 967 Debug.Assert(action.m_ActionMap != null, "Action must have action map"); 968 Debug.Assert(maps.Contains(action.m_ActionMap), "Action map must be contained in state"); 969 970 EnableControls(action); 971 972 // Put action into waiting state. 973 var actionIndex = action.m_ActionIndexInState; 974 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, 975 "Action index out of range when enabling single action"); 976 actionStates[actionIndex].phase = InputActionPhase.Waiting; 977 ++action.m_ActionMap.m_EnabledActionsCount; 978 979 HookOnBeforeUpdate(); 980 NotifyListenersOfActionChange(InputActionChange.ActionEnabled, action); 981 } 982 983 private void EnableControls(InputAction action) 984 { 985 Debug.Assert(action != null, "Action must not be null"); 986 Debug.Assert(action.m_ActionMap != null, "Action must have action map"); 987 Debug.Assert(maps.Contains(action.m_ActionMap), "Map must be contained in state"); 988 989 var actionIndex = action.m_ActionIndexInState; 990 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, 991 "Action index out of range when enabling controls"); 992 993 var map = action.m_ActionMap; 994 var mapIndex = map.m_MapIndexInState; 995 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index out of range in EnableControls"); 996 997 // Go through all bindings in the map and for all that belong to the given action, 998 // enable the associated controls. 999 var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; 1000 var bindingCount = mapIndices[mapIndex].bindingCount; 1001 var bindingStatesPtr = memory.bindingStates; 1002 for (var i = 0; i < bindingCount; ++i) 1003 { 1004 var bindingIndex = bindingStartIndex + i; 1005 var bindingState = &bindingStatesPtr[bindingIndex]; 1006 if (bindingState->actionIndex != actionIndex) 1007 continue; 1008 1009 // Composites enable en-bloc through the composite binding itself. 1010 if (bindingState->isPartOfComposite) 1011 continue; 1012 1013 var controlCount = bindingState->controlCount; 1014 if (controlCount == 0) 1015 continue; 1016 1017 EnableControls(mapIndex, bindingState->controlStartIndex, controlCount); 1018 } 1019 } 1020 1021 public void DisableAllActions(InputActionMap map) 1022 { 1023 Debug.Assert(map != null, "Map must not be null"); 1024 Debug.Assert(map.m_Actions != null, "Map must have actions"); 1025 Debug.Assert(maps.Contains(map), "Map must be contained in state"); 1026 1027 DisableControls(map); 1028 1029 // Mark all actions as disabled. 1030 var mapIndex = map.m_MapIndexInState; 1031 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index out of range in DisableAllActions"); 1032 var actionStartIndex = mapIndices[mapIndex].actionStartIndex; 1033 var actionCount = mapIndices[mapIndex].actionCount; 1034 var allActionsEnabled = map.m_EnabledActionsCount == actionCount; 1035 for (var i = 0; i < actionCount; ++i) 1036 { 1037 var actionIndex = actionStartIndex + i; 1038 if (actionStates[actionIndex].phase != InputActionPhase.Disabled) 1039 { 1040 ResetActionState(actionIndex, toPhase: InputActionPhase.Disabled); 1041 if (!allActionsEnabled) 1042 NotifyListenersOfActionChange(InputActionChange.ActionDisabled, map.m_Actions[i]); 1043 } 1044 } 1045 map.m_EnabledActionsCount = 0; 1046 1047 // Make sure that if we happen to get here with one of the hidden action maps we create for singleton 1048 // action, we notify on the action, not the hidden map. 1049 if (map.m_SingletonAction != null) 1050 NotifyListenersOfActionChange(InputActionChange.ActionDisabled, map.m_SingletonAction); 1051 else if (allActionsEnabled) 1052 NotifyListenersOfActionChange(InputActionChange.ActionMapDisabled, map); 1053 } 1054 1055 public void DisableControls(InputActionMap map) 1056 { 1057 Debug.Assert(map != null, "Map must not be null"); 1058 Debug.Assert(map.m_Actions != null, "Map must have actions"); 1059 Debug.Assert(maps.Contains(map), "Map must be contained in state"); 1060 1061 var mapIndex = map.m_MapIndexInState; 1062 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index out of range in DisableControls(InputActionMap)"); 1063 1064 // Remove state monitors from all controls. 1065 var controlCount = mapIndices[mapIndex].controlCount; 1066 var controlStartIndex = mapIndices[mapIndex].controlStartIndex; 1067 if (controlCount > 0) 1068 DisableControls(mapIndex, controlStartIndex, controlCount); 1069 } 1070 1071 public void DisableSingleAction(InputAction action) 1072 { 1073 Debug.Assert(action != null, "Action must not be null"); 1074 Debug.Assert(action.m_ActionMap != null, "Action must have action map"); 1075 Debug.Assert(maps.Contains(action.m_ActionMap), "Action map must be contained in state"); 1076 1077 DisableControls(action); 1078 ResetActionState(action.m_ActionIndexInState, toPhase: InputActionPhase.Disabled); 1079 --action.m_ActionMap.m_EnabledActionsCount; 1080 1081 NotifyListenersOfActionChange(InputActionChange.ActionDisabled, action); 1082 } 1083 1084 private void DisableControls(InputAction action) 1085 { 1086 Debug.Assert(action != null, "Action must not be null"); 1087 Debug.Assert(action.m_ActionMap != null, "Action must have action map"); 1088 Debug.Assert(maps.Contains(action.m_ActionMap), "Action map must be contained in state"); 1089 1090 var actionIndex = action.m_ActionIndexInState; 1091 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, 1092 "Action index out of range when disabling controls"); 1093 1094 var map = action.m_ActionMap; 1095 var mapIndex = map.m_MapIndexInState; 1096 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index out of range in DisableControls(InputAction)"); 1097 1098 // Go through all bindings in the map and for all that belong to the given action, 1099 // disable the associated controls. 1100 var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; 1101 var bindingCount = mapIndices[mapIndex].bindingCount; 1102 var bindingStatesPtr = memory.bindingStates; 1103 for (var i = 0; i < bindingCount; ++i) 1104 { 1105 var bindingIndex = bindingStartIndex + i; 1106 var bindingState = &bindingStatesPtr[bindingIndex]; 1107 if (bindingState->actionIndex != actionIndex) 1108 continue; 1109 1110 // Composites enable en-bloc through the composite binding itself. 1111 if (bindingState->isPartOfComposite) 1112 continue; 1113 1114 var controlCount = bindingState->controlCount; 1115 if (controlCount == 0) 1116 continue; 1117 1118 DisableControls(mapIndex, bindingState->controlStartIndex, controlCount); 1119 } 1120 } 1121 1122 ////REVIEW: can we have a method on InputManager doing this in bulk? 1123 1124 ////NOTE: This must not enable only a partial set of controls on a binding (currently we have no setup that would lead to that) 1125 private void EnableControls(int mapIndex, int controlStartIndex, int numControls) 1126 { 1127 Debug.Assert(controls != null, "State must have controls"); 1128 Debug.Assert(controlStartIndex >= 0 && (controlStartIndex < totalControlCount || numControls == 0), 1129 "Control start index out of range"); 1130 Debug.Assert(controlStartIndex + numControls <= totalControlCount, "Control range out of bounds"); 1131 1132 var manager = InputSystem.s_Manager; 1133 for (var i = 0; i < numControls; ++i) 1134 { 1135 var controlIndex = controlStartIndex + i; 1136 1137 // We don't want to add multiple state monitors for the same control. This can happen if enabling 1138 // single actions is mixed with enabling actions maps containing them. 1139 if (IsControlEnabled(controlIndex)) 1140 continue; 1141 1142 var bindingIndex = controlIndexToBindingIndex[controlIndex]; 1143 var mapControlAndBindingIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex); 1144 1145 var bindingStatePtr = &bindingStates[bindingIndex]; 1146 if (bindingStatePtr->wantsInitialStateCheck) 1147 SetInitialStateCheckPending(bindingStatePtr, true); 1148 manager.AddStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex, controlGroupingAndComplexity[controlIndex * 2]); 1149 1150 SetControlEnabled(controlIndex, true); 1151 } 1152 } 1153 1154 private void DisableControls(int mapIndex, int controlStartIndex, int numControls) 1155 { 1156 Debug.Assert(controls != null, "State must have controls"); 1157 Debug.Assert(controlStartIndex >= 0 && (controlStartIndex < totalControlCount || numControls == 0), 1158 "Control start index out of range"); 1159 Debug.Assert(controlStartIndex + numControls <= totalControlCount, "Control range out of bounds"); 1160 1161 var manager = InputSystem.s_Manager; 1162 for (var i = 0; i < numControls; ++i) 1163 { 1164 var controlIndex = controlStartIndex + i; 1165 ////TODO: This can be done much more efficiently by at least going byte by byte in the mask instead of just bit by bit 1166 if (!IsControlEnabled(controlIndex)) 1167 continue; 1168 1169 var bindingIndex = controlIndexToBindingIndex[controlIndex]; 1170 var mapControlAndBindingIndex = ToCombinedMapAndControlAndBindingIndex(mapIndex, controlIndex, bindingIndex); 1171 var bindingStatePtr = &bindingStates[bindingIndex]; 1172 if (bindingStatePtr->wantsInitialStateCheck) 1173 SetInitialStateCheckPending(bindingStatePtr, false); 1174 manager.RemoveStateChangeMonitor(controls[controlIndex], this, mapControlAndBindingIndex); 1175 1176 // Ensure that pressTime is reset if the composite binding is reenable. ISXB-505 1177 bindingStatePtr->pressTime = default; 1178 1179 SetControlEnabled(controlIndex, false); 1180 } 1181 } 1182 1183 public void SetInitialStateCheckPending(int actionIndex, bool value = true) 1184 { 1185 var mapIndex = actionStates[actionIndex].mapIndex; 1186 var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; 1187 var bindingCount = mapIndices[mapIndex].bindingCount; 1188 for (var i = 0; i < bindingCount; ++i) 1189 { 1190 ref var bindingState = ref bindingStates[bindingStartIndex + i]; 1191 if (bindingState.actionIndex == actionIndex && !bindingState.isPartOfComposite) 1192 bindingState.initialStateCheckPending = value; 1193 } 1194 } 1195 1196 private void SetInitialStateCheckPending(BindingState* bindingStatePtr, bool value) 1197 { 1198 if (bindingStatePtr->isPartOfComposite) 1199 { 1200 // For composites, we always flag the composite itself as wanting an initial state check. This 1201 // way, we don't have to worry about triggering the composite multiple times when several of its 1202 // controls are actuated. 1203 var compositeIndex = bindingStatePtr->compositeOrCompositeBindingIndex; 1204 bindingStates[compositeIndex].initialStateCheckPending = value; 1205 } 1206 else 1207 { 1208 bindingStatePtr->initialStateCheckPending = value; 1209 } 1210 } 1211 1212 private bool IsControlEnabled(int controlIndex) 1213 { 1214 var intIndex = controlIndex / 32; 1215 var mask = 1U << (controlIndex % 32); 1216 return (enabledControls[intIndex] & mask) != 0; 1217 } 1218 1219 private void SetControlEnabled(int controlIndex, bool state) 1220 { 1221 var intIndex = controlIndex / 32; 1222 var mask = 1U << (controlIndex % 32); 1223 1224 if (state) 1225 enabledControls[intIndex] |= mask; 1226 else 1227 enabledControls[intIndex] &= ~mask; 1228 } 1229 1230 private void HookOnBeforeUpdate() 1231 { 1232 if (m_OnBeforeUpdateHooked) 1233 return; 1234 1235 if (m_OnBeforeUpdateDelegate == null) 1236 m_OnBeforeUpdateDelegate = OnBeforeInitialUpdate; 1237 InputSystem.s_Manager.onBeforeUpdate += m_OnBeforeUpdateDelegate; 1238 m_OnBeforeUpdateHooked = true; 1239 } 1240 1241 private void UnhookOnBeforeUpdate() 1242 { 1243 if (!m_OnBeforeUpdateHooked) 1244 return; 1245 1246 InputSystem.s_Manager.onBeforeUpdate -= m_OnBeforeUpdateDelegate; 1247 m_OnBeforeUpdateHooked = false; 1248 } 1249 1250 // We hook this into InputManager.onBeforeUpdate every time actions are enabled and then take it off 1251 // the list after the first call. Inside here we check whether any actions we enabled already have 1252 // non-default state on bound controls. 1253 // 1254 // NOTE: We do this as a callback from onBeforeUpdate rather than directly when the action is enabled 1255 // to ensure that the callbacks happen during input processing and not randomly from wherever 1256 // an action happens to be enabled. 1257 private void OnBeforeInitialUpdate() 1258 { 1259 if (InputState.currentUpdateType == InputUpdateType.BeforeRender 1260 #if UNITY_EDITOR 1261 || InputState.currentUpdateType == InputUpdateType.Editor 1262 #endif 1263 ) 1264 return; 1265 1266 // Remove us from the callback as the processing we're doing here is a one-time thing. 1267 UnhookOnBeforeUpdate(); 1268 1269 k_InputInitialActionStateCheckMarker.Begin(); 1270 1271 // Use current time as time of control state change. 1272 var time = InputState.currentTime; 1273 1274 ////REVIEW: should we store this data in a separate place rather than go through all bindingStates? 1275 1276 // Go through all binding states and for every binding that needs an initial state check, 1277 // go through all bound controls and for each one that isn't in its default state, pretend 1278 // that the control just got actuated. 1279 var manager = InputSystem.s_Manager; 1280 for (var bindingIndex = 0; bindingIndex < totalBindingCount; ++bindingIndex) 1281 { 1282 ref var bindingState = ref bindingStates[bindingIndex]; 1283 if (!bindingState.initialStateCheckPending) 1284 continue; 1285 1286 Debug.Assert(!bindingState.isPartOfComposite, "Initial state check flag must be set on composite, not on its parts"); 1287 bindingState.initialStateCheckPending = false; 1288 1289 var controlStartIndex = bindingState.controlStartIndex; 1290 var controlCount = bindingState.controlCount; 1291 1292 var isComposite = bindingState.isComposite; 1293 var didFindControlToSignal = false; 1294 for (var n = 0; n < controlCount; ++n) 1295 { 1296 var controlIndex = controlStartIndex + n; 1297 var control = controls[controlIndex]; 1298 1299 // Leave any control alone that is already driving an interaction and/or action. 1300 if (IsActiveControl(bindingIndex, controlIndex)) 1301 continue; 1302 1303 if (!control.CheckStateIsAtDefault()) 1304 { 1305 // Update press times. 1306 if (control.IsValueConsideredPressed(control.magnitude)) 1307 { 1308 // ReSharper disable once CompareOfFloatsByEqualityOperator 1309 if (bindingState.pressTime == default || bindingState.pressTime > time) 1310 bindingState.pressTime = time; 1311 } 1312 1313 // For composites, any one actuated control will lead to the composite being 1314 // processed as a whole so we can stop here. This also ensures that we are 1315 // not triggering the composite repeatedly if there are multiple actuated 1316 // controls bound to its parts. 1317 if (isComposite && didFindControlToSignal) 1318 continue; 1319 1320 manager.SignalStateChangeMonitor(control, this); 1321 didFindControlToSignal = true; 1322 } 1323 } 1324 } 1325 manager.FireStateChangeNotifications(); 1326 1327 k_InputInitialActionStateCheckMarker.End(); 1328 } 1329 1330 // Called from InputManager when one of our state change monitors has fired. 1331 // Tells us the time of the change *according to the state events coming in*. 1332 // Also tells us which control of the controls we are binding to triggered the 1333 // change and relays the binding index we gave it when we called AddChangeMonitor. 1334 void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl control, double time, 1335 InputEventPtr eventPtr, long mapControlAndBindingIndex) 1336 { 1337 #if UNITY_EDITOR 1338 if (InputState.currentUpdateType == InputUpdateType.Editor) 1339 return; 1340 #endif 1341 1342 SplitUpMapAndControlAndBindingIndex(mapControlAndBindingIndex, out var mapIndex, out var controlIndex, out var bindingIndex); 1343 ProcessControlStateChange(mapIndex, controlIndex, bindingIndex, time, eventPtr); 1344 } 1345 1346 void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double time, 1347 long mapControlAndBindingIndex, int interactionIndex) 1348 { 1349 SplitUpMapAndControlAndBindingIndex(mapControlAndBindingIndex, out var mapIndex, out var controlIndex, out var bindingIndex); 1350 ProcessTimeout(time, mapIndex, controlIndex, bindingIndex, interactionIndex); 1351 } 1352 1353 /// <summary> 1354 /// Bit pack the mapIndex, controlIndex, bindingIndex and complexity components into a single long monitor index value. 1355 /// </summary> 1356 /// <param name="mapIndex">The mapIndex value to pack.</param> 1357 /// <param name="controlIndex">The controlIndex value to pack.</param> 1358 /// <param name="bindingIndex">The bindingIndex value to pack..</param> 1359 /// <remarks> 1360 /// We mangle the various indices we use into a single long for association with state change 1361 /// monitors. While we could look up map and binding indices from control indices, keeping 1362 /// all the information together avoids having to unnecessarily jump around in memory to grab 1363 /// the various pieces of data. 1364 /// The complexity component is implicitly derived and does not need to be passed as an argument. 1365 /// </remarks> 1366 private long ToCombinedMapAndControlAndBindingIndex(int mapIndex, int controlIndex, int bindingIndex) 1367 { 1368 // We have limits on the numbers of maps, controls, and bindings we allow in any single 1369 // action state (see TriggerState.kMaxNumXXX). 1370 var complexity = controlGroupingAndComplexity[controlIndex * 2 + 1]; 1371 var result = (long)controlIndex; 1372 result |= (long)bindingIndex << 24; 1373 result |= (long)mapIndex << 40; 1374 result |= (long)complexity << 48; 1375 return result; 1376 } 1377 1378 /// <summary> 1379 /// Extract the mapIndex, controlIndex and bindingIndex components from the provided bit packed argument (monitor index). 1380 /// </summary> 1381 /// <param name="mapControlAndBindingIndex">Represents a monitor index, which is a bit packed field containing multiple components.</param> 1382 /// <param name="mapIndex">Will hold the extracted mapIndex value after the function completes.</param> 1383 /// <param name="controlIndex">Will hold the extracted controlIndex value after the function completes.</param> 1384 /// <param name="bindingIndex">Will hold the extracted bindingIndex value after the function completes.</param> 1385 private void SplitUpMapAndControlAndBindingIndex(long mapControlAndBindingIndex, out int mapIndex, 1386 out int controlIndex, out int bindingIndex) 1387 { 1388 controlIndex = (int)(mapControlAndBindingIndex & 0x00ffffff); 1389 bindingIndex = (int)((mapControlAndBindingIndex >> 24) & 0xffff); 1390 mapIndex = (int)((mapControlAndBindingIndex >> 40) & 0xff); 1391 } 1392 1393 /// <summary> 1394 /// Extract the 'complexity' component from the provided bit packed argument (monitor index). 1395 /// </summary> 1396 /// <param name="mapControlAndBindingIndex">Represents a monitor index, which is a bit packed field containing multiple components.</param> 1397 internal static int GetComplexityFromMonitorIndex(long mapControlAndBindingIndex) 1398 { 1399 return (int)((mapControlAndBindingIndex >> 48) & 0xff); 1400 } 1401 1402 /// <summary> 1403 /// Process a state change that has happened in one of the controls attached 1404 /// to this action map state. 1405 /// </summary> 1406 /// <param name="mapIndex">Index of the action map to which the binding belongs.</param> 1407 /// <param name="controlIndex">Index of the control that changed state.</param> 1408 /// <param name="bindingIndex">Index of the binding associated with the given control.</param> 1409 /// <param name="time">The timestamp associated with the state change (comes from the state change event).</param> 1410 /// <param name="eventPtr">Event (if any) that triggered the state change.</param> 1411 /// <remarks> 1412 /// This is where we end up if one of the state monitors we've put in the system has triggered. 1413 /// From here we go back to the associated binding and then let it figure out what the state change 1414 /// means for it. 1415 /// 1416 /// Note that we get called for any change in state even if the change in state does not actually 1417 /// result in a change of value on the respective control. 1418 /// </remarks> 1419 private void ProcessControlStateChange(int mapIndex, int controlIndex, int bindingIndex, double time, InputEventPtr eventPtr) 1420 { 1421 Debug.Assert(mapIndex >= 0 && mapIndex < totalMapCount, "Map index out of range in ProcessControlStateChange"); 1422 Debug.Assert(controlIndex >= 0 && controlIndex < totalControlCount, "Control index out of range"); 1423 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 1424 1425 using (InputActionRebindingExtensions.DeferBindingResolution()) 1426 { 1427 // Callbacks can do pretty much anything and thus trigger arbitrary state/configuration 1428 // changes in the system. We have to ensure that while we're executing callbacks, our 1429 // current InputActionState is not getting changed from under us. We dictate that while 1430 // m_InProcessControlStateChange is true, no binding resolution can be triggered on the state and 1431 // it cannot be destroyed. 1432 // 1433 // This is also why we defer binding resolution above. If there is a configuration change 1434 // triggered by an action callback, the state will be marked dirty and re-resolved after 1435 // we have completed the callback. 1436 m_InProcessControlStateChange = true; 1437 m_CurrentlyProcessingThisEvent = eventPtr; 1438 1439 try 1440 { 1441 var bindingStatePtr = &bindingStates[bindingIndex]; 1442 var actionIndex = bindingStatePtr->actionIndex; 1443 1444 var trigger = new TriggerState 1445 { 1446 mapIndex = mapIndex, 1447 controlIndex = controlIndex, 1448 bindingIndex = bindingIndex, 1449 interactionIndex = kInvalidIndex, 1450 time = time, 1451 startTime = time, 1452 isPassThrough = actionIndex != kInvalidIndex && actionStates[actionIndex].isPassThrough, 1453 isButton = actionIndex != kInvalidIndex && actionStates[actionIndex].isButton, 1454 }; 1455 1456 // If we have pending initial state checks that will run in the next update, 1457 // force-reset the flag on the control that just triggered. This ensures that we're 1458 // not triggering an action twice from the same state change in case the initial state 1459 // check happens later (see Actions_ValueActionsEnabledInOnEvent_DoNotReactToCurrentStateOfControlTwice). 1460 if (m_OnBeforeUpdateHooked) 1461 bindingStatePtr->initialStateCheckPending = false; 1462 1463 // Store magnitude. We do this once and then only read it from here. 1464 var control = controls[controlIndex]; 1465 trigger.magnitude = control.CheckStateIsAtDefault() ? 0f : control.magnitude; 1466 controlMagnitudes[controlIndex] = trigger.magnitude; 1467 1468 // Update press times. 1469 if (control.IsValueConsideredPressed(trigger.magnitude)) 1470 { 1471 // ReSharper disable once CompareOfFloatsByEqualityOperator 1472 if (bindingStatePtr->pressTime == default || bindingStatePtr->pressTime > trigger.time) 1473 bindingStatePtr->pressTime = trigger.time; 1474 } 1475 1476 // If the binding is part of a composite, check for interactions on the composite 1477 // itself and give them a first shot at processing the value change. 1478 var haveInteractionsOnComposite = false; 1479 var compositeAlreadyTriggered = false; 1480 if (bindingStatePtr->isPartOfComposite) 1481 { 1482 var compositeBindingIndex = bindingStatePtr->compositeOrCompositeBindingIndex; 1483 var compositeBindingPtr = &bindingStates[compositeBindingIndex]; 1484 1485 // If the composite has already been triggered from the very same event set a flag so it isn't triggered again. 1486 // Example: KeyboardState change that includes both A and W key state changes and we're looking 1487 // at a WASD composite binding. There's a state change monitor on both the A and the W 1488 // key and thus the manager will notify us individually of both changes. However, we 1489 // want to perform the action only once. 1490 // NOTE: Do NOT ignore this Event, we still need finish processing the individual button states. 1491 if (!ShouldIgnoreInputOnCompositeBinding(compositeBindingPtr, eventPtr)) 1492 { 1493 // Update magnitude for composite. 1494 var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; 1495 var compositeContext = new InputBindingCompositeContext 1496 { 1497 m_State = this, 1498 m_BindingIndex = compositeBindingIndex 1499 }; 1500 trigger.magnitude = composites[compositeIndex].EvaluateMagnitude(ref compositeContext); 1501 memory.compositeMagnitudes[compositeIndex] = trigger.magnitude; 1502 1503 // Run through interactions on composite. 1504 var interactionCountOnComposite = compositeBindingPtr->interactionCount; 1505 if (interactionCountOnComposite > 0) 1506 { 1507 haveInteractionsOnComposite = true; 1508 ProcessInteractions(ref trigger, 1509 compositeBindingPtr->interactionStartIndex, 1510 interactionCountOnComposite); 1511 } 1512 } 1513 else 1514 { 1515 compositeAlreadyTriggered = true; 1516 } 1517 } 1518 1519 // Check if we have multiple concurrent actuations on the same action. This may lead us 1520 // to ignore certain inputs (e.g. when we get an input of lesser magnitude while already having 1521 // one of higher magnitude) or may even lead us to switch to processing a different binding 1522 // (e.g. when an input of previously greater magnitude has now fallen below the level of another 1523 // ongoing input with now higher magnitude). 1524 // 1525 // If Composite has already been triggered, skip this step; it's unnecessary and could also 1526 // cause a processing issue if we switch to another binding. 1527 var isConflictingInput = false; 1528 if (!compositeAlreadyTriggered) 1529 { 1530 isConflictingInput = IsConflictingInput(ref trigger, actionIndex); 1531 bindingStatePtr = &bindingStates[trigger.bindingIndex]; // IsConflictingInput may switch us to a different binding. 1532 } 1533 1534 // Process button presses/releases. 1535 // We MUST execute this processing even if Composite has already been triggered to ensure button states 1536 // are properly updated (ISXB-746) 1537 if (!isConflictingInput) 1538 ProcessButtonState(ref trigger, actionIndex, bindingStatePtr); 1539 1540 // If we have interactions, let them do all the processing. The presence of an interaction 1541 // essentially bypasses the default phase progression logic of an action. 1542 // Interactions are skipped if compositeAlreadyTriggered is set. 1543 var interactionCount = bindingStatePtr->interactionCount; 1544 if (interactionCount > 0 && !bindingStatePtr->isPartOfComposite) 1545 { 1546 ProcessInteractions(ref trigger, bindingStatePtr->interactionStartIndex, interactionCount); 1547 } 1548 else if (!haveInteractionsOnComposite && !isConflictingInput && !compositeAlreadyTriggered) 1549 { 1550 ProcessDefaultInteraction(ref trigger, actionIndex); 1551 } 1552 } 1553 finally 1554 { 1555 m_InProcessControlStateChange = default; 1556 m_CurrentlyProcessingThisEvent = default; 1557 } 1558 } 1559 } 1560 1561 private void ProcessButtonState(ref TriggerState trigger, int actionIndex, BindingState* bindingStatePtr) 1562 { 1563 var control = controls[trigger.controlIndex]; 1564 var pressPoint = control.isButton 1565 ? ((ButtonControl)control).pressPointOrDefault 1566 : ButtonControl.s_GlobalDefaultButtonPressPoint; 1567 1568 // NOTE: This method relies on conflict resolution happening *first*. Otherwise, we may inadvertently 1569 // detect a "release" from a control that is not actually driving the action. 1570 1571 // Record release time on the binding. 1572 // NOTE: Explicitly look up control magnitude here instead of using trigger.magnitude 1573 // as for part bindings, the trigger will have the magnitude of the whole composite. 1574 var controlActuation = controlMagnitudes[trigger.controlIndex]; 1575 if (controlActuation <= pressPoint * ButtonControl.s_GlobalDefaultButtonReleaseThreshold) 1576 bindingStatePtr->pressTime = 0d; 1577 1578 var actuation = trigger.magnitude; 1579 var actionState = &actionStates[actionIndex]; 1580 if (!actionState->isPressed && actuation >= pressPoint) 1581 { 1582 actionState->pressedInUpdate = InputUpdate.s_UpdateStepCount; 1583 actionState->isPressed = true; 1584 actionState->frame = Time.frameCount; 1585 } 1586 else if (actionState->isPressed) 1587 { 1588 var releasePoint = pressPoint * ButtonControl.s_GlobalDefaultButtonReleaseThreshold; 1589 if (actuation <= releasePoint) 1590 { 1591 actionState->releasedInUpdate = InputUpdate.s_UpdateStepCount; 1592 actionState->isPressed = false; 1593 actionState->frame = Time.frameCount; 1594 } 1595 } 1596 } 1597 1598 /// <summary> 1599 /// Whether the given state change on a composite binding should be ignored. 1600 /// </summary> 1601 /// <param name="binding"></param> 1602 /// <param name="eventPtr"></param> 1603 /// <returns></returns> 1604 /// <remarks> 1605 /// Each state event may change the state of arbitrary many controls on a device and thus may trigger 1606 /// several bindings at once that are part of the same composite binding. We still want to trigger the 1607 /// composite binding only once for the event. 1608 /// 1609 /// To do so, we store the ID of the event on the binding and ignore events if they have the same 1610 /// ID as the one we've already recorded. 1611 /// </remarks> 1612 private static bool ShouldIgnoreInputOnCompositeBinding(BindingState* binding, InputEvent* eventPtr) 1613 { 1614 if (eventPtr == null) 1615 return false; 1616 1617 var eventId = eventPtr->eventId; 1618 if (eventId != 0 && binding->triggerEventIdForComposite == eventId) 1619 return true; 1620 1621 binding->triggerEventIdForComposite = eventId; 1622 return false; 1623 } 1624 1625 /// <summary> 1626 /// Whether the given control state should be ignored. 1627 /// </summary> 1628 /// <param name="trigger"></param> 1629 /// <param name="actionIndex"></param> 1630 /// <returns></returns> 1631 /// <remarks> 1632 /// If an action has multiple controls bound to it, control state changes on the action may conflict with each other. 1633 /// If that happens, we resolve the conflict by always sticking to the most actuated control. 1634 /// 1635 /// Pass-through actions (<see cref="InputActionType.PassThrough"/>) will always bypass conflict resolution and respond 1636 /// to every value change. 1637 /// 1638 /// Actions that are resolved to only a single control will early out of conflict resolution. 1639 /// 1640 /// Actions that are bound to multiple controls but have only one control actuated will early out of conflict 1641 /// resolution as well. 1642 /// 1643 /// Note that conflict resolution here is entirely tied to magnitude. This ignores other qualities that the value 1644 /// of a control may have. For example, one 2D vector may have a similar magnitude to another yet point in an 1645 /// entirely different direction. 1646 /// 1647 /// There are other conflict resolution mechanisms that could be used. For example, we could average the values 1648 /// from all controls. However, it would not necessarily result in more useful conflict resolution and would 1649 /// at the same time be much more expensive. 1650 /// </remarks> 1651 private bool IsConflictingInput(ref TriggerState trigger, int actionIndex) 1652 { 1653 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, 1654 "Action index out of range when checking for conflicting control input"); 1655 1656 // The goal of this method is to provide conflict resolution but do so ONLY if it is 1657 // really needed. In the vast majority of cases, this method should do almost nothing and 1658 // simply return straight away. 1659 1660 // If conflict resolution is disabled on the action, early out. This is the case for pass-through 1661 // actions and for actions that cannot get into an ambiguous state based on the controls they 1662 // are bound to. 1663 var actionState = &actionStates[actionIndex]; 1664 if (!actionState->mayNeedConflictResolution) 1665 return false; 1666 1667 // Anything past here happens only for actions that may have conflicts. 1668 // Anything below here we want to avoid executing whenever we can. 1669 Debug.Assert(actionState->mayNeedConflictResolution); 1670 1671 k_InputActionResolveConflictMarker.Begin(); 1672 1673 // We take a local copy of this value, so we can change it to use the starting control of composites 1674 // for simpler conflict resolution (so composites always use the same value), but still report the actually 1675 // actuated control to the user. 1676 var triggerControlIndex = trigger.controlIndex; 1677 if (bindingStates[trigger.bindingIndex].isPartOfComposite) 1678 { 1679 // For actions that need conflict resolution, we force TriggerState.controlIndex to the 1680 // first control in a composite. Otherwise it becomes much harder to tell if the we have 1681 // multiple concurrent actuations or not. 1682 // Since composites always evaluate as a whole instead of as single controls, having 1683 // triggerControlIndex differ from the state monitor that fired should be fine. 1684 var compositeBindingIndex = bindingStates[trigger.bindingIndex].compositeOrCompositeBindingIndex; 1685 triggerControlIndex = bindingStates[compositeBindingIndex].controlStartIndex; 1686 Debug.Assert(triggerControlIndex >= 0 && triggerControlIndex < totalControlCount, 1687 "Control start index on composite binding out of range"); 1688 } 1689 1690 // Determine which control to consider the one currently associated with the action. 1691 // We do the same thing as for the triggered control and in the case of a composite, 1692 // switch to the first control of the composite. 1693 var actionStateControlIndex = actionState->controlIndex; 1694 if (bindingStates[actionState->bindingIndex].isPartOfComposite) 1695 { 1696 var compositeBindingIndex = bindingStates[actionState->bindingIndex].compositeOrCompositeBindingIndex; 1697 actionStateControlIndex = bindingStates[compositeBindingIndex].controlStartIndex; 1698 } 1699 1700 // Never ignore state changes for actions that aren't currently driven by 1701 // anything. 1702 if (actionStateControlIndex == kInvalidIndex) 1703 { 1704 actionState->magnitude = trigger.magnitude; 1705 k_InputActionResolveConflictMarker.End(); 1706 return false; 1707 } 1708 1709 // Find out if we get triggered from the control that is actively driving the action. 1710 var isControlCurrentlyDrivingTheAction = triggerControlIndex == actionStateControlIndex || 1711 controls[triggerControlIndex] == controls[actionStateControlIndex]; // Same control, different binding. 1712 1713 // If the control is actuated *more* than the current level of actuation we recorded for the 1714 // action, we process the state change normally. If this isn't the control that is already 1715 // driving the action, it will become the one now. 1716 // 1717 // NOTE: For composites, we're looking at the combined actuation of the entire binding here, 1718 // not just at the actuation level of the individual control. ComputeMagnitude() 1719 // automatically takes care of that for us. 1720 if (trigger.magnitude > actionState->magnitude) 1721 { 1722 // If this is not the control that is currently driving the action, we know 1723 // there are multiple controls that are concurrently actuated on the action. 1724 // Remember that so that when the controls are released again, we can more 1725 // efficiently determine whether we need to take multiple bound controls into 1726 // account or not. 1727 // NOTE: For composites, we have forced triggerControlIndex to the first control 1728 // in the composite. See above. 1729 if (trigger.magnitude > 0 && !isControlCurrentlyDrivingTheAction && actionState->magnitude > 0) 1730 actionState->hasMultipleConcurrentActuations = true; 1731 1732 // Keep recorded magnitude in action state up to date. 1733 actionState->magnitude = trigger.magnitude; 1734 k_InputActionResolveConflictMarker.End(); 1735 return false; 1736 } 1737 1738 // If the control is actuated *less* then the current level of actuation we 1739 // recorded for the action *and* the control that changed is the one that is currently 1740 // driving the action, we have to check whether there is another actuation 1741 // that is now *higher* than what we're getting from the current control. 1742 if (trigger.magnitude < actionState->magnitude) 1743 { 1744 // If we're not currently driving the action, it's simple. Doesn't matter that we lowered 1745 // actuation as we didn't have the highest actuation anyway. 1746 if (!isControlCurrentlyDrivingTheAction) 1747 { 1748 k_InputActionResolveConflictMarker.End(); 1749 ////REVIEW: should we *count* actuations instead? (problem is that then we have to reliably determine when a control 1750 //// first actuates; the current solution will occasionally run conflict resolution when it doesn't have to 1751 //// but won't require the extra bookkeeping) 1752 // Do NOT let this control state change affect the action. 1753 if (trigger.magnitude > 0) 1754 actionState->hasMultipleConcurrentActuations = true; 1755 return true; 1756 } 1757 1758 // If we don't have multiple controls that are currently actuated, it's simple. 1759 if (!actionState->hasMultipleConcurrentActuations) 1760 { 1761 // Keep recorded magnitude in action state up to date. 1762 actionState->magnitude = trigger.magnitude; 1763 k_InputActionResolveConflictMarker.End(); 1764 return false; 1765 } 1766 1767 ////REVIEW: is there a simpler way we can do this??? 1768 1769 // So, now we know we are actually looking at a potential conflict. Multiple 1770 // controls bound to the action are actuated but we don't yet know whether 1771 // any of them is actuated *more* than the control that had just changed value. 1772 // Go through the bindings for the action and see what we've got. 1773 var bindingStartIndex = GetActionBindingStartIndexAndCount(actionIndex, out var bindingCount); 1774 var highestActuationLevel = trigger.magnitude; 1775 var controlWithHighestActuation = kInvalidIndex; 1776 var bindingWithHighestActuation = kInvalidIndex; 1777 var numActuations = 0; 1778 for (var i = 0; i < bindingCount; ++i) 1779 { 1780 var bindingIndex = memory.actionBindingIndices[bindingStartIndex + i]; 1781 var binding = &memory.bindingStates[bindingIndex]; 1782 1783 if (binding->isComposite) 1784 { 1785 // Composite bindings result in a single actuation value regardless of how 1786 // many controls are bound through the parts of the composite. 1787 1788 var firstControlIndex = binding->controlStartIndex; 1789 var compositeIndex = binding->compositeOrCompositeBindingIndex; 1790 1791 Debug.Assert(compositeIndex >= 0 && compositeIndex < totalCompositeCount, 1792 "Composite index out of range on composite"); 1793 1794 var magnitude = memory.compositeMagnitudes[compositeIndex]; 1795 if (magnitude > 0) 1796 ++numActuations; 1797 if (magnitude > highestActuationLevel) 1798 { 1799 Debug.Assert(firstControlIndex >= 0 && firstControlIndex < totalControlCount, 1800 "Control start index out of range on composite"); 1801 1802 controlWithHighestActuation = firstControlIndex; 1803 bindingWithHighestActuation = controlIndexToBindingIndex[firstControlIndex]; 1804 highestActuationLevel = magnitude; 1805 } 1806 } 1807 else if (!binding->isPartOfComposite) 1808 { 1809 // Check actuation of each control on the binding. 1810 for (var n = 0; n < binding->controlCount; ++n) 1811 { 1812 var controlIndex = binding->controlStartIndex + n; 1813 var magnitude = memory.controlMagnitudes[controlIndex]; 1814 1815 if (magnitude > 0) 1816 ++numActuations; 1817 1818 if (magnitude > highestActuationLevel) 1819 { 1820 controlWithHighestActuation = controlIndex; 1821 bindingWithHighestActuation = bindingIndex; 1822 highestActuationLevel = magnitude; 1823 } 1824 } 1825 } 1826 } 1827 1828 // Update our record of whether there are multiple concurrent actuations. 1829 if (numActuations <= 1) 1830 actionState->hasMultipleConcurrentActuations = false; 1831 1832 // If we didn't find a control with a higher actuation level, then go and process 1833 // the control value change. 1834 if (controlWithHighestActuation != kInvalidIndex) 1835 { 1836 // We do have a control with a higher actuation level. Switch from our current 1837 // control to processing the control with the now highest actuation level. 1838 // 1839 // NOTE: We are processing an artificial control state change here. Information 1840 // such as the timestamp will not correspond to when the control actually 1841 // changed value. However, if we skip processing this as a separate control 1842 // change here, interactions may not behave properly as they would not be 1843 // seeing that we just lowered the actuation level on the action. 1844 trigger.controlIndex = controlWithHighestActuation; 1845 trigger.bindingIndex = bindingWithHighestActuation; 1846 trigger.magnitude = highestActuationLevel; 1847 1848 // If we're switching to a different binding, we may also have to switch to a 1849 // different stack of interactions. 1850 if (actionState->bindingIndex != bindingWithHighestActuation) 1851 { 1852 // If there's an interaction currently driving the action, reset it. 1853 // NOTE: This will also cancel an ongoing timer. So, say we're currently 0.5 seconds into 1854 // a 1 second "Hold" when the user shifts to a different control, then this code here 1855 // will *cancel* the current "Hold" and restart from scratch. 1856 if (actionState->interactionIndex != kInvalidIndex) 1857 ResetInteractionState(actionState->interactionIndex); 1858 1859 // If there's an interaction in progress on the new binding, let 1860 // it drive the action. 1861 var bindingState = &bindingStates[bindingWithHighestActuation]; 1862 var interactionCount = bindingState->interactionCount; 1863 var interactionStartIndex = bindingState->interactionStartIndex; 1864 for (var i = 0; i < interactionCount; ++i) 1865 { 1866 if (!interactionStates[interactionStartIndex + i].phase.IsInProgress()) 1867 continue; 1868 1869 actionState->interactionIndex = interactionStartIndex + i; 1870 trigger.interactionIndex = interactionStartIndex + i; 1871 break; 1872 } 1873 } 1874 1875 // We're switching the action to a different control so regardless of whether 1876 // the processing of the control state change results in a call to ChangePhaseOfAction, 1877 // we need to record this or the disambiguation code may start ignoring valid input. 1878 actionState->controlIndex = controlWithHighestActuation; 1879 actionState->bindingIndex = bindingWithHighestActuation; 1880 actionState->magnitude = highestActuationLevel; 1881 1882 k_InputActionResolveConflictMarker.End(); 1883 return false; 1884 } 1885 } 1886 1887 k_InputActionResolveConflictMarker.End(); 1888 1889 // If we're not really effecting any change on the action, ignore the control state change. 1890 // NOTE: We may be looking at a control here that points in a completely direction, for example, even 1891 // though it has the same magnitude. However, we require a control to *increase* absolute actuation 1892 // before we let it drive the action. 1893 if (!isControlCurrentlyDrivingTheAction && Mathf.Approximately(trigger.magnitude, actionState->magnitude)) 1894 { 1895 // If we do have an actuation on a control that isn't currently driving the action, flag the action has 1896 // having multiple concurrent inputs ATM. 1897 if (trigger.magnitude > 0) 1898 actionState->hasMultipleConcurrentActuations = true; 1899 return true; 1900 } 1901 1902 return false; 1903 } 1904 1905 private ushort GetActionBindingStartIndexAndCount(int actionIndex, out ushort bindingCount) 1906 { 1907 bindingCount = memory.actionBindingIndicesAndCounts[actionIndex * 2 + 1]; 1908 return memory.actionBindingIndicesAndCounts[actionIndex * 2]; 1909 } 1910 1911 /// <summary> 1912 /// When there is no interaction on an action, this method perform the default interaction logic that we 1913 /// run when a bound control changes value. 1914 /// </summary> 1915 /// <param name="trigger">Control trigger state.</param> 1916 /// <param name="actionIndex"></param> 1917 /// <remarks> 1918 /// The default interaction does not have its own <see cref="InteractionState"/>. Whatever we do in here, 1919 /// we store directly on the action state. 1920 /// 1921 /// The default interaction is basically a sort of optimization where we don't require having an explicit 1922 /// interaction object. Conceptually, it can be thought of, however, as putting this interaction on any 1923 /// binding that doesn't have any other interaction on it. 1924 /// </remarks> 1925 private void ProcessDefaultInteraction(ref TriggerState trigger, int actionIndex) 1926 { 1927 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, 1928 "Action index out of range when processing default interaction"); 1929 1930 var actionState = &actionStates[actionIndex]; 1931 switch (actionState->phase) 1932 { 1933 case InputActionPhase.Waiting: 1934 { 1935 // Pass-through actions we perform on every value change and then go back 1936 // to waiting. 1937 if (trigger.isPassThrough) 1938 { 1939 ChangePhaseOfAction(InputActionPhase.Performed, ref trigger, 1940 phaseAfterPerformedOrCanceled: InputActionPhase.Waiting); 1941 break; 1942 } 1943 // Button actions need to cross the button-press threshold. 1944 if (trigger.isButton) 1945 { 1946 var actuation = trigger.magnitude; 1947 if (actuation > 0) 1948 ChangePhaseOfAction(InputActionPhase.Started, ref trigger); 1949 var threshold = controls[trigger.controlIndex] is ButtonControl button ? button.pressPointOrDefault : ButtonControl.s_GlobalDefaultButtonPressPoint; 1950 if (actuation >= threshold) 1951 { 1952 ChangePhaseOfAction(InputActionPhase.Performed, ref trigger, 1953 phaseAfterPerformedOrCanceled: InputActionPhase.Performed); 1954 } 1955 } 1956 else 1957 { 1958 // Value-type action. 1959 // Ignore if the control has not crossed its actuation threshold. 1960 if (IsActuated(ref trigger)) 1961 { 1962 ////REVIEW: Why is it we don't stay in performed but rather go back to started all the time? 1963 1964 // Go into started, then perform and then go back to started. 1965 ChangePhaseOfAction(InputActionPhase.Started, ref trigger); 1966 ChangePhaseOfAction(InputActionPhase.Performed, ref trigger, 1967 phaseAfterPerformedOrCanceled: InputActionPhase.Started); 1968 } 1969 } 1970 1971 break; 1972 } 1973 1974 case InputActionPhase.Started: 1975 { 1976 if (actionState->isButton) 1977 { 1978 var actuation = trigger.magnitude; 1979 var threshold = controls[trigger.controlIndex] is ButtonControl button ? button.pressPointOrDefault : ButtonControl.s_GlobalDefaultButtonPressPoint; 1980 if (actuation >= threshold) 1981 { 1982 // Button crossed press threshold. Perform. 1983 ChangePhaseOfAction(InputActionPhase.Performed, ref trigger, 1984 phaseAfterPerformedOrCanceled: InputActionPhase.Performed); 1985 } 1986 else if (Mathf.Approximately(actuation, 0)) 1987 { 1988 // Button is no longer actuated. Never reached threshold to perform. 1989 // Cancel. 1990 ChangePhaseOfAction(InputActionPhase.Canceled, ref trigger); 1991 } 1992 } 1993 else 1994 { 1995 if (!IsActuated(ref trigger)) 1996 { 1997 // Control went back to below actuation threshold. Cancel interaction. 1998 ChangePhaseOfAction(InputActionPhase.Canceled, ref trigger); 1999 } 2000 else 2001 { 2002 // Control changed value above magnitude threshold. Perform and remain started. 2003 ChangePhaseOfAction(InputActionPhase.Performed, ref trigger, 2004 phaseAfterPerformedOrCanceled: InputActionPhase.Started); 2005 } 2006 } 2007 break; 2008 } 2009 2010 case InputActionPhase.Performed: 2011 { 2012 if (actionState->isButton) 2013 { 2014 var actuation = trigger.magnitude; 2015 var pressPoint = controls[trigger.controlIndex] is ButtonControl button ? button.pressPointOrDefault : ButtonControl.s_GlobalDefaultButtonPressPoint; 2016 if (Mathf.Approximately(0f, actuation)) 2017 { 2018 ChangePhaseOfAction(InputActionPhase.Canceled, ref trigger); 2019 } 2020 else 2021 { 2022 var threshold = pressPoint * ButtonControl.s_GlobalDefaultButtonReleaseThreshold; 2023 if (actuation <= threshold) 2024 { 2025 // Button released to below threshold but not fully released. 2026 ChangePhaseOfAction(InputActionPhase.Started, ref trigger); 2027 } 2028 } 2029 } 2030 else if (actionState->isPassThrough) 2031 { 2032 ////REVIEW: even for pass-through actions, shouldn't we cancel when seeing a default value? 2033 ChangePhaseOfAction(InputActionPhase.Performed, ref trigger, 2034 phaseAfterPerformedOrCanceled: InputActionPhase.Performed); 2035 } 2036 break; 2037 } 2038 2039 default: 2040 Debug.Assert(false, "Should not get here"); 2041 break; 2042 } 2043 } 2044 2045 private void ProcessInteractions(ref TriggerState trigger, int interactionStartIndex, int interactionCount) 2046 { 2047 var context = new InputInteractionContext 2048 { 2049 m_State = this, 2050 m_TriggerState = trigger 2051 }; 2052 2053 for (var i = 0; i < interactionCount; ++i) 2054 { 2055 var index = interactionStartIndex + i; 2056 var state = interactionStates[index]; 2057 var interaction = interactions[index]; 2058 2059 context.m_TriggerState.phase = state.phase; 2060 context.m_TriggerState.startTime = state.startTime; 2061 context.m_TriggerState.interactionIndex = index; 2062 2063 interaction.Process(ref context); 2064 } 2065 } 2066 2067 private void ProcessTimeout(double time, int mapIndex, int controlIndex, int bindingIndex, int interactionIndex) 2068 { 2069 Debug.Assert(controlIndex >= 0 && controlIndex < totalControlCount, "Control index out of range"); 2070 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2071 Debug.Assert(interactionIndex >= 0 && interactionIndex < totalInteractionCount, "Interaction index out of range"); 2072 2073 ref var currentState = ref interactionStates[interactionIndex]; 2074 2075 var context = new InputInteractionContext 2076 { 2077 m_State = this, 2078 m_TriggerState = 2079 new TriggerState 2080 { 2081 phase = currentState.phase, 2082 time = time, 2083 mapIndex = mapIndex, 2084 controlIndex = controlIndex, 2085 bindingIndex = bindingIndex, 2086 interactionIndex = interactionIndex, 2087 startTime = currentState.startTime 2088 }, 2089 timerHasExpired = true, 2090 }; 2091 2092 currentState.isTimerRunning = false; 2093 currentState.totalTimeoutCompletionTimeRemaining = 2094 Mathf.Max(currentState.totalTimeoutCompletionTimeRemaining - currentState.timerDuration, 0); 2095 currentState.timerDuration = default; 2096 2097 // Let interaction handle timer expiration. 2098 interactions[interactionIndex].Process(ref context); 2099 } 2100 2101 internal void SetTotalTimeoutCompletionTime(float seconds, ref TriggerState trigger) 2102 { 2103 Debug.Assert(trigger.interactionIndex >= 0 && trigger.interactionIndex < totalInteractionCount, "Interaction index out of range"); 2104 2105 ref var interactionState = ref interactionStates[trigger.interactionIndex]; 2106 interactionState.totalTimeoutCompletionDone = 0; 2107 interactionState.totalTimeoutCompletionTimeRemaining = seconds; 2108 } 2109 2110 internal void StartTimeout(float seconds, ref TriggerState trigger) 2111 { 2112 Debug.Assert(trigger.mapIndex >= 0 && trigger.mapIndex < totalMapCount, "Map index out of range"); 2113 Debug.Assert(trigger.controlIndex >= 0 && trigger.controlIndex < totalControlCount, "Control index out of range"); 2114 Debug.Assert(trigger.interactionIndex >= 0 && trigger.interactionIndex < totalInteractionCount, "Interaction index out of range"); 2115 2116 var manager = InputSystem.s_Manager; 2117 var currentTime = trigger.time; 2118 var control = controls[trigger.controlIndex]; 2119 var interactionIndex = trigger.interactionIndex; 2120 var monitorIndex = 2121 ToCombinedMapAndControlAndBindingIndex(trigger.mapIndex, trigger.controlIndex, trigger.bindingIndex); 2122 2123 // If there's already a timeout running, cancel it first. 2124 ref var interactionState = ref interactionStates[interactionIndex]; 2125 if (interactionState.isTimerRunning) 2126 StopTimeout(interactionIndex); 2127 2128 // Add new timeout. 2129 manager.AddStateChangeMonitorTimeout(control, this, currentTime + seconds, monitorIndex, 2130 interactionIndex); 2131 2132 // Update state. 2133 interactionState.isTimerRunning = true; 2134 interactionState.timerStartTime = currentTime; 2135 interactionState.timerDuration = seconds; 2136 interactionState.timerMonitorIndex = monitorIndex; 2137 } 2138 2139 private void StopTimeout(int interactionIndex) 2140 { 2141 Debug.Assert(interactionIndex >= 0 && interactionIndex < totalInteractionCount, "Interaction index out of range"); 2142 2143 ref var interactionState = ref interactionStates[interactionIndex]; 2144 2145 var manager = InputSystem.s_Manager; 2146 manager.RemoveStateChangeMonitorTimeout(this, interactionState.timerMonitorIndex, interactionIndex); 2147 2148 // Update state. 2149 interactionState.isTimerRunning = false; 2150 interactionState.totalTimeoutCompletionDone += interactionState.timerDuration; 2151 interactionState.totalTimeoutCompletionTimeRemaining = 2152 Mathf.Max(interactionState.totalTimeoutCompletionTimeRemaining - interactionState.timerDuration, 0); 2153 interactionState.timerDuration = default; 2154 interactionState.timerStartTime = default; 2155 interactionState.timerMonitorIndex = default; 2156 } 2157 2158 /// <summary> 2159 /// Perform a phase change on the given interaction. Only visible to observers 2160 /// if it happens to change the phase of the action, too. 2161 /// </summary> 2162 /// <param name="newPhase">New phase to transition the interaction to.</param> 2163 /// <param name="trigger">Information about the binding and control that triggered the phase change.</param> 2164 /// <param name="phaseAfterPerformed">If <paramref name="newPhase"/> is <see cref="InputActionPhase.Performed"/>, 2165 /// this determines which phase to transition to after the action has been performed. This would usually be 2166 /// <see cref="InputActionPhase.Waiting"/> (default), <see cref="InputActionPhase.Started"/> (if the action is supposed 2167 /// to be oscillate between started and performed), or <see cref="InputActionPhase.Performed"/> (if the action is 2168 /// supposed to perform over and over again until canceled).</param> 2169 /// <param name="phaseAfterCanceled">If <paramref name="newPhase"/> is <see cref="InputActionPhase.Canceled"/>, 2170 /// this determines which phase to transition to after the action has been canceled.</param> 2171 /// <param name="processNextInteractionOnCancel">Indicates if the system should try and change the phase of other 2172 /// interactions on the same action that are already started or performed after cancelling this interaction. This should be 2173 /// false when resetting interactions.</param> 2174 /// <remarks> 2175 /// Multiple interactions on the same binding can be started concurrently but the 2176 /// first interaction that starts will get to drive an action until it either cancels 2177 /// or performs the action. 2178 /// 2179 /// If an interaction driving an action performs it, all interactions will reset and 2180 /// go back waiting. 2181 /// 2182 /// If an interaction driving an action cancels it, the next interaction in the list which 2183 /// has already started will get to drive the action (example: a TapInteraction and a 2184 /// SlowTapInteraction both start and the TapInteraction gets to drive the action because 2185 /// it comes first; then the TapInteraction cancels because the button is held for too 2186 /// long and the SlowTapInteraction will get to drive the action next). 2187 /// </remarks> 2188 internal void ChangePhaseOfInteraction(InputActionPhase newPhase, ref TriggerState trigger, 2189 InputActionPhase phaseAfterPerformed = InputActionPhase.Waiting, 2190 InputActionPhase phaseAfterCanceled = InputActionPhase.Waiting, 2191 bool processNextInteractionOnCancel = true) 2192 { 2193 var interactionIndex = trigger.interactionIndex; 2194 var bindingIndex = trigger.bindingIndex; 2195 2196 Debug.Assert(interactionIndex >= 0 && interactionIndex < totalInteractionCount, "Interaction index out of range"); 2197 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2198 2199 ////TODO: need to make sure that performed and canceled phase changes happen on the *same* binding&control 2200 //// as the start of the phase 2201 2202 var phaseAfterPerformedOrCanceled = InputActionPhase.Waiting; 2203 if (newPhase == InputActionPhase.Performed) 2204 phaseAfterPerformedOrCanceled = phaseAfterPerformed; 2205 else if (newPhase == InputActionPhase.Canceled) 2206 phaseAfterPerformedOrCanceled = phaseAfterCanceled; 2207 2208 // Any time an interaction changes phase, we cancel all pending timeouts. 2209 ref var interactionState = ref interactionStates[interactionIndex]; 2210 if (interactionState.isTimerRunning) 2211 StopTimeout(trigger.interactionIndex); 2212 2213 // Update interaction state. 2214 interactionState.phase = newPhase; 2215 interactionState.triggerControlIndex = trigger.controlIndex; 2216 interactionState.startTime = trigger.startTime; 2217 if (newPhase == InputActionPhase.Performed) 2218 interactionState.performedTime = trigger.time; 2219 2220 // See if it affects the phase of an associated action. 2221 var actionIndex = bindingStates[bindingIndex].actionIndex; // We already had to tap this array and entry in ProcessControlStateChange. 2222 if (actionIndex != kInvalidIndex) 2223 { 2224 if (actionStates[actionIndex].phase == InputActionPhase.Waiting) 2225 { 2226 // We're the first interaction to go to the start phase. 2227 if (!ChangePhaseOfAction(newPhase, ref trigger, phaseAfterPerformedOrCanceled)) 2228 return; 2229 } 2230 else if (newPhase == InputActionPhase.Canceled && actionStates[actionIndex].interactionIndex == trigger.interactionIndex) 2231 { 2232 // We're canceling but maybe there's another interaction ready 2233 // to go into start phase. *Or* there's an interaction that has 2234 // already performed. 2235 2236 if (!ChangePhaseOfAction(newPhase, ref trigger, phaseAfterPerformedOrCanceled)) 2237 return; 2238 2239 if (processNextInteractionOnCancel == false) 2240 return; 2241 2242 var interactionStartIndex = bindingStates[bindingIndex].interactionStartIndex; 2243 var numInteractions = bindingStates[bindingIndex].interactionCount; 2244 for (var i = 0; i < numInteractions; ++i) 2245 { 2246 var index = interactionStartIndex + i; 2247 if (index != trigger.interactionIndex && (interactionStates[index].phase == InputActionPhase.Started || 2248 interactionStates[index].phase == InputActionPhase.Performed)) 2249 { 2250 // Trigger start. 2251 var startTime = interactionStates[index].startTime; 2252 var triggerForInteraction = new TriggerState 2253 { 2254 phase = InputActionPhase.Started, 2255 controlIndex = interactionStates[index].triggerControlIndex, 2256 bindingIndex = trigger.bindingIndex, 2257 interactionIndex = index, 2258 mapIndex = trigger.mapIndex, 2259 time = startTime, 2260 startTime = startTime, 2261 }; 2262 if (!ChangePhaseOfAction(InputActionPhase.Started, ref triggerForInteraction, phaseAfterPerformedOrCanceled)) 2263 return; 2264 2265 // If the interaction has already performed, trigger it now. 2266 if (interactionStates[index].phase == InputActionPhase.Performed) 2267 { 2268 triggerForInteraction = new TriggerState 2269 { 2270 phase = InputActionPhase.Performed, 2271 controlIndex = interactionStates[index].triggerControlIndex, 2272 bindingIndex = trigger.bindingIndex, 2273 interactionIndex = index, 2274 mapIndex = trigger.mapIndex, 2275 time = interactionStates[index].performedTime, // Time when the interaction performed. 2276 startTime = startTime, 2277 }; 2278 if (!ChangePhaseOfAction(InputActionPhase.Performed, ref triggerForInteraction, phaseAfterPerformedOrCanceled)) 2279 return; 2280 2281 // We performed the action, 2282 // so reset remaining interaction to waiting state. 2283 for (; i < numInteractions; ++i) 2284 { 2285 index = interactionStartIndex + i; 2286 ResetInteractionState(index); 2287 } 2288 } 2289 break; 2290 } 2291 } 2292 } 2293 else if (actionStates[actionIndex].interactionIndex == trigger.interactionIndex) 2294 { 2295 // Any other phase change goes to action if we're the interaction driving 2296 // the current phase. 2297 if (!ChangePhaseOfAction(newPhase, ref trigger, phaseAfterPerformedOrCanceled)) 2298 return; 2299 2300 // We're the interaction driving the action and we performed the action, 2301 // so reset any other interaction to waiting state. 2302 if (newPhase == InputActionPhase.Performed) 2303 { 2304 var interactionStartIndex = bindingStates[bindingIndex].interactionStartIndex; 2305 var numInteractions = bindingStates[bindingIndex].interactionCount; 2306 for (var i = 0; i < numInteractions; ++i) 2307 { 2308 var index = interactionStartIndex + i; 2309 if (index != trigger.interactionIndex) 2310 ResetInteractionState(index); 2311 } 2312 } 2313 } 2314 } 2315 2316 // If the interaction performed or canceled, go back to waiting. 2317 // Exception: if it was performed and we're to remain in started state, set the interaction 2318 // to started. Note that for that phase transition, there are no callbacks being 2319 // triggered (i.e. we don't call 'started' every time after 'performed'). 2320 if (newPhase == InputActionPhase.Performed && 2321 actionIndex != kInvalidIndex && !actionStates[actionIndex].isPerformed && 2322 actionStates[actionIndex].interactionIndex != trigger.interactionIndex) 2323 { 2324 // If the action was not already performed and we performed but we're not the interaction driving the action. 2325 // We want to stay performed to make sure that if the interaction that is currently driving the action 2326 // cancels, we get to perform the action. If we go back to waiting here, then the system can't tell 2327 // that there's another interaction ready to perform (in fact, that has already performed). 2328 } 2329 else if (newPhase == InputActionPhase.Performed && phaseAfterPerformed != InputActionPhase.Waiting) 2330 { 2331 interactionState.phase = phaseAfterPerformed; 2332 } 2333 else if (newPhase == InputActionPhase.Performed || newPhase == InputActionPhase.Canceled) 2334 { 2335 ResetInteractionState(trigger.interactionIndex); 2336 } 2337 } 2338 2339 /// <summary> 2340 /// Change the current phase of the action referenced by <paramref name="trigger"/> to <paramref name="newPhase"/>. 2341 /// </summary> 2342 /// <param name="newPhase">New phase to transition to.</param> 2343 /// <param name="trigger">Trigger that caused the change in phase.</param> 2344 /// <param name="phaseAfterPerformedOrCanceled">The phase to immediately transition to after <paramref name="newPhase"/> 2345 /// when that is <see cref="InputActionPhase.Performed"/> or <see cref="InputActionPhase.Canceled"/> (<see cref="InputActionPhase.Waiting"/> 2346 /// by default).</param> 2347 /// <remarks> 2348 /// The change in phase is visible to observers, i.e. on the various callbacks and notifications. 2349 /// 2350 /// If <paramref name="newPhase"/> is <see cref="InputActionPhase.Performed"/> or <see cref="InputActionPhase.Canceled"/>, 2351 /// the action will subsequently immediately transition to <paramref name="phaseAfterPerformedOrCanceled"/> 2352 /// (<see cref="InputActionPhase.Waiting"/> by default). This change is not visible to observers, i.e. there won't 2353 /// be another run through callbacks. 2354 /// </remarks> 2355 private bool ChangePhaseOfAction(InputActionPhase newPhase, ref TriggerState trigger, 2356 InputActionPhase phaseAfterPerformedOrCanceled = InputActionPhase.Waiting) 2357 { 2358 Debug.Assert(newPhase != InputActionPhase.Disabled, "Should not disable an action using this method"); 2359 Debug.Assert(trigger.mapIndex >= 0 && trigger.mapIndex < totalMapCount, "Map index out of range"); 2360 Debug.Assert(trigger.controlIndex >= 0 && trigger.controlIndex < totalControlCount, "Control index out of range"); 2361 Debug.Assert(trigger.bindingIndex >= 0 && trigger.bindingIndex < totalBindingCount, "Binding index out of range"); 2362 2363 var actionIndex = bindingStates[trigger.bindingIndex].actionIndex; 2364 if (actionIndex == kInvalidIndex) 2365 return true; // No action associated with binding. 2366 2367 // Ignore if action is disabled. 2368 var actionState = &actionStates[actionIndex]; 2369 if (actionState->isDisabled) 2370 return true; 2371 2372 // We mark the action as in-processing while we execute its phase transitions and perform 2373 // callbacks. The callbacks may alter system state such that the action may get disabled 2374 // (and potentially re-enabled) while the callback is in progress. We need to make sure that 2375 // if that happens, we don't go and then do more processing on the action. 2376 actionState->inProcessing = true; 2377 try 2378 { 2379 // Enforce transition constraints. 2380 if (actionState->isPassThrough && trigger.interactionIndex == kInvalidIndex) 2381 { 2382 // No constraints on pass-through actions except if there are interactions driving the action. 2383 ChangePhaseOfActionInternal(actionIndex, actionState, newPhase, ref trigger, 2384 isDisablingAction: newPhase == InputActionPhase.Canceled && phaseAfterPerformedOrCanceled == InputActionPhase.Disabled); 2385 if (!actionState->inProcessing) 2386 return false; 2387 } 2388 else if (newPhase == InputActionPhase.Performed && actionState->phase == InputActionPhase.Waiting) 2389 { 2390 // Going from waiting to performed, we make a detour via started. 2391 ChangePhaseOfActionInternal(actionIndex, actionState, InputActionPhase.Started, ref trigger); 2392 if (!actionState->inProcessing) 2393 return false; 2394 2395 // Then we perform. 2396 ChangePhaseOfActionInternal(actionIndex, actionState, newPhase, ref trigger); 2397 if (!actionState->inProcessing) 2398 return false; 2399 2400 // And finally, if we're going back to waiting, we make a detour via canceled. 2401 if (phaseAfterPerformedOrCanceled == InputActionPhase.Waiting) 2402 ChangePhaseOfActionInternal(actionIndex, actionState, InputActionPhase.Canceled, ref trigger); 2403 if (!actionState->inProcessing) 2404 return false; 2405 2406 actionState->phase = phaseAfterPerformedOrCanceled; 2407 } 2408 else if (actionState->phase != newPhase || newPhase == InputActionPhase.Performed) // We allow Performed to trigger repeatedly. 2409 { 2410 ChangePhaseOfActionInternal(actionIndex, actionState, newPhase, ref trigger, 2411 isDisablingAction: newPhase == InputActionPhase.Canceled && phaseAfterPerformedOrCanceled == InputActionPhase.Disabled); 2412 if (!actionState->inProcessing) 2413 return false; 2414 2415 if (newPhase == InputActionPhase.Performed || newPhase == InputActionPhase.Canceled) 2416 actionState->phase = phaseAfterPerformedOrCanceled; 2417 } 2418 } 2419 finally 2420 { 2421 actionState->inProcessing = false; 2422 } 2423 2424 // If we're now waiting, reset control state. This is important for the disambiguation code 2425 // to not consider whatever control actuation happened on the action last. 2426 if (actionState->phase == InputActionPhase.Waiting) 2427 { 2428 actionState->controlIndex = kInvalidIndex; 2429 actionState->flags &= ~TriggerState.Flags.HaveMagnitude; 2430 } 2431 2432 return true; 2433 } 2434 2435 private void ChangePhaseOfActionInternal(int actionIndex, TriggerState* actionState, InputActionPhase newPhase, ref TriggerState trigger, bool isDisablingAction = false) 2436 { 2437 Debug.Assert(trigger.mapIndex == actionState->mapIndex, 2438 "Map index on trigger does not correspond to map index of trigger state"); 2439 2440 // Update action state. 2441 var newState = trigger; 2442 2443 // We need to make sure here that any HaveMagnitude flag we may be carrying over from actionState 2444 // is handled correctly (case 1239551). 2445 newState.flags = actionState->flags; // Preserve flags. 2446 if (newPhase != InputActionPhase.Canceled) 2447 newState.magnitude = trigger.magnitude; 2448 else 2449 newState.magnitude = 0f; 2450 2451 newState.phase = newPhase; 2452 newState.frame = Time.frameCount; 2453 if (newPhase == InputActionPhase.Performed) 2454 { 2455 newState.lastPerformedInUpdate = InputUpdate.s_UpdateStepCount; 2456 newState.lastCanceledInUpdate = actionState->lastCanceledInUpdate; 2457 2458 // When we perform an action, we mark the event handled such that FireStateChangeNotifications() 2459 // can then reset state monitors in the same group. 2460 // NOTE: We don't consume for controls at binding complexity 1. Those we fire in unison. 2461 if (controlGroupingAndComplexity[trigger.controlIndex * 2 + 1] > 1 && 2462 // we can end up switching to performed state from an interaction with a timeout, at which point 2463 // the original event will probably have been removed from memory, so make sure to check 2464 // we still have one 2465 m_CurrentlyProcessingThisEvent.valid) 2466 m_CurrentlyProcessingThisEvent.handled = true; 2467 } 2468 else if (newPhase == InputActionPhase.Canceled) 2469 { 2470 newState.lastCanceledInUpdate = InputUpdate.s_UpdateStepCount; 2471 newState.lastPerformedInUpdate = actionState->lastPerformedInUpdate; 2472 } 2473 else 2474 { 2475 newState.lastPerformedInUpdate = actionState->lastPerformedInUpdate; 2476 newState.lastCanceledInUpdate = actionState->lastCanceledInUpdate; 2477 } 2478 2479 // When we go from Performed to Disabling, we take a detour through Canceled. 2480 // To replicate the behavior of releasedInUpdate where it doesn't get updated when the action is disabled 2481 // from being performed, we skip updating lastCompletedInUpdate if Disabled is the phase after Canceled. 2482 if (actionState->phase == InputActionPhase.Performed && newPhase != InputActionPhase.Performed && !isDisablingAction) 2483 newState.lastCompletedInUpdate = InputUpdate.s_UpdateStepCount; 2484 else 2485 newState.lastCompletedInUpdate = actionState->lastCompletedInUpdate; 2486 2487 newState.pressedInUpdate = actionState->pressedInUpdate; 2488 newState.releasedInUpdate = actionState->releasedInUpdate; 2489 if (newPhase == InputActionPhase.Started) 2490 newState.startTime = newState.time; 2491 *actionState = newState; 2492 2493 // Let listeners know. 2494 var map = maps[trigger.mapIndex]; 2495 Debug.Assert(actionIndex >= mapIndices[trigger.mapIndex].actionStartIndex, 2496 "actionIndex is below actionStartIndex for map that the action belongs to"); 2497 var action = map.m_Actions[actionIndex - mapIndices[trigger.mapIndex].actionStartIndex]; 2498 trigger.phase = newPhase; 2499 switch (newPhase) 2500 { 2501 case InputActionPhase.Started: 2502 { 2503 Debug.Assert(trigger.controlIndex != -1, "Must have control to start an action"); 2504 CallActionListeners(actionIndex, map, newPhase, ref action.m_OnStarted, "started"); 2505 break; 2506 } 2507 2508 case InputActionPhase.Performed: 2509 { 2510 Debug.Assert(trigger.controlIndex != -1, "Must have control to perform an action"); 2511 CallActionListeners(actionIndex, map, newPhase, ref action.m_OnPerformed, "performed"); 2512 break; 2513 } 2514 2515 case InputActionPhase.Canceled: 2516 { 2517 Debug.Assert(trigger.controlIndex != -1, "When canceling, must have control that started action"); 2518 CallActionListeners(actionIndex, map, newPhase, ref action.m_OnCanceled, "canceled"); 2519 break; 2520 } 2521 } 2522 } 2523 2524 private void CallActionListeners(int actionIndex, InputActionMap actionMap, InputActionPhase phase, ref CallbackArray<InputActionListener> listeners, string callbackName) 2525 { 2526 // If there's no listeners, don't bother with anything else. 2527 var callbacksOnMap = actionMap.m_ActionCallbacks; 2528 if (listeners.length == 0 && callbacksOnMap.length == 0 && s_GlobalState.onActionChange.length == 0) 2529 return; 2530 2531 var context = new InputAction.CallbackContext 2532 { 2533 m_State = this, 2534 m_ActionIndex = actionIndex, 2535 }; 2536 2537 k_InputActionCallbackMarker.Begin(); 2538 2539 // Global callback goes first. 2540 var action = context.action; 2541 if (s_GlobalState.onActionChange.length > 0) 2542 { 2543 InputActionChange change; 2544 switch (phase) 2545 { 2546 case InputActionPhase.Started: 2547 change = InputActionChange.ActionStarted; 2548 break; 2549 case InputActionPhase.Performed: 2550 change = InputActionChange.ActionPerformed; 2551 break; 2552 case InputActionPhase.Canceled: 2553 change = InputActionChange.ActionCanceled; 2554 break; 2555 default: 2556 Debug.Assert(false, "Should not reach here"); 2557 return; 2558 } 2559 2560 DelegateHelpers.InvokeCallbacksSafe(ref s_GlobalState.onActionChange, action, change, k_InputOnActionChangeMarker, "InputSystem.onActionChange"); 2561 } 2562 2563 // Run callbacks (if any) directly on action. 2564 DelegateHelpers.InvokeCallbacksSafe(ref listeners, context, callbackName, action); 2565 2566 // Run callbacks (if any) on action map. 2567 DelegateHelpers.InvokeCallbacksSafe(ref callbacksOnMap, context, callbackName, actionMap); 2568 2569 k_InputActionCallbackMarker.End(); 2570 } 2571 2572 private object GetActionOrNoneString(ref TriggerState trigger) 2573 { 2574 var action = GetActionOrNull(ref trigger); 2575 if (action == null) 2576 return "<none>"; 2577 return action; 2578 } 2579 2580 internal InputAction GetActionOrNull(int bindingIndex) 2581 { 2582 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2583 2584 var actionIndex = bindingStates[bindingIndex].actionIndex; 2585 if (actionIndex == kInvalidIndex) 2586 return null; 2587 2588 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, 2589 "Action index out of range when getting action"); 2590 var mapIndex = bindingStates[bindingIndex].mapIndex; 2591 var actionStartIndex = mapIndices[mapIndex].actionStartIndex; 2592 return maps[mapIndex].m_Actions[actionIndex - actionStartIndex]; 2593 } 2594 2595 internal InputAction GetActionOrNull(ref TriggerState trigger) 2596 { 2597 Debug.Assert(trigger.mapIndex >= 0 && trigger.mapIndex < totalMapCount, "Map index out of range"); 2598 Debug.Assert(trigger.bindingIndex >= 0 && trigger.bindingIndex < totalBindingCount, "Binding index out of range"); 2599 2600 var actionIndex = bindingStates[trigger.bindingIndex].actionIndex; 2601 if (actionIndex == kInvalidIndex) 2602 return null; 2603 2604 Debug.Assert(actionIndex >= 0 && actionIndex < totalActionCount, "Action index out of range"); 2605 var actionStartIndex = mapIndices[trigger.mapIndex].actionStartIndex; 2606 return maps[trigger.mapIndex].m_Actions[actionIndex - actionStartIndex]; 2607 } 2608 2609 internal InputControl GetControl(ref TriggerState trigger) 2610 { 2611 Debug.Assert(trigger.controlIndex != kInvalidIndex, "Control index is invalid"); 2612 Debug.Assert(trigger.controlIndex >= 0 && trigger.controlIndex < totalControlCount, "Control index out of range"); 2613 return controls[trigger.controlIndex]; 2614 } 2615 2616 private IInputInteraction GetInteractionOrNull(ref TriggerState trigger) 2617 { 2618 if (trigger.interactionIndex == kInvalidIndex) 2619 return null; 2620 2621 Debug.Assert(trigger.interactionIndex >= 0 && trigger.interactionIndex < totalInteractionCount, "Interaction index out of range"); 2622 return interactions[trigger.interactionIndex]; 2623 } 2624 2625 internal int GetBindingIndexInMap(int bindingIndex) 2626 { 2627 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2628 var mapIndex = bindingStates[bindingIndex].mapIndex; 2629 var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; 2630 return bindingIndex - bindingStartIndex; 2631 } 2632 2633 internal int GetBindingIndexInState(int mapIndex, int bindingIndexInMap) 2634 { 2635 var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; 2636 return bindingStartIndex + bindingIndexInMap; 2637 } 2638 2639 // Iterators may not use unsafe code so do the detour here. 2640 internal ref BindingState GetBindingState(int bindingIndex) 2641 { 2642 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2643 return ref bindingStates[bindingIndex]; 2644 } 2645 2646 internal ref InputBinding GetBinding(int bindingIndex) 2647 { 2648 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2649 var mapIndex = bindingStates[bindingIndex].mapIndex; 2650 var bindingStartIndex = mapIndices[mapIndex].bindingStartIndex; 2651 return ref maps[mapIndex].m_Bindings[bindingIndex - bindingStartIndex]; 2652 } 2653 2654 internal InputActionMap GetActionMap(int bindingIndex) 2655 { 2656 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2657 var mapIndex = bindingStates[bindingIndex].mapIndex; 2658 return maps[mapIndex]; 2659 } 2660 2661 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "mapIndex", Justification = "Keep this for future implementation")] 2662 private void ResetInteractionStateAndCancelIfNecessary(int mapIndex, int bindingIndex, int interactionIndex, InputActionPhase phaseAfterCanceled) 2663 { 2664 Debug.Assert(interactionIndex >= 0 && interactionIndex < totalInteractionCount, "Interaction index out of range"); 2665 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2666 2667 // If interaction is currently driving an action and it has been started or performed, 2668 // cancel it. 2669 // 2670 // NOTE: We could just blindly call ChangePhaseOfInteraction() and it would handle the case of 2671 // when the interaction is currently driving the action automatically. However, doing so 2672 // would give other interactions a chance to take over which is something we don't want to 2673 // happen when resetting actions. 2674 var actionIndex = bindingStates[bindingIndex].actionIndex; 2675 if (actionStates[actionIndex].interactionIndex == interactionIndex) 2676 { 2677 switch (interactionStates[interactionIndex].phase) 2678 { 2679 case InputActionPhase.Started: 2680 case InputActionPhase.Performed: 2681 ChangePhaseOfInteraction(InputActionPhase.Canceled, ref actionStates[actionIndex], 2682 phaseAfterCanceled: phaseAfterCanceled, 2683 processNextInteractionOnCancel: false); 2684 break; 2685 } 2686 2687 actionStates[actionIndex].interactionIndex = kInvalidIndex; 2688 } 2689 2690 ResetInteractionState(interactionIndex); 2691 } 2692 2693 private void ResetInteractionState(int interactionIndex) 2694 { 2695 Debug.Assert(interactionIndex >= 0 && interactionIndex < totalInteractionCount, "Interaction index out of range"); 2696 2697 // Clean up internal state that the interaction may keep. 2698 interactions[interactionIndex].Reset(); 2699 2700 // Clean up timer. 2701 if (interactionStates[interactionIndex].isTimerRunning) 2702 StopTimeout(interactionIndex); 2703 2704 // Reset state record. 2705 interactionStates[interactionIndex] = 2706 new InteractionState 2707 { 2708 // We never set interactions to disabled. This way we don't have to go through them 2709 // when we disable/enable actions. 2710 phase = InputActionPhase.Waiting, 2711 triggerControlIndex = kInvalidIndex 2712 }; 2713 } 2714 2715 internal int GetValueSizeInBytes(int bindingIndex, int controlIndex) 2716 { 2717 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2718 Debug.Assert(controlIndex >= 0 && controlIndex < totalControlCount, "Control index out of range"); 2719 2720 if (bindingStates[bindingIndex].isPartOfComposite) ////TODO: instead, just have compositeOrCompositeBindingIndex be invalid 2721 { 2722 var compositeBindingIndex = bindingStates[bindingIndex].compositeOrCompositeBindingIndex; 2723 var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; 2724 var compositeObject = composites[compositeIndex]; 2725 Debug.Assert(compositeObject != null, "Composite object on composite state is null"); 2726 2727 return compositeObject.valueSizeInBytes; 2728 } 2729 2730 var control = controls[controlIndex]; 2731 Debug.Assert(control != null, "Control at given index is null"); 2732 return control.valueSizeInBytes; 2733 } 2734 2735 internal Type GetValueType(int bindingIndex, int controlIndex) 2736 { 2737 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2738 Debug.Assert(controlIndex >= 0 && controlIndex < totalControlCount, "Control index out of range"); 2739 2740 if (bindingStates[bindingIndex].isPartOfComposite) ////TODO: instead, just have compositeOrCompositeBindingIndex be invalid 2741 { 2742 var compositeBindingIndex = bindingStates[bindingIndex].compositeOrCompositeBindingIndex; 2743 var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; 2744 var compositeObject = composites[compositeIndex]; 2745 Debug.Assert(compositeObject != null, "Composite object is null"); 2746 2747 return compositeObject.valueType; 2748 } 2749 2750 var control = controls[controlIndex]; 2751 Debug.Assert(control != null, "Control is null"); 2752 return control.valueType; 2753 } 2754 2755 internal static bool IsActuated(ref TriggerState trigger, float threshold = 0) 2756 { 2757 var magnitude = trigger.magnitude; 2758 if (magnitude < 0) 2759 return true; 2760 if (Mathf.Approximately(threshold, 0)) 2761 return magnitude > 0; 2762 return magnitude >= threshold; 2763 } 2764 2765 ////REVIEW: we can unify the reading paths once we have blittable type constraints 2766 2767 internal void ReadValue(int bindingIndex, int controlIndex, void* buffer, int bufferSize, bool ignoreComposites = false) 2768 { 2769 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index out of range"); 2770 Debug.Assert(controlIndex >= 0 && controlIndex < totalControlCount, "Control index out of range"); 2771 2772 InputControl control = null; 2773 2774 // If the binding that triggered the action is part of a composite, let 2775 // the composite determine the value we return. 2776 if (!ignoreComposites && bindingStates[bindingIndex].isPartOfComposite) 2777 { 2778 var compositeBindingIndex = bindingStates[bindingIndex].compositeOrCompositeBindingIndex; 2779 var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; 2780 var compositeObject = composites[compositeIndex]; 2781 Debug.Assert(compositeObject != null, "Composite object is null"); 2782 2783 var context = new InputBindingCompositeContext 2784 { 2785 m_State = this, 2786 m_BindingIndex = compositeBindingIndex 2787 }; 2788 2789 compositeObject.ReadValue(ref context, buffer, bufferSize); 2790 2791 // Switch bindingIndex to that of composite so that we use the right processors. 2792 bindingIndex = compositeBindingIndex; 2793 } 2794 else 2795 { 2796 control = controls[controlIndex]; 2797 Debug.Assert(control != null, "Control is null"); 2798 control.ReadValueIntoBuffer(buffer, bufferSize); 2799 } 2800 2801 // Run value through processors, if any. 2802 var processorCount = bindingStates[bindingIndex].processorCount; 2803 if (processorCount > 0) 2804 { 2805 var processorStartIndex = bindingStates[bindingIndex].processorStartIndex; 2806 for (var i = 0; i < processorCount; ++i) 2807 processors[processorStartIndex + i].Process(buffer, bufferSize, control); 2808 } 2809 } 2810 2811 internal TValue ReadValue<TValue>(int bindingIndex, int controlIndex, bool ignoreComposites = false) 2812 where TValue : struct 2813 { 2814 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index is out of range"); 2815 2816 var value = default(TValue); 2817 2818 // In the case of a composite, this will be null. 2819 InputControl<TValue> controlOfType = null; 2820 2821 // If the binding that triggered the action is part of a composite, let 2822 // the composite determine the value we return. 2823 if (!ignoreComposites && bindingStates[bindingIndex].isPartOfComposite) 2824 { 2825 var compositeBindingIndex = bindingStates[bindingIndex].compositeOrCompositeBindingIndex; 2826 Debug.Assert(compositeBindingIndex >= 0 && compositeBindingIndex < totalBindingCount, "Composite binding index is out of range"); 2827 var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; 2828 var compositeObject = composites[compositeIndex]; 2829 Debug.Assert(compositeObject != null, "Composite object is null"); 2830 2831 var context = new InputBindingCompositeContext 2832 { 2833 m_State = this, 2834 m_BindingIndex = compositeBindingIndex 2835 }; 2836 2837 var compositeOfType = compositeObject as InputBindingComposite<TValue>; 2838 if (compositeOfType == null) 2839 { 2840 // Composite is not derived from InputBindingComposite<TValue>. Do an explicit value 2841 // type check here. Might be a composite like OneModifierComposite that dynamically 2842 // determines its value type based on what its parts are bound to. 2843 var valueType = compositeObject.valueType; 2844 if (!valueType.IsAssignableFrom(typeof(TValue))) 2845 throw new InvalidOperationException( 2846 $"Cannot read value of type '{typeof(TValue).Name}' from composite '{compositeObject}' bound to action '{GetActionOrNull(bindingIndex)}' (composite is a '{compositeIndex.GetType().Name}' with value type '{TypeHelpers.GetNiceTypeName(valueType)}')"); 2847 2848 compositeObject.ReadValue(ref context, UnsafeUtility.AddressOf(ref value), UnsafeUtility.SizeOf<TValue>()); 2849 } 2850 else 2851 { 2852 value = compositeOfType.ReadValue(ref context); 2853 } 2854 2855 // Switch bindingIndex to that of composite so that we use the right processors. 2856 bindingIndex = compositeBindingIndex; 2857 } 2858 else 2859 { 2860 if (controlIndex != kInvalidIndex) 2861 { 2862 var control = controls[controlIndex]; 2863 Debug.Assert(control != null, "Control is null"); 2864 2865 controlOfType = control as InputControl<TValue>; 2866 if (controlOfType == null) 2867 throw new InvalidOperationException( 2868 $"Cannot read value of type '{TypeHelpers.GetNiceTypeName(typeof(TValue))}' from control '{control.path}' bound to action '{GetActionOrNull(bindingIndex)}' (control is a '{control.GetType().Name}' with value type '{TypeHelpers.GetNiceTypeName(control.valueType)}')"); 2869 2870 value = controlOfType.value; 2871 } 2872 } 2873 2874 // Run value through processors, if any. 2875 return ApplyProcessors(bindingIndex, value, controlOfType); 2876 } 2877 2878 internal TValue ApplyProcessors<TValue>(int bindingIndex, TValue value, InputControl<TValue> controlOfType = null) 2879 where TValue : struct 2880 { 2881 var processorCount = bindingStates[bindingIndex].processorCount; 2882 if (processorCount > 0) 2883 { 2884 var processorStartIndex = bindingStates[bindingIndex].processorStartIndex; 2885 for (var i = 0; i < processorCount; ++i) 2886 { 2887 if (processors[processorStartIndex + i] is InputProcessor<TValue> processor) 2888 value = processor.Process(value, controlOfType); 2889 } 2890 } 2891 2892 return value; 2893 } 2894 2895 public float EvaluateCompositePartMagnitude(int bindingIndex, int partNumber) 2896 { 2897 var firstChildBindingIndex = bindingIndex + 1; 2898 var currentMagnitude = float.MinValue; 2899 for (var index = firstChildBindingIndex; index < totalBindingCount && bindingStates[index].isPartOfComposite; ++index) 2900 { 2901 if (bindingStates[index].partIndex != partNumber) 2902 continue; 2903 2904 var controlCount = bindingStates[index].controlCount; 2905 var controlStartIndex = bindingStates[index].controlStartIndex; 2906 for (var i = 0; i < controlCount; ++i) 2907 { 2908 var control = controls[controlStartIndex + i]; 2909 2910 // NOTE: We do *NOT* go to controlMagnitudes here. The reason is we may not yet have received the ProcessControlStateChange 2911 // call for a specific control that is part of the composite and thus controlMagnitudes may not yet have been updated 2912 // for a specific control. 2913 currentMagnitude = Mathf.Max(control.magnitude, currentMagnitude); 2914 } 2915 } 2916 2917 return currentMagnitude; 2918 } 2919 2920 internal double GetCompositePartPressTime(int bindingIndex, int partNumber) 2921 { 2922 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index is out of range"); 2923 Debug.Assert(bindingStates[bindingIndex].isComposite, "Binding must be a composite"); 2924 2925 var firstChildBindingIndex = bindingIndex + 1; 2926 var pressTime = double.MaxValue; 2927 for (var index = firstChildBindingIndex; index < totalBindingCount && bindingStates[index].isPartOfComposite; ++index) 2928 { 2929 ref var bindingState = ref bindingStates[index]; 2930 2931 if (bindingState.partIndex != partNumber) 2932 continue; 2933 2934 // ReSharper disable once CompareOfFloatsByEqualityOperator 2935 if (bindingState.pressTime != default && bindingState.pressTime < pressTime) 2936 pressTime = bindingState.pressTime; 2937 } 2938 2939 // ReSharper disable once CompareOfFloatsByEqualityOperator 2940 if (pressTime == double.MaxValue) 2941 return -1d; 2942 2943 return pressTime; 2944 } 2945 2946 /// <summary> 2947 /// Read the value of the given part of a composite binding. 2948 /// </summary> 2949 /// <param name="bindingIndex">Index of the composite binding in <see cref="bindingStates"/>.</param> 2950 /// <param name="partNumber">Index of the part. Note that part indices start at 1!</param> 2951 /// <typeparam name="TValue">Value type to read. Must correspond to the value of bound controls or an exception will 2952 /// be thrown.</typeparam> 2953 /// <returns>Greatest value from among the bound controls for the given part.</returns> 2954 /// <remarks> 2955 /// Composites are composed of "parts". Each part has an associated name (e.g. "negative" or "positive") which is 2956 /// referenced by <see cref="InputBinding.name"/> of bindings that are part of the composite. However, multiple 2957 /// bindings may reference the same part (e.g. there could be a binding for "W" and another binding for "UpArrow" 2958 /// and both would reference the "Up" part). 2959 /// 2960 /// However, a given composite will only be interested in a single value for any given part. What we do is give 2961 /// a composite an integer key for every part. When it asks for a value for the given part, we go through all 2962 /// bindings that reference the given part and return the greatest value from among the controls of all those 2963 /// bindings. 2964 /// 2965 /// <example> 2966 /// <code> 2967 /// // Read a float value from the second part of the composite binding at index 3. 2968 /// ReadCompositePartValue&lt;float&gt;(3, 2); 2969 /// </code> 2970 /// </example> 2971 /// </remarks> 2972 internal TValue ReadCompositePartValue<TValue, TComparer>(int bindingIndex, int partNumber, 2973 bool* buttonValuePtr, out int controlIndex, TComparer comparer = default) 2974 where TValue : struct 2975 where TComparer : IComparer<TValue> 2976 { 2977 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index is out of range"); 2978 Debug.Assert(bindingStates[bindingIndex].isComposite, "Binding must be a composite"); 2979 2980 var result = default(TValue); 2981 var firstChildBindingIndex = bindingIndex + 1; 2982 var isFirstValue = true; 2983 2984 controlIndex = kInvalidIndex; 2985 2986 // Find the binding in the composite that has both the given part number and 2987 // the greatest value. 2988 // 2989 // NOTE: It is tempting to go by control magnitudes instead as those are readily available to us (controlMagnitudes) 2990 // and avoids us reading values that we're not going to use. Unfortunately, we can't do that as several controls 2991 // used by a composite may all have been updated with a single event (e.g. WASD on a keyboard will usually see 2992 // just one update that refreshes the entire state of the keyboard). In that case, one of the controls will 2993 // see its state monitor trigger first and in turn trigger processing of the action and composite. Thus only 2994 // that one single control would have its value refreshed in controlMagnitudes whereas the other control magnitudes 2995 // would be stale. 2996 for (var index = firstChildBindingIndex; index < totalBindingCount && bindingStates[index].isPartOfComposite; ++index) 2997 { 2998 if (bindingStates[index].partIndex != partNumber) 2999 continue; 3000 3001 var controlCount = bindingStates[index].controlCount; 3002 var controlStartIndex = bindingStates[index].controlStartIndex; 3003 for (var i = 0; i < controlCount; ++i) 3004 { 3005 var thisControlIndex = controlStartIndex + i; 3006 var value = ReadValue<TValue>(index, thisControlIndex, ignoreComposites: true); 3007 3008 if (isFirstValue) 3009 { 3010 result = value; 3011 controlIndex = thisControlIndex; 3012 isFirstValue = false; 3013 } 3014 else if (comparer.Compare(value, result) > 0) 3015 { 3016 result = value; 3017 controlIndex = thisControlIndex; 3018 } 3019 3020 if (buttonValuePtr != null && controlIndex == thisControlIndex) 3021 { 3022 var control = controls[thisControlIndex]; 3023 if (control is ButtonControl button) 3024 { 3025 *buttonValuePtr = button.isPressed; 3026 } 3027 else if (control is InputControl<float>) 3028 { 3029 var valuePtr = UnsafeUtility.AddressOf(ref value); 3030 *buttonValuePtr = *(float*)valuePtr >= ButtonControl.s_GlobalDefaultButtonPressPoint; 3031 } 3032 3033 ////REVIEW: Early out here as soon as *any* button is pressed? Technically, the comparer 3034 //// could still select a different control, though... 3035 } 3036 } 3037 } 3038 3039 return result; 3040 } 3041 3042 internal bool ReadCompositePartValue(int bindingIndex, int partNumber, void* buffer, int bufferSize) 3043 { 3044 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index is out of range"); 3045 Debug.Assert(bindingStates[bindingIndex].isComposite, "Binding must be a composite"); 3046 3047 var firstChildBindingIndex = bindingIndex + 1; 3048 3049 // Find the binding in the composite that has both the given part number and 3050 // the greatest amount of actuation. 3051 var currentMagnitude = float.MinValue; 3052 for (var index = firstChildBindingIndex; index < totalBindingCount && bindingStates[index].isPartOfComposite; ++index) 3053 { 3054 if (bindingStates[index].partIndex != partNumber) 3055 continue; 3056 3057 var controlCount = bindingStates[index].controlCount; 3058 var controlStartIndex = bindingStates[index].controlStartIndex; 3059 for (var i = 0; i < controlCount; ++i) 3060 { 3061 var thisControlIndex = controlStartIndex + i; 3062 3063 // Check if the control has greater actuation than the most actuated control 3064 // we've found so far. 3065 // 3066 // NOTE: We cannot rely on controlMagnitudes here as several controls used by a composite may all have been updated 3067 // with a single event (e.g. WASD on a keyboard will usually see just one update that refreshes the entire state 3068 // of the keyboard). In that case, one of the controls will see its state monitor trigger first and in turn 3069 // trigger processing of the action and composite. Thus only that one single control would have its value 3070 // refreshed in controlMagnitudes whereas the other control magnitudes would be stale. 3071 var control = controls[thisControlIndex]; 3072 var magnitude = control.magnitude; 3073 if (magnitude < currentMagnitude) 3074 continue; 3075 3076 // If so, read the value. 3077 ReadValue(index, thisControlIndex, buffer, bufferSize, ignoreComposites: true); 3078 currentMagnitude = magnitude; 3079 } 3080 } 3081 3082 return currentMagnitude > float.MinValue; 3083 } 3084 3085 internal object ReadCompositePartValueAsObject(int bindingIndex, int partNumber) 3086 { 3087 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index is out of range"); 3088 Debug.Assert(bindingStates[bindingIndex].isComposite, "Binding must be a composite"); 3089 3090 var firstChildBindingIndex = bindingIndex + 1; 3091 3092 // Find the binding in the composite that both has the given part number and 3093 // the greatest amount of actuation. 3094 var currentMagnitude = float.MinValue; 3095 object currentValue = null; 3096 for (var index = firstChildBindingIndex; index < totalBindingCount && bindingStates[index].isPartOfComposite; ++index) 3097 { 3098 if (bindingStates[index].partIndex != partNumber) 3099 continue; 3100 3101 var controlCount = bindingStates[index].controlCount; 3102 var controlStartIndex = bindingStates[index].controlStartIndex; 3103 for (var i = 0; i < controlCount; ++i) 3104 { 3105 var thisControlIndex = controlStartIndex + i; 3106 3107 // Check if the control has greater actuation than the most actuated control 3108 // we've found so far. 3109 // 3110 // NOTE: We cannot rely on controlMagnitudes here as several controls used by a composite may all have been updated 3111 // with a single event (e.g. WASD on a keyboard will usually see just one update that refreshes the entire state 3112 // of the keyboard). In that case, one of the controls will see its state monitor trigger first and in turn 3113 // trigger processing of the action and composite. Thus only that one single control would have its value 3114 // refreshed in controlMagnitudes whereas the other control magnitudes would be stale. 3115 var control = controls[thisControlIndex]; 3116 var magnitude = control.magnitude; 3117 if (magnitude < currentMagnitude) 3118 continue; 3119 3120 // If so, read the value. 3121 currentValue = ReadValueAsObject(index, thisControlIndex, ignoreComposites: true); 3122 currentMagnitude = magnitude; 3123 } 3124 } 3125 3126 return currentValue; 3127 } 3128 3129 internal object ReadValueAsObject(int bindingIndex, int controlIndex, bool ignoreComposites = false) 3130 { 3131 Debug.Assert(bindingIndex >= 0 && bindingIndex < totalBindingCount, "Binding index is out of range"); 3132 3133 InputControl control = null; 3134 object value = null; 3135 3136 // If the binding that triggered the action is part of a composite, let 3137 // the composite determine the value we return. 3138 if (!ignoreComposites && bindingStates[bindingIndex].isPartOfComposite) ////TODO: instead, just have compositeOrCompositeBindingIndex be invalid 3139 { 3140 var compositeBindingIndex = bindingStates[bindingIndex].compositeOrCompositeBindingIndex; 3141 Debug.Assert(compositeBindingIndex >= 0 && compositeBindingIndex < totalBindingCount, "Binding index is out of range"); 3142 var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; 3143 var compositeObject = composites[compositeIndex]; 3144 Debug.Assert(compositeObject != null, "Composite object is null"); 3145 3146 var context = new InputBindingCompositeContext 3147 { 3148 m_State = this, 3149 m_BindingIndex = compositeBindingIndex 3150 }; 3151 3152 value = compositeObject.ReadValueAsObject(ref context); 3153 3154 // Switch bindingIndex to that of composite so that we use the right processors. 3155 bindingIndex = compositeBindingIndex; 3156 } 3157 else 3158 { 3159 if (controlIndex != kInvalidIndex) 3160 { 3161 control = controls[controlIndex]; 3162 Debug.Assert(control != null, "Control is null"); 3163 value = control.ReadValueAsObject(); 3164 } 3165 } 3166 3167 if (value != null) 3168 { 3169 // Run value through processors, if any. 3170 var processorCount = bindingStates[bindingIndex].processorCount; 3171 if (processorCount > 0) 3172 { 3173 var processorStartIndex = bindingStates[bindingIndex].processorStartIndex; 3174 for (var i = 0; i < processorCount; ++i) 3175 value = processors[processorStartIndex + i].ProcessAsObject(value, control); 3176 } 3177 } 3178 3179 return value; 3180 } 3181 3182 internal bool ReadValueAsButton(int bindingIndex, int controlIndex) 3183 { 3184 var buttonControl = default(ButtonControl); 3185 if (!bindingStates[bindingIndex].isPartOfComposite) 3186 buttonControl = controls[controlIndex] as ButtonControl; 3187 3188 // Read float value. 3189 var floatValue = ReadValue<float>(bindingIndex, controlIndex); 3190 3191 // Compare to press point. 3192 if (buttonControl != null) 3193 return floatValue >= buttonControl.pressPointOrDefault; 3194 return floatValue >= ButtonControl.s_GlobalDefaultButtonPressPoint; 3195 } 3196 3197 /// <summary> 3198 /// Records the current state of a single interaction attached to a binding. 3199 /// Each interaction keeps track of its own trigger control and phase progression. 3200 /// </summary> 3201 [StructLayout(LayoutKind.Explicit, Size = 48)] 3202 internal struct InteractionState 3203 { 3204 [FieldOffset(0)] private ushort m_TriggerControlIndex; 3205 [FieldOffset(2)] private byte m_Phase; 3206 [FieldOffset(3)] private byte m_Flags; 3207 [FieldOffset(4)] private float m_TimerDuration; 3208 [FieldOffset(8)] private double m_StartTime; 3209 [FieldOffset(16)] private double m_TimerStartTime; 3210 [FieldOffset(24)] private double m_PerformedTime; 3211 [FieldOffset(32)] private float m_TotalTimeoutCompletionTimeDone; 3212 [FieldOffset(36)] private float m_TotalTimeoutCompletionTimeRemaining; 3213 [FieldOffset(40)] private long m_TimerMonitorIndex; 3214 3215 public int triggerControlIndex 3216 { 3217 get 3218 { 3219 if (m_TriggerControlIndex == ushort.MaxValue) 3220 return kInvalidIndex; 3221 return m_TriggerControlIndex; 3222 } 3223 set 3224 { 3225 if (value == kInvalidIndex) 3226 m_TriggerControlIndex = ushort.MaxValue; 3227 else 3228 { 3229 if (value < 0 || value >= ushort.MaxValue) 3230 throw new NotSupportedException("More than ushort.MaxValue-1 controls in a single InputActionState"); 3231 m_TriggerControlIndex = (ushort)value; 3232 } 3233 } 3234 } 3235 3236 public double startTime 3237 { 3238 get => m_StartTime; 3239 set => m_StartTime = value; 3240 } 3241 3242 public double performedTime 3243 { 3244 get => m_PerformedTime; 3245 set => m_PerformedTime = value; 3246 } 3247 3248 public double timerStartTime 3249 { 3250 get => m_TimerStartTime; 3251 set => m_TimerStartTime = value; 3252 } 3253 3254 public float timerDuration 3255 { 3256 get => m_TimerDuration; 3257 set => m_TimerDuration = value; 3258 } 3259 3260 public float totalTimeoutCompletionDone 3261 { 3262 get => m_TotalTimeoutCompletionTimeDone; 3263 set => m_TotalTimeoutCompletionTimeDone = value; 3264 } 3265 3266 public float totalTimeoutCompletionTimeRemaining 3267 { 3268 get => m_TotalTimeoutCompletionTimeRemaining; 3269 set => m_TotalTimeoutCompletionTimeRemaining = value; 3270 } 3271 3272 public long timerMonitorIndex 3273 { 3274 get => m_TimerMonitorIndex; 3275 set => m_TimerMonitorIndex = value; 3276 } 3277 3278 public bool isTimerRunning 3279 { 3280 get => ((Flags)m_Flags & Flags.TimerRunning) == Flags.TimerRunning; 3281 set 3282 { 3283 if (value) 3284 m_Flags |= (byte)Flags.TimerRunning; 3285 else 3286 { 3287 var mask = ~Flags.TimerRunning; 3288 m_Flags &= (byte)mask; 3289 } 3290 } 3291 } 3292 3293 public InputActionPhase phase 3294 { 3295 get => (InputActionPhase)m_Phase; 3296 set => m_Phase = (byte)value; 3297 } 3298 3299 [Flags] 3300 private enum Flags 3301 { 3302 TimerRunning = 1 << 0, 3303 } 3304 } 3305 3306 /// <summary> 3307 /// Runtime state for a single binding. 3308 /// </summary> 3309 /// <remarks> 3310 /// Correlated to the <see cref="InputBinding"/> it corresponds to by the index in the binding 3311 /// array. 3312 /// </remarks> 3313 [StructLayout(LayoutKind.Explicit, Size = 32)] 3314 internal struct BindingState 3315 { 3316 [FieldOffset(0)] private byte m_ControlCount; 3317 [FieldOffset(1)] private byte m_InteractionCount; 3318 [FieldOffset(2)] private byte m_ProcessorCount; 3319 [FieldOffset(3)] private byte m_MapIndex; 3320 [FieldOffset(4)] private byte m_Flags; 3321 [FieldOffset(5)] private byte m_PartIndex; 3322 [FieldOffset(6)] private ushort m_ActionIndex; 3323 [FieldOffset(8)] private ushort m_CompositeOrCompositeBindingIndex; 3324 [FieldOffset(10)] private ushort m_ProcessorStartIndex; 3325 [FieldOffset(12)] private ushort m_InteractionStartIndex; 3326 [FieldOffset(14)] private ushort m_ControlStartIndex; 3327 [FieldOffset(16)] private double m_PressTime; 3328 [FieldOffset(24)] private int m_TriggerEventIdForComposite; 3329 [FieldOffset(28)] private int __padding; // m_PressTime double must be aligned 3330 3331 [Flags] 3332 public enum Flags 3333 { 3334 ChainsWithNext = 1 << 0, 3335 EndOfChain = 1 << 1, 3336 Composite = 1 << 2, 3337 PartOfComposite = 1 << 3, 3338 InitialStateCheckPending = 1 << 4, 3339 WantsInitialStateCheck = 1 << 5, 3340 } 3341 3342 /// <summary> 3343 /// Index into <see cref="controls"/> of first control associated with the binding. 3344 /// </summary> 3345 /// <remarks> 3346 /// For composites, this is the index of the first control that is bound by any of the parts in the composite. 3347 /// </remarks> 3348 public int controlStartIndex 3349 { 3350 get => m_ControlStartIndex; 3351 set 3352 { 3353 Debug.Assert(value != kInvalidIndex, "Control state index is invalid"); 3354 if (value >= ushort.MaxValue) 3355 throw new NotSupportedException("Total control count in state cannot exceed byte.MaxValue=" + ushort.MaxValue); 3356 m_ControlStartIndex = (ushort)value; 3357 } 3358 } 3359 3360 /// <summary> 3361 /// Number of controls associated with this binding. 3362 /// </summary> 3363 /// <remarks> 3364 /// For composites, this is the total number of controls bound by all parts of the composite combined. 3365 /// </remarks> 3366 public int controlCount 3367 { 3368 get => m_ControlCount; 3369 set 3370 { 3371 if (value >= byte.MaxValue) 3372 throw new NotSupportedException("Control count per binding cannot exceed byte.MaxValue=" + byte.MaxValue); 3373 m_ControlCount = (byte)value; 3374 } 3375 } 3376 3377 /// <summary> 3378 /// Index into <see cref="InputActionState.interactionStates"/> of first interaction associated with the binding. 3379 /// </summary> 3380 public int interactionStartIndex 3381 { 3382 get 3383 { 3384 if (m_InteractionStartIndex == ushort.MaxValue) 3385 return kInvalidIndex; 3386 return m_InteractionStartIndex; 3387 } 3388 set 3389 { 3390 if (value == kInvalidIndex) 3391 m_InteractionStartIndex = ushort.MaxValue; 3392 else 3393 { 3394 if (value >= ushort.MaxValue) 3395 throw new NotSupportedException("Interaction count cannot exceed ushort.MaxValue=" + ushort.MaxValue); 3396 m_InteractionStartIndex = (ushort)value; 3397 } 3398 } 3399 } 3400 3401 /// <summary> 3402 /// Number of interactions associated with this binding. 3403 /// </summary> 3404 public int interactionCount 3405 { 3406 get => m_InteractionCount; 3407 set 3408 { 3409 if (value >= byte.MaxValue) 3410 throw new NotSupportedException("Interaction count per binding cannot exceed byte.MaxValue=" + byte.MaxValue); 3411 m_InteractionCount = (byte)value; 3412 } 3413 } 3414 3415 public int processorStartIndex 3416 { 3417 get 3418 { 3419 if (m_ProcessorStartIndex == ushort.MaxValue) 3420 return kInvalidIndex; 3421 return m_ProcessorStartIndex; 3422 } 3423 set 3424 { 3425 if (value == kInvalidIndex) 3426 m_ProcessorStartIndex = ushort.MaxValue; 3427 else 3428 { 3429 if (value >= ushort.MaxValue) 3430 throw new NotSupportedException("Processor count cannot exceed ushort.MaxValue=" + ushort.MaxValue); 3431 m_ProcessorStartIndex = (ushort)value; 3432 } 3433 } 3434 } 3435 3436 public int processorCount 3437 { 3438 get => m_ProcessorCount; 3439 set 3440 { 3441 if (value >= byte.MaxValue) 3442 throw new NotSupportedException("Processor count per binding cannot exceed byte.MaxValue=" + byte.MaxValue); 3443 m_ProcessorCount = (byte)value; 3444 } 3445 } 3446 3447 /// <summary> 3448 /// Index of the action being triggered by the binding (if any). 3449 /// </summary> 3450 /// <remarks> 3451 /// For bindings that don't trigger actions, this is <see cref="kInvalidIndex"/>. 3452 /// 3453 /// For bindings that are part of a composite, we force this to be the action set on the composite itself. 3454 /// </remarks> 3455 public int actionIndex 3456 { 3457 get 3458 { 3459 if (m_ActionIndex == ushort.MaxValue) 3460 return kInvalidIndex; 3461 return m_ActionIndex; 3462 } 3463 set 3464 { 3465 if (value == kInvalidIndex) 3466 m_ActionIndex = ushort.MaxValue; 3467 else 3468 { 3469 if (value >= ushort.MaxValue) 3470 throw new NotSupportedException("Action count cannot exceed ushort.MaxValue=" + ushort.MaxValue); 3471 m_ActionIndex = (ushort)value; 3472 } 3473 } 3474 } 3475 3476 public int mapIndex 3477 { 3478 get => m_MapIndex; 3479 set 3480 { 3481 Debug.Assert(value != kInvalidIndex, "Map index is invalid"); 3482 if (value >= byte.MaxValue) 3483 throw new NotSupportedException("Map count cannot exceed byte.MaxValue=" + byte.MaxValue); 3484 m_MapIndex = (byte)value; 3485 } 3486 } 3487 3488 /// <summary> 3489 /// If this is a composite binding, this is the index of the composite in <see cref="composites"/>. 3490 /// If the binding is part of a composite, this is the index of the binding that is the composite. 3491 /// If the binding is neither a composite nor part of a composite, this is <see cref="kInvalidIndex"/>. 3492 /// </summary> 3493 public int compositeOrCompositeBindingIndex 3494 { 3495 get 3496 { 3497 if (m_CompositeOrCompositeBindingIndex == ushort.MaxValue) 3498 return kInvalidIndex; 3499 return m_CompositeOrCompositeBindingIndex; 3500 } 3501 set 3502 { 3503 if (value == kInvalidIndex) 3504 m_CompositeOrCompositeBindingIndex = ushort.MaxValue; 3505 else 3506 { 3507 if (value >= ushort.MaxValue) 3508 throw new NotSupportedException("Composite count cannot exceed ushort.MaxValue=" + ushort.MaxValue); 3509 m_CompositeOrCompositeBindingIndex = (ushort)value; 3510 } 3511 } 3512 } 3513 3514 /// <summary> 3515 /// <see cref="InputEvent.eventId">ID</see> of the event that last triggered the binding. 3516 /// </summary> 3517 /// <remarks> 3518 /// We only store this for composites ATM. 3519 /// </remarks> 3520 public int triggerEventIdForComposite 3521 { 3522 get => m_TriggerEventIdForComposite; 3523 set => m_TriggerEventIdForComposite = value; 3524 } 3525 3526 // For now, we only record this for part bindings! 3527 public double pressTime 3528 { 3529 get => m_PressTime; 3530 set => m_PressTime = value; 3531 } 3532 3533 public Flags flags 3534 { 3535 get => (Flags)m_Flags; 3536 set => m_Flags = (byte)value; 3537 } 3538 3539 public bool chainsWithNext 3540 { 3541 get => (flags & Flags.ChainsWithNext) == Flags.ChainsWithNext; 3542 set 3543 { 3544 if (value) 3545 flags |= Flags.ChainsWithNext; 3546 else 3547 flags &= ~Flags.ChainsWithNext; 3548 } 3549 } 3550 3551 public bool isEndOfChain 3552 { 3553 get => (flags & Flags.EndOfChain) == Flags.EndOfChain; 3554 set 3555 { 3556 if (value) 3557 flags |= Flags.EndOfChain; 3558 else 3559 flags &= ~Flags.EndOfChain; 3560 } 3561 } 3562 3563 public bool isPartOfChain => chainsWithNext || isEndOfChain; 3564 3565 public bool isComposite 3566 { 3567 get => (flags & Flags.Composite) == Flags.Composite; 3568 set 3569 { 3570 if (value) 3571 flags |= Flags.Composite; 3572 else 3573 flags &= ~Flags.Composite; 3574 } 3575 } 3576 3577 public bool isPartOfComposite 3578 { 3579 get => (flags & Flags.PartOfComposite) == Flags.PartOfComposite; 3580 set 3581 { 3582 if (value) 3583 flags |= Flags.PartOfComposite; 3584 else 3585 flags &= ~Flags.PartOfComposite; 3586 } 3587 } 3588 3589 public bool initialStateCheckPending 3590 { 3591 get => (flags & Flags.InitialStateCheckPending) != 0; 3592 set 3593 { 3594 if (value) 3595 flags |= Flags.InitialStateCheckPending; 3596 else 3597 flags &= ~Flags.InitialStateCheckPending; 3598 } 3599 } 3600 3601 public bool wantsInitialStateCheck 3602 { 3603 get => (flags & Flags.WantsInitialStateCheck) != 0; 3604 set 3605 { 3606 if (value) 3607 flags |= Flags.WantsInitialStateCheck; 3608 else 3609 flags &= ~Flags.WantsInitialStateCheck; 3610 } 3611 } 3612 3613 public int partIndex 3614 { 3615 get => m_PartIndex; 3616 set 3617 { 3618 if (partIndex < 0) 3619 throw new ArgumentOutOfRangeException(nameof(value), "Part index must not be negative"); 3620 if (partIndex > byte.MaxValue) 3621 throw new InvalidOperationException("Part count must not exceed byte.MaxValue=" + byte.MaxValue); 3622 m_PartIndex = (byte)value; 3623 } 3624 } 3625 } 3626 3627 /// <summary> 3628 /// Record of an input control change and its related data. 3629 /// </summary> 3630 /// <remarks> 3631 /// This serves a dual purpose. One is, trigger states represent control actuations while we process them. The 3632 /// other is to represent the current actuation state of an action as a whole. The latter is stored in <see cref="actionStates"/> 3633 /// while the former is passed around as temporary instances on the stack. 3634 /// </remarks> 3635 [StructLayout(LayoutKind.Explicit, Size = 56)] 3636 public struct TriggerState 3637 { 3638 public const int kMaxNumMaps = byte.MaxValue; 3639 public const int kMaxNumControls = ushort.MaxValue; 3640 public const int kMaxNumBindings = ushort.MaxValue; 3641 3642 [FieldOffset(0)] private byte m_Phase; 3643 [FieldOffset(1)] private byte m_Flags; 3644 [FieldOffset(2)] private byte m_MapIndex; 3645 // One byte available here. 3646 [FieldOffset(4)] private ushort m_ControlIndex; 3647 // Two bytes available here. 3648 ////REVIEW: can we condense these to floats? would save us a whopping 8 bytes 3649 [FieldOffset(8)] private double m_Time; 3650 [FieldOffset(16)] private double m_StartTime; 3651 [FieldOffset(24)] private ushort m_BindingIndex; 3652 [FieldOffset(26)] private ushort m_InteractionIndex; 3653 [FieldOffset(28)] private float m_Magnitude; 3654 [FieldOffset(32)] private uint m_LastPerformedInUpdate; 3655 [FieldOffset(36)] private uint m_LastCanceledInUpdate; 3656 [FieldOffset(40)] private uint m_PressedInUpdate; 3657 [FieldOffset(44)] private uint m_ReleasedInUpdate; 3658 [FieldOffset(48)] private uint m_LastCompletedInUpdate; 3659 [FieldOffset(52)] private int m_Frame; 3660 3661 /// <summary> 3662 /// Phase being triggered by the control value change. 3663 /// </summary> 3664 public InputActionPhase phase 3665 { 3666 get => (InputActionPhase)m_Phase; 3667 set => m_Phase = (byte)value; 3668 } 3669 3670 public bool isDisabled => phase == InputActionPhase.Disabled; 3671 public bool isWaiting => phase == InputActionPhase.Waiting; 3672 public bool isStarted => phase == InputActionPhase.Started; 3673 public bool isPerformed => phase == InputActionPhase.Performed; 3674 public bool isCanceled => phase == InputActionPhase.Canceled; 3675 3676 /// <summary> 3677 /// The time the binding got triggered. 3678 /// </summary> 3679 public double time 3680 { 3681 get => m_Time; 3682 set => m_Time = value; 3683 } 3684 3685 /// <summary> 3686 /// The time when the binding moved into <see cref="InputActionPhase.Started"/>. 3687 /// </summary> 3688 public double startTime 3689 { 3690 get => m_StartTime; 3691 set => m_StartTime = value; 3692 } 3693 3694 /// <summary> 3695 /// Amount of actuation on the control. 3696 /// </summary> 3697 /// <remarks> 3698 /// This is only valid if <see cref="haveMagnitude"/> is true. 3699 /// 3700 /// Note that this may differ from the actuation stored for <see cref="controlIndex"/> in <see 3701 /// cref="UnmanagedMemory.controlMagnitudes"/> if the binding is a composite. 3702 /// </remarks> 3703 public float magnitude 3704 { 3705 get => m_Magnitude; 3706 set 3707 { 3708 flags |= Flags.HaveMagnitude; 3709 m_Magnitude = value; 3710 } 3711 } 3712 3713 /// <summary> 3714 /// Whether <see cref="magnitude"/> has been set. 3715 /// </summary> 3716 /// <remarks> 3717 /// Magnitude computation is expensive so we only want to do it once. Also, we sometimes need to compare 3718 /// a current magnitude to a magnitude value from a previous frame and the magnitude of the control 3719 /// may have already changed. 3720 /// </remarks> 3721 public bool haveMagnitude => (flags & Flags.HaveMagnitude) != 0; 3722 3723 /// <summary> 3724 /// Index of the action map in <see cref="maps"/> that contains the binding that triggered. 3725 /// </summary> 3726 public int mapIndex 3727 { 3728 get => m_MapIndex; 3729 set 3730 { 3731 if (value < 0 || value > kMaxNumMaps) 3732 throw new NotSupportedException("More than byte.MaxValue InputActionMaps in a single InputActionState"); 3733 m_MapIndex = (byte)value; 3734 } 3735 } 3736 3737 /// <summary> 3738 /// Index of the control currently driving the action or <see cref="kInvalidIndex"/> if none. 3739 /// </summary> 3740 public int controlIndex 3741 { 3742 get 3743 { 3744 if (m_ControlIndex == kMaxNumControls) 3745 return kInvalidIndex; 3746 return m_ControlIndex; 3747 } 3748 set 3749 { 3750 if (value == kInvalidIndex) 3751 m_ControlIndex = ushort.MaxValue; 3752 else 3753 { 3754 if (value < 0 || value >= kMaxNumControls) 3755 throw new NotSupportedException("More than ushort.MaxValue-1 controls in a single InputActionState"); 3756 m_ControlIndex = (ushort)value; 3757 } 3758 } 3759 } 3760 3761 /// <summary> 3762 /// Index into <see cref="bindingStates"/> for the binding that triggered. 3763 /// </summary> 3764 /// <remarks> 3765 /// This corresponds 1:1 to an <see cref="InputBinding"/>. 3766 /// </remarks> 3767 public int bindingIndex 3768 { 3769 get => m_BindingIndex; 3770 set 3771 { 3772 if (value < 0 || value > kMaxNumBindings) 3773 throw new NotSupportedException("More than ushort.MaxValue bindings in a single InputActionState"); 3774 m_BindingIndex = (ushort)value; 3775 } 3776 } 3777 3778 /// <summary> 3779 /// Index into <see cref="InputActionState.interactionStates"/> for the interaction that triggered. 3780 /// </summary> 3781 /// <remarks> 3782 /// Is <see cref="InputActionState.kInvalidIndex"/> if there is no interaction present on the binding. 3783 /// </remarks> 3784 public int interactionIndex 3785 { 3786 get 3787 { 3788 if (m_InteractionIndex == ushort.MaxValue) 3789 return kInvalidIndex; 3790 return m_InteractionIndex; 3791 } 3792 set 3793 { 3794 if (value == kInvalidIndex) 3795 m_InteractionIndex = ushort.MaxValue; 3796 else 3797 { 3798 if (value < 0 || value >= ushort.MaxValue) 3799 throw new NotSupportedException("More than ushort.MaxValue-1 interactions in a single InputActionState"); 3800 m_InteractionIndex = (ushort)value; 3801 } 3802 } 3803 } 3804 3805 /// <summary> 3806 /// Update step count (<see cref="InputUpdate.s_UpdateStepCount"/>) in which action triggered/performed last. 3807 /// Zero if the action did not trigger yet. Also reset to zero when the action is hard reset. 3808 /// </summary> 3809 public uint lastPerformedInUpdate 3810 { 3811 get => m_LastPerformedInUpdate; 3812 set => m_LastPerformedInUpdate = value; 3813 } 3814 3815 internal int frame 3816 { 3817 get => m_Frame; 3818 set => m_Frame = value; 3819 } 3820 3821 /// <summary> 3822 /// Update step count (<see cref="InputUpdate.s_UpdateStepCount"/>) in which action completed last. 3823 /// Zero if the action did not become completed yet. Also reset to zero when the action is hard reset. 3824 /// </summary> 3825 public uint lastCompletedInUpdate 3826 { 3827 get => m_LastCompletedInUpdate; 3828 set => m_LastCompletedInUpdate = value; 3829 } 3830 3831 public uint lastCanceledInUpdate 3832 { 3833 get => m_LastCanceledInUpdate; 3834 set => m_LastCanceledInUpdate = value; 3835 } 3836 3837 public uint pressedInUpdate 3838 { 3839 get => m_PressedInUpdate; 3840 set => m_PressedInUpdate = value; 3841 } 3842 3843 public uint releasedInUpdate 3844 { 3845 get => m_ReleasedInUpdate; 3846 set => m_ReleasedInUpdate = value; 3847 } 3848 3849 /// <summary> 3850 /// Whether the action associated with the trigger state is marked as pass-through. 3851 /// </summary> 3852 /// <seealso cref="InputActionType.PassThrough"/> 3853 public bool isPassThrough 3854 { 3855 get => (flags & Flags.PassThrough) != 0; 3856 set 3857 { 3858 if (value) 3859 flags |= Flags.PassThrough; 3860 else 3861 flags &= ~Flags.PassThrough; 3862 } 3863 } 3864 3865 /// <summary> 3866 /// Whether the action associated with the trigger state is a button-type action. 3867 /// </summary> 3868 /// <seealso cref="InputActionType.Button"/> 3869 public bool isButton 3870 { 3871 get => (flags & Flags.Button) != 0; 3872 set 3873 { 3874 if (value) 3875 flags |= Flags.Button; 3876 else 3877 flags &= ~Flags.Button; 3878 } 3879 } 3880 3881 public bool isPressed 3882 { 3883 get => (flags & Flags.Pressed) != 0; 3884 set 3885 { 3886 if (value) 3887 flags |= Flags.Pressed; 3888 else 3889 flags &= ~Flags.Pressed; 3890 } 3891 } 3892 3893 /// <summary> 3894 /// Whether the action may potentially see multiple concurrent actuations from its bindings 3895 /// and wants them resolved automatically. 3896 /// </summary> 3897 /// <remarks> 3898 /// We use this to gate some of the more expensive checks that are pointless to 3899 /// perform if we don't have to disambiguate input from concurrent sources. 3900 /// 3901 /// Always disabled if <see cref="isPassThrough"/> is true. 3902 /// </remarks> 3903 public bool mayNeedConflictResolution 3904 { 3905 get => (flags & Flags.MayNeedConflictResolution) != 0; 3906 set 3907 { 3908 if (value) 3909 flags |= Flags.MayNeedConflictResolution; 3910 else 3911 flags &= ~Flags.MayNeedConflictResolution; 3912 } 3913 } 3914 3915 /// <summary> 3916 /// Whether the action currently has several concurrent actuations from its bindings. 3917 /// </summary> 3918 /// <remarks> 3919 /// This is only used when automatic conflict resolution is enabled (<see cref="mayNeedConflictResolution"/>). 3920 /// </remarks> 3921 public bool hasMultipleConcurrentActuations 3922 { 3923 get => (flags & Flags.HasMultipleConcurrentActuations) != 0; 3924 set 3925 { 3926 if (value) 3927 flags |= Flags.HasMultipleConcurrentActuations; 3928 else 3929 flags &= ~Flags.HasMultipleConcurrentActuations; 3930 } 3931 } 3932 3933 public bool inProcessing 3934 { 3935 get => (flags & Flags.InProcessing) != 0; 3936 set 3937 { 3938 if (value) 3939 flags |= Flags.InProcessing; 3940 else 3941 flags &= ~Flags.InProcessing; 3942 } 3943 } 3944 3945 public Flags flags 3946 { 3947 get => (Flags)m_Flags; 3948 set => m_Flags = (byte)value; 3949 } 3950 3951 [Flags] 3952 public enum Flags 3953 { 3954 /// <summary> 3955 /// Whether <see cref="magnitude"/> has been set. 3956 /// </summary> 3957 HaveMagnitude = 1 << 0, 3958 3959 /// <summary> 3960 /// Whether the action associated with the trigger state is marked as pass-through. 3961 /// </summary> 3962 /// <seealso cref="InputActionType.PassThrough"/> 3963 PassThrough = 1 << 1, 3964 3965 /// <summary> 3966 /// Whether the action has more than one control bound to it. 3967 /// </summary> 3968 /// <remarks> 3969 /// An action may have arbitrary many bindings yet may still resolve only to a single control 3970 /// at runtime. In that case, this flag is NOT set. We only set it if binding resolution for 3971 /// an action indeed ended up with multiple controls able to trigger the same action. 3972 /// </remarks> 3973 MayNeedConflictResolution = 1 << 2, 3974 3975 /// <summary> 3976 /// Whether there are currently multiple bound controls that are actuated. 3977 /// </summary> 3978 /// <remarks> 3979 /// This is only used if <see cref="TriggerState.mayNeedConflictResolution"/> is true. 3980 /// </remarks> 3981 HasMultipleConcurrentActuations = 1 << 3, 3982 3983 InProcessing = 1 << 4, 3984 3985 /// <summary> 3986 /// Whether the action associated with the trigger state is a button-type action. 3987 /// </summary> 3988 /// <seealso cref="InputActionType.Button"/> 3989 Button = 1 << 5, 3990 3991 Pressed = 1 << 6, 3992 } 3993 } 3994 3995 /// <summary> 3996 /// Tells us where the data for a single action map is found in the 3997 /// various arrays. 3998 /// </summary> 3999 public struct ActionMapIndices 4000 { 4001 public int actionStartIndex; 4002 public int actionCount; 4003 public int controlStartIndex; 4004 public int controlCount; 4005 public int bindingStartIndex; 4006 public int bindingCount; 4007 public int interactionStartIndex; 4008 public int interactionCount; 4009 public int processorStartIndex; 4010 public int processorCount; 4011 public int compositeStartIndex; 4012 public int compositeCount; 4013 } 4014 4015 /// <summary> 4016 /// Unmanaged memory kept for action maps. 4017 /// </summary> 4018 /// <remarks> 4019 /// Most of the dynamic execution state for actions we keep in a single block of unmanaged memory. 4020 /// Essentially, only the C# heap objects (like IInputInteraction and such) we keep in managed arrays. 4021 /// Aside from being able to condense the data into a single block of memory and not having to have 4022 /// it spread out on the GC heap, we gain the advantage of being able to freely allocate and re-allocate 4023 /// these blocks without creating garbage on the GC heap. 4024 /// 4025 /// The data here is set up by <see cref="InputBindingResolver"/>. 4026 /// </remarks> 4027 public struct UnmanagedMemory : IDisposable 4028 { 4029 public bool isAllocated => basePtr != null; 4030 4031 public void* basePtr; 4032 4033 /// <summary> 4034 /// Number of action maps and entries in <see cref="mapIndices"/> and <see cref="maps"/>. 4035 /// </summary> 4036 public int mapCount; 4037 4038 /// <summary> 4039 /// Total number of actions (i.e. from all maps combined) and entries in <see cref="actionStates"/>. 4040 /// </summary> 4041 public int actionCount; 4042 4043 /// <summary> 4044 /// Total number of interactions and entries in <see cref="interactionStates"/> and <see cref="interactions"/>. 4045 /// </summary> 4046 public int interactionCount; 4047 4048 /// <summary> 4049 /// Total number of bindings and entries in <see cref="bindingStates"/>. 4050 /// </summary> 4051 public int bindingCount; 4052 4053 /// <summary> 4054 /// Total number of bound controls and entries in <see cref="controls"/>. 4055 /// </summary> 4056 public int controlCount; 4057 4058 /// <summary> 4059 /// Total number of composite bindings and entries in <see cref="composites"/>. 4060 /// </summary> 4061 public int compositeCount; 4062 4063 /// <summary> 4064 /// Total size of allocated unmanaged memory. 4065 /// </summary> 4066 public int sizeInBytes => 4067 mapCount * sizeof(ActionMapIndices) + // mapIndices 4068 actionCount * sizeof(TriggerState) + // actionStates 4069 bindingCount * sizeof(BindingState) + // bindingStates 4070 interactionCount * sizeof(InteractionState) + // interactionStates 4071 controlCount * sizeof(float) + // controlMagnitudes 4072 compositeCount * sizeof(float) + // compositeMagnitudes 4073 controlCount * sizeof(int) + // controlIndexToBindingIndex 4074 controlCount * sizeof(ushort) * 2 + // controlGrouping 4075 actionCount * sizeof(ushort) * 2 + // actionBindingIndicesAndCounts 4076 bindingCount * sizeof(ushort) + // actionBindingIndices 4077 (controlCount + 31) / 32 * sizeof(int); // enabledControlsArray 4078 4079 /// <summary> 4080 /// Trigger state of all actions added to the state. 4081 /// </summary> 4082 /// <remarks> 4083 /// This array also tells which actions are enabled or disabled. Any action with phase 4084 /// <see cref="InputActionPhase.Disabled"/> is disabled. 4085 /// </remarks> 4086 public TriggerState* actionStates; 4087 4088 /// <summary> 4089 /// State of all bindings added to the state. 4090 /// </summary> 4091 /// <remarks> 4092 /// For the most part, this is read-only information set up during resolution. 4093 /// </remarks> 4094 public BindingState* bindingStates; 4095 4096 /// <summary> 4097 /// State of all interactions on bindings in the action map. 4098 /// </summary> 4099 /// <remarks> 4100 /// Any interaction mentioned on any of the bindings gets its own execution state record 4101 /// in here. The interactions for any one binding are grouped together. 4102 /// </remarks> 4103 public InteractionState* interactionStates; 4104 4105 /// <summary> 4106 /// Current remembered level of actuation of each of the controls in <see cref="controls"/>. 4107 /// </summary> 4108 /// <remarks> 4109 /// This array is NOT kept strictly up to date. In fact, we only use it for conflict resolution 4110 /// between multiple bound controls at the moment. Meaning that in the majority of cases, the magnitude 4111 /// stored for a control here will NOT be up to date. 4112 /// 4113 /// Also note that for controls that are part of composites, this will NOT be the magnitude of the 4114 /// control but rather be the magnitude of the entire compound. 4115 /// </remarks> 4116 public float* controlMagnitudes; 4117 4118 public float* compositeMagnitudes; 4119 4120 public int* enabledControls; 4121 4122 /// <summary> 4123 /// Array of pair of ints, one pair for each action (same index as <see cref="actionStates"/>). First int 4124 /// is the index into <see cref="actionBindingIndices"/> where bindings of action are found and second int 4125 /// is the count of bindings on action. 4126 /// </summary> 4127 public ushort* actionBindingIndicesAndCounts; 4128 4129 /// <summary> 4130 /// Array of indices into <see cref="bindingStates"/>. The indices for every action are laid out sequentially. 4131 /// The array slice corresponding to each action can be determined by looking it up in <see cref="actionBindingIndicesAndCounts"/>. 4132 /// </summary> 4133 public ushort* actionBindingIndices; 4134 4135 ////REVIEW: make this an array of shorts rather than ints? 4136 public int* controlIndexToBindingIndex; 4137 4138 // Two shorts per control. First one is group number. Second one is complexity count. 4139 public ushort* controlGroupingAndComplexity; 4140 public bool controlGroupingInitialized; 4141 4142 public ActionMapIndices* mapIndices; 4143 4144 public void Allocate(int mapCount, int actionCount, int bindingCount, int controlCount, int interactionCount, int compositeCount) 4145 { 4146 Debug.Assert(basePtr == null, "Memory already allocated! Free first!"); 4147 Debug.Assert(mapCount >= 1, "Map count out of range"); 4148 Debug.Assert(actionCount >= 0, "Action count out of range"); 4149 Debug.Assert(bindingCount >= 0, "Binding count out of range"); 4150 Debug.Assert(interactionCount >= 0, "Interaction count out of range"); 4151 Debug.Assert(compositeCount >= 0, "Composite count out of range"); 4152 4153 this.mapCount = mapCount; 4154 this.actionCount = actionCount; 4155 this.interactionCount = interactionCount; 4156 this.bindingCount = bindingCount; 4157 this.controlCount = controlCount; 4158 this.compositeCount = compositeCount; 4159 4160 var numBytes = sizeInBytes; 4161 var ptr = (byte*)UnsafeUtility.Malloc(numBytes, 8, Allocator.Persistent); 4162 UnsafeUtility.MemClear(ptr, numBytes); 4163 4164 basePtr = ptr; 4165 4166 // NOTE: This depends on the individual structs being sufficiently aligned in order to not 4167 // cause any misalignment here. TriggerState, InteractionState, and BindingState all 4168 // contain doubles so put them first in memory to make sure they get proper alignment. 4169 actionStates = (TriggerState*)ptr; ptr += actionCount * sizeof(TriggerState); 4170 interactionStates = (InteractionState*)ptr; ptr += interactionCount * sizeof(InteractionState); 4171 bindingStates = (BindingState*)ptr; ptr += bindingCount * sizeof(BindingState); 4172 mapIndices = (ActionMapIndices*)ptr; ptr += mapCount * sizeof(ActionMapIndices); 4173 controlMagnitudes = (float*)ptr; ptr += controlCount * sizeof(float); 4174 compositeMagnitudes = (float*)ptr; ptr += compositeCount * sizeof(float); 4175 controlIndexToBindingIndex = (int*)ptr; ptr += controlCount * sizeof(int); 4176 controlGroupingAndComplexity = (ushort*)ptr; ptr += controlCount * sizeof(ushort) * 2; 4177 actionBindingIndicesAndCounts = (ushort*)ptr; ptr += actionCount * sizeof(ushort) * 2; 4178 actionBindingIndices = (ushort*)ptr; ptr += bindingCount * sizeof(ushort); 4179 enabledControls = (int*)ptr; ptr += (controlCount + 31) / 32 * sizeof(int); 4180 } 4181 4182 public void Dispose() 4183 { 4184 if (basePtr == null) 4185 return; 4186 4187 UnsafeUtility.Free(basePtr, Allocator.Persistent); 4188 4189 basePtr = null; 4190 actionStates = null; 4191 interactionStates = null; 4192 bindingStates = null; 4193 mapIndices = null; 4194 controlMagnitudes = null; 4195 compositeMagnitudes = null; 4196 controlIndexToBindingIndex = null; 4197 controlGroupingAndComplexity = null; 4198 actionBindingIndices = null; 4199 actionBindingIndicesAndCounts = null; 4200 4201 mapCount = 0; 4202 actionCount = 0; 4203 bindingCount = 0; 4204 controlCount = 0; 4205 interactionCount = 0; 4206 compositeCount = 0; 4207 } 4208 4209 public void CopyDataFrom(UnmanagedMemory memory) 4210 { 4211 Debug.Assert(memory.basePtr != null, "Given struct has no allocated data"); 4212 4213 // Even if a certain array is empty (e.g. we have no controls), we set the pointer 4214 // in Allocate() to something other than null. 4215 4216 UnsafeUtility.MemCpy(mapIndices, memory.mapIndices, memory.mapCount * sizeof(ActionMapIndices)); 4217 UnsafeUtility.MemCpy(actionStates, memory.actionStates, memory.actionCount * sizeof(TriggerState)); 4218 UnsafeUtility.MemCpy(bindingStates, memory.bindingStates, memory.bindingCount * sizeof(BindingState)); 4219 UnsafeUtility.MemCpy(interactionStates, memory.interactionStates, memory.interactionCount * sizeof(InteractionState)); 4220 UnsafeUtility.MemCpy(controlMagnitudes, memory.controlMagnitudes, memory.controlCount * sizeof(float)); 4221 UnsafeUtility.MemCpy(compositeMagnitudes, memory.compositeMagnitudes, memory.compositeCount * sizeof(float)); 4222 UnsafeUtility.MemCpy(controlIndexToBindingIndex, memory.controlIndexToBindingIndex, memory.controlCount * sizeof(int)); 4223 UnsafeUtility.MemCpy(controlGroupingAndComplexity, memory.controlGroupingAndComplexity, memory.controlCount * sizeof(ushort) * 2); 4224 UnsafeUtility.MemCpy(actionBindingIndicesAndCounts, memory.actionBindingIndicesAndCounts, memory.actionCount * sizeof(ushort) * 2); 4225 UnsafeUtility.MemCpy(actionBindingIndices, memory.actionBindingIndices, memory.bindingCount * sizeof(ushort)); 4226 UnsafeUtility.MemCpy(enabledControls, memory.enabledControls, (memory.controlCount + 31) / 32 * sizeof(int)); 4227 } 4228 4229 public UnmanagedMemory Clone() 4230 { 4231 if (!isAllocated) 4232 return new UnmanagedMemory(); 4233 4234 var clone = new UnmanagedMemory(); 4235 clone.Allocate( 4236 mapCount: mapCount, 4237 actionCount: actionCount, 4238 controlCount: controlCount, 4239 bindingCount: bindingCount, 4240 interactionCount: interactionCount, 4241 compositeCount: compositeCount); 4242 clone.CopyDataFrom(this); 4243 4244 return clone; 4245 } 4246 } 4247 4248 #region Global State 4249 4250 /// <summary> 4251 /// Global state containing a list of weak references to all action map states currently in the system. 4252 /// </summary> 4253 /// <remarks> 4254 /// When the control setup in the system changes, we need a way for control resolution that 4255 /// has already been done to be invalidated and redone. We also want a way to find all 4256 /// currently enabled actions in the system. 4257 /// 4258 /// Both of these needs are served by this global list. 4259 /// </remarks> 4260 internal struct GlobalState 4261 { 4262 internal InlinedArray<GCHandle> globalList; 4263 internal CallbackArray<Action<object, InputActionChange>> onActionChange; 4264 internal CallbackArray<Action<object>> onActionControlsChanged; 4265 } 4266 4267 internal static GlobalState s_GlobalState; 4268 4269 internal static ISavedState SaveAndResetState() 4270 { 4271 // Save current state 4272 var savedState = new SavedStructState<GlobalState>( 4273 ref s_GlobalState, 4274 (ref GlobalState state) => s_GlobalState = state, // restore 4275 () => ResetGlobals()); // static dispose 4276 4277 // Reset global state 4278 s_GlobalState = default; 4279 4280 return savedState; 4281 } 4282 4283 private void AddToGlobalList() 4284 { 4285 CompactGlobalList(); 4286 var handle = GCHandle.Alloc(this, GCHandleType.Weak); 4287 s_GlobalState.globalList.AppendWithCapacity(handle); 4288 } 4289 4290 private void RemoveMapFromGlobalList() 4291 { 4292 var count = s_GlobalState.globalList.length; 4293 for (var i = 0; i < count; ++i) 4294 if (s_GlobalState.globalList[i].Target == this) 4295 { 4296 s_GlobalState.globalList[i].Free(); 4297 s_GlobalState.globalList.RemoveAtByMovingTailWithCapacity(i); 4298 break; 4299 } 4300 } 4301 4302 /// <summary> 4303 /// Remove any entries for states that have been reclaimed by GC. 4304 /// </summary> 4305 private static void CompactGlobalList() 4306 { 4307 var length = s_GlobalState.globalList.length; 4308 var head = 0; 4309 for (var i = 0; i < length; ++i) 4310 { 4311 var handle = s_GlobalState.globalList[i]; 4312 if (handle.IsAllocated && handle.Target != null) 4313 { 4314 if (head != i) 4315 s_GlobalState.globalList[head] = handle; 4316 ++head; 4317 } 4318 else 4319 { 4320 if (handle.IsAllocated) 4321 s_GlobalState.globalList[i].Free(); 4322 s_GlobalState.globalList[i] = default; 4323 } 4324 } 4325 s_GlobalState.globalList.length = head; 4326 } 4327 4328 internal void NotifyListenersOfActionChange(InputActionChange change) 4329 { 4330 for (var i = 0; i < totalMapCount; ++i) 4331 { 4332 var map = maps[i]; 4333 if (map.m_SingletonAction != null) 4334 { 4335 NotifyListenersOfActionChange(change, map.m_SingletonAction); 4336 } 4337 else if (map.m_Asset == null) 4338 { 4339 NotifyListenersOfActionChange(change, map); 4340 } 4341 else 4342 { 4343 NotifyListenersOfActionChange(change, map.m_Asset); 4344 return; 4345 } 4346 } 4347 } 4348 4349 internal static void NotifyListenersOfActionChange(InputActionChange change, object actionOrMapOrAsset) 4350 { 4351 Debug.Assert(actionOrMapOrAsset != null, "Should have action or action map or asset object to notify about"); 4352 Debug.Assert(actionOrMapOrAsset is InputAction || (actionOrMapOrAsset as InputActionMap)?.m_SingletonAction == null, 4353 "Must not send notifications for changes made to hidden action maps of singleton actions"); 4354 4355 DelegateHelpers.InvokeCallbacksSafe(ref s_GlobalState.onActionChange, actionOrMapOrAsset, change, k_InputOnActionChangeMarker, "InputSystem.onActionChange"); 4356 if (change == InputActionChange.BoundControlsChanged) 4357 DelegateHelpers.InvokeCallbacksSafe(ref s_GlobalState.onActionControlsChanged, actionOrMapOrAsset, "onActionControlsChange"); 4358 } 4359 4360 /// <summary> 4361 /// Nuke global state we have to keep track of action map states. 4362 /// </summary> 4363 private static void ResetGlobals() 4364 { 4365 DestroyAllActionMapStates(); 4366 for (var i = 0; i < s_GlobalState.globalList.length; ++i) 4367 if (s_GlobalState.globalList[i].IsAllocated) 4368 s_GlobalState.globalList[i].Free(); 4369 s_GlobalState.globalList.length = 0; 4370 s_GlobalState.onActionChange.Clear(); 4371 s_GlobalState.onActionControlsChanged.Clear(); 4372 } 4373 4374 // Walk all maps with enabled actions and add all enabled actions to the given list. 4375 internal static int FindAllEnabledActions(List<InputAction> result) 4376 { 4377 var numFound = 0; 4378 var stateCount = s_GlobalState.globalList.length; 4379 for (var i = 0; i < stateCount; ++i) 4380 { 4381 var handle = s_GlobalState.globalList[i]; 4382 if (!handle.IsAllocated) 4383 continue; 4384 var state = (InputActionState)handle.Target; 4385 if (state == null) 4386 continue; 4387 4388 var mapCount = state.totalMapCount; 4389 var maps = state.maps; 4390 for (var n = 0; n < mapCount; ++n) 4391 { 4392 var map = maps[n]; 4393 if (!map.enabled) 4394 continue; 4395 4396 var actions = map.m_Actions; 4397 var actionCount = actions.Length; 4398 if (map.m_EnabledActionsCount == actionCount) 4399 { 4400 result.AddRange(actions); 4401 numFound += actionCount; 4402 } 4403 else 4404 { 4405 var actionStartIndex = state.mapIndices[map.m_MapIndexInState].actionStartIndex; 4406 for (var k = 0; k < actionCount; ++k) 4407 { 4408 if (state.actionStates[actionStartIndex + k].phase != InputActionPhase.Disabled) 4409 { 4410 result.Add(actions[k]); 4411 ++numFound; 4412 } 4413 } 4414 } 4415 } 4416 } 4417 4418 return numFound; 4419 } 4420 4421 ////TODO: when re-resolving, we need to preserve InteractionStates and not just reset them 4422 4423 /// <summary> 4424 /// Deal with the fact that the control setup in the system may change at any time and can affect 4425 /// actions that had their controls already resolved. 4426 /// </summary> 4427 /// <remarks> 4428 /// Note that this method can NOT deal with changes other than the control setup in the system 4429 /// changing. Specifically, it will NOT handle configuration changes in action maps (e.g. bindings 4430 /// being altered) correctly. 4431 /// 4432 /// We get called from <see cref="InputManager"/> directly rather than hooking into <see cref="InputSystem.onDeviceChange"/> 4433 /// so that we're not adding needless calls for device changes that are not of interest to us. 4434 /// </remarks> 4435 internal static void OnDeviceChange(InputDevice device, InputDeviceChange change) 4436 { 4437 Debug.Assert(device != null, "Device is null"); 4438 ////REVIEW: should we ignore disconnected devices in InputBindingResolver? 4439 Debug.Assert( 4440 change == InputDeviceChange.Added || change == InputDeviceChange.Removed || 4441 change == InputDeviceChange.UsageChanged || change == InputDeviceChange.ConfigurationChanged || 4442 change == InputDeviceChange.SoftReset || change == InputDeviceChange.HardReset, 4443 "Should only be called for relevant changes"); 4444 4445 for (var i = 0; i < s_GlobalState.globalList.length; ++i) 4446 { 4447 var handle = s_GlobalState.globalList[i]; 4448 if (!handle.IsAllocated || handle.Target == null) 4449 { 4450 // Stale entry in the list. State has already been reclaimed by GC. Remove it. 4451 if (handle.IsAllocated) 4452 s_GlobalState.globalList[i].Free(); 4453 s_GlobalState.globalList.RemoveAtWithCapacity(i); 4454 --i; 4455 continue; 4456 } 4457 var state = (InputActionState)handle.Target; 4458 4459 // If this state is not affected by the change, skip. 4460 var needsFullResolve = true; 4461 switch (change) 4462 { 4463 case InputDeviceChange.Added: 4464 if (!state.CanUseDevice(device)) 4465 continue; 4466 needsFullResolve = false; 4467 break; 4468 4469 case InputDeviceChange.Removed: 4470 if (!state.IsUsingDevice(device)) 4471 continue; 4472 4473 // If the device is listed in a device mask (on either a map or an asset) in the 4474 // state, remove it (see Actions_WhenDeviceIsRemoved_DeviceIsRemovedFromDeviceMask). 4475 for (var n = 0; n < state.totalMapCount; ++n) 4476 { 4477 var map = state.maps[n]; 4478 map.m_Devices.Remove(device); 4479 map.asset?.m_Devices.Remove(device); 4480 } 4481 4482 needsFullResolve = false; 4483 break; 4484 4485 // NOTE: ConfigurationChanges can affect display names of controls which may make a device usable that 4486 // we didn't find anything usable on before. 4487 case InputDeviceChange.ConfigurationChanged: 4488 case InputDeviceChange.UsageChanged: 4489 if (!state.IsUsingDevice(device) && !state.CanUseDevice(device)) 4490 continue; 4491 // Full resolve necessary! 4492 break; 4493 4494 // On reset, cancel all actions currently in progress from the device that got reset. 4495 // If we simply let change monitors trigger, we will respond to things like button releases 4496 // that are in fact just resets of buttons to their default state. 4497 case InputDeviceChange.SoftReset: 4498 case InputDeviceChange.HardReset: 4499 if (!state.IsUsingDevice(device)) 4500 continue; 4501 state.ResetActionStatesDrivenBy(device); 4502 continue; // No re-resolving necessary. 4503 } 4504 4505 // Trigger a lazy-resolve on all action maps in the state. 4506 for (var n = 0; n < state.totalMapCount; ++n) 4507 { 4508 if (state.maps[n].LazyResolveBindings(fullResolve: needsFullResolve)) 4509 { 4510 // Map has chosen to resolve right away. This will resolve bindings for *all* 4511 // maps in the state, so we're done here. 4512 break; 4513 } 4514 } 4515 } 4516 } 4517 4518 internal static void DeferredResolutionOfBindings() 4519 { 4520 ++InputActionMap.s_DeferBindingResolution; 4521 try 4522 { 4523 if (InputActionMap.s_NeedToResolveBindings) 4524 { 4525 for (var i = 0; i < s_GlobalState.globalList.length; ++i) 4526 { 4527 var handle = s_GlobalState.globalList[i]; 4528 4529 var state = handle.IsAllocated ? (InputActionState)handle.Target : null; 4530 if (state == null) 4531 { 4532 // Stale entry in the list. State has already been reclaimed by GC. Remove it. 4533 if (handle.IsAllocated) 4534 s_GlobalState.globalList[i].Free(); 4535 s_GlobalState.globalList.RemoveAtWithCapacity(i); 4536 --i; 4537 continue; 4538 } 4539 4540 for (var n = 0; n < state.totalMapCount; ++n) 4541 state.maps[n].ResolveBindingsIfNecessary(); 4542 } 4543 InputActionMap.s_NeedToResolveBindings = false; 4544 } 4545 } 4546 finally 4547 { 4548 --InputActionMap.s_DeferBindingResolution; 4549 } 4550 } 4551 4552 internal static void DisableAllActions() 4553 { 4554 for (var i = 0; i < s_GlobalState.globalList.length; ++i) 4555 { 4556 var handle = s_GlobalState.globalList[i]; 4557 if (!handle.IsAllocated || handle.Target == null) 4558 continue; 4559 var state = (InputActionState)handle.Target; 4560 4561 var mapCount = state.totalMapCount; 4562 var maps = state.maps; 4563 for (var n = 0; n < mapCount; ++n) 4564 { 4565 maps[n].Disable(); 4566 Debug.Assert(!maps[n].enabled, "Map is still enabled after calling Disable"); 4567 } 4568 } 4569 } 4570 4571 /// <summary> 4572 /// Forcibly destroy all states currently on the global list. 4573 /// </summary> 4574 /// <remarks> 4575 /// We do this when exiting play mode in the editor to make sure we are cleaning up our 4576 /// unmanaged memory allocations. 4577 /// </remarks> 4578 internal static void DestroyAllActionMapStates() 4579 { 4580 while (s_GlobalState.globalList.length > 0) 4581 { 4582 var index = s_GlobalState.globalList.length - 1; 4583 var handle = s_GlobalState.globalList[index]; 4584 if (!handle.IsAllocated || handle.Target == null) 4585 { 4586 // Already destroyed. 4587 if (handle.IsAllocated) 4588 s_GlobalState.globalList[index].Free(); 4589 s_GlobalState.globalList.RemoveAtWithCapacity(index); 4590 continue; 4591 } 4592 4593 var state = (InputActionState)handle.Target; 4594 state.Destroy(); 4595 } 4596 } 4597 4598 #endregion 4599 } 4600}