Пример #1
0
        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);
        }
Пример #2
0
        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);
            }
        }
Пример #3
0
        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);
        }
Пример #4
0
        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);
                }
            }
        }
Пример #5
0
 public void Trigger <TValue>(InputAction action, InputControl <TValue> control, TValue value)
     where TValue : struct
 {
     throw new NotImplementedException();
 }
Пример #6
0
        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));
        }
Пример #7
0
        ////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,
            });
        }
Пример #8
0
 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();
 }
Пример #10
0
 // 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);
        }
Пример #13
0
        ////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);
        }
Пример #14
0
        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);
        }
Пример #15
0
 public static void OnFirstUserInteraction(InputManager manager, double time, InputControl control)
 {
 }
Пример #16
0
        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;
        }
Пример #17
0
        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);
        }