1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192 |
- using System;
- using System.Collections.Generic;
- using System.Text;
- using UnityEngine.InputSystem.Controls;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEngine.InputSystem.Utilities;
-
- ////TODO: add ability to add to existing arrays rather than creating per-device arrays
-
- ////TODO: the next step here is to write a code generator that generates code for a given layout that when
- //// executed, does what InputDeviceBuilder does but without the use of reflection and much more quickly
-
- ////REVIEW: it probably makes sense to have an initial phase where we process the initial set of
- //// device discoveries from native and keep the layout cache around instead of throwing
- //// it away after the creation of every single device; best approach may be to just
- //// reuse the same InputDeviceBuilder instance over and over
-
- ////TODO: ensure that things are aligned properly for ARM; should that be done on the reading side or in the state layouts?
- //// (make sure that alignment works the same on *all* platforms; otherwise editor will not be able to process events from players properly)
-
- namespace UnityEngine.InputSystem.Layouts
- {
- /// <summary>
- /// Turns a device layout into an actual <see cref="InputDevice"/> instance.
- /// </summary>
- /// <remarks>
- /// Ultimately produces a device but can also be used to query the control setup described
- /// by a layout.
- ///
- /// Can be used both to create control hierarchies from scratch as well as to re-create or
- /// change existing hierarchies.
- ///
- /// InputDeviceBuilder is the only way to create control hierarchies. InputControls cannot be
- /// <c>new</c>'d directly.
- ///
- /// Also computes a final state layout when setup is finished.
- ///
- /// Note that InputDeviceBuilders generate garbage. They are meant to be used for initialization only. Don't
- /// use them during normal gameplay.
- ///
- /// Running an *existing* device through another control build is a *destructive* operation.
- /// Existing controls may be reused while at the same time the hierarchy and even the device instance
- /// itself may change.
- /// </remarks>
- internal struct InputDeviceBuilder : IDisposable
- {
- public void Setup(InternedString layout, InternedString variants,
- InputDeviceDescription deviceDescription = default)
- {
- m_LayoutCacheRef = InputControlLayout.CacheRef();
-
- InstantiateLayout(layout, variants, new InternedString(), null);
- FinalizeControlHierarchy();
-
- m_StateOffsetToControlMap.Sort();
-
- m_Device.m_Description = deviceDescription;
- m_Device.m_StateOffsetToControlMap = m_StateOffsetToControlMap.ToArray();
-
- m_Device.CallFinishSetupRecursive();
- }
-
- // Complete the setup and return the full control hierarchy setup
- // with its device root.
- public InputDevice Finish()
- {
- var device = m_Device;
-
- // Kill off our state.
- Reset();
-
- return device;
- }
-
- public void Dispose()
- {
- m_LayoutCacheRef.Dispose();
- }
-
- private InputDevice m_Device;
-
- // Make sure the global layout cache sticks around for at least as long
- // as the device builder so that we don't load layouts over and over.
- private InputControlLayout.CacheRefInstance m_LayoutCacheRef;
-
- // Table mapping (lower-cased) control paths to control layouts that contain
- // overrides for the control at the given path.
- private Dictionary<string, InputControlLayout.ControlItem> m_ChildControlOverrides;
-
- private List<uint> m_StateOffsetToControlMap;
-
- private StringBuilder m_StringBuilder;
-
- // Reset the setup in a way where it can be reused for another setup.
- // Should retain allocations that can be reused.
- private void Reset()
- {
- m_Device = null;
- m_ChildControlOverrides?.Clear();
- m_StateOffsetToControlMap?.Clear();
- // Leave the cache in place so we can reuse them in another setup path.
- }
-
- private InputControl InstantiateLayout(InternedString layout, InternedString variants, InternedString name, InputControl parent)
- {
- // Look up layout by name.
- var layoutInstance = FindOrLoadLayout(layout);
-
- // Create control hierarchy.
- return InstantiateLayout(layoutInstance, variants, name, parent);
- }
-
- private InputControl InstantiateLayout(InputControlLayout layout, InternedString variants, InternedString name,
- InputControl parent)
- {
- Debug.Assert(layout.type != null, "Layout has no type set on it");
-
- // No, so create a new control.
- var controlObject = Activator.CreateInstance(layout.type);
- if (!(controlObject is InputControl control))
- {
- throw new InvalidOperationException(
- $"Type '{layout.type.Name}' referenced by layout '{layout.name}' is not an InputControl");
- }
-
- // If it's a device, perform some extra work specific to the control
- // hierarchy root.
- if (control is InputDevice controlAsDevice)
- {
- if (parent != null)
- throw new InvalidOperationException(
- $"Cannot instantiate device layout '{layout.name}' as child of '{parent.path}'; devices must be added at root");
-
- m_Device = controlAsDevice;
- m_Device.m_StateBlock.byteOffset = 0;
- m_Device.m_StateBlock.bitOffset = 0;
- m_Device.m_StateBlock.format = layout.stateFormat;
-
- // If we have an existing device, we'll start the various control arrays
- // from scratch. Note that all the controls still refer to the existing
- // arrays and so we can iterate children, for example, just fine while
- // we are rebuilding the control hierarchy.
- m_Device.m_AliasesForEachControl = null;
- m_Device.m_ChildrenForEachControl = null;
- m_Device.m_UsagesForEachControl = null;
- m_Device.m_UsageToControl = null;
-
- if (layout.m_UpdateBeforeRender == true)
- m_Device.m_DeviceFlags |= InputDevice.DeviceFlags.UpdateBeforeRender;
- if (layout.canRunInBackground != null)
- {
- m_Device.m_DeviceFlags |= InputDevice.DeviceFlags.CanRunInBackgroundHasBeenQueried;
- if (layout.canRunInBackground == true)
- m_Device.m_DeviceFlags |= InputDevice.DeviceFlags.CanRunInBackground;
- }
- }
- else if (parent == null)
- {
- // Someone did "new InputDeviceBuilder(...)" with a control layout.
- // We don't support creating control hierarchies without a device at the root.
- throw new InvalidOperationException(
- $"Toplevel layout used with InputDeviceBuilder must be a device layout; '{layout.name}' is a control layout");
- }
-
- // Name defaults to name of layout.
- if (name.IsEmpty())
- {
- name = layout.name;
-
- // If there's a namespace in the layout name, snip it out.
- var indexOfLastColon = name.ToString().LastIndexOf(':');
- if (indexOfLastColon != -1)
- name = new InternedString(name.ToString().Substring(indexOfLastColon + 1));
- }
-
- // Make sure name does not contain any slashes.
- if (name.ToString().IndexOf(InputControlPath.Separator) != -1)
- name = new InternedString(name.ToString().CleanSlashes());
-
- // Variant defaults to variants of layout.
- if (variants.IsEmpty())
- {
- variants = layout.variants;
-
- if (variants.IsEmpty())
- variants = InputControlLayout.DefaultVariant;
- }
-
- control.m_Name = name;
- control.m_DisplayNameFromLayout = layout.m_DisplayName; // No short display names at layout roots.
- control.m_Layout = layout.name;
- control.m_Variants = variants;
- control.m_Parent = parent;
- control.m_Device = m_Device;
-
- // this has to be done down here instead of in the device block above because the state for the
- // device needs to be set up before setting noisy or it will throw because the device's m_Device
- // hasn't been set yet. Yes, a device's m_Device is itself.
- if (control is InputDevice)
- control.noisy = layout.isNoisy;
-
- // Create children and configure their settings from our
- // layout values.
- var haveChildrenUsingStateFromOtherControl = false;
- try
- {
- // Pass list of existing control on to function as we may have decided to not
- // actually reuse the existing control (and thus control.m_ChildrenReadOnly will
- // now be blank) but still want crawling down the hierarchy to preserve existing
- // controls where possible.
- AddChildControls(layout, variants, control,
- ref haveChildrenUsingStateFromOtherControl);
- }
- catch
- {
- ////TODO: remove control from collection and rethrow
- throw;
- }
-
- // Come up with a layout for our state.
- ComputeStateLayout(control);
-
- // Finally, if we have child controls that take their state blocks from other
- // controls, assign them their blocks now.
- if (haveChildrenUsingStateFromOtherControl)
- {
- var controls = layout.m_Controls;
- for (var i = 0; i < controls.Length; ++i)
- {
- ref var item = ref controls[i];
- if (string.IsNullOrEmpty(item.useStateFrom))
- continue;
- ApplyUseStateFrom(control, ref item, layout);
- }
- }
-
- return control;
- }
-
- private const uint kSizeForControlUsingStateFromOtherControl = InputStateBlock.InvalidOffset;
-
- private void AddChildControls(InputControlLayout layout, InternedString variants, InputControl parent,
- ref bool haveChildrenUsingStateFromOtherControls)
- {
- var controlLayouts = layout.m_Controls;
- if (controlLayouts == null)
- return;
-
- // Find out how many direct children we will add.
- var childCount = 0;
- var haveControlLayoutWithPath = false;
- for (var i = 0; i < controlLayouts.Length; ++i)
- {
- // Skip if variants don't match.
- if (!controlLayouts[i].variants.IsEmpty() &&
- !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(controlLayouts[i].variants,
- variants, InputControlLayout.VariantSeparator[0]))
- continue;
-
- ////REVIEW: I'm not sure this is good enough. ATM if you have a control layout with
- //// name "foo" and one with name "foo/bar", then the latter is taken as an override
- //// but the former isn't. However, whether it has a slash in the path or not shouldn't
- //// matter. If a control layout of the same name already exists, it should be
- //// considered an override, if not, it shouldn't.
- // Not a new child if it's a layout reaching in to the hierarchy to modify
- // an existing child.
- if (controlLayouts[i].isModifyingExistingControl)
- {
- if (controlLayouts[i].isArray)
- throw new NotSupportedException(
- $"Control '{controlLayouts[i].name}' in layout '{layout.name}' is modifying the child of another control but is marked as an array");
-
- haveControlLayoutWithPath = true;
- InsertChildControlOverride(parent, ref controlLayouts[i]);
- continue;
- }
-
- if (controlLayouts[i].isArray)
- childCount += controlLayouts[i].arraySize;
- else
- ++childCount;
- }
-
- // Nothing to do if there's no children.
- if (childCount == 0)
- {
- parent.m_ChildCount = default;
- parent.m_ChildStartIndex = default;
- haveChildrenUsingStateFromOtherControls = false;
- return;
- }
-
- // Add room for us in the device's child array.
- var firstChildIndex = ArrayHelpers.GrowBy(ref m_Device.m_ChildrenForEachControl, childCount);
-
- // Add controls from all control layouts except the ones that have
- // paths in them.
- var childIndex = firstChildIndex;
- for (var i = 0; i < controlLayouts.Length; ++i)
- {
- var controlLayout = controlLayouts[i];
-
- // Skip control layouts that don't add controls but rather modify child
- // controls of other controls added by the layout. We do a second pass
- // to apply their settings.
- if (controlLayout.isModifyingExistingControl)
- continue;
-
- // If the control is part of a variant, skip it if it isn't in the variants we're
- // looking for.
- if (!controlLayout.variants.IsEmpty() &&
- !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(controlLayout.variants,
- variants, InputControlLayout.VariantSeparator[0]))
- continue;
-
- // If it's an array, add a control for each array element.
- if (controlLayout.isArray)
- {
- for (var n = 0; n < controlLayout.arraySize; ++n)
- {
- var name = controlLayout.name + n;
- var control = AddChildControl(layout, variants, parent, ref haveChildrenUsingStateFromOtherControls,
- controlLayout, childIndex, nameOverride: name);
- ++childIndex;
-
- // Adjust offset, if the control uses explicit offsets.
- if (control.m_StateBlock.byteOffset != InputStateBlock.InvalidOffset)
- control.m_StateBlock.byteOffset += (uint)n * control.m_StateBlock.alignedSizeInBytes;
- }
- }
- else
- {
- AddChildControl(layout, variants, parent, ref haveChildrenUsingStateFromOtherControls,
- controlLayout, childIndex);
- ++childIndex;
- }
- }
-
- parent.m_ChildCount = childCount;
- parent.m_ChildStartIndex = firstChildIndex;
-
- ////REVIEW: there's probably a better way to do this based on m_ChildControlOverrides
- // We apply all overrides through m_ChildControlOverrides. However, there may be a control item
- // that *adds* a child control to another existing control. This will look the same as overriding
- // properties on a child control just that in this case the child control doesn't exist.
- //
- // Go through all the controls and check for ones that need to be added.
- if (haveControlLayoutWithPath)
- {
- for (var i = 0; i < controlLayouts.Length; ++i)
- {
- var controlLayout = controlLayouts[i];
- if (!controlLayout.isModifyingExistingControl)
- continue;
-
- // If the control is part of a variants, skip it if it isn't the variants we're
- // looking for.
- if (!controlLayout.variants.IsEmpty() &&
- !StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(controlLayouts[i].variants,
- variants, InputControlLayout.VariantSeparator[0]))
- continue;
-
- AddChildControlIfMissing(layout, variants, parent, ref haveChildrenUsingStateFromOtherControls,
- ref controlLayout);
- }
- }
- }
-
- private InputControl AddChildControl(InputControlLayout layout, InternedString variants, InputControl parent,
- ref bool haveChildrenUsingStateFromOtherControls,
- InputControlLayout.ControlItem controlItem,
- int childIndex, string nameOverride = null)
- {
- var name = nameOverride != null ? new InternedString(nameOverride) : controlItem.name;
-
- ////REVIEW: can we check this in InputControlLayout instead?
- if (string.IsNullOrEmpty(controlItem.layout))
- throw new InvalidOperationException($"Layout has not been set on control '{controlItem.name}' in '{layout.name}'");
-
- // See if there is an override for the control.
- if (m_ChildControlOverrides != null)
- {
- var pathLowerCase = ChildControlOverridePath(parent, name);
- if (m_ChildControlOverrides.TryGetValue(pathLowerCase, out var controlOverride))
- controlItem = controlOverride.Merge(controlItem);
- }
-
- // Get name of layout to use for control.
- var layoutName = controlItem.layout;
-
- // Create control.
- InputControl control;
- try
- {
- control = InstantiateLayout(layoutName, variants, name, parent);
- }
- catch (InputControlLayout.LayoutNotFoundException exception)
- {
- // Throw better exception that gives more info.
- throw new InputControlLayout.LayoutNotFoundException(
- $"Cannot find layout '{exception.layout}' used in control '{name}' of layout '{layout.name}'",
- exception);
- }
-
- // Add to array.
- // NOTE: AddChildControls and InstantiateLayout take care of growing the array and making
- // room for the immediate children of each control.
- m_Device.m_ChildrenForEachControl[childIndex] = control;
-
- // Set flags and misc things.
- control.noisy = controlItem.isNoisy;
- control.synthetic = controlItem.isSynthetic;
- control.usesStateFromOtherControl = !string.IsNullOrEmpty(controlItem.useStateFrom);
- control.dontReset = (control.noisy || controlItem.dontReset) && !control.usesStateFromOtherControl; // Imply dontReset for noisy controls.
- if (control.noisy)
- m_Device.noisy = true;
- control.isButton = control is ButtonControl;
- if (control.dontReset)
- m_Device.hasDontResetControls = true;
-
- // Remember the display names from the layout. We later do a proper pass once we have
- // the full hierarchy to set final names.
- control.m_DisplayNameFromLayout = controlItem.displayName;
- control.m_ShortDisplayNameFromLayout = controlItem.shortDisplayName;
-
- // Set default value.
- control.m_DefaultState = controlItem.defaultState;
- if (!control.m_DefaultState.isEmpty)
- m_Device.hasControlsWithDefaultState = true;
-
- // Set min and max value. Don't just overwrite here as the control's constructor may
- // have set a default value.
- if (!controlItem.minValue.isEmpty)
- control.m_MinValue = controlItem.minValue;
- if (!controlItem.maxValue.isEmpty)
- control.m_MaxValue = controlItem.maxValue;
-
- // Pass state block config on to control.
- if (!control.usesStateFromOtherControl)
- {
- control.m_StateBlock.byteOffset = controlItem.offset;
- control.m_StateBlock.bitOffset = controlItem.bit;
- if (controlItem.sizeInBits != 0)
- control.m_StateBlock.sizeInBits = controlItem.sizeInBits;
- if (controlItem.format != 0)
- SetFormat(control, controlItem);
- }
- else
- {
- // Mark controls that don't have state blocks of their own but rather get their
- // blocks from other controls by setting their state size to InvalidOffset.
- control.m_StateBlock.sizeInBits = kSizeForControlUsingStateFromOtherControl;
- haveChildrenUsingStateFromOtherControls = true;
- }
-
- ////REVIEW: the constant appending to m_UsagesForEachControl and m_AliasesForEachControl may lead to a lot
- //// of successive re-allocations
-
- // Add usages.
- var usages = controlItem.usages;
- if (usages.Count > 0)
- {
- var usageCount = usages.Count;
- var usageIndex =
- ArrayHelpers.AppendToImmutable(ref m_Device.m_UsagesForEachControl, usages.m_Array);
- control.m_UsageStartIndex = usageIndex;
- control.m_UsageCount = usageCount;
-
- ArrayHelpers.GrowBy(ref m_Device.m_UsageToControl, usageCount);
- for (var n = 0; n < usageCount; ++n)
- m_Device.m_UsageToControl[usageIndex + n] = control;
- }
-
- // Add aliases.
- if (controlItem.aliases.Count > 0)
- {
- var aliasCount = controlItem.aliases.Count;
- var aliasIndex =
- ArrayHelpers.AppendToImmutable(ref m_Device.m_AliasesForEachControl, controlItem.aliases.m_Array);
- control.m_AliasStartIndex = aliasIndex;
- control.m_AliasCount = aliasCount;
- }
-
- // Set parameters.
- if (controlItem.parameters.Count > 0)
- NamedValue.ApplyAllToObject(control, controlItem.parameters);
-
- // Add processors.
- if (controlItem.processors.Count > 0)
- AddProcessors(control, ref controlItem, layout.name);
-
- return control;
- }
-
- private void InsertChildControlOverride(InputControl parent, ref InputControlLayout.ControlItem controlItem)
- {
- if (m_ChildControlOverrides == null)
- m_ChildControlOverrides = new Dictionary<string, InputControlLayout.ControlItem>();
-
- // See if there are existing overrides for the control.
- var pathLowerCase = ChildControlOverridePath(parent, controlItem.name);
- if (!m_ChildControlOverrides.TryGetValue(pathLowerCase, out var existingOverrides))
- {
- // So, so just insert our overrides and we're done.
- m_ChildControlOverrides[pathLowerCase] = controlItem;
- return;
- }
-
- // Yes, there's existing overrides so we have to merge.
- // NOTE: The existing override's properties take precedence here. This is because
- // the override has been established from higher up in the layout hierarchy.
- existingOverrides = existingOverrides.Merge(controlItem);
- m_ChildControlOverrides[pathLowerCase] = existingOverrides;
- }
-
- private string ChildControlOverridePath(InputControl parent, InternedString controlName)
- {
- var pathLowerCase = controlName.ToLower();
- for (var current = parent; current != m_Device; current = current.m_Parent)
- pathLowerCase = $"{current.m_Name.ToLower()}/{pathLowerCase}";
- return pathLowerCase;
- }
-
- private void AddChildControlIfMissing(InputControlLayout layout, InternedString variants, InputControl parent,
- ref bool haveChildrenUsingStateFromOtherControls,
- ref InputControlLayout.ControlItem controlItem)
- {
- ////TODO: support arrays (we may modify an entire array in bulk)
-
- // Find the child control.
- var child = InputControlPath.TryFindChild(parent, controlItem.name);
- if (child != null)
- return;
-
- // We're adding a child somewhere in the existing hierarchy. This is a tricky
- // case as we have to potentially shift indices around in the hierarchy to make
- // room for the new control.
-
- ////TODO: this path does not support recovering existing controls? does it matter?
-
- child = InsertChildControl(layout, variants, parent,
- ref haveChildrenUsingStateFromOtherControls, ref controlItem);
-
- // Apply layout change.
- if (!ReferenceEquals(child.parent, parent))
- ComputeStateLayout(child.parent);
- }
-
- private InputControl InsertChildControl(InputControlLayout layout, InternedString variant, InputControl parent,
- ref bool haveChildrenUsingStateFromOtherControls,
- ref InputControlLayout.ControlItem controlItem)
- {
- var path = controlItem.name.ToString();
-
- // First we need to find the immediate parent from the given path.
- var indexOfSlash = path.LastIndexOf('/');
- if (indexOfSlash == -1)
- throw new InvalidOperationException("InsertChildControl has to be called with a slash-separated path");
- Debug.Assert(indexOfSlash != 0, "Could not find slash in path");
- var immediateParentPath = path.Substring(0, indexOfSlash);
- var immediateParent = InputControlPath.TryFindChild(parent, immediateParentPath);
- if (immediateParent == null)
- throw new InvalidOperationException(
- $"Cannot find parent '{immediateParentPath}' of control '{controlItem.name}' in layout '{layout.name}'");
-
- var controlName = path.Substring(indexOfSlash + 1);
- if (controlName.Length == 0)
- throw new InvalidOperationException(
- $"Path cannot end in '/' (control '{controlItem.name}' in layout '{layout.name}')");
-
- // Make room in the device's child array.
- var childStartIndex = immediateParent.m_ChildStartIndex;
- if (childStartIndex == default)
- {
- // First child of parent.
- childStartIndex = m_Device.m_ChildrenForEachControl.LengthSafe();
- immediateParent.m_ChildStartIndex = childStartIndex;
- }
- var childIndex = childStartIndex + immediateParent.m_ChildCount;
- ShiftChildIndicesInHierarchyOneUp(m_Device, childIndex, immediateParent);
- ArrayHelpers.InsertAt(ref m_Device.m_ChildrenForEachControl, childIndex, null);
- ++immediateParent.m_ChildCount;
-
- // Insert the child.
- // NOTE: This may *add several* controls depending on the layout of the control we are inserting.
- // The children will be appended to the child array.
- var control = AddChildControl(layout, variant, immediateParent,
- ref haveChildrenUsingStateFromOtherControls, controlItem, childIndex, controlName);
-
- return control;
- }
-
- private static void ApplyUseStateFrom(InputControl parent, ref InputControlLayout.ControlItem controlItem, InputControlLayout layout)
- {
- var child = InputControlPath.TryFindChild(parent, controlItem.name);
- Debug.Assert(child != null, "Could not find child control which should be present at this point");
-
- // Find the referenced control.
- var referencedControl = InputControlPath.TryFindChild(parent, controlItem.useStateFrom);
- if (referencedControl == null)
- throw new InvalidOperationException(
- $"Cannot find control '{controlItem.useStateFrom}' referenced in 'useStateFrom' of control '{controlItem.name}' in layout '{layout.name}'");
-
- // Copy its state settings.
- child.m_StateBlock = referencedControl.m_StateBlock;
- child.usesStateFromOtherControl = true;
- child.dontReset = referencedControl.dontReset;
-
- // At this point, all byteOffsets are relative to parents so we need to
- // walk up the referenced control's parent chain and add offsets until
- // we are at the same level that we are at.
- if (child.parent != referencedControl.parent)
- for (var parentInChain = referencedControl.parent; parentInChain != parent; parentInChain = parentInChain.parent)
- child.m_StateBlock.byteOffset += parentInChain.m_StateBlock.byteOffset;
- }
-
- private static void ShiftChildIndicesInHierarchyOneUp(InputDevice device, int startIndex, InputControl exceptControl)
- {
- var controls = device.m_ChildrenForEachControl;
- var count = controls.Length;
- for (var i = 0; i < count; ++i)
- {
- var control = controls[i];
- if (control != null && control != exceptControl && control.m_ChildStartIndex >= startIndex)
- ++control.m_ChildStartIndex;
- }
- }
-
- // NOTE: We can only do this once we've initialized the names on the parent control. I.e. it has to be
- // done in the second pass we do over the control hierarchy.
- private void SetDisplayName(InputControl control, string longDisplayNameFromLayout, string shortDisplayNameFromLayout, bool shortName)
- {
- var displayNameFromLayout = shortName ? shortDisplayNameFromLayout : longDisplayNameFromLayout;
-
- // Display name may not be set in layout.
- if (string.IsNullOrEmpty(displayNameFromLayout))
- {
- // For short names, we leave it unassigned if there's nothing in the layout
- // except if it's a nested control where the parent has a short name.
- if (shortName)
- {
- if (control.parent != null && control.parent != control.device)
- {
- if (m_StringBuilder == null)
- m_StringBuilder = new StringBuilder();
- m_StringBuilder.Length = 0;
- AddParentDisplayNameRecursive(control.parent, m_StringBuilder, true);
- if (m_StringBuilder.Length == 0)
- {
- control.m_ShortDisplayNameFromLayout = null;
- return;
- }
-
- if (!string.IsNullOrEmpty(longDisplayNameFromLayout))
- m_StringBuilder.Append(longDisplayNameFromLayout);
- else
- m_StringBuilder.Append(control.name);
- control.m_ShortDisplayNameFromLayout = m_StringBuilder.ToString();
- return;
- }
-
- control.m_ShortDisplayNameFromLayout = null;
- return;
- }
-
- ////REVIEW: automatically uppercase or prettify this?
- // For long names, we default to the control's name.
- displayNameFromLayout = control.name;
- }
-
- // If it's a nested control, synthesize a path that includes parents.
- if (control.parent != null && control.parent != control.device)
- {
- if (m_StringBuilder == null)
- m_StringBuilder = new StringBuilder();
- m_StringBuilder.Length = 0;
- AddParentDisplayNameRecursive(control.parent, m_StringBuilder, shortName);
- m_StringBuilder.Append(displayNameFromLayout);
- displayNameFromLayout = m_StringBuilder.ToString();
- }
-
- // Assign.
- if (shortName)
- control.m_ShortDisplayNameFromLayout = displayNameFromLayout;
- else
- control.m_DisplayNameFromLayout = displayNameFromLayout;
- }
-
- private static void AddParentDisplayNameRecursive(InputControl control, StringBuilder stringBuilder,
- bool shortName)
- {
- if (control.parent != null && control.parent != control.device)
- AddParentDisplayNameRecursive(control.parent, stringBuilder, shortName);
-
- if (shortName)
- {
- var text = control.shortDisplayName;
- if (string.IsNullOrEmpty(text))
- text = control.displayName;
-
- stringBuilder.Append(text);
- }
- else
- {
- stringBuilder.Append(control.displayName);
- }
- stringBuilder.Append(' ');
- }
-
- private static void AddProcessors(InputControl control, ref InputControlLayout.ControlItem controlItem, string layoutName)
- {
- var processorCount = controlItem.processors.Count;
- for (var n = 0; n < processorCount; ++n)
- {
- var name = controlItem.processors[n].name;
- var type = InputProcessor.s_Processors.LookupTypeRegistration(name);
- if (type == null)
- throw new InvalidOperationException(
- $"Cannot find processor '{name}' referenced by control '{controlItem.name}' in layout '{layoutName}'");
-
- var processor = Activator.CreateInstance(type);
-
- var parameters = controlItem.processors[n].parameters;
- if (parameters.Count > 0)
- NamedValue.ApplyAllToObject(processor, parameters);
-
- control.AddProcessor(processor);
- }
- }
-
- private static void SetFormat(InputControl control, InputControlLayout.ControlItem controlItem)
- {
- control.m_StateBlock.format = controlItem.format;
- if (controlItem.sizeInBits == 0)
- {
- var primitiveFormatSize = InputStateBlock.GetSizeOfPrimitiveFormatInBits(controlItem.format);
- if (primitiveFormatSize != -1)
- control.m_StateBlock.sizeInBits = (uint)primitiveFormatSize;
- }
- }
-
- private static InputControlLayout FindOrLoadLayout(string name)
- {
- Debug.Assert(InputControlLayout.s_CacheInstanceRef > 0, "Should have acquired layout cache reference");
- return InputControlLayout.cache.FindOrLoadLayout(name);
- }
-
- private static void ComputeStateLayout(InputControl control)
- {
- var children = control.children;
-
- // If the control has a format but no size specified and the format is a
- // primitive format, just set the size automatically.
- if (control.m_StateBlock.sizeInBits == 0 && control.m_StateBlock.format != 0)
- {
- var sizeInBits = InputStateBlock.GetSizeOfPrimitiveFormatInBits(control.m_StateBlock.format);
- if (sizeInBits != -1)
- control.m_StateBlock.sizeInBits = (uint)sizeInBits;
- }
-
- // If state size is not set, it means it's computed from the size of the
- // children so make sure we actually have children.
- if (control.m_StateBlock.sizeInBits == 0 && children.Count == 0)
- {
- throw new InvalidOperationException(
- $"Control '{control.path}' with layout '{control.layout}' has no size set and has no children to compute size from");
- }
-
- // If there's no children, our job is done.
- if (children.Count == 0)
- return;
-
- // First deal with children that want fixed offsets. All the other ones
- // will get appended to the end.
- var firstUnfixedByteOffset = 0u;
- foreach (var child in children)
- {
- Debug.Assert(child.m_StateBlock.sizeInBits != 0, "Size of state block not set on child");
-
- // Skip children using state from other controls.
- if (child.m_StateBlock.sizeInBits == kSizeForControlUsingStateFromOtherControl)
- continue;
-
- // Make sure the child has a valid size set on it.
- var childSizeInBits = child.m_StateBlock.sizeInBits;
- if (childSizeInBits == 0 || childSizeInBits == InputStateBlock.InvalidOffset)
- throw new InvalidOperationException(
- $"Child '{child.name}' of '{control.name}' has no size set!");
-
- // Skip children that don't have fixed offsets.
- if (child.m_StateBlock.byteOffset == InputStateBlock.InvalidOffset ||
- child.m_StateBlock.byteOffset == InputStateBlock.AutomaticOffset)
- continue;
-
- // At this point, if the child has no valid bit offset, put it at #0 now.
- if (child.m_StateBlock.bitOffset == InputStateBlock.InvalidOffset)
- child.m_StateBlock.bitOffset = 0;
-
- // See if the control bumps our fixed layout size.
- var endOffset =
- MemoryHelpers.ComputeFollowingByteOffset(child.m_StateBlock.byteOffset, child.m_StateBlock.bitOffset + childSizeInBits);
- if (endOffset > firstUnfixedByteOffset)
- firstUnfixedByteOffset = endOffset;
- }
-
- ////TODO: this doesn't support mixed automatic and fixed layouting *within* bitfields;
- //// I think it's okay not to support that but we should at least detect it
-
- // Now assign an offset to every control that wants an
- // automatic offset. For bitfields, we need to delay advancing byte
- // offsets until we've seen all bits in the fields.
- // NOTE: Bit addressing controls using automatic offsets *must* be consecutive.
- var runningByteOffset = firstUnfixedByteOffset;
- InputControl firstBitAddressingChild = null;
- var bitfieldSizeInBits = 0u;
- foreach (var child in children)
- {
- // Skip children with fixed offsets.
- if (child.m_StateBlock.byteOffset != InputStateBlock.InvalidOffset &&
- child.m_StateBlock.byteOffset != InputStateBlock.AutomaticOffset)
- continue;
-
- // Skip children using state from other controls.
- if (child.m_StateBlock.sizeInBits == kSizeForControlUsingStateFromOtherControl)
- continue;
-
- // See if it's a bit addressing control.
- var isBitAddressingChild = (child.m_StateBlock.sizeInBits % 8) != 0;
- if (isBitAddressingChild)
- {
- // Remember start of bitfield group.
- if (firstBitAddressingChild == null)
- firstBitAddressingChild = child;
-
- // Keep a running count of the size of the bitfield.
- if (child.m_StateBlock.bitOffset == InputStateBlock.InvalidOffset ||
- child.m_StateBlock.bitOffset == InputStateBlock.AutomaticOffset)
- {
- // Put child at current bit offset.
- child.m_StateBlock.bitOffset = bitfieldSizeInBits;
-
- bitfieldSizeInBits += child.m_StateBlock.sizeInBits;
- }
- else
- {
- // Child already has bit offset. Keep it but make sure we're accounting for it
- // in the bitfield size.
- var lastBit = child.m_StateBlock.bitOffset + child.m_StateBlock.sizeInBits;
- if (lastBit > bitfieldSizeInBits)
- bitfieldSizeInBits = lastBit;
- }
- }
- else
- {
- // Terminate bitfield group (if there was one).
- if (firstBitAddressingChild != null)
- {
- runningByteOffset = MemoryHelpers.ComputeFollowingByteOffset(runningByteOffset, bitfieldSizeInBits);
- firstBitAddressingChild = null;
- }
-
- if (child.m_StateBlock.bitOffset == InputStateBlock.InvalidOffset)
- child.m_StateBlock.bitOffset = 0;
-
- // Conform to memory addressing constraints of CPU architecture. If we don't do
- // this, ARMs will end up choking on misaligned memory accesses.
- runningByteOffset = MemoryHelpers.AlignNatural(runningByteOffset, child.m_StateBlock.alignedSizeInBytes);
- }
-
- ////FIXME: seems like this should take bitOffset into account
- child.m_StateBlock.byteOffset = runningByteOffset;
-
- if (!isBitAddressingChild)
- runningByteOffset =
- MemoryHelpers.ComputeFollowingByteOffset(runningByteOffset, child.m_StateBlock.sizeInBits);
- }
-
- // Compute total size.
- // If we ended on a bitfield, account for its size.
- if (firstBitAddressingChild != null)
- runningByteOffset = MemoryHelpers.ComputeFollowingByteOffset(runningByteOffset, bitfieldSizeInBits);
- var totalSizeInBytes = runningByteOffset;
-
- // Set size. We force all parents to the combined size of their children.
- control.m_StateBlock.sizeInBits = totalSizeInBytes * 8;
- }
-
- private void FinalizeControlHierarchy()
- {
- if (m_StateOffsetToControlMap == null)
- m_StateOffsetToControlMap = new List<uint>();
-
- if (m_Device.allControls.Count > (1U << InputDevice.kControlIndexBits))
- throw new NotSupportedException($"Device '{m_Device}' exceeds maximum supported control count of {1U << InputDevice.kControlIndexBits} (has {m_Device.allControls.Count} controls)");
-
- var rootNode = new InputDevice.ControlBitRangeNode((ushort)(m_Device.m_StateBlock.sizeInBits - 1));
- m_Device.m_ControlTreeNodes = new InputDevice.ControlBitRangeNode[1];
- m_Device.m_ControlTreeNodes[0] = rootNode;
-
- var controlIndiciesNextFreeIndex = 0;
- // Device is not in m_ChildrenForEachControl so use index -1.
- FinalizeControlHierarchyRecursive(m_Device, -1, m_Device.m_ChildrenForEachControl, false, false, ref controlIndiciesNextFreeIndex);
- }
-
- private void FinalizeControlHierarchyRecursive(InputControl control, int controlIndex, InputControl[] allControls, bool noisy, bool dontReset, ref int controlIndiciesNextFreeIndex)
- {
- // Make sure we're staying within limits on state offsets and sizes.
- if (control.m_ChildCount == 0)
- {
- if (control.m_StateBlock.effectiveBitOffset >= (1U << InputDevice.kStateOffsetBits))
- throw new NotSupportedException($"Control '{control}' exceeds maximum supported state bit offset of {(1U << InputDevice.kStateOffsetBits) - 1} (bit offset {control.stateBlock.effectiveBitOffset})");
- if (control.m_StateBlock.sizeInBits >= (1U << InputDevice.kStateSizeBits))
- throw new NotSupportedException($"Control '{control}' exceeds maximum supported state bit size of {(1U << InputDevice.kStateSizeBits) - 1} (bit offset {control.stateBlock.sizeInBits})");
- }
-
- // Construct control bit range tree
- if (control != m_Device)
- InsertControlBitRangeNode(ref m_Device.m_ControlTreeNodes[0], control, ref controlIndiciesNextFreeIndex, 0);
-
- // Add all leaf controls to state offset mapping.
- if (control.m_ChildCount == 0)
- m_StateOffsetToControlMap.Add(
- InputDevice.EncodeStateOffsetToControlMapEntry((uint)controlIndex, control.m_StateBlock.effectiveBitOffset, control.m_StateBlock.sizeInBits));
-
- // Set final display names. This may overwrite the ones supplied by the layout so temporarily
- // store the values here.
- var displayNameFromLayout = control.m_DisplayNameFromLayout;
- var shortDisplayNameFromLayout = control.m_ShortDisplayNameFromLayout;
- SetDisplayName(control, displayNameFromLayout, shortDisplayNameFromLayout, false);
- SetDisplayName(control, displayNameFromLayout, shortDisplayNameFromLayout, true);
-
- if (control != control.device)
- {
- if (noisy)
- control.noisy = true;
- else
- noisy = control.noisy;
-
- if (dontReset)
- control.dontReset = true;
- else
- dontReset = control.dontReset;
- }
-
- // Recurse into children. Also bake our state offset into our children.
- var ourOffset = control.m_StateBlock.byteOffset;
- var childCount = control.m_ChildCount;
- var childStartIndex = control.m_ChildStartIndex;
- for (var i = 0; i < childCount; ++i)
- {
- var childIndex = childStartIndex + i;
- var child = allControls[childIndex];
- child.m_StateBlock.byteOffset += ourOffset;
-
- FinalizeControlHierarchyRecursive(child, childIndex, allControls, noisy, dontReset, ref controlIndiciesNextFreeIndex);
- }
-
- control.isSetupFinished = true;
- }
-
- private void InsertControlBitRangeNode(ref InputDevice.ControlBitRangeNode parent, InputControl control, ref int controlIndiciesNextFreeIndex, ushort startOffset)
- {
- InputDevice.ControlBitRangeNode leftNode;
- InputDevice.ControlBitRangeNode rightNode;
-
- // we don't recalculate mid-points for nodes that have already been created
- if (parent.leftChildIndex == -1)
- {
- var midPoint = GetBestMidPoint(parent, startOffset);
- leftNode = new InputDevice.ControlBitRangeNode(midPoint);
- rightNode = new InputDevice.ControlBitRangeNode(parent.endBitOffset);
- AddChildren(ref parent, leftNode, rightNode);
- }
- else
- {
- leftNode = m_Device.m_ControlTreeNodes[parent.leftChildIndex];
- rightNode = m_Device.m_ControlTreeNodes[parent.leftChildIndex + 1];
- }
-
-
- // if the control starts in the left node and ends in the right, add a pointer to both nodes and return
- if (control.m_StateBlock.effectiveBitOffset < leftNode.endBitOffset &&
- control.m_StateBlock.effectiveBitOffset + control.m_StateBlock.sizeInBits > leftNode.endBitOffset)
- {
- AddControlToNode(control, ref controlIndiciesNextFreeIndex, parent.leftChildIndex);
- AddControlToNode(control, ref controlIndiciesNextFreeIndex, parent.leftChildIndex + 1);
- return;
- }
-
- // if it exactly fits one of the nodes, add a pointer to just that node and return
- if (control.m_StateBlock.effectiveBitOffset == startOffset &&
- control.m_StateBlock.effectiveBitOffset + control.m_StateBlock.sizeInBits == leftNode.endBitOffset)
- {
- AddControlToNode(control, ref controlIndiciesNextFreeIndex, parent.leftChildIndex);
- return;
- }
-
- if (control.m_StateBlock.effectiveBitOffset == leftNode.endBitOffset &&
- control.m_StateBlock.effectiveBitOffset + control.m_StateBlock.sizeInBits == rightNode.endBitOffset)
- {
- AddControlToNode(control, ref controlIndiciesNextFreeIndex, parent.leftChildIndex + 1);
- return;
- }
-
- // otherwise, if the node ends in the left node, recurse left
- if (control.m_StateBlock.effectiveBitOffset < leftNode.endBitOffset)
- InsertControlBitRangeNode(ref m_Device.m_ControlTreeNodes[parent.leftChildIndex], control,
- ref controlIndiciesNextFreeIndex, startOffset);
- else
- InsertControlBitRangeNode(ref m_Device.m_ControlTreeNodes[parent.leftChildIndex + 1], control,
- ref controlIndiciesNextFreeIndex, leftNode.endBitOffset);
- }
-
- private ushort GetBestMidPoint(InputDevice.ControlBitRangeNode parent, ushort startOffset)
- {
- // find the absolute mid-point, rounded up
- var absoluteMidPoint = (ushort)(startOffset + ((parent.endBitOffset - startOffset - 1) / 2 + 1));
- var closestControlEndPointToMidPoint = ushort.MaxValue;
- var closestControlStartPointToMidPoint = ushort.MaxValue;
-
- // go through all controls and find the start and end offsets that are closest to the absolute mid-point
- foreach (var control in m_Device.m_ChildrenForEachControl)
- {
- var stateBlock = control.m_StateBlock;
-
- // don't consider controls that end before the start of the parent range, or start after
- // the end of the parent range
- if (stateBlock.effectiveBitOffset + stateBlock.sizeInBits - 1 < startOffset ||
- stateBlock.effectiveBitOffset >= parent.endBitOffset)
- continue;
-
- // don't consider controls that are larger than the parent range
- if (stateBlock.sizeInBits > parent.endBitOffset - startOffset)
- continue;
-
- // don't consider controls that start or end on the same boundary as the parent
- if (stateBlock.effectiveBitOffset == startOffset ||
- stateBlock.effectiveBitOffset + stateBlock.sizeInBits == parent.endBitOffset)
- continue;
-
- if (Math.Abs(stateBlock.effectiveBitOffset + stateBlock.sizeInBits - (int)absoluteMidPoint) <
- Math.Abs(closestControlEndPointToMidPoint - absoluteMidPoint) &&
- stateBlock.effectiveBitOffset + stateBlock.sizeInBits < parent.endBitOffset)
- {
- closestControlEndPointToMidPoint = (ushort)(stateBlock.effectiveBitOffset + stateBlock.sizeInBits);
- }
-
- if (Math.Abs(stateBlock.effectiveBitOffset - (int)absoluteMidPoint) <
- Math.Abs(closestControlStartPointToMidPoint - absoluteMidPoint) &&
- stateBlock.effectiveBitOffset >= startOffset)
- {
- closestControlStartPointToMidPoint = (ushort)stateBlock.effectiveBitOffset;
- }
- }
-
- var absoluteMidPointCollisions = 0;
- var controlStartMidPointCollisions = 0;
- var controlEndMidPointCollisions = 0;
-
- // figure out which of the possible midpoints intersects the fewest controls. The one with the fewest
- // is the best one because it means fewer controls will be added to this node.
- foreach (var control in m_Device.m_ChildrenForEachControl)
- {
- if (closestControlStartPointToMidPoint != ushort.MaxValue &&
- closestControlStartPointToMidPoint > control.m_StateBlock.effectiveBitOffset &&
- closestControlStartPointToMidPoint < control.m_StateBlock.effectiveBitOffset + control.m_StateBlock.sizeInBits)
- controlStartMidPointCollisions++;
-
- if (closestControlEndPointToMidPoint != ushort.MaxValue &&
- closestControlEndPointToMidPoint > control.m_StateBlock.effectiveBitOffset &&
- closestControlEndPointToMidPoint < control.m_StateBlock.effectiveBitOffset + control.m_StateBlock.sizeInBits)
- controlEndMidPointCollisions++;
-
- if (absoluteMidPoint > control.m_StateBlock.effectiveBitOffset &&
- absoluteMidPoint < control.m_StateBlock.effectiveBitOffset + control.m_StateBlock.sizeInBits)
- absoluteMidPointCollisions++;
- }
-
- if (closestControlEndPointToMidPoint != ushort.MaxValue &&
- controlEndMidPointCollisions <= controlStartMidPointCollisions &&
- controlEndMidPointCollisions <= absoluteMidPointCollisions)
- {
- Debug.Assert(closestControlEndPointToMidPoint >= startOffset && closestControlEndPointToMidPoint <= startOffset + parent.endBitOffset);
- return closestControlEndPointToMidPoint;
- }
-
- if (closestControlStartPointToMidPoint != ushort.MaxValue &&
- controlStartMidPointCollisions <= controlEndMidPointCollisions &&
- controlStartMidPointCollisions <= absoluteMidPointCollisions)
- {
- Debug.Assert(closestControlStartPointToMidPoint >= startOffset && closestControlStartPointToMidPoint <= startOffset + parent.endBitOffset);
- return closestControlStartPointToMidPoint;
- }
-
- Debug.Assert(absoluteMidPoint >= startOffset && absoluteMidPoint <= startOffset + parent.endBitOffset);
- return absoluteMidPoint;
- }
-
- private void AddControlToNode(InputControl control, ref int controlIndiciesNextFreeIndex, int nodeIndex)
- {
- Debug.Assert(m_Device.m_ControlTreeNodes[nodeIndex].controlCount < 255,
- "Control bit range nodes can address maximum of 255 controls.");
-
- ref var node = ref m_Device.m_ControlTreeNodes[nodeIndex];
- var leafControlStartIndex = node.controlStartIndex;
- if (node.controlCount == 0)
- {
- node.controlStartIndex = (ushort)controlIndiciesNextFreeIndex;
- leafControlStartIndex = node.controlStartIndex;
- }
-
- ArrayHelpers.InsertAt(ref m_Device.m_ControlTreeIndices,
- node.controlStartIndex + node.controlCount,
- GetControlIndex(control));
- ++node.controlCount;
- ++controlIndiciesNextFreeIndex;
-
- // bump up all the start indicies for nodes that have a start index larger than the one we just inserted into
- for (var i = 0; i < m_Device.m_ControlTreeNodes.Length; i++)
- {
- if (m_Device.m_ControlTreeNodes[i].controlCount == 0 ||
- m_Device.m_ControlTreeNodes[i].controlStartIndex <= leafControlStartIndex)
- continue;
-
- ++m_Device.m_ControlTreeNodes[i].controlStartIndex;
- }
- }
-
- private void AddChildren(ref InputDevice.ControlBitRangeNode parent, InputDevice.ControlBitRangeNode left, InputDevice.ControlBitRangeNode right)
- {
- // if this node has a child start index, its already in the tree
- if (parent.leftChildIndex != -1)
- return;
-
- var startIndex = m_Device.m_ControlTreeNodes.Length;
- parent.leftChildIndex = (short)startIndex;
- Array.Resize(ref m_Device.m_ControlTreeNodes, startIndex + 2);
- m_Device.m_ControlTreeNodes[startIndex] = left;
- m_Device.m_ControlTreeNodes[startIndex + 1] = right;
- }
-
- private ushort GetControlIndex(InputControl control)
- {
- for (var i = 0; i < m_Device.m_ChildrenForEachControl.Length; i++)
- {
- if (control == m_Device.m_ChildrenForEachControl[i])
- return (ushort)i;
- }
-
- throw new InvalidOperationException($"InputDeviceBuilder error. Couldn't find control {control}.");
- }
-
- private static InputDeviceBuilder s_Instance;
- private static int s_InstanceRef;
-
- internal static ref InputDeviceBuilder instance
- {
- get
- {
- Debug.Assert(s_InstanceRef > 0, "Must hold an instance reference");
- return ref s_Instance;
- }
- }
-
- internal static RefInstance Ref()
- {
- Debug.Assert(s_Instance.m_Device == null,
- "InputDeviceBuilder is already in use! Cannot use the builder recursively");
- ++s_InstanceRef;
- return new RefInstance();
- }
-
- // Helper that allows setting up an InputDeviceBuilder such that it will either be created
- // locally and temporarily or, if one already exists globally, reused.
- internal struct RefInstance : IDisposable
- {
- public void Dispose()
- {
- --s_InstanceRef;
- if (s_InstanceRef <= 0)
- {
- s_Instance.Dispose();
- s_Instance = default;
- s_InstanceRef = 0;
- }
- else
- // Make sure we reset when there is an exception.
- s_Instance.Reset();
- }
- }
- }
- }
|