private InputControl AddChildControl(InputControlLayout layout, InternedString variant, InputControl parent, ReadOnlyArray <InputControl>?existingChildren, ref bool haveChildrenUsingStateFromOtherControls, ref InputControlLayout.ControlItem controlItem, ref int childIndex, string nameOverride = null) { var name = nameOverride ?? controlItem.name; var nameLowerCase = name.ToLower(); var nameInterned = new InternedString(name); string path = null; ////REVIEW: can we check this in InputControlLayout instead? if (string.IsNullOrEmpty(controlItem.layout)) { throw new Exception(string.Format("Layout has not been set on control '{0}' in '{1}'", controlItem.name, layout.name)); } // See if there is an override for the control. InputControlLayout.ControlItem?controlOverride = null; if (m_ChildControlOverrides != null) { path = string.Format("{0}/{1}", parent.path, name); var pathLowerCase = path.ToLower(); InputControlLayout.ControlItem match; if (m_ChildControlOverrides.TryGetValue(pathLowerCase, out match)) { controlOverride = match; } } // Get name of layout to use for control. var layoutName = controlItem.layout; if (controlOverride != null && !controlOverride.Value.layout.IsEmpty()) { layoutName = controlOverride.Value.layout; } // See if we have an existing control that we might be able to re-use. InputControl existingControl = null; if (existingChildren != null) { var existingChildCount = existingChildren.Value.Count; for (var n = 0; n < existingChildCount; ++n) { var existingChild = existingChildren.Value[n]; if (existingChild.layout == layoutName && existingChild.name.ToLower() == nameLowerCase) { existingControl = existingChild; break; } } } // Create control. InputControl control; try { control = InstantiateLayout(layoutName, variant, nameInterned, parent, existingControl); } catch (InputControlLayout.LayoutNotFoundException exception) { // Throw better exception that gives more info. throw new Exception( string.Format("Cannot find layout '{0}' used in control '{1}' of layout '{2}'", exception.layout, name, layout.name), exception); } // Add to array. m_Device.m_ChildrenForEachControl[childIndex] = control; ++childIndex; // Set display name. control.m_DisplayNameFromLayout = controlItem.displayName; // Set flags. control.m_IsNoisy = controlItem.isNoisy; // Pass state block config on to control. var usesStateFromOtherControl = !string.IsNullOrEmpty(controlItem.useStateFrom); if (!usesStateFromOtherControl) { control.m_StateBlock.byteOffset = controlItem.offset; if (controlItem.bit != InputStateBlock.kInvalidOffset) { 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 kInvalidOffset. 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. if (controlItem.usages.Count > 0) { var usageCount = controlItem.usages.Count; var usageIndex = ArrayHelpers.AppendToImmutable(ref m_Device.m_UsagesForEachControl, controlItem.usages.m_Array); control.m_UsagesReadOnly = new ReadOnlyArray <InternedString>(m_Device.m_UsagesForEachControl, usageIndex, 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_AliasesReadOnly = new ReadOnlyArray <InternedString>(m_Device.m_AliasesForEachControl, aliasIndex, aliasCount); } // Set parameters. if (controlItem.parameters.Count > 0) { SetParameters(control, controlItem.parameters); } // Add processors. if (controlItem.processors.Count > 0) { AddProcessors(control, ref controlItem, layout.name); } return(control); }
private void ModifyChildControl(InputControlLayout layout, InternedString variant, InputControl parent, ref bool haveChildrenUsingStateFromOtherControls, ref InputControlLayout.ControlItem controlItem) { ////TODO: support arrays (we may modify an entire array in bulk) // Controls layout themselves as we come back up the hierarchy. However, when we // apply layout modifications reaching *into* the hierarchy, we need to retrigger // layouting on their parents. var haveChangedLayoutOfParent = false; // Find the child control. var child = TryGetControl(parent, controlItem.name); if (child == null) { // 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, variant, parent, ref haveChildrenUsingStateFromOtherControls, ref controlItem); haveChangedLayoutOfParent = true; } else { // Apply modifications. if (controlItem.sizeInBits != 0 && child.m_StateBlock.sizeInBits != controlItem.sizeInBits) { child.m_StateBlock.sizeInBits = controlItem.sizeInBits; } if (controlItem.format != 0 && child.m_StateBlock.format != controlItem.format) { SetFormat(child, controlItem); haveChangedLayoutOfParent = true; } ////REVIEW: ATM, when you move a child with a fixed offset, we only move the child //// and don't move the parent or siblings. What this means is that if you move //// leftStick/x, for example, leftStick stays put. ATM you have to move *all* //// controls that are part of a chain manually. Not sure what the best behavior //// is. If we opt to move parents along with children, we have to make sure we //// are not colliding with any other relocations of children (e.g. if you move //// both leftStick/x and leftStick/y, leftStick itself should move only once and //// not at all if there indeed is a leftStick control layout with an offset; //// so, it'd get quite complicated) if (controlItem.offset != InputStateBlock.kInvalidOffset) { child.m_StateBlock.byteOffset = controlItem.offset; } if (controlItem.bit != InputStateBlock.kInvalidOffset) { child.m_StateBlock.bitOffset = controlItem.bit; } if (controlItem.processors.Count > 0) { AddProcessors(child, ref controlItem, layout.name); } ////REVIEW: ATM parameters applied using this path add on top instead of just overriding existing parameters if (controlItem.parameters.Count > 0) { SetParameters(child, controlItem.parameters); } if (!string.IsNullOrEmpty(controlItem.displayName)) { child.m_DisplayNameFromLayout = controlItem.displayName; } ////TODO: usages ////TODO: other modifications } // Apply layout change. ////REVIEW: not sure what's better here; trigger this immediately means we may trigger //// it a number of times on the same parent but doing it as a final pass would //// require either collecting the necessary parents or doing another pass through //// the list of control layouts if (haveChangedLayoutOfParent && !ReferenceEquals(child.parent, parent)) { ComputeStateLayout(child.parent); } }
private InputControl InstantiateLayout(InputControlLayout layout, InternedString variant, InternedString name, InputControl parent, InputControl existingControl) { InputControl control; // If we have an existing control, see whether it's usable. if (existingControl != null && existingControl.layout == layout.name && existingControl.GetType() == layout.type) { control = existingControl; ////FIXME: the re-use path probably has some data that could stick around when it shouldn't control.m_UsagesReadOnly = new ReadOnlyArray <InternedString>(); control.ClearProcessors(); } else { Debug.Assert(layout.type != null); // No, so create a new control. var controlObject = Activator.CreateInstance(layout.type); control = controlObject as InputControl; if (control == null) { throw new Exception(string.Format("Type '{0}' referenced by layout '{1}' is not an InputControl", layout.type.Name, layout.name)); } } // If it's a device, perform some extra work specific to the control // hierarchy root. var controlAsDevice = control as InputDevice; if (controlAsDevice != null) { if (parent != null) { throw new Exception(string.Format( "Cannot instantiate device layout '{0}' as child of '{1}'; devices must be added at root", layout.name, parent.path)); } m_Device = controlAsDevice; m_Device.m_StateBlock.byteOffset = 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; // But we preserve IDs and descriptions of existing devices. if (existingControl != null) { var existingDevice = (InputDevice)existingControl; m_Device.m_Id = existingDevice.m_Id; m_Device.m_Description = existingDevice.m_Description; } if (layout.m_UpdateBeforeRender == true) { m_Device.m_Flags |= InputDevice.Flags.UpdateBeforeRender; } } 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( string.Format( "Toplevel layout used with InputDeviceBuilder must be a device layout; '{0}' is a control layout", layout.name)); } // 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)); } } // Variant defaults to variant of layout. if (variant.IsEmpty()) { variant = layout.variant; if (variant.IsEmpty()) { variant = InputControlLayout.DefaultVariant; } } control.m_Name = name; control.m_DisplayNameFromLayout = layout.m_DisplayName; control.m_Layout = layout.name; control.m_Variant = variant; control.m_Parent = parent; control.m_Device = m_Device; // 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, variant, control, existingControl != null ? existingControl.m_ChildrenReadOnly : (ReadOnlyArray <InputControl>?)null, 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) { foreach (var controlLayout in layout.controls) { if (string.IsNullOrEmpty(controlLayout.useStateFrom)) { continue; } var child = TryGetControl(control, controlLayout.name); Debug.Assert(child != null); // Find the referenced control. var referencedControl = TryGetControl(control, controlLayout.useStateFrom); if (referencedControl == null) { throw new Exception( string.Format( "Cannot find control '{0}' referenced in 'useStateFrom' of control '{1}' in layout '{2}'", controlLayout.useStateFrom, controlLayout.name, layout.name)); } // Copy its state settings. child.m_StateBlock = referencedControl.m_StateBlock; // 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. for (var parentInChain = referencedControl.parent; parentInChain != control; parentInChain = parentInChain.parent) { child.m_StateBlock.byteOffset += parentInChain.m_StateBlock.byteOffset; } } } return(control); }
private void AddChildControls(InputControlLayout layout, InternedString variant, InputControl parent, ReadOnlyArray <InputControl>?existingChildren, 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) { ////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].isModifyingChildControlByPath) { if (controlLayouts[i].isArray) { throw new NotSupportedException(string.Format( "Control '{0}' in layout '{1}' is modifying the child of another control but is marked as an array", controlLayouts[i].name, layout.name)); } haveControlLayoutWithPath = true; InsertChildControlOverrides(parent, ref controlLayouts[i]); continue; } // Skip if variant doesn't match. if (!controlLayouts[i].variant.IsEmpty() && controlLayouts[i].variant != variant) { continue; } if (controlLayouts[i].isArray) { childCount += controlLayouts[i].arraySize; } else { ++childCount; } } // 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.isModifyingChildControlByPath) { continue; } // If the control is part of a variant, skip it if it isn't the variant we're // looking for. if (!controlLayout.variant.IsEmpty() && controlLayout.variant != variant) { 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, variant, parent, existingChildren, ref haveChildrenUsingStateFromOtherControls, ref controlLayout, ref childIndex, nameOverride: name); // Adjust offset, if the control uses explicit offsets. if (control.m_StateBlock.byteOffset != InputStateBlock.kInvalidOffset) { control.m_StateBlock.byteOffset = (uint)n * control.m_StateBlock.alignedSizeInBytes; } } } else { AddChildControl(layout, variant, parent, existingChildren, ref haveChildrenUsingStateFromOtherControls, ref controlLayout, ref childIndex); } } // Install child array on parent. We will later patch up the array // reference again as we finalize the hierarchy. However, the reference // will point to a valid child array all the same even while we are // constructing the hierarchy. // // NOTE: It's important to do this *after* the loop above where we call InstantiateLayout for each child // as each child may end up moving the m_ChildrenForEachControl array around. parent.m_ChildrenReadOnly = new ReadOnlyArray <InputControl>(m_Device.m_ChildrenForEachControl, firstChildIndex, childCount); ////TODO: replace the entire post-creation modification logic here with using m_ChildControlOverrides //// (note that we have to *merge* into the table; if there's already overrides, only replace properties that haven't been set) //// (however, this will also require moving the child insertion logic somewhere else) // Apply control modifications from control layouts with paths. if (haveControlLayoutWithPath) { for (var i = 0; i < controlLayouts.Length; ++i) { var controlLayout = controlLayouts[i]; if (!controlLayout.isModifyingChildControlByPath) { continue; } // If the control is part of a variant, skip it if it isn't the variant we're // looking for. if (!controlLayout.variant.IsEmpty() && controlLayout.variant != variant) { continue; } ModifyChildControl(layout, variant, parent, ref haveChildrenUsingStateFromOtherControls, ref controlLayout); } } }
public void Trigger <TValue>(InputAction action, InputControl <TValue> control, TValue value) where TValue : struct { throw new NotImplementedException(); }
private InputControl InstantiateLayout(InternedString layout, InternedString variant, InternedString name, InputControl parent, InputControl existingControl) { // Look up layout by name. var layoutInstance = FindOrLoadLayout(layout); // Create control hierarchy. return(InstantiateLayout(layoutInstance, variant, name, parent, existingControl)); }
////REVIEW: have mode where instead of matching only the first device that matches a requirement, we match as many //// as we can get? (could be useful for single-player) /// <summary> /// Based on a list of devices, make a selection that matches the <see cref="deviceRequirements">requirements</see> /// imposed by the control scheme. /// </summary> /// <param name="devices">A list of devices to choose from.</param> /// <returns>A <see cref="MatchResult"/> structure containing the result of the pick. Note that this structure /// must be manually <see cref="MatchResult.Dispose">disposed</see> or unmanaged memory will be leaked.</returns> /// <remarks> /// Does not allocate managed memory. /// </remarks> public MatchResult PickDevicesFrom(InputControlList <InputDevice> devices) { // Empty device requirements match anything while not really picking anything. if (m_DeviceRequirements == null || m_DeviceRequirements.Length == 0) { return(new MatchResult { m_Result = MatchResult.Result.AllSatisfied, }); } // Go through each requirement and match it. // NOTE: Even if `devices` is empty, we don't know yet whether we have a NoMatch. // All our devices may be optional. var haveAllRequired = true; var haveAllOptional = true; var requirementCount = m_DeviceRequirements.Length; var controls = new InputControlList <InputControl>(Allocator.Persistent, requirementCount); try { var orChainIsSatisfied = false; var orChainHasRequiredDevices = false; for (var i = 0; i < requirementCount; ++i) { var isOR = m_DeviceRequirements[i].isOR; var isOptional = m_DeviceRequirements[i].isOptional; // If this is an OR requirement and we already have a match in this OR chain, // skip this requirement. if (isOR && orChainIsSatisfied) { // Skill need to add an entry for this requirement. controls.Add(null); continue; } // Null and empty paths shouldn't make it into the list but make double // sure here. Simply ignore entries that don't have a path. var path = m_DeviceRequirements[i].controlPath; if (string.IsNullOrEmpty(path)) { controls.Add(null); continue; } // Find the first matching control among the devices we have. InputControl match = null; for (var n = 0; n < devices.Count; ++n) { var device = devices[n]; // See if we have a match. var matchedControl = InputControlPath.TryFindControl(device, path); if (matchedControl == null) { continue; // No. } // We have a match but if we've already match the same control through another requirement, // we can't use the match. if (controls.Contains(matchedControl)) { continue; } match = matchedControl; break; } // Check requirements in AND and OR chains. We look ahead here to find out whether // the next requirement is starting an OR chain. As the OR combines with the previous // requirement in the list, this affects our current requirement. var nextIsOR = i + 1 < requirementCount && m_DeviceRequirements[i + 1].isOR; if (nextIsOR) { // Shouldn't get here if the chain is already satisfied. Should be handled // at beginning of loop and we shouldn't even be looking at finding controls // in that case. Debug.Assert(!orChainIsSatisfied); // It's an OR with the next requirement. Depends on the outcome of other matches whether // we're good or not. if (match != null) { // First match in this chain. orChainIsSatisfied = true; } else { // Chain not satisfied yet. if (!isOptional) { orChainHasRequiredDevices = true; } } } else if (isOR && i == requirementCount - 1) { // It's an OR at the very end of the requirements list. Terminate // the OR chain. if (match == null) { if (orChainHasRequiredDevices) { haveAllRequired = false; } else { haveAllOptional = false; } } } else { // It's an AND. if (match == null) { if (isOptional) { haveAllOptional = false; } else { haveAllRequired = false; } } // Terminate ongoing OR chain. if (i > 0 && m_DeviceRequirements[i - 1].isOR) { if (!orChainIsSatisfied) { if (orChainHasRequiredDevices) { haveAllRequired = false; } else { haveAllOptional = false; } } orChainIsSatisfied = false; } } // Add match to list. Maybe null. controls.Add(match); } // We should have matched each of our requirements. Debug.Assert(controls.Count == requirementCount); } catch (Exception) { controls.Dispose(); throw; } return(new MatchResult { m_Result = !haveAllRequired ? MatchResult.Result.MissingRequired : !haveAllOptional ? MatchResult.Result.MissingOptional : MatchResult.Result.AllSatisfied, m_Controls = controls, m_Requirements = m_DeviceRequirements, }); }
public static InputControl TryFindChild(InputControl control, string path, int indexInPath = 0) { return(TryFindChild <InputControl>(control, path, indexInPath)); }
public static RebindOperation PerformUserRebind(InputAction action, InputControl cancel) { throw new NotImplementedException(); }
// Invoked by reflection in ActionMap. internal void SetReferenceControl(Type type) { m_ReferenceControl = (InputControl <T>)Activator.CreateInstance(type); }
public static int ApplyBindingOverridesOnMatchingControls(this InputActionMap actionMap, InputControl control) { if (actionMap == null) { throw new ArgumentNullException("actionMap"); } if (control == null) { throw new ArgumentNullException("control"); } var actions = actionMap.actions; var actionCount = actions.Count; var numMatchingControls = 0; for (var i = 0; i < actionCount; ++i) { var action = actions[i]; numMatchingControls = action.ApplyBindingOverridesOnMatchingControls(control); } return(numMatchingControls); }
////REVIEW: how does this system work in combination with actual user overrides //// (answer: we rebind based on the base path not the override path; thus user overrides are unaffected; //// and hopefully operate on more than just the path; probably action+path or something) ////TODO: add option to suppress any non-matching binding by setting its override to an empty path ////TODO: need ability to do this with a list of controls // For all bindings in the given action, if a binding matches a control in the given control // hierarchy, set an override on the binding to refer specifically to that control. // // Returns the number of overrides that have been applied. // // Use case: Say you have a local co-op game and a single action map that represents the // actions of any single player. To end up with action maps that are specific to // a certain player, you could, for example, clone the action map four times, and then // take four gamepad devices and use the methods here to have bindings be overridden // on each map to refer to a specific gamepad instance. // // Another example is having two XRControllers and two action maps can be on either hand. // At runtime you can dynamically override and re-override the bindings on the action maps // to use them with the controllers as desired. public static int ApplyBindingOverridesOnMatchingControls(this InputAction action, InputControl control) { if (action == null) { throw new ArgumentNullException("action"); } if (control == null) { throw new ArgumentNullException("control"); } var bindings = action.bindings; var bindingsCount = bindings.Count; var numMatchingControls = 0; for (var i = 0; i < bindingsCount; ++i) { var matchingControl = InputControlPath.TryFindControl(control, bindings[i].path); if (matchingControl == null) { continue; } action.ApplyBindingOverride(i, matchingControl.path); ++numMatchingControls; } return(numMatchingControls); }
////TODO: refactor this to use the new PathParser /// <summary> /// Recursively match path elements in <paramref name="path"/>. /// </summary> /// <param name="control">Current control we're at.</param> /// <param name="path">Control path we are matching against.</param> /// <param name="indexInPath">Index of current component in <paramref name="path"/>.</param> /// <param name="matches"></param> /// <param name="matchMultiple"></param> /// <typeparam name="TControl"></typeparam> /// <returns></returns> private static TControl MatchControlsRecursive <TControl>(InputControl control, string path, int indexInPath, ref InputControlList <TControl> matches, bool matchMultiple) where TControl : InputControl { var pathLength = path.Length; // Try to get a match. A path spec has three components: // "<layout>{usage}name" // All are optional but at least one component must be present. // Names can be aliases, too. // We don't tap InputControl.path strings of controls so as to not create a // bunch of string objects while feeling our way down the hierarchy. var controlIsMatch = true; // Match by layout. if (path[indexInPath] == '<') { ++indexInPath; controlIsMatch = MatchPathComponent(control.layout, path, ref indexInPath, PathComponentType.Layout); // If the layout isn't a match, walk up the base layout // chain and match each base layout. if (!controlIsMatch) { var baseLayout = control.m_Layout; while (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(baseLayout, out baseLayout)) { controlIsMatch = MatchPathComponent(baseLayout, path, ref indexInPath, PathComponentType.Layout); if (controlIsMatch) { break; } } } } // Match by usage. if (indexInPath < pathLength && path[indexInPath] == '{' && controlIsMatch) { ++indexInPath; for (var i = 0; i < control.usages.Count; ++i) { controlIsMatch = MatchPathComponent(control.usages[i], path, ref indexInPath, PathComponentType.Usage); if (controlIsMatch) { break; } } } // Match by name. if (indexInPath < pathLength && controlIsMatch && path[indexInPath] != '/') { // Normal name match. controlIsMatch = MatchPathComponent(control.name, path, ref indexInPath, PathComponentType.Name); // Alternative match by alias. if (!controlIsMatch) { for (var i = 0; i < control.aliases.Count && !controlIsMatch; ++i) { controlIsMatch = MatchPathComponent(control.aliases[i], path, ref indexInPath, PathComponentType.Name); } } } // If we have a match, return it or, if there's children, recurse into them. if (controlIsMatch) { // If we ended up on a wildcard, we've successfully matched it. if (indexInPath < pathLength && path[indexInPath] == '*') { ++indexInPath; } // If we've reached the end of the path, we have a match. if (indexInPath == pathLength) { // Check type. var match = control as TControl; if (match == null) { return(null); } if (matchMultiple) { matches.Add(match); } return(match); } // If we've reached a separator, dive into our children. if (path[indexInPath] == '/') { ++indexInPath; // Silently accept trailing slashes. if (indexInPath == pathLength) { // Check type. var match = control as TControl; if (match == null) { return(null); } if (matchMultiple) { matches.Add(match); } return(match); } // See if we want to match children by usage or by name. TControl lastMatch; if (path[indexInPath] == '{') { ////TODO: support scavenging a subhierarchy for usages if (!ReferenceEquals(control.device, control)) { throw new NotImplementedException( "Matching usages inside subcontrols instead of at device root"); } // Usages are kind of like entry points that can route to anywhere else // on a device's control hierarchy and then we keep going from that re-routed // point. lastMatch = MatchByUsageAtDeviceRootRecursive(control.device, path, indexInPath, ref matches, matchMultiple); } else { // Go through children and see what we can match. lastMatch = MatchChildrenRecursive(control, path, indexInPath, ref matches, matchMultiple); } return(lastMatch); } } return(null); }
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 ArgumentException("InsertChildControl has to be called with a slash-separated path", "path"); } Debug.Assert(indexOfSlash != 0); var immediateParentPath = path.Substring(0, indexOfSlash); var immediateParent = InputControlPath.TryFindChild(parent, immediateParentPath); if (immediateParent == null) { throw new Exception( string.Format("Cannot find parent '{0}' of control '{1}' in layout '{2}'", immediateParentPath, controlItem.name, layout.name)); } var controlName = path.Substring(indexOfSlash + 1); if (controlName.Length == 0) { throw new Exception( string.Format("Path cannot end in '/' (control '{0}' in layout '{1}')", controlItem.name, layout.name)); } // Make room in the device's child array. var childStartIndex = immediateParent.m_ChildrenReadOnly.m_StartIndex; var childIndex = childStartIndex + immediateParent.m_ChildrenReadOnly.m_Length; ArrayHelpers.InsertAt(ref m_Device.m_ChildrenForEachControl, childIndex, null); ++immediateParent.m_ChildrenReadOnly.m_Length; // Insert the child. var control = AddChildControl(layout, variant, immediateParent, null, ref haveChildrenUsingStateFromOtherControls, ref controlItem, ref childIndex, controlName); // Adjust indices of control's that have been shifted around by our insertion. ShiftChildIndicesInHierarchyOneUp(parent, childIndex); return(control); }
public static void OnFirstUserInteraction(InputManager manager, double time, InputControl control) { }
private static void ComputeStateLayout(InputControl control) { var children = control.m_ChildrenReadOnly; // 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 Exception( string.Format( "Control '{0}' with layout '{1}' has no size set and has no children to compute size from", control.path, control.layout)); } // 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); // 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) { throw new Exception( string.Format("Child '{0}' of '{1}' has no size set!", child.name, control.name)); } // Skip children that don't have fixed offsets. if (child.m_StateBlock.byteOffset == InputStateBlock.kInvalidOffset) { continue; } 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.kInvalidOffset) { 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.kInvalidOffset) { bitfieldSizeInBits += child.m_StateBlock.sizeInBits; } else { 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; } } 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; }
public static unsafe void *GetStatePtrFromStateEvent(this InputControl control, InputEventPtr eventPtr) { if (control == null) { throw new ArgumentNullException("control"); } if (!eventPtr.valid) { throw new ArgumentNullException("eventPtr"); } uint stateOffset; FourCC stateFormat; uint stateSizeInBytes; void * statePtr; if (eventPtr.IsA <DeltaStateEvent>()) { var deltaEvent = DeltaStateEvent.From(eventPtr); // If it's a delta event, we need to subtract the delta state offset if it's not set to the root of the device stateOffset = deltaEvent->stateOffset; stateFormat = deltaEvent->stateFormat; stateSizeInBytes = deltaEvent->deltaStateSizeInBytes; statePtr = deltaEvent->deltaState; } else if (eventPtr.IsA <StateEvent>()) { var stateEvent = StateEvent.From(eventPtr); stateOffset = 0; stateFormat = stateEvent->stateFormat; stateSizeInBytes = stateEvent->stateSizeInBytes; statePtr = stateEvent->state; } else { throw new ArgumentException("Event must be a state or delta state event", "eventPtr"); } // Make sure we have a state event compatible with our device. The event doesn't // have to be specifically for our device (we don't require device IDs to match) but // the formats have to match and the size must be within range of what we're trying // to read. var device = control.device; if (stateFormat != device.m_StateBlock.format) { throw new InvalidOperationException( string.Format( "Cannot read control '{0}' from {1} with format {2}; device '{3}' expects format {4}", control.path, eventPtr.type, stateFormat, device, device.m_StateBlock.format)); } // Once a device has been added, global state buffer offsets are baked into control hierarchies. // We need to unsubtract those offsets here. stateOffset += device.m_StateBlock.byteOffset; if (control.m_StateBlock.byteOffset - stateOffset + control.m_StateBlock.alignedSizeInBytes > stateSizeInBytes) { return(null); } return((byte *)statePtr - (int)stateOffset); }