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