Няма описание
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputDeviceBuilder.cs 57KB

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