forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			415 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.Collections.Generic;
 | 
						|
using UnityEngine.InputSystem;
 | 
						|
using UnityEngine.InputSystem.Controls;
 | 
						|
 | 
						|
namespace UnityEngine.XR.Content.Interaction
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// Allows for actions to 'lock' the input controls they use so that others actions using the same controls will not receive input at the same time.
 | 
						|
    /// InputMediator works by injecting input processors on every binding in active action maps.
 | 
						|
    /// Usage is via <see cref="ConsumeControl"/> and <see cref="ReleaseControl"/>.
 | 
						|
    /// </summary>
 | 
						|
    public static class InputMediator
 | 
						|
    {
 | 
						|
        /// <summary>
 | 
						|
        /// Substring of the names that all of the input processors that are injected have.
 | 
						|
        /// </summary>
 | 
						|
        /// <seealso cref="Initialize"/>
 | 
						|
        const string k_ConsumeKey = "Consume";
 | 
						|
        static bool s_Updating;
 | 
						|
 | 
						|
        // Data associated with each control, storing if an action has locked it,
 | 
						|
        // and other actions that are allowed to make use of this control at the same time
 | 
						|
        class ConsumptionState
 | 
						|
        {
 | 
						|
            public int m_LockedAction = -1;
 | 
						|
            public int m_AllowedAction1 = -1;
 | 
						|
            public int m_AllowedAction2 = -1;
 | 
						|
 | 
						|
            public bool m_Automatic;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Generic Consumption processor - handles all the aspects of looking up actions that have locked a control
 | 
						|
        /// Implementations merely need to implement the methods to determine if a control has returned to rest (and thus should reset)
 | 
						|
        /// And the 'identity' value of a control, which is the value it should have when the control is locked
 | 
						|
        /// </summary>
 | 
						|
        /// <typeparam name="TValue"></typeparam>
 | 
						|
        abstract class ConsumeProcessor<TValue> : InputProcessor<TValue> where TValue : struct
 | 
						|
        {
 | 
						|
            public int m_ActionIndex = -1;
 | 
						|
 | 
						|
            public override TValue Process(TValue value, InputControl control)
 | 
						|
            {
 | 
						|
                // Check the dictionary for this control
 | 
						|
                // If it does not exist, proceed unhindered
 | 
						|
                if (control == null || !s_ConsumedControls.TryGetValue(control, out var currentState))
 | 
						|
                    return value;
 | 
						|
 | 
						|
                // If there is no locked action, also proceed unhindered
 | 
						|
                if (currentState.m_LockedAction == -1)
 | 
						|
                    return value;
 | 
						|
 | 
						|
                // Check for an action match
 | 
						|
                var actionMatched = (currentState.m_LockedAction == m_ActionIndex) || (currentState.m_AllowedAction1 == m_ActionIndex) || (currentState.m_AllowedAction2 == m_ActionIndex);
 | 
						|
 | 
						|
                // Check if we should automatically release
 | 
						|
                if (actionMatched)
 | 
						|
                {
 | 
						|
                    if (currentState.m_Automatic && ValueNearZero(value))
 | 
						|
                        currentState.m_LockedAction = -1;
 | 
						|
 | 
						|
                    return value;
 | 
						|
                }
 | 
						|
 | 
						|
                return IdentityValue();
 | 
						|
            }
 | 
						|
 | 
						|
            public abstract bool ValueNearZero(TValue value);
 | 
						|
 | 
						|
            public abstract TValue IdentityValue();
 | 
						|
        }
 | 
						|
 | 
						|
        class ConsumeFloat : ConsumeProcessor<float>
 | 
						|
        {
 | 
						|
            public override bool ValueNearZero(float value)
 | 
						|
            {
 | 
						|
                return value < float.Epsilon;
 | 
						|
            }
 | 
						|
 | 
						|
            public override float IdentityValue()
 | 
						|
            {
 | 
						|
                return 0.0f;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        class ConsumeVector2 : ConsumeProcessor<Vector2>
 | 
						|
        {
 | 
						|
            public override bool ValueNearZero(Vector2 value)
 | 
						|
            {
 | 
						|
                return value.sqrMagnitude < float.Epsilon;
 | 
						|
            }
 | 
						|
 | 
						|
            public override Vector2 IdentityValue()
 | 
						|
            {
 | 
						|
                return Vector2.zero;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        class ConsumeVector3 : ConsumeProcessor<Vector3>
 | 
						|
        {
 | 
						|
            public override bool ValueNearZero(Vector3 value)
 | 
						|
            {
 | 
						|
                return value.sqrMagnitude < float.Epsilon;
 | 
						|
            }
 | 
						|
 | 
						|
            public override Vector3 IdentityValue()
 | 
						|
            {
 | 
						|
                return Vector3.zero;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        class ConsumeQuaternion : ConsumeProcessor<Quaternion>
 | 
						|
        {
 | 
						|
            public override bool ValueNearZero(Quaternion value)
 | 
						|
            {
 | 
						|
                return Quaternion.Angle(value, Quaternion.identity) < float.Epsilon;
 | 
						|
            }
 | 
						|
 | 
						|
            public override Quaternion IdentityValue()
 | 
						|
            {
 | 
						|
                return Quaternion.identity;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        static Dictionary<InputControl, ConsumptionState> s_ConsumedControls = new Dictionary<InputControl, ConsumptionState>();
 | 
						|
        static Dictionary<InputAction, int> s_ActionIndices = new Dictionary<InputAction, int>();
 | 
						|
        static HashSet<InputAction> s_InitializedActions = new HashSet<InputAction>();
 | 
						|
 | 
						|
        [RuntimeInitializeOnLoadMethod]
 | 
						|
        static void Initialize()
 | 
						|
        {
 | 
						|
            InputSystem.InputSystem.RegisterProcessor<ConsumeFloat>(nameof(ConsumeFloat));
 | 
						|
            InputSystem.InputSystem.RegisterProcessor<ConsumeVector2>(nameof(ConsumeVector2));
 | 
						|
            InputSystem.InputSystem.RegisterProcessor<ConsumeVector3>(nameof(ConsumeVector3));
 | 
						|
            InputSystem.InputSystem.RegisterProcessor<ConsumeQuaternion>(nameof(ConsumeQuaternion));
 | 
						|
            Application.quitting += OnApplicationQuitting;
 | 
						|
            InputSystem.InputSystem.onActionChange += OnActionChange;
 | 
						|
            InitializeConsumeProcessors();
 | 
						|
        }
 | 
						|
 | 
						|
        static void OnApplicationQuitting()
 | 
						|
        {
 | 
						|
            InputSystem.InputSystem.onActionChange -= OnActionChange;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Attempts to 'lock' the controls belonging to an action - which means other actions using the same control will only get zero/identity values during this time
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="source">The action that should lock their controls</param>
 | 
						|
        /// <param name="automaticRelease">If the control lock should release automatically when the controls go to a resting state</param>
 | 
						|
        /// <param name="force">If the action should forcefully take a lock from another consuming action</param>
 | 
						|
        /// <param name="friendAction1">An additional action that can access these controls at this time</param>
 | 
						|
        /// <param name="friendAction2">An additional action that can access these controls at this time</param>
 | 
						|
        /// <returns>False if _any_ of the associated controls were unable to be locked </returns>
 | 
						|
        public static bool ConsumeControl(InputAction source, bool automaticRelease, bool force = false, InputAction friendAction1 = null, InputAction friendAction2 = null)
 | 
						|
        {
 | 
						|
            if (source == null)
 | 
						|
                return false;
 | 
						|
 | 
						|
            var actionIndex1 = GetActionIndex(source);
 | 
						|
            var actionIndex2 = GetActionIndex(friendAction1);
 | 
						|
            var actionIndex3 = GetActionIndex(friendAction2);
 | 
						|
            var lockCount = 0;
 | 
						|
 | 
						|
            var sourceControls = source.controls;
 | 
						|
            foreach (var currentControl in sourceControls)
 | 
						|
            {
 | 
						|
                // Check to see if it is in the list already
 | 
						|
                // If not, make an entry for it
 | 
						|
                if (!s_ConsumedControls.TryGetValue(currentControl, out var controlState))
 | 
						|
                {
 | 
						|
                    var parent = currentControl.parent;
 | 
						|
                    if (currentControl is AxisControl && parent is Vector2Control)
 | 
						|
                    {
 | 
						|
                        if (!s_ConsumedControls.TryGetValue(parent, out controlState))
 | 
						|
                        {
 | 
						|
                            controlState = new ConsumptionState { m_Automatic = automaticRelease };
 | 
						|
                            s_ConsumedControls.Add(parent, controlState);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        controlState = new ConsumptionState { m_Automatic = automaticRelease };
 | 
						|
                    }
 | 
						|
 | 
						|
                    s_ConsumedControls.Add(currentControl, controlState);
 | 
						|
                }
 | 
						|
 | 
						|
                if (force || controlState.m_LockedAction == -1)
 | 
						|
                {
 | 
						|
                    controlState.m_LockedAction = actionIndex1;
 | 
						|
                    controlState.m_AllowedAction1 = actionIndex2;
 | 
						|
                    controlState.m_AllowedAction2 = actionIndex3;
 | 
						|
                    lockCount++;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return (lockCount == sourceControls.Count);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Releases an action's lock over its associated controls. Other actions using the same controls will begin receiving input again
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="source">The action that is attempting to release its lock</param>
 | 
						|
        /// <param name="force">If this input lock should be released regardless of requesting action</param>
 | 
						|
        /// <returns>False if _any_ of the associated controls were unable to be released </returns>
 | 
						|
        public static bool ReleaseControl(InputAction source, bool force = false)
 | 
						|
        {
 | 
						|
            if (source == null)
 | 
						|
                return false;
 | 
						|
 | 
						|
            var actionIndex = GetActionIndex(source);
 | 
						|
            var lockCount = 0;
 | 
						|
 | 
						|
            var sourceControls = source.controls;
 | 
						|
            foreach (var currentControl in sourceControls)
 | 
						|
            {
 | 
						|
                // Check to see if it is in the list already
 | 
						|
                // If not, nothing to release
 | 
						|
                if (!s_ConsumedControls.TryGetValue(currentControl, out var controlState))
 | 
						|
                {
 | 
						|
                    lockCount++;
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
 | 
						|
                if (force || controlState.m_LockedAction == actionIndex)
 | 
						|
                {
 | 
						|
                    controlState.m_LockedAction = -1;
 | 
						|
                    lockCount++;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            return (lockCount == sourceControls.Count);
 | 
						|
        }
 | 
						|
 | 
						|
        static void InitializeConsumeProcessors()
 | 
						|
        {
 | 
						|
            s_Updating = true;
 | 
						|
            var actionList = InputSystem.InputSystem.ListEnabledActions();
 | 
						|
            foreach (var action in actionList)
 | 
						|
            {
 | 
						|
                EnsureConsumeProcessorAdded(action);
 | 
						|
 | 
						|
                // Since this list only contains currently enabled actions,
 | 
						|
                // any actions that are enabled later will need to
 | 
						|
                // have the consume processor added. Since those actions may not
 | 
						|
                // trigger a BoundControlsChanged change, the OnActionChange event handler
 | 
						|
                // will check against this list and append to it as actions are enabled.
 | 
						|
                // This set is checked against for performance reasons
 | 
						|
                // to avoid the more costly EnsureConsumeProcessorAdded(InputAction) method.
 | 
						|
                s_InitializedActions.Add(action);
 | 
						|
            }
 | 
						|
            s_Updating = false;
 | 
						|
        }
 | 
						|
 | 
						|
        static void OnActionChange(object actionSource, InputActionChange change)
 | 
						|
        {
 | 
						|
            if (s_Updating)
 | 
						|
                return;
 | 
						|
 | 
						|
            s_Updating = true;
 | 
						|
 | 
						|
            if (change == InputActionChange.ActionEnabled)
 | 
						|
            {
 | 
						|
                var action = (InputAction)actionSource;
 | 
						|
                if (s_InitializedActions.Add(action))
 | 
						|
                    EnsureConsumeProcessorAdded(action);
 | 
						|
            }
 | 
						|
            else if (change == InputActionChange.ActionMapEnabled)
 | 
						|
            {
 | 
						|
                var actionMap = (InputActionMap)actionSource;
 | 
						|
                foreach (var action in actionMap.actions)
 | 
						|
                {
 | 
						|
                    if (s_InitializedActions.Add(action))
 | 
						|
                        EnsureConsumeProcessorAdded(action);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (change == InputActionChange.BoundControlsChanged)
 | 
						|
            {
 | 
						|
                // We skip pure actions here as they can get into an invalid state if bindings were changed
 | 
						|
                if (actionSource is InputActionMap actionMap)
 | 
						|
                {
 | 
						|
                    EnsureConsumeProcessorAdded(actionMap);
 | 
						|
                }
 | 
						|
                else if (actionSource is InputActionAsset actionAsset)
 | 
						|
                {
 | 
						|
                    EnsureConsumeProcessorAdded(actionAsset);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            s_Updating = false;
 | 
						|
        }
 | 
						|
 | 
						|
        static string ControlTypeToConsumeType(string controlType)
 | 
						|
        {
 | 
						|
            switch (controlType)
 | 
						|
            {
 | 
						|
                case "Single":
 | 
						|
                case "Button":
 | 
						|
                case "float":
 | 
						|
                    return nameof(ConsumeFloat);
 | 
						|
                case "Vector2":
 | 
						|
                    return nameof(ConsumeVector2);
 | 
						|
                case "Vector3":
 | 
						|
                    return nameof(ConsumeVector3);
 | 
						|
                case "Quaternion":
 | 
						|
                    return nameof(ConsumeQuaternion);
 | 
						|
            }
 | 
						|
            return "";
 | 
						|
        }
 | 
						|
 | 
						|
        static string ProcessBindingControl(string bindingPath)
 | 
						|
        {
 | 
						|
            var control = InputSystem.InputSystem.FindControl(bindingPath);
 | 
						|
            var consumeType = "";
 | 
						|
 | 
						|
            if (control != null)
 | 
						|
                consumeType = ControlTypeToConsumeType(control.valueType.Name);
 | 
						|
            else
 | 
						|
            {
 | 
						|
                // Try to fall back based on path keywords
 | 
						|
                var bindingLower = bindingPath.ToLower();
 | 
						|
                if (bindingLower.EndsWith("position"))
 | 
						|
                    consumeType = ControlTypeToConsumeType("Vector3");
 | 
						|
 | 
						|
                if (bindingLower.EndsWith("rotation"))
 | 
						|
                    consumeType = ControlTypeToConsumeType("Quaternion");
 | 
						|
 | 
						|
                if (bindingLower.EndsWith("x"))
 | 
						|
                    consumeType = ControlTypeToConsumeType("float");
 | 
						|
 | 
						|
                if (bindingLower.EndsWith("y"))
 | 
						|
                    consumeType = ControlTypeToConsumeType("float");
 | 
						|
 | 
						|
                if (bindingLower.EndsWith("axis"))
 | 
						|
                    consumeType = ControlTypeToConsumeType("Vector2");
 | 
						|
            }
 | 
						|
 | 
						|
            if (string.IsNullOrEmpty(consumeType))
 | 
						|
                return "";
 | 
						|
 | 
						|
            return consumeType;
 | 
						|
        }
 | 
						|
 | 
						|
        static void EnsureConsumeProcessorAdded(InputAction action)
 | 
						|
        {
 | 
						|
            var bindingCount = action.bindings.Count;
 | 
						|
            for (var i = 0; i < bindingCount; i++)
 | 
						|
            {
 | 
						|
                var currentBinding = action.bindings[i];
 | 
						|
 | 
						|
                // Ignore composites, but not parts of composites
 | 
						|
                if (currentBinding.isComposite)
 | 
						|
                    continue;
 | 
						|
 | 
						|
                // Ignore bindings that aren't ready yet
 | 
						|
                if (currentBinding.effectiveProcessors == null)
 | 
						|
                    continue;
 | 
						|
 | 
						|
                var actionIndex = GetActionIndex(action);
 | 
						|
                if (!currentBinding.effectiveProcessors.Contains(k_ConsumeKey))
 | 
						|
                {
 | 
						|
                    // Ignore unused bindings
 | 
						|
                    if (string.IsNullOrEmpty(currentBinding.path))
 | 
						|
                        continue;
 | 
						|
 | 
						|
                    // Get the binding's control type and cache it in the control lookup
 | 
						|
                    var bindingType = ProcessBindingControl(currentBinding.path);
 | 
						|
 | 
						|
                    // If the composite can't figure out its type, then skip it
 | 
						|
                    if (string.IsNullOrEmpty(bindingType))
 | 
						|
                    {
 | 
						|
                        //Debug.LogWarning($"Could not add consume processor for binding { currentBinding.path }, in {action.name}");
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (currentBinding.processors.Length > 0)
 | 
						|
                        action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"{bindingType}(m_ActionIndex={actionIndex}), {currentBinding.processors}" });
 | 
						|
                    else
 | 
						|
                        action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"{bindingType}(m_ActionIndex={actionIndex})" });
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        static void EnsureConsumeProcessorAdded(InputActionMap actionMap)
 | 
						|
        {
 | 
						|
            foreach (var action in actionMap.actions)
 | 
						|
            {
 | 
						|
                EnsureConsumeProcessorAdded(action);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        static void EnsureConsumeProcessorAdded(InputActionAsset actionAsset)
 | 
						|
        {
 | 
						|
            foreach (var map in actionAsset.actionMaps)
 | 
						|
            {
 | 
						|
                EnsureConsumeProcessorAdded(map);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        static int GetActionIndex(InputAction source)
 | 
						|
        {
 | 
						|
            if (source == null)
 | 
						|
                return -1;
 | 
						|
 | 
						|
            if (!s_ActionIndices.TryGetValue(source, out var actionIndex))
 | 
						|
            {
 | 
						|
                actionIndex = s_ActionIndices.Count;
 | 
						|
                s_ActionIndices.Add(source, actionIndex);
 | 
						|
            }
 | 
						|
 | 
						|
            return actionIndex;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |