InputBindingResolver.cs 36 KB

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