A game about forced loneliness, made by TACStudios
at master 41 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Reflection; 4using Unity.Collections; 5using UnityEngine.InputSystem.Utilities; 6 7////TODO: reuse interaction, processor, and composite instances from prior resolves 8 9namespace UnityEngine.InputSystem 10{ 11 /// <summary> 12 /// Heart of the binding resolution machinery. Consumes lists of bindings 13 /// and spits out out a list of resolved bindings together with their needed 14 /// execution state. 15 /// </summary> 16 /// <remarks> 17 /// One or more <see cref="InputActionMap">action maps</see> can be added to the same 18 /// resolver. The result is a combination of the binding state of all maps. 19 /// 20 /// The data set up by a resolver is for consumption by <see cref="InputActionState"/>. 21 /// Essentially, InputBindingResolver does all the wiring and <see cref="InputActionState"/> 22 /// does all the actual execution based on the resulting data. 23 /// </remarks> 24 /// <seealso cref="InputActionState.Initialize"/> 25 internal struct InputBindingResolver : IDisposable 26 { 27 public int totalProcessorCount; 28 public int totalCompositeCount; 29 public int totalInteractionCount; 30 public int totalMapCount => memory.mapCount; 31 public int totalActionCount => memory.actionCount; 32 public int totalBindingCount => memory.bindingCount; 33 public int totalControlCount => memory.controlCount; 34 35 public InputActionMap[] maps; 36 public InputControl[] controls; 37 public InputActionState.UnmanagedMemory memory; 38 public IInputInteraction[] interactions; 39 public InputProcessor[] processors; 40 public InputBindingComposite[] composites; 41 42 /// <summary> 43 /// Binding mask used to globally mask out bindings. 44 /// </summary> 45 /// <remarks> 46 /// This is empty by default. 47 /// 48 /// The bindings of each map will be <see cref="InputBinding.Matches">matched</see> against this 49 /// binding. Any bindings that don't match will get skipped and not resolved to controls. 50 /// 51 /// Note that regardless of whether a binding will be resolved to controls or not, it will get 52 /// an entry in <see cref="memory"/>. Otherwise we would have to have a more complicated 53 /// mapping from <see cref="InputActionMap.bindings"/> to a binding state in <see cref="memory"/>. 54 /// </remarks> 55 public InputBinding? bindingMask; 56 57 private bool m_IsControlOnlyResolve; 58 59 /// <summary> 60 /// Release native memory held by the resolver. 61 /// </summary> 62 public void Dispose() 63 { 64 memory.Dispose(); 65 } 66 67 /// <summary> 68 /// Steal the already allocated arrays from the given state. 69 /// </summary> 70 /// <param name="state">Action map state that was previously created.</param> 71 /// <param name="isFullResolve">If false, the only thing that is allowed to change in the re-resolution 72 /// is the list of controls. In other words, devices may have been added or removed but otherwise the configuration 73 /// is exactly the same as in the last resolve. If true, anything may have changed and the resolver will only reuse 74 /// allocations but not contents.</param> 75 public void StartWithPreviousResolve(InputActionState state, bool isFullResolve) 76 { 77 Debug.Assert(state != null, "Received null state"); 78 Debug.Assert(!state.isProcessingControlStateChange, 79 "Cannot re-resolve bindings for an InputActionState that is currently executing an action callback; binding resolution must be deferred to until after the callback has completed"); 80 81 m_IsControlOnlyResolve = !isFullResolve; 82 83 maps = state.maps; 84 interactions = state.interactions; 85 processors = state.processors; 86 composites = state.composites; 87 controls = state.controls; 88 89 // Clear the arrays so that we don't leave references around. 90 if (isFullResolve) 91 { 92 if (maps != null) 93 Array.Clear(maps, 0, state.totalMapCount); 94 if (interactions != null) 95 Array.Clear(interactions, 0, state.totalInteractionCount); 96 if (processors != null) 97 Array.Clear(processors, 0, state.totalProcessorCount); 98 if (composites != null) 99 Array.Clear(composites, 0, state.totalCompositeCount); 100 } 101 if (controls != null) // Always clear this one as every resolve will change it. 102 Array.Clear(controls, 0, state.totalControlCount); 103 104 // Null out the arrays on the state so that there is no strange bugs with 105 // the state reading from arrays that no longer belong to it. 106 state.maps = null; 107 state.interactions = null; 108 state.processors = null; 109 state.composites = null; 110 state.controls = null; 111 } 112 113 /// <summary> 114 /// Resolve and add all bindings and actions from the given map. 115 /// </summary> 116 /// <param name="actionMap"></param> 117 /// <remarks> 118 /// This is where all binding resolution happens for actions. The method walks through the binding array 119 /// in <paramref name="actionMap"/> and adds any controls, interactions, processors, and composites as it goes. 120 /// </remarks> 121 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")] 122 public unsafe void AddActionMap(InputActionMap actionMap) 123 { 124 Debug.Assert(actionMap != null, "Received null map"); 125 126 InputSystem.EnsureInitialized(); 127 128 var actionsInThisMap = actionMap.m_Actions; 129 var bindingsInThisMap = actionMap.m_Bindings; 130 var bindingCountInThisMap = bindingsInThisMap?.Length ?? 0; 131 var actionCountInThisMap = actionsInThisMap?.Length ?? 0; 132 var mapIndex = totalMapCount; 133 134 // Keep track of indices for this map. 135 var actionStartIndex = totalActionCount; 136 var bindingStartIndex = totalBindingCount; 137 var controlStartIndex = totalControlCount; 138 var interactionStartIndex = totalInteractionCount; 139 var processorStartIndex = totalProcessorCount; 140 var compositeStartIndex = totalCompositeCount; 141 142 // Allocate an initial block of memory. We probably will have to re-allocate once 143 // at the end to accommodate interactions and controls added from the map. 144 var newMemory = new InputActionState.UnmanagedMemory(); 145 newMemory.Allocate( 146 mapCount: totalMapCount + 1, 147 actionCount: totalActionCount + actionCountInThisMap, 148 bindingCount: totalBindingCount + bindingCountInThisMap, 149 // We reallocate for the following once we know the final count. 150 interactionCount: totalInteractionCount, 151 compositeCount: totalCompositeCount, 152 controlCount: totalControlCount); 153 if (memory.isAllocated) 154 newMemory.CopyDataFrom(memory); 155 156 ////TODO: make sure composite objects get all the bindings they need 157 ////TODO: handle case where we have bindings resolving to the same control 158 //// (not so clear cut what to do there; each binding may have a different interaction setup, for example) 159 var currentCompositeBindingIndex = InputActionState.kInvalidIndex; 160 var currentCompositeIndex = InputActionState.kInvalidIndex; 161 var currentCompositePartCount = 0; 162 var currentCompositeActionIndexInMap = InputActionState.kInvalidIndex; 163 InputAction currentCompositeAction = null; 164 var bindingMaskOnThisMap = actionMap.m_BindingMask; 165 var devicesForThisMap = actionMap.devices; 166 var isSingletonAction = actionMap.m_SingletonAction != null; 167 168 // Can't use `using` as we need to use it with `ref`. 169 var resolvedControls = new InputControlList<InputControl>(Allocator.Temp); 170 171 // We gather all controls in temporary memory and then move them over into newMemory once 172 // we're done resolving. 173 try 174 { 175 for (var n = 0; n < bindingCountInThisMap; ++n) 176 { 177 var bindingStatesPtr = newMemory.bindingStates; 178 ref var unresolvedBinding = ref bindingsInThisMap[n]; 179 var bindingIndex = bindingStartIndex + n; 180 var isComposite = unresolvedBinding.isComposite; 181 var isPartOfComposite = !isComposite && unresolvedBinding.isPartOfComposite; 182 var bindingState = &bindingStatesPtr[bindingIndex]; 183 184 try 185 { 186 ////TODO: if it's a composite, check if any of the children matches our binding masks (if any) and skip composite if none do 187 188 var firstControlIndex = 0; // numControls dictates whether this is a valid index or not. 189 var firstInteractionIndex = InputActionState.kInvalidIndex; 190 var firstProcessorIndex = InputActionState.kInvalidIndex; 191 var actionIndexForBinding = InputActionState.kInvalidIndex; 192 var partIndex = InputActionState.kInvalidIndex; 193 194 var numControls = 0; 195 var numInteractions = 0; 196 var numProcessors = 0; 197 198 // Make sure that if it's part of a composite, we are actually part of a composite. 199 if (isPartOfComposite && currentCompositeBindingIndex == InputActionState.kInvalidIndex) 200 throw new InvalidOperationException( 201 $"Binding '{unresolvedBinding}' is marked as being part of a composite but the preceding binding is not a composite"); 202 203 // Try to find action. 204 // 205 // NOTE: We ignore actions on bindings that are part of composites. We only allow 206 // actions to be triggered from the composite itself. 207 var actionIndexInMap = InputActionState.kInvalidIndex; 208 var actionName = unresolvedBinding.action; 209 InputAction action = null; 210 if (!isPartOfComposite) 211 { 212 if (isSingletonAction) 213 { 214 // Singleton actions always ignore names. 215 actionIndexInMap = 0; 216 } 217 else if (!string.IsNullOrEmpty(actionName)) 218 { 219 ////REVIEW: should we fail here if we don't manage to find the action 220 actionIndexInMap = actionMap.FindActionIndex(actionName); 221 } 222 223 if (actionIndexInMap != InputActionState.kInvalidIndex) 224 action = actionsInThisMap[actionIndexInMap]; 225 } 226 else 227 { 228 actionIndexInMap = currentCompositeActionIndexInMap; 229 action = currentCompositeAction; 230 } 231 232 // If it's a composite, start a chain. 233 if (isComposite) 234 { 235 currentCompositeBindingIndex = bindingIndex; 236 currentCompositeAction = action; 237 currentCompositeActionIndexInMap = actionIndexInMap; 238 } 239 240 // Determine if the binding is disabled. 241 // Disabled if path is empty. 242 var path = unresolvedBinding.effectivePath; 243 var bindingIsDisabled = string.IsNullOrEmpty(path) 244 245 // Also, if we can't find the action to trigger for the binding, we just go and disable 246 // the binding. 247 || action == null 248 249 // Also, disabled if binding doesn't match with our binding mask (might be empty). 250 || (!isComposite && bindingMask != null && 251 !bindingMask.Value.Matches(ref unresolvedBinding, 252 InputBinding.MatchOptions.EmptyGroupMatchesAny)) 253 254 // Also, disabled if binding doesn't match the binding mask on the map (might be empty). 255 || (!isComposite && bindingMaskOnThisMap != null && 256 !bindingMaskOnThisMap.Value.Matches(ref unresolvedBinding, 257 InputBinding.MatchOptions.EmptyGroupMatchesAny)) 258 259 // Finally, also disabled if binding doesn't match the binding mask on the action (might be empty). 260 || (!isComposite && action?.m_BindingMask != null && 261 !action.m_BindingMask.Value.Matches(ref unresolvedBinding, 262 InputBinding.MatchOptions.EmptyGroupMatchesAny)); 263 264 // If the binding isn't disabled, look up controls now. We do this first as we may still disable the 265 // binding if it doesn't resolve to any controls or resolves only to controls already bound to by 266 // other bindings. 267 // 268 // NOTE: We continuously add controls here to `resolvedControls`. Once we've completed our 269 // pass over the bindings in the map, `resolvedControls` will have all the controls for 270 // the current map. 271 if (!bindingIsDisabled && !isComposite) 272 { 273 firstControlIndex = memory.controlCount + resolvedControls.Count; 274 if (devicesForThisMap != null) 275 { 276 // Search in devices for only this map. 277 var list = devicesForThisMap.Value; 278 for (var i = 0; i < list.Count; ++i) 279 { 280 var device = list[i]; 281 if (!device.added) 282 continue; // Skip devices that have been removed. 283 numControls += InputControlPath.TryFindControls(device, path, 0, ref resolvedControls); 284 } 285 } 286 else 287 { 288 // Search globally. 289 numControls = InputSystem.FindControls(path, ref resolvedControls); 290 } 291 } 292 293 // If the binding isn't disabled, resolve its controls, processors, and interactions. 294 if (!bindingIsDisabled) 295 { 296 // NOTE: When isFullResolve==false, it is *imperative* that we do count processor and interaction 297 // counts here come out exactly the same as in the previous full resolve. 298 299 // Instantiate processors. 300 var processorString = unresolvedBinding.effectiveProcessors; 301 if (!string.IsNullOrEmpty(processorString)) 302 { 303 // Add processors from binding. 304 firstProcessorIndex = InstantiateWithParameters(InputProcessor.s_Processors, processorString, 305 ref processors, ref totalProcessorCount, actionMap, ref unresolvedBinding); 306 if (firstProcessorIndex != InputActionState.kInvalidIndex) 307 numProcessors = totalProcessorCount - firstProcessorIndex; 308 } 309 if (!string.IsNullOrEmpty(action.m_Processors)) 310 { 311 // Add processors from action. 312 var index = InstantiateWithParameters(InputProcessor.s_Processors, action.m_Processors, ref processors, 313 ref totalProcessorCount, actionMap, ref unresolvedBinding); 314 if (index != InputActionState.kInvalidIndex) 315 { 316 if (firstProcessorIndex == InputActionState.kInvalidIndex) 317 firstProcessorIndex = index; 318 numProcessors += totalProcessorCount - index; 319 } 320 } 321 322 // Instantiate interactions. 323 if (isPartOfComposite) 324 { 325 // Composite's part use composite interactions 326 if (currentCompositeBindingIndex != InputActionState.kInvalidIndex) 327 { 328 firstInteractionIndex = bindingStatesPtr[currentCompositeBindingIndex].interactionStartIndex; 329 numInteractions = bindingStatesPtr[currentCompositeBindingIndex].interactionCount; 330 } 331 } 332 else 333 { 334 var interactionString = unresolvedBinding.effectiveInteractions; 335 if (!string.IsNullOrEmpty(interactionString)) 336 { 337 // Add interactions from binding. 338 firstInteractionIndex = InstantiateWithParameters(InputInteraction.s_Interactions, interactionString, 339 ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding); 340 if (firstInteractionIndex != InputActionState.kInvalidIndex) 341 numInteractions = totalInteractionCount - firstInteractionIndex; 342 } 343 if (!string.IsNullOrEmpty(action.m_Interactions)) 344 { 345 // Add interactions from action. 346 var index = InstantiateWithParameters(InputInteraction.s_Interactions, action.m_Interactions, 347 ref interactions, ref totalInteractionCount, actionMap, ref unresolvedBinding); 348 if (index != InputActionState.kInvalidIndex) 349 { 350 if (firstInteractionIndex == InputActionState.kInvalidIndex) 351 firstInteractionIndex = index; 352 numInteractions += totalInteractionCount - index; 353 } 354 } 355 } 356 357 // If it's the start of a composite chain, create the composite. 358 if (isComposite) 359 { 360 // The composite binding entry itself does not resolve to any controls. 361 // It creates a composite binding object which is then populated from 362 // subsequent bindings. 363 364 // Instantiate. For composites, the path is the name of the composite. 365 var composite = InstantiateBindingComposite(ref unresolvedBinding, actionMap); 366 currentCompositeIndex = 367 ArrayHelpers.AppendWithCapacity(ref composites, ref totalCompositeCount, composite); 368 369 // Record where the controls for parts of the composite start. 370 firstControlIndex = memory.controlCount + resolvedControls.Count; 371 } 372 else 373 { 374 // If we've reached the end of a composite chain, finish 375 // off the current composite. 376 if (!isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex) 377 { 378 currentCompositePartCount = 0; 379 currentCompositeBindingIndex = InputActionState.kInvalidIndex; 380 currentCompositeIndex = InputActionState.kInvalidIndex; 381 currentCompositeAction = null; 382 currentCompositeActionIndexInMap = InputActionState.kInvalidIndex; 383 } 384 } 385 } 386 387 // If the binding is part of a composite, pass the resolved controls 388 // on to the composite. 389 if (isPartOfComposite && currentCompositeBindingIndex != InputActionState.kInvalidIndex && numControls > 0) 390 { 391 // Make sure the binding is named. The name determines what in the composite 392 // to bind to. 393 if (string.IsNullOrEmpty(unresolvedBinding.name)) 394 throw new InvalidOperationException( 395 $"Binding '{unresolvedBinding}' that is part of composite '{composites[currentCompositeIndex]}' is missing a name"); 396 397 // Assign an index to the current part of the composite which 398 // can be used by the composite to read input from this part. 399 partIndex = AssignCompositePartIndex(composites[currentCompositeIndex], unresolvedBinding.name, 400 ref currentCompositePartCount); 401 402 // Keep track of total number of controls bound in the composite. 403 bindingStatesPtr[currentCompositeBindingIndex].controlCount += numControls; 404 405 // Force action index on part binding to be same as that of composite. 406 actionIndexForBinding = bindingStatesPtr[currentCompositeBindingIndex].actionIndex; 407 } 408 else if (actionIndexInMap != InputActionState.kInvalidIndex) 409 { 410 actionIndexForBinding = actionStartIndex + actionIndexInMap; 411 } 412 413 // Store resolved binding. 414 *bindingState = new InputActionState.BindingState 415 { 416 controlStartIndex = firstControlIndex, 417 // For composites, this will be adjusted as we add each part. 418 controlCount = numControls, 419 interactionStartIndex = firstInteractionIndex, 420 interactionCount = numInteractions, 421 processorStartIndex = firstProcessorIndex, 422 processorCount = numProcessors, 423 isComposite = isComposite, 424 isPartOfComposite = unresolvedBinding.isPartOfComposite, 425 partIndex = partIndex, 426 actionIndex = actionIndexForBinding, 427 compositeOrCompositeBindingIndex = isComposite ? currentCompositeIndex : currentCompositeBindingIndex, 428 mapIndex = totalMapCount, 429 wantsInitialStateCheck = action?.wantsInitialStateCheck ?? false 430 }; 431 } 432 catch (Exception exception) 433 { 434 Debug.LogError( 435 $"{exception.GetType().Name} while resolving binding '{unresolvedBinding}' in action map '{actionMap}'"); 436 Debug.LogException(exception); 437 438 // Don't swallow exceptions that indicate something is wrong in the code rather than 439 // in the data. 440 if (exception.IsExceptionIndicatingBugInCode()) 441 throw; 442 } 443 } 444 445 // Re-allocate memory to accommodate controls and interaction states. The count for those 446 // we only know once we've completed all resolution. 447 var controlCountInThisMap = resolvedControls.Count; 448 var newTotalControlCount = memory.controlCount + controlCountInThisMap; 449 if (newMemory.interactionCount != totalInteractionCount || 450 newMemory.compositeCount != totalCompositeCount || 451 newMemory.controlCount != newTotalControlCount) 452 { 453 var finalMemory = new InputActionState.UnmanagedMemory(); 454 455 finalMemory.Allocate( 456 mapCount: newMemory.mapCount, 457 actionCount: newMemory.actionCount, 458 bindingCount: newMemory.bindingCount, 459 controlCount: newTotalControlCount, 460 interactionCount: totalInteractionCount, 461 compositeCount: totalCompositeCount); 462 463 finalMemory.CopyDataFrom(newMemory); 464 465 newMemory.Dispose(); 466 newMemory = finalMemory; 467 } 468 469 // Add controls to array. 470 var controlCountInArray = memory.controlCount; 471 ArrayHelpers.AppendListWithCapacity(ref controls, ref controlCountInArray, resolvedControls); 472 Debug.Assert(controlCountInArray == newTotalControlCount, 473 "Control array should have combined count of old and new controls"); 474 475 // Set up control to binding index mapping. 476 for (var i = 0; i < bindingCountInThisMap; ++i) 477 { 478 var bindingStatesPtr = newMemory.bindingStates; 479 var bindingState = &bindingStatesPtr[bindingStartIndex + i]; 480 var numControls = bindingState->controlCount; 481 var startIndex = bindingState->controlStartIndex; 482 for (var n = 0; n < numControls; ++n) 483 newMemory.controlIndexToBindingIndex[startIndex + n] = bindingStartIndex + i; 484 } 485 486 // Initialize initial interaction states. 487 for (var i = memory.interactionCount; i < newMemory.interactionCount; ++i) 488 { 489 ref var interactionState = ref newMemory.interactionStates[i]; 490 interactionState.phase = InputActionPhase.Waiting; 491 interactionState.triggerControlIndex = InputActionState.kInvalidIndex; 492 } 493 494 // Initialize action data. 495 var runningIndexInBindingIndices = memory.bindingCount; 496 for (var i = 0; i < actionCountInThisMap; ++i) 497 { 498 var action = actionsInThisMap[i]; 499 var actionIndex = actionStartIndex + i; 500 501 // Correlate action with its trigger state. 502 action.m_ActionIndexInState = actionIndex; 503 504 Debug.Assert(runningIndexInBindingIndices < ushort.MaxValue, "Binding start index on action exceeds limit"); 505 newMemory.actionBindingIndicesAndCounts[actionIndex * 2] = (ushort)runningIndexInBindingIndices; 506 507 // Collect bindings for action. 508 var firstBindingIndexForAction = -1; 509 var bindingCountForAction = 0; 510 var numPossibleConcurrentActuations = 0; 511 512 for (var n = 0; n < bindingCountInThisMap; ++n) 513 { 514 var bindingIndex = bindingStartIndex + n; 515 var bindingState = &newMemory.bindingStates[bindingIndex]; 516 if (bindingState->actionIndex != actionIndex) 517 continue; 518 if (bindingState->isPartOfComposite) 519 continue; 520 521 Debug.Assert(bindingIndex <= ushort.MaxValue, "Binding index exceeds limit"); 522 newMemory.actionBindingIndices[runningIndexInBindingIndices] = (ushort)bindingIndex; 523 ++runningIndexInBindingIndices; 524 ++bindingCountForAction; 525 526 if (firstBindingIndexForAction == -1) 527 firstBindingIndexForAction = bindingIndex; 528 529 // Keep track of how many concurrent actuations we may be seeing on the action so that 530 // we know whether we need to enable conflict resolution or not. 531 if (bindingState->isComposite) 532 { 533 // Composite binding. Actuates as a whole. Check if the composite has successfully 534 // resolved any controls. If so, it adds one possible actuation. 535 if (bindingState->controlCount > 0) 536 ++numPossibleConcurrentActuations; 537 } 538 else 539 { 540 // Normal binding. Every successfully resolved control results in one possible actuation. 541 numPossibleConcurrentActuations += bindingState->controlCount; 542 } 543 } 544 545 if (firstBindingIndexForAction == -1) 546 firstBindingIndexForAction = 0; 547 548 Debug.Assert(bindingCountForAction < ushort.MaxValue, "Binding count on action exceeds limit"); 549 newMemory.actionBindingIndicesAndCounts[actionIndex * 2 + 1] = (ushort)bindingCountForAction; 550 551 // See if we may need conflict resolution on this action. Never needed for pass-through actions. 552 // Otherwise, if we have more than one bound control or have several bindings and one of them 553 // is a composite, we enable it. 554 var isPassThroughAction = action.type == InputActionType.PassThrough; 555 var isButtonAction = action.type == InputActionType.Button; 556 var mayNeedConflictResolution = !isPassThroughAction && numPossibleConcurrentActuations > 1; 557 558 // Initialize initial trigger state. 559 newMemory.actionStates[actionIndex] = 560 new InputActionState.TriggerState 561 { 562 phase = InputActionPhase.Disabled, 563 mapIndex = mapIndex, 564 controlIndex = InputActionState.kInvalidIndex, 565 interactionIndex = InputActionState.kInvalidIndex, 566 isPassThrough = isPassThroughAction, 567 isButton = isButtonAction, 568 mayNeedConflictResolution = mayNeedConflictResolution, 569 bindingIndex = firstBindingIndexForAction 570 }; 571 } 572 573 // Store indices for map. 574 newMemory.mapIndices[mapIndex] = 575 new InputActionState.ActionMapIndices 576 { 577 actionStartIndex = actionStartIndex, 578 actionCount = actionCountInThisMap, 579 controlStartIndex = controlStartIndex, 580 controlCount = controlCountInThisMap, 581 bindingStartIndex = bindingStartIndex, 582 bindingCount = bindingCountInThisMap, 583 interactionStartIndex = interactionStartIndex, 584 interactionCount = totalInteractionCount - interactionStartIndex, 585 processorStartIndex = processorStartIndex, 586 processorCount = totalProcessorCount - processorStartIndex, 587 compositeStartIndex = compositeStartIndex, 588 compositeCount = totalCompositeCount - compositeStartIndex, 589 }; 590 actionMap.m_MapIndexInState = mapIndex; 591 var finalActionMapCount = memory.mapCount; 592 ArrayHelpers.AppendWithCapacity(ref maps, ref finalActionMapCount, actionMap, capacityIncrement: 4); 593 Debug.Assert(finalActionMapCount == newMemory.mapCount, 594 "Final action map count should match old action map count plus one"); 595 596 // As a final act, swap the new memory in. 597 memory.Dispose(); 598 memory = newMemory; 599 } 600 catch (Exception) 601 { 602 // Don't leak our native memory when we throw an exception. 603 newMemory.Dispose(); 604 throw; 605 } 606 finally 607 { 608 resolvedControls.Dispose(); 609 } 610 } 611 612 private List<NameAndParameters> m_Parameters; // We retain this to reuse the allocation. 613 private int InstantiateWithParameters<TType>(TypeTable registrations, string namesAndParameters, ref TType[] array, ref int count, InputActionMap actionMap, ref InputBinding binding) 614 { 615 if (!NameAndParameters.ParseMultiple(namesAndParameters, ref m_Parameters)) 616 return InputActionState.kInvalidIndex; 617 618 var firstIndex = count; 619 for (var i = 0; i < m_Parameters.Count; ++i) 620 { 621 // Look up type. 622 var objectRegistrationName = m_Parameters[i].name; 623 var type = registrations.LookupTypeRegistration(objectRegistrationName); 624 if (type == null) 625 { 626 Debug.LogError( 627 $"No {typeof(TType).Name} with name '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has been registered"); 628 continue; 629 } 630 631 if (!m_IsControlOnlyResolve) 632 { 633 // Instantiate it. 634 if (!(Activator.CreateInstance(type) is TType instance)) 635 { 636 Debug.LogError( 637 $"Type '{type.Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') is not an {typeof(TType).Name}"); 638 continue; 639 } 640 641 // Pass parameters to it. 642 ApplyParameters(m_Parameters[i].parameters, instance, actionMap, ref binding, objectRegistrationName, 643 namesAndParameters); 644 645 // Add to list. 646 ArrayHelpers.AppendWithCapacity(ref array, ref count, instance); 647 } 648 else 649 { 650 Debug.Assert(type.IsInstanceOfType(array[count]), "Type of instance in array does not match expected type"); 651 ++count; 652 } 653 } 654 655 return firstIndex; 656 } 657 658 private static InputBindingComposite InstantiateBindingComposite(ref InputBinding binding, InputActionMap actionMap) 659 { 660 var nameAndParametersParsed = NameAndParameters.Parse(binding.effectivePath); 661 662 // Look up. 663 var type = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParametersParsed.name); 664 if (type == null) 665 throw new InvalidOperationException( 666 $"No binding composite with name '{nameAndParametersParsed.name}' has been registered"); 667 668 // Instantiate. 669 if (!(Activator.CreateInstance(type) is InputBindingComposite instance)) 670 throw new InvalidOperationException( 671 $"Registered type '{type.Name}' used for '{nameAndParametersParsed.name}' is not an InputBindingComposite"); 672 673 // Set parameters. 674 ApplyParameters(nameAndParametersParsed.parameters, instance, actionMap, ref binding, nameAndParametersParsed.name, 675 binding.effectivePath); 676 677 return instance; 678 } 679 680 private static void ApplyParameters(ReadOnlyArray<NamedValue> parameters, object instance, InputActionMap actionMap, ref InputBinding binding, string objectRegistrationName, string namesAndParameters) 681 { 682 foreach (var parameter in parameters) 683 { 684 // Find field. 685 var field = instance.GetType().GetField(parameter.name, 686 BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 687 if (field == null) 688 { 689 Debug.LogError( 690 $"Type '{instance.GetType().Name}' registered as '{objectRegistrationName}' (mentioned in '{namesAndParameters}') has no public field called '{parameter.name}'"); 691 continue; 692 } 693 var fieldTypeCode = Type.GetTypeCode(field.FieldType); 694 695 // See if we have a parameter override. 696 var parameterOverride = 697 InputActionRebindingExtensions.ParameterOverride.Find(actionMap, ref binding, parameter.name, objectRegistrationName); 698 var value = parameterOverride != null 699 ? parameterOverride.Value.value 700 : parameter.value; 701 702 field.SetValue(instance, value.ConvertTo(fieldTypeCode).ToObject()); 703 } 704 } 705 706 private static int AssignCompositePartIndex(object composite, string name, ref int currentCompositePartCount) 707 { 708 var type = composite.GetType(); 709 710 ////REVIEW: check for [InputControl] attribute? 711 712 ////TODO: allow this to be a property instead 713 // Look up field. 714 var field = type.GetField(name, 715 BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 716 if (field == null) 717 throw new InvalidOperationException( 718 $"Cannot find public field '{name}' used as parameter of binding composite '{composite}' of type '{type}'"); 719 720 ////REVIEW: should we wrap part numbers in a struct instead of using int? 721 722 // Type-check. 723 var fieldType = field.FieldType; 724 if (fieldType != typeof(int)) 725 throw new InvalidOperationException( 726 $"Field '{name}' used as a parameter of binding composite '{composite}' must be of type 'int' but is of type '{type.Name}' instead"); 727 728 ////REVIEW: this creates garbage; need a better solution to get to zero garbage during re-resolving 729 // See if we've already assigned a part index. This can happen if there are multiple bindings 730 // for the same named slot on the composite (e.g. multiple "Negative" bindings on an axis composite). 731 var partIndex = (int)field.GetValue(composite); 732 if (partIndex == 0) 733 { 734 // No, not assigned yet. Create new part index. 735 partIndex = ++currentCompositePartCount; 736 field.SetValue(composite, partIndex); 737 } 738 739 return partIndex; 740 } 741 } 742}