InputDeviceBuilder.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using UnityEngine.InputSystem.LowLevel;
  5. using UnityEngine.InputSystem.Utilities;
  6. ////TODO: add ability to add to existing arrays rather than creating per-device arrays
  7. ////TODO: the next step here is to write a code generator that generates code for a given layout that when
  8. //// executed, does what InputDeviceBuilder does but without the use of reflection and much more quickly
  9. ////REVIEW: it probably makes sense to have an initial phase where we process the initial set of
  10. //// device discoveries from native and keep the layout cache around instead of throwing
  11. //// it away after the creation of every single device; best approach may be to just
  12. //// reuse the same InputDeviceBuilder instance over and over
  13. ////TODO: ensure that things are aligned properly for ARM; should that be done on the reading side or in the state layouts?
  14. //// (make sure that alignment works the same on *all* platforms; otherwise editor will not be able to process events from players properly)
  15. namespace UnityEngine.InputSystem.Layouts
  16. {
  17. /// <summary>
  18. /// Turns a device layout into an actual <see cref="InputDevice"/> instance.
  19. /// </summary>
  20. /// <remarks>
  21. /// Ultimately produces a device but can also be used to query the control setup described
  22. /// by a layout.
  23. ///
  24. /// Can be used both to create control hierarchies from scratch as well as to re-create or
  25. /// change existing hierarchies.
  26. ///
  27. /// InputDeviceBuilder is the only way to create control hierarchies. InputControls cannot be
  28. /// <c>new</c>'d directly.
  29. ///
  30. /// Also computes a final state layout when setup is finished.
  31. ///
  32. /// Note that InputDeviceBuilders generate garbage. They are meant to be used for initialization only. Don't
  33. /// use them during normal gameplay.
  34. ///
  35. /// Running an *existing* device through another control build is a *destructive* operation.
  36. /// Existing controls may be reused while at the same time the hierarchy and even the device instance
  37. /// itself may change.
  38. /// </remarks>
  39. internal struct InputDeviceBuilder : IDisposable
  40. {
  41. public void Setup(InternedString layout, InternedString variants,
  42. InputDeviceDescription deviceDescription = default)
  43. {
  44. m_LayoutCacheRef = InputControlLayout.CacheRef();
  45. InstantiateLayout(layout, variants, new InternedString(), null);
  46. FinalizeControlHierarchy();
  47. m_Device.m_Description = deviceDescription;
  48. m_Device.CallFinishSetupRecursive();
  49. }
  50. // Complete the setup and return the full control hierarchy setup
  51. // with its device root.
  52. public InputDevice Finish()
  53. {
  54. var device = m_Device;
  55. // Kill off our state.
  56. Reset();
  57. return device;
  58. }
  59. public void Dispose()
  60. {
  61. m_LayoutCacheRef.Dispose();
  62. }
  63. private InputDevice m_Device;
  64. // Make sure the global layout cache sticks around for at least as long
  65. // as the device builder so that we don't load layouts over and over.
  66. private InputControlLayout.CacheRefInstance m_LayoutCacheRef;
  67. // Table mapping (lower-cased) control paths to control layouts that contain
  68. // overrides for the control at the given path.
  69. private Dictionary<string, InputControlLayout.ControlItem> m_ChildControlOverrides;
  70. private StringBuilder m_StringBuilder;
  71. // Reset the setup in a way where it can be reused for another setup.
  72. // Should retain allocations that can be reused.
  73. private void Reset()
  74. {
  75. m_Device = null;
  76. m_ChildControlOverrides?.Clear();
  77. // Leave the cache in place so we can reuse them in another setup path.
  78. }
  79. private InputControl InstantiateLayout(InternedString layout, InternedString variants, InternedString name, InputControl parent)
  80. {
  81. // Look up layout by name.
  82. var layoutInstance = FindOrLoadLayout(layout);
  83. // Create control hierarchy.
  84. return InstantiateLayout(layoutInstance, variants, name, parent);
  85. }
  86. private InputControl InstantiateLayout(InputControlLayout layout, InternedString variants, InternedString name,
  87. InputControl parent)
  88. {
  89. Debug.Assert(layout.type != null, "Layout has no type set on it");
  90. // No, so create a new control.
  91. var controlObject = Activator.CreateInstance(layout.type);
  92. if (!(controlObject is InputControl control))
  93. {
  94. throw new InvalidOperationException(
  95. $"Type '{layout.type.Name}' referenced by layout '{layout.name}' is not an InputControl");
  96. }
  97. // If it's a device, perform some extra work specific to the control
  98. // hierarchy root.
  99. if (control is InputDevice controlAsDevice)
  100. {
  101. if (parent != null)
  102. throw new InvalidOperationException(
  103. $"Cannot instantiate device layout '{layout.name}' as child of '{parent.path}'; devices must be added at root");
  104. m_Device = controlAsDevice;
  105. m_Device.m_StateBlock.byteOffset = 0;
  106. m_Device.m_StateBlock.bitOffset = 0;
  107. m_Device.m_StateBlock.format = layout.stateFormat;
  108. // If we have an existing device, we'll start the various control arrays
  109. // from scratch. Note that all the controls still refer to the existing
  110. // arrays and so we can iterate children, for example, just fine while
  111. // we are rebuilding the control hierarchy.
  112. m_Device.m_AliasesForEachControl = null;
  113. m_Device.m_ChildrenForEachControl = null;
  114. m_Device.m_UsagesForEachControl = null;
  115. m_Device.m_UsageToControl = null;
  116. if (layout.m_UpdateBeforeRender == true)
  117. m_Device.m_DeviceFlags |= InputDevice.DeviceFlags.UpdateBeforeRender;
  118. }
  119. else if (parent == null)
  120. {
  121. // Someone did "new InputDeviceBuilder(...)" with a control layout.
  122. // We don't support creating control hierarchies without a device at the root.
  123. throw new InvalidOperationException(
  124. $"Toplevel layout used with InputDeviceBuilder must be a device layout; '{layout.name}' is a control layout");
  125. }
  126. // Name defaults to name of layout.
  127. if (name.IsEmpty())
  128. {
  129. name = layout.name;
  130. // If there's a namespace in the layout name, snip it out.
  131. var indexOfLastColon = name.ToString().LastIndexOf(':');
  132. if (indexOfLastColon != -1)
  133. name = new InternedString(name.ToString().Substring(indexOfLastColon + 1));
  134. }
  135. // Variant defaults to variants of layout.
  136. if (variants.IsEmpty())
  137. {
  138. variants = layout.variants;
  139. if (variants.IsEmpty())
  140. variants = InputControlLayout.DefaultVariant;
  141. }
  142. control.m_Name = name;
  143. control.m_DisplayNameFromLayout = layout.m_DisplayName; // No short display names at layout roots.
  144. control.m_Layout = layout.name;
  145. control.m_Variants = variants;
  146. control.m_Parent = parent;
  147. control.m_Device = m_Device;
  148. // Create children and configure their settings from our
  149. // layout values.
  150. var haveChildrenUsingStateFromOtherControl = false;
  151. try
  152. {
  153. // Pass list of existing control on to function as we may have decided to not
  154. // actually reuse the existing control (and thus control.m_ChildrenReadOnly will
  155. // now be blank) but still want crawling down the hierarchy to preserve existing
  156. // controls where possible.
  157. AddChildControls(layout, variants, control,
  158. ref haveChildrenUsingStateFromOtherControl);
  159. }
  160. catch
  161. {
  162. ////TODO: remove control from collection and rethrow
  163. throw;
  164. }
  165. // Come up with a layout for our state.
  166. ComputeStateLayout(control);
  167. // Finally, if we have child controls that take their state blocks from other
  168. // controls, assign them their blocks now.
  169. if (haveChildrenUsingStateFromOtherControl)
  170. {
  171. var controls = layout.m_Controls;
  172. for (var i = 0; i < controls.Length; ++i)
  173. {
  174. ref var item = ref controls[i];
  175. if (string.IsNullOrEmpty(item.useStateFrom))
  176. continue;
  177. ApplyUseStateFrom(control, ref item, layout);
  178. }
  179. }
  180. return control;
  181. }
  182. private const uint kSizeForControlUsingStateFromOtherControl = InputStateBlock.InvalidOffset;
  183. private void AddChildControls(InputControlLayout layout, InternedString variants, InputControl parent,
  184. ref bool haveChildrenUsingStateFromOtherControls)
  185. {
  186. var controlLayouts = layout.m_Controls;
  187. if (controlLayouts == null)
  188. return;
  189. // Find out how many direct children we will add.
  190. var childCount = 0;
  191. var haveControlLayoutWithPath = false;
  192. for (var i = 0; i < controlLayouts.Length; ++i)
  193. {
  194. // Skip if variants don't match.
  195. if (!controlLayouts[i].variants.IsEmpty() &&
  196. !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(controlLayouts[i].variants,
  197. variants, ','))
  198. continue;
  199. ////REVIEW: I'm not sure this is good enough. ATM if you have a control layout with
  200. //// name "foo" and one with name "foo/bar", then the latter is taken as an override
  201. //// but the former isn't. However, whether it has a slash in the path or not shouldn't
  202. //// matter. If a control layout of the same name already exists, it should be
  203. //// considered an override, if not, it shouldn't.
  204. // Not a new child if it's a layout reaching in to the hierarchy to modify
  205. // an existing child.
  206. if (controlLayouts[i].isModifyingExistingControl)
  207. {
  208. if (controlLayouts[i].isArray)
  209. throw new NotSupportedException(
  210. $"Control '{controlLayouts[i].name}' in layout '{layout.name}' is modifying the child of another control but is marked as an array");
  211. haveControlLayoutWithPath = true;
  212. InsertChildControlOverride(parent, ref controlLayouts[i]);
  213. continue;
  214. }
  215. if (controlLayouts[i].isArray)
  216. childCount += controlLayouts[i].arraySize;
  217. else
  218. ++childCount;
  219. }
  220. // Nothing to do if there's no children.
  221. if (childCount == 0)
  222. {
  223. parent.m_ChildCount = default;
  224. parent.m_ChildStartIndex = default;
  225. haveChildrenUsingStateFromOtherControls = false;
  226. return;
  227. }
  228. // Add room for us in the device's child array.
  229. var firstChildIndex = ArrayHelpers.GrowBy(ref m_Device.m_ChildrenForEachControl, childCount);
  230. // Add controls from all control layouts except the ones that have
  231. // paths in them.
  232. var childIndex = firstChildIndex;
  233. for (var i = 0; i < controlLayouts.Length; ++i)
  234. {
  235. var controlLayout = controlLayouts[i];
  236. // Skip control layouts that don't add controls but rather modify child
  237. // controls of other controls added by the layout. We do a second pass
  238. // to apply their settings.
  239. if (controlLayout.isModifyingExistingControl)
  240. continue;
  241. // If the control is part of a variant, skip it if it isn't in the variants we're
  242. // looking for.
  243. if (!controlLayout.variants.IsEmpty() &&
  244. !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(controlLayout.variants,
  245. variants, ','))
  246. continue;
  247. // If it's an array, add a control for each array element.
  248. if (controlLayout.isArray)
  249. {
  250. for (var n = 0; n < controlLayout.arraySize; ++n)
  251. {
  252. var name = controlLayout.name + n;
  253. var control = AddChildControl(layout, variants, parent, ref haveChildrenUsingStateFromOtherControls,
  254. controlLayout, childIndex, nameOverride: name);
  255. ++childIndex;
  256. // Adjust offset, if the control uses explicit offsets.
  257. if (control.m_StateBlock.byteOffset != InputStateBlock.InvalidOffset)
  258. control.m_StateBlock.byteOffset += (uint)n * control.m_StateBlock.alignedSizeInBytes;
  259. }
  260. }
  261. else
  262. {
  263. AddChildControl(layout, variants, parent, ref haveChildrenUsingStateFromOtherControls,
  264. controlLayout, childIndex);
  265. ++childIndex;
  266. }
  267. }
  268. parent.m_ChildCount = childCount;
  269. parent.m_ChildStartIndex = firstChildIndex;
  270. ////REVIEW: there's probably a better way to do this based on m_ChildControlOverrides
  271. // We apply all overrides through m_ChildControlOverrides. However, there may be a control item
  272. // that *adds* a child control to another existing control. This will look the same as overriding
  273. // properties on a child control just that in this case the child control doesn't exist.
  274. //
  275. // Go through all the controls and check for ones that need to be added.
  276. if (haveControlLayoutWithPath)
  277. {
  278. for (var i = 0; i < controlLayouts.Length; ++i)
  279. {
  280. var controlLayout = controlLayouts[i];
  281. if (!controlLayout.isModifyingExistingControl)
  282. continue;
  283. // If the control is part of a variants, skip it if it isn't the variants we're
  284. // looking for.
  285. if (!controlLayout.variants.IsEmpty() && controlLayout.variants != variants)
  286. continue;
  287. AddChildControlIfMissing(layout, variants, parent, ref haveChildrenUsingStateFromOtherControls,
  288. ref controlLayout);
  289. }
  290. }
  291. }
  292. private InputControl AddChildControl(InputControlLayout layout, InternedString variants, InputControl parent,
  293. ref bool haveChildrenUsingStateFromOtherControls,
  294. InputControlLayout.ControlItem controlItem,
  295. int childIndex, string nameOverride = null)
  296. {
  297. var name = nameOverride ?? controlItem.name;
  298. var nameInterned = new InternedString(name);
  299. ////REVIEW: can we check this in InputControlLayout instead?
  300. if (string.IsNullOrEmpty(controlItem.layout))
  301. throw new InvalidOperationException($"Layout has not been set on control '{controlItem.name}' in '{layout.name}'");
  302. // See if there is an override for the control.
  303. if (m_ChildControlOverrides != null)
  304. {
  305. var path = $"{parent.path}/{name}";
  306. var pathLowerCase = path.ToLower();
  307. if (m_ChildControlOverrides.TryGetValue(pathLowerCase, out var controlOverride))
  308. controlItem = controlOverride.Merge(controlItem);
  309. }
  310. // Get name of layout to use for control.
  311. var layoutName = controlItem.layout;
  312. // Create control.
  313. InputControl control;
  314. try
  315. {
  316. control = InstantiateLayout(layoutName, variants, nameInterned, parent);
  317. }
  318. catch (InputControlLayout.LayoutNotFoundException exception)
  319. {
  320. // Throw better exception that gives more info.
  321. throw new InputControlLayout.LayoutNotFoundException(
  322. $"Cannot find layout '{exception.layout}' used in control '{name}' of layout '{layout.name}'",
  323. exception);
  324. }
  325. // Add to array.
  326. // NOTE: AddChildControls and InstantiateLayout take care of growing the array and making
  327. // room for the immediate children of each control.
  328. m_Device.m_ChildrenForEachControl[childIndex] = control;
  329. // Set flags and misc things.
  330. control.noisy = controlItem.isNoisy;
  331. control.synthetic = controlItem.isSynthetic;
  332. if (control.noisy)
  333. m_Device.noisy = true;
  334. // Remember the display names from the layout. We later do a proper pass once we have
  335. // the full hierarchy to set final names.
  336. control.m_DisplayNameFromLayout = controlItem.displayName;
  337. control.m_ShortDisplayNameFromLayout = controlItem.shortDisplayName;
  338. // Set default value.
  339. control.m_DefaultState = controlItem.defaultState;
  340. if (!control.m_DefaultState.isEmpty)
  341. m_Device.hasControlsWithDefaultState = true;
  342. // Set min and max value. Don't just overwrite here as the control's constructor may
  343. // have set a default value.
  344. if (!controlItem.minValue.isEmpty)
  345. control.m_MinValue = controlItem.minValue;
  346. if (!controlItem.maxValue.isEmpty)
  347. control.m_MaxValue = controlItem.maxValue;
  348. // Pass state block config on to control.
  349. var usesStateFromOtherControl = !string.IsNullOrEmpty(controlItem.useStateFrom);
  350. if (!usesStateFromOtherControl)
  351. {
  352. control.m_StateBlock.byteOffset = controlItem.offset;
  353. control.m_StateBlock.bitOffset = controlItem.bit;
  354. if (controlItem.sizeInBits != 0)
  355. control.m_StateBlock.sizeInBits = controlItem.sizeInBits;
  356. if (controlItem.format != 0)
  357. SetFormat(control, controlItem);
  358. }
  359. else
  360. {
  361. // Mark controls that don't have state blocks of their own but rather get their
  362. // blocks from other controls by setting their state size to InvalidOffset.
  363. control.m_StateBlock.sizeInBits = kSizeForControlUsingStateFromOtherControl;
  364. haveChildrenUsingStateFromOtherControls = true;
  365. }
  366. ////REVIEW: the constant appending to m_UsagesForEachControl and m_AliasesForEachControl may lead to a lot
  367. //// of successive re-allocations
  368. // Add usages.
  369. var usages = controlItem.usages;
  370. if (usages.Count > 0)
  371. {
  372. var usageCount = usages.Count;
  373. var usageIndex =
  374. ArrayHelpers.AppendToImmutable(ref m_Device.m_UsagesForEachControl, usages.m_Array);
  375. control.m_UsageStartIndex = usageIndex;
  376. control.m_UsageCount = usageCount;
  377. ArrayHelpers.GrowBy(ref m_Device.m_UsageToControl, usageCount);
  378. for (var n = 0; n < usageCount; ++n)
  379. m_Device.m_UsageToControl[usageIndex + n] = control;
  380. }
  381. // Add aliases.
  382. if (controlItem.aliases.Count > 0)
  383. {
  384. var aliasCount = controlItem.aliases.Count;
  385. var aliasIndex =
  386. ArrayHelpers.AppendToImmutable(ref m_Device.m_AliasesForEachControl, controlItem.aliases.m_Array);
  387. control.m_AliasStartIndex = aliasIndex;
  388. control.m_AliasCount = aliasCount;
  389. }
  390. // Set parameters.
  391. if (controlItem.parameters.Count > 0)
  392. NamedValue.ApplyAllToObject(control, controlItem.parameters);
  393. // Add processors.
  394. if (controlItem.processors.Count > 0)
  395. AddProcessors(control, ref controlItem, layout.name);
  396. return control;
  397. }
  398. private void InsertChildControlOverride(InputControl parent, ref InputControlLayout.ControlItem controlItem)
  399. {
  400. if (m_ChildControlOverrides == null)
  401. m_ChildControlOverrides = new Dictionary<string, InputControlLayout.ControlItem>();
  402. var path = InputControlPath.Combine(parent, controlItem.name);
  403. var pathLowerCase = path.ToLower();
  404. // See if there are existing overrides for the control.
  405. if (!m_ChildControlOverrides.TryGetValue(pathLowerCase, out var existingOverrides))
  406. {
  407. // So, so just insert our overrides and we're done.
  408. m_ChildControlOverrides[pathLowerCase] = controlItem;
  409. return;
  410. }
  411. // Yes, there's existing overrides so we have to merge.
  412. // NOTE: The existing override's properties take precedence here. This is because
  413. // the override has been established from higher up in the layout hierarchy.
  414. existingOverrides = existingOverrides.Merge(controlItem);
  415. m_ChildControlOverrides[pathLowerCase] = existingOverrides;
  416. }
  417. private void AddChildControlIfMissing(InputControlLayout layout, InternedString variants, InputControl parent,
  418. ref bool haveChildrenUsingStateFromOtherControls,
  419. ref InputControlLayout.ControlItem controlItem)
  420. {
  421. ////TODO: support arrays (we may modify an entire array in bulk)
  422. // Find the child control.
  423. var child = InputControlPath.TryFindChild(parent, controlItem.name);
  424. if (child != null)
  425. return;
  426. // We're adding a child somewhere in the existing hierarchy. This is a tricky
  427. // case as we have to potentially shift indices around in the hierarchy to make
  428. // room for the new control.
  429. ////TODO: this path does not support recovering existing controls? does it matter?
  430. child = InsertChildControl(layout, variants, parent,
  431. ref haveChildrenUsingStateFromOtherControls, ref controlItem);
  432. // Apply layout change.
  433. if (!ReferenceEquals(child.parent, parent))
  434. ComputeStateLayout(child.parent);
  435. }
  436. private InputControl InsertChildControl(InputControlLayout layout, InternedString variant, InputControl parent,
  437. ref bool haveChildrenUsingStateFromOtherControls,
  438. ref InputControlLayout.ControlItem controlItem)
  439. {
  440. var path = controlItem.name.ToString();
  441. // First we need to find the immediate parent from the given path.
  442. var indexOfSlash = path.LastIndexOf('/');
  443. if (indexOfSlash == -1)
  444. throw new InvalidOperationException("InsertChildControl has to be called with a slash-separated path");
  445. Debug.Assert(indexOfSlash != 0, "Could not find slash in path");
  446. var immediateParentPath = path.Substring(0, indexOfSlash);
  447. var immediateParent = InputControlPath.TryFindChild(parent, immediateParentPath);
  448. if (immediateParent == null)
  449. throw new InvalidOperationException(
  450. $"Cannot find parent '{immediateParentPath}' of control '{controlItem.name}' in layout '{layout.name}'");
  451. var controlName = path.Substring(indexOfSlash + 1);
  452. if (controlName.Length == 0)
  453. throw new InvalidOperationException(
  454. $"Path cannot end in '/' (control '{controlItem.name}' in layout '{layout.name}')");
  455. // Make room in the device's child array.
  456. var childStartIndex = immediateParent.m_ChildStartIndex;
  457. if (childStartIndex == default)
  458. {
  459. // First child of parent.
  460. childStartIndex = m_Device.m_ChildrenForEachControl.LengthSafe();
  461. immediateParent.m_ChildStartIndex = childStartIndex;
  462. }
  463. var childIndex = childStartIndex + immediateParent.m_ChildCount;
  464. ShiftChildIndicesInHierarchyOneUp(m_Device, childIndex, immediateParent);
  465. ArrayHelpers.InsertAt(ref m_Device.m_ChildrenForEachControl, childIndex, null);
  466. ++immediateParent.m_ChildCount;
  467. // Insert the child.
  468. // NOTE: This may *add several* controls depending on the layout of the control we are inserting.
  469. // The children will be appended to the child array.
  470. var control = AddChildControl(layout, variant, immediateParent,
  471. ref haveChildrenUsingStateFromOtherControls, controlItem, childIndex, controlName);
  472. return control;
  473. }
  474. private static void ApplyUseStateFrom(InputControl parent, ref InputControlLayout.ControlItem controlItem, InputControlLayout layout)
  475. {
  476. var child = InputControlPath.TryFindChild(parent, controlItem.name);
  477. Debug.Assert(child != null, "Could not find child control which should be present at this point");
  478. // Find the referenced control.
  479. var referencedControl = InputControlPath.TryFindChild(parent, controlItem.useStateFrom);
  480. if (referencedControl == null)
  481. throw new InvalidOperationException(
  482. $"Cannot find control '{controlItem.useStateFrom}' referenced in 'useStateFrom' of control '{controlItem.name}' in layout '{layout.name}'");
  483. // Copy its state settings.
  484. child.m_StateBlock = referencedControl.m_StateBlock;
  485. // At this point, all byteOffsets are relative to parents so we need to
  486. // walk up the referenced control's parent chain and add offsets until
  487. // we are at the same level that we are at.
  488. if (child.parent != referencedControl.parent)
  489. for (var parentInChain = referencedControl.parent; parentInChain != parent; parentInChain = parentInChain.parent)
  490. child.m_StateBlock.byteOffset += parentInChain.m_StateBlock.byteOffset;
  491. }
  492. private static void ShiftChildIndicesInHierarchyOneUp(InputDevice device, int startIndex, InputControl exceptControl)
  493. {
  494. var controls = device.m_ChildrenForEachControl;
  495. var count = controls.Length;
  496. for (var i = 0; i < count; ++i)
  497. {
  498. var control = controls[i];
  499. if (control != null && control != exceptControl && control.m_ChildStartIndex >= startIndex)
  500. ++control.m_ChildStartIndex;
  501. }
  502. }
  503. // NOTE: We can only do this once we've initialized the names on the parent control. I.e. it has to be
  504. // done in the second pass we do over the control hierarchy.
  505. private void SetDisplayName(InputControl control, string longDisplayNameFromLayout, string shortDisplayNameFromLayout, bool shortName)
  506. {
  507. var displayNameFromLayout = shortName ? shortDisplayNameFromLayout : longDisplayNameFromLayout;
  508. // Display name may not be set in layout.
  509. if (string.IsNullOrEmpty(displayNameFromLayout))
  510. {
  511. // For short names, we leave it unassigned if there's nothing in the layout
  512. // except if it's a nested control where the parent has a short name.
  513. if (shortName)
  514. {
  515. if (control.parent != null && control.parent != control.device)
  516. {
  517. if (m_StringBuilder == null)
  518. m_StringBuilder = new StringBuilder();
  519. m_StringBuilder.Length = 0;
  520. AddParentDisplayNameRecursive(control.parent, m_StringBuilder, true);
  521. if (m_StringBuilder.Length == 0)
  522. {
  523. control.m_ShortDisplayNameFromLayout = null;
  524. return;
  525. }
  526. if (!string.IsNullOrEmpty(longDisplayNameFromLayout))
  527. m_StringBuilder.Append(longDisplayNameFromLayout);
  528. else
  529. m_StringBuilder.Append(control.name);
  530. control.m_ShortDisplayNameFromLayout = m_StringBuilder.ToString();
  531. return;
  532. }
  533. control.m_ShortDisplayNameFromLayout = null;
  534. return;
  535. }
  536. ////REVIEW: automatically uppercase or prettify this?
  537. // For long names, we default to the control's name.
  538. displayNameFromLayout = control.name;
  539. }
  540. // If it's a nested control, synthesize a path that includes parents.
  541. if (control.parent != null && control.parent != control.device)
  542. {
  543. if (m_StringBuilder == null)
  544. m_StringBuilder = new StringBuilder();
  545. m_StringBuilder.Length = 0;
  546. AddParentDisplayNameRecursive(control.parent, m_StringBuilder, shortName);
  547. m_StringBuilder.Append(displayNameFromLayout);
  548. displayNameFromLayout = m_StringBuilder.ToString();
  549. }
  550. // Assign.
  551. if (shortName)
  552. control.m_ShortDisplayNameFromLayout = displayNameFromLayout;
  553. else
  554. control.m_DisplayNameFromLayout = displayNameFromLayout;
  555. }
  556. private static void AddParentDisplayNameRecursive(InputControl control, StringBuilder stringBuilder,
  557. bool shortName)
  558. {
  559. if (control.parent != null && control.parent != control.device)
  560. AddParentDisplayNameRecursive(control.parent, stringBuilder, shortName);
  561. if (shortName)
  562. {
  563. var text = control.shortDisplayName;
  564. if (string.IsNullOrEmpty(text))
  565. text = control.displayName;
  566. stringBuilder.Append(text);
  567. }
  568. else
  569. {
  570. stringBuilder.Append(control.displayName);
  571. }
  572. stringBuilder.Append(' ');
  573. }
  574. private static void AddProcessors(InputControl control, ref InputControlLayout.ControlItem controlItem, string layoutName)
  575. {
  576. var processorCount = controlItem.processors.Count;
  577. for (var n = 0; n < processorCount; ++n)
  578. {
  579. var name = controlItem.processors[n].name;
  580. var type = InputProcessor.s_Processors.LookupTypeRegistration(name);
  581. if (type == null)
  582. throw new InvalidOperationException(
  583. $"Cannot find processor '{name}' referenced by control '{controlItem.name}' in layout '{layoutName}'");
  584. var processor = Activator.CreateInstance(type);
  585. var parameters = controlItem.processors[n].parameters;
  586. if (parameters.Count > 0)
  587. NamedValue.ApplyAllToObject(processor, parameters);
  588. control.AddProcessor(processor);
  589. }
  590. }
  591. private static void SetFormat(InputControl control, InputControlLayout.ControlItem controlItem)
  592. {
  593. control.m_StateBlock.format = controlItem.format;
  594. if (controlItem.sizeInBits == 0)
  595. {
  596. var primitiveFormatSize = InputStateBlock.GetSizeOfPrimitiveFormatInBits(controlItem.format);
  597. if (primitiveFormatSize != -1)
  598. control.m_StateBlock.sizeInBits = (uint)primitiveFormatSize;
  599. }
  600. }
  601. private InputControlLayout FindOrLoadLayout(string name)
  602. {
  603. Debug.Assert(InputControlLayout.s_CacheInstanceRef > 0, "Should have acquired layout cache reference");
  604. return InputControlLayout.cache.FindOrLoadLayout(name);
  605. }
  606. private static void ComputeStateLayout(InputControl control)
  607. {
  608. var children = control.children;
  609. // If the control has a format but no size specified and the format is a
  610. // primitive format, just set the size automatically.
  611. if (control.m_StateBlock.sizeInBits == 0 && control.m_StateBlock.format != 0)
  612. {
  613. var sizeInBits = InputStateBlock.GetSizeOfPrimitiveFormatInBits(control.m_StateBlock.format);
  614. if (sizeInBits != -1)
  615. control.m_StateBlock.sizeInBits = (uint)sizeInBits;
  616. }
  617. // If state size is not set, it means it's computed from the size of the
  618. // children so make sure we actually have children.
  619. if (control.m_StateBlock.sizeInBits == 0 && children.Count == 0)
  620. {
  621. throw new InvalidOperationException(
  622. $"Control '{control.path}' with layout '{control.layout}' has no size set and has no children to compute size from");
  623. }
  624. // If there's no children, our job is done.
  625. if (children.Count == 0)
  626. return;
  627. // First deal with children that want fixed offsets. All the other ones
  628. // will get appended to the end.
  629. var firstUnfixedByteOffset = 0u;
  630. foreach (var child in children)
  631. {
  632. Debug.Assert(child.m_StateBlock.sizeInBits != 0, "Size of state block not set on child");
  633. // Skip children using state from other controls.
  634. if (child.m_StateBlock.sizeInBits == kSizeForControlUsingStateFromOtherControl)
  635. continue;
  636. // Make sure the child has a valid size set on it.
  637. var childSizeInBits = child.m_StateBlock.sizeInBits;
  638. if (childSizeInBits == 0 || childSizeInBits == InputStateBlock.InvalidOffset)
  639. throw new InvalidOperationException(
  640. $"Child '{child.name}' of '{control.name}' has no size set!");
  641. // Skip children that don't have fixed offsets.
  642. if (child.m_StateBlock.byteOffset == InputStateBlock.InvalidOffset ||
  643. child.m_StateBlock.byteOffset == InputStateBlock.AutomaticOffset)
  644. continue;
  645. // At this point, if the child has no valid bit offset, put it at #0 now.
  646. if (child.m_StateBlock.bitOffset == InputStateBlock.InvalidOffset)
  647. child.m_StateBlock.bitOffset = 0;
  648. // See if the control bumps our fixed layout size.
  649. var endOffset =
  650. MemoryHelpers.ComputeFollowingByteOffset(child.m_StateBlock.byteOffset, child.m_StateBlock.bitOffset + childSizeInBits);
  651. if (endOffset > firstUnfixedByteOffset)
  652. firstUnfixedByteOffset = endOffset;
  653. }
  654. ////TODO: this doesn't support mixed automatic and fixed layouting *within* bitfields;
  655. //// I think it's okay not to support that but we should at least detect it
  656. // Now assign an offset to every control that wants an
  657. // automatic offset. For bitfields, we need to delay advancing byte
  658. // offsets until we've seen all bits in the fields.
  659. // NOTE: Bit addressing controls using automatic offsets *must* be consecutive.
  660. var runningByteOffset = firstUnfixedByteOffset;
  661. InputControl firstBitAddressingChild = null;
  662. var bitfieldSizeInBits = 0u;
  663. foreach (var child in children)
  664. {
  665. // Skip children with fixed offsets.
  666. if (child.m_StateBlock.byteOffset != InputStateBlock.InvalidOffset &&
  667. child.m_StateBlock.byteOffset != InputStateBlock.AutomaticOffset)
  668. continue;
  669. // Skip children using state from other controls.
  670. if (child.m_StateBlock.sizeInBits == kSizeForControlUsingStateFromOtherControl)
  671. continue;
  672. // See if it's a bit addressing control.
  673. var isBitAddressingChild = (child.m_StateBlock.sizeInBits % 8) != 0;
  674. if (isBitAddressingChild)
  675. {
  676. // Remember start of bitfield group.
  677. if (firstBitAddressingChild == null)
  678. firstBitAddressingChild = child;
  679. // Keep a running count of the size of the bitfield.
  680. if (child.m_StateBlock.bitOffset == InputStateBlock.InvalidOffset ||
  681. child.m_StateBlock.bitOffset == InputStateBlock.AutomaticOffset)
  682. {
  683. // Put child at current bit offset.
  684. child.m_StateBlock.bitOffset = bitfieldSizeInBits;
  685. bitfieldSizeInBits += child.m_StateBlock.sizeInBits;
  686. }
  687. else
  688. {
  689. // Child already has bit offset. Keep it but make sure we're accounting for it
  690. // in the bitfield size.
  691. var lastBit = child.m_StateBlock.bitOffset + child.m_StateBlock.sizeInBits;
  692. if (lastBit > bitfieldSizeInBits)
  693. bitfieldSizeInBits = lastBit;
  694. }
  695. }
  696. else
  697. {
  698. // Terminate bitfield group (if there was one).
  699. if (firstBitAddressingChild != null)
  700. {
  701. runningByteOffset = MemoryHelpers.ComputeFollowingByteOffset(runningByteOffset, bitfieldSizeInBits);
  702. firstBitAddressingChild = null;
  703. }
  704. if (child.m_StateBlock.bitOffset == InputStateBlock.InvalidOffset)
  705. child.m_StateBlock.bitOffset = 0;
  706. }
  707. ////FIXME: seems like this should take bitOffset into account
  708. child.m_StateBlock.byteOffset = runningByteOffset;
  709. if (!isBitAddressingChild)
  710. runningByteOffset =
  711. MemoryHelpers.ComputeFollowingByteOffset(runningByteOffset, child.m_StateBlock.sizeInBits);
  712. }
  713. // Compute total size.
  714. // If we ended on a bitfield, account for its size.
  715. if (firstBitAddressingChild != null)
  716. runningByteOffset = MemoryHelpers.ComputeFollowingByteOffset(runningByteOffset, bitfieldSizeInBits);
  717. var totalSizeInBytes = runningByteOffset;
  718. // Set size. We force all parents to the combined size of their children.
  719. control.m_StateBlock.sizeInBits = totalSizeInBytes * 8;
  720. }
  721. private void FinalizeControlHierarchy()
  722. {
  723. FinalizeControlHierarchyRecursive(m_Device);
  724. }
  725. private void FinalizeControlHierarchyRecursive(InputControl control)
  726. {
  727. // Set final display names. This may overwrite the ones supplied by the layout so temporarily
  728. // store the values here.
  729. var displayNameFromLayout = control.m_DisplayNameFromLayout;
  730. var shortDisplayNameFromLayout = control.m_ShortDisplayNameFromLayout;
  731. SetDisplayName(control, displayNameFromLayout, shortDisplayNameFromLayout, false);
  732. SetDisplayName(control, displayNameFromLayout, shortDisplayNameFromLayout, true);
  733. // Recurse into children. Also bake our state offset into our children.
  734. var ourOffset = control.m_StateBlock.byteOffset;
  735. foreach (var child in control.children)
  736. {
  737. child.m_StateBlock.byteOffset += ourOffset;
  738. FinalizeControlHierarchyRecursive(child);
  739. }
  740. }
  741. private static InputDeviceBuilder s_Instance;
  742. private static int s_InstanceRef;
  743. internal static ref InputDeviceBuilder instance
  744. {
  745. get
  746. {
  747. Debug.Assert(s_InstanceRef > 0, "Must hold an instance reference");
  748. return ref s_Instance;
  749. }
  750. }
  751. internal static RefInstance Ref()
  752. {
  753. Debug.Assert(s_Instance.m_Device == null,
  754. "InputDeviceBuilder is already in use! Cannot use the builder recursively");
  755. ++s_InstanceRef;
  756. return new RefInstance();
  757. }
  758. // Helper that allows setting up an InputDeviceBuilder such that it will either be created
  759. // locally and temporarily or, if one already exists globally, reused.
  760. internal struct RefInstance : IDisposable
  761. {
  762. public void Dispose()
  763. {
  764. --s_InstanceRef;
  765. if (s_InstanceRef <= 0)
  766. {
  767. s_Instance.Dispose();
  768. s_Instance = default;
  769. s_InstanceRef = 0;
  770. }
  771. else
  772. // Make sure we reset when there is an exception.
  773. s_Instance.Reset();
  774. }
  775. }
  776. }
  777. }