| using System; | |
| using Unity.Collections.LowLevel.Unsafe; | |
| using UnityEngine.InputSystem.Layouts; | |
| using UnityEngine.InputSystem.Utilities; | |
| using UnityEngine.Scripting; | |
| ////TODO: come up with a mechanism to allow (certain) processors to be stateful | |
| ////TODO: cache processors globally; there's no need to instantiate the same processor with the same parameters multiple times | |
| //// (except if they do end up being stateful) | |
| namespace UnityEngine.InputSystem | |
| { | |
| /// <summary> | |
| /// A processor that conditions/transforms input values. | |
| /// </summary> | |
| /// <remarks> | |
| /// To define a custom processor, it is usable best to derive from <see cref="InputProcessor{TValue}"/> | |
| /// instead of from this class. Doing so will avoid having to deal with things such as the raw memory | |
| /// buffers of <see cref="Process"/>. | |
| /// | |
| /// Note, however, that if you do want to define a processor that can process more than one type of | |
| /// value, you can derive directly from this class. | |
| /// </remarks> | |
| /// <seealso cref="InputBinding.processors"/> | |
| /// <seealso cref="InputControlLayout.ControlItem.processors"/> | |
| /// <seealso cref="InputSystem.RegisterProcessor{T}"/> | |
| /// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/> | |
| /// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> | |
| public abstract class InputProcessor | |
| { | |
| /// <summary> | |
| /// Process an input value, given as an object, and return the processed value as an object. | |
| /// </summary> | |
| /// <param name="value">A value matching the processor's value type.</param> | |
| /// <param name="control">Optional control that the value originated from. Must have the same value type | |
| /// that the processor has.</param> | |
| /// <returns>A processed value based on <paramref name="value"/>.</returns> | |
| /// <remarks> | |
| /// This method allocates GC heap memory. To process values without allocating GC memory, it is necessary to either know | |
| /// the value type of a processor at compile time and call <see cref="InputProcessor{TValue}.Process(TValue,UnityEngine.InputSystem.InputControl)"/> | |
| /// directly or to use <see cref="Process"/> instead and process values in raw memory buffers. | |
| /// </remarks> | |
| public abstract object ProcessAsObject(object value, InputControl control); | |
| /// <summary> | |
| /// Process an input value stored in the given memory buffer. | |
| /// </summary> | |
| /// <param name="buffer">Memory buffer containing the input value. Must be at least large enough | |
| /// to hold one full value as indicated by <paramref name="bufferSize"/>.</param> | |
| /// <param name="bufferSize">Size (in bytes) of the value inside <paramref name="buffer"/>.</param> | |
| /// <param name="control">Optional control that the value originated from. Must have the same value type | |
| /// that the processor has.</param> | |
| /// <remarks> | |
| /// This method allows processing values of arbitrary size without allocating memory on the GC heap. | |
| /// </remarks> | |
| public abstract unsafe void Process(void* buffer, int bufferSize, InputControl control); | |
| internal static TypeTable s_Processors; | |
| /// <summary> | |
| /// Get the value type of a processor without having to instantiate it and use <see cref="valueType"/>. | |
| /// </summary> | |
| /// <param name="processorType"></param> | |
| /// <returns>Value type of the given processor or null if it could not be determined statically.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="processorType"/> is null.</exception> | |
| /// <remarks> | |
| /// This method is reliant on the processor being based on <see cref="InputProcessor{TValue}"/>. It will return | |
| /// the <c>TValue</c> argument used with the class. If the processor is not based on <see cref="InputProcessor{TValue}"/>, | |
| /// this method returns null. | |
| /// </remarks> | |
| internal static Type GetValueTypeFromType(Type processorType) | |
| { | |
| if (processorType == null) | |
| throw new ArgumentNullException(nameof(processorType)); | |
| return TypeHelpers.GetGenericTypeArgumentFromHierarchy(processorType, typeof(InputProcessor<>), 0); | |
| } | |
| /// <summary> | |
| /// Caching policy regarding usage of return value from processors. | |
| /// </summary> | |
| public enum CachingPolicy | |
| { | |
| /// <summary> | |
| /// Cache result value if unprocessed value has not been changed. | |
| /// </summary> | |
| CacheResult = 0, | |
| /// <summary> | |
| /// Process value every call to <see cref="InputControl{TValue}.ReadValue()"/> even if unprocessed value has not been changed. | |
| /// </summary> | |
| EvaluateOnEveryRead = 1 | |
| } | |
| /// <summary> | |
| /// Caching policy of the processor. Override this property to provide a different value. | |
| /// </summary> | |
| public virtual CachingPolicy cachingPolicy => CachingPolicy.CacheResult; | |
| } | |
| /// <summary> | |
| /// A processor that conditions/transforms input values. | |
| /// </summary> | |
| /// <typeparam name="TValue">Type of value to be processed. Only InputControls that use the | |
| /// same value type will be compatible with the processor.</typeparam> | |
| /// <remarks> | |
| /// Each <see cref="InputControl"/> can have a stack of processors assigned to it. | |
| /// | |
| /// Note that processors CANNOT be stateful. If you need processing that requires keeping | |
| /// mutating state over time, use InputActions. All mutable state needs to be | |
| /// kept in the central state buffers. | |
| /// | |
| /// However, processors can have configurable parameters. Every public field on a processor | |
| /// object can be set using "parameters" in JSON or by supplying parameters through the | |
| /// <see cref="InputControlAttribute.processors"/> field. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // To register the processor, call | |
| /// // | |
| /// // InputSystem.RegisterProcessor<ScalingProcessor>("scale"); | |
| /// // | |
| /// public class ScalingProcessor : InputProcessor<float> | |
| /// { | |
| /// // This field can be set as a parameter. See examples below. | |
| /// // If not explicitly configured, will have its default value. | |
| /// public float factor = 2.0f; | |
| /// | |
| /// public float Process(float value, InputControl control) | |
| /// { | |
| /// return value * factor; | |
| /// } | |
| /// } | |
| /// | |
| /// // Use processor in JSON: | |
| /// const string json = @" | |
| /// { | |
| /// ""name"" : ""MyDevice"", | |
| /// ""controls"" : [ | |
| /// { ""name"" : ""axis"", ""layout"" : ""Axis"", ""processors"" : ""scale(factor=4)"" } | |
| /// ] | |
| /// } | |
| /// "; | |
| /// | |
| /// // Use processor on C# state struct: | |
| /// public struct MyDeviceState : IInputStateTypeInfo | |
| /// { | |
| /// [InputControl(layout = "Axis", processors = "scale(factor=4)"] | |
| /// public float axis; | |
| /// } | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// See <see cref="Editor.InputParameterEditor{T}"/> for how to define custom parameter | |
| /// editing UIs for processors. | |
| /// </remarks> | |
| /// <seealso cref="InputSystem.RegisterProcessor"/> | |
| public abstract class InputProcessor<TValue> : InputProcessor | |
| where TValue : struct | |
| { | |
| /// <summary> | |
| /// Process the given value and return the result. | |
| /// </summary> | |
| /// <remarks> | |
| /// The implementation of this method must not be stateful. | |
| /// </remarks> | |
| /// <param name="value">Input value to process.</param> | |
| /// <param name="control">Control that the value originally came from. This can be null if the value did | |
| /// not originate from a control. This can be the case, for example, if the processor sits on a composite | |
| /// binding (<see cref="InputBindingComposite"/>) as composites are not directly associated with controls | |
| /// but rather source their values through their child bindings.</param> | |
| /// <returns>Processed input value.</returns> | |
| public abstract TValue Process(TValue value, InputControl control); | |
| public override object ProcessAsObject(object value, InputControl control) | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| if (!(value is TValue)) | |
| throw new ArgumentException( | |
| $"Expecting value of type '{typeof(TValue).Name}' but got value '{value}' of type '{value.GetType().Name}'", | |
| nameof(value)); | |
| var valueOfType = (TValue)value; | |
| return Process(valueOfType, control); | |
| } | |
| public override unsafe void Process(void* buffer, int bufferSize, InputControl control) | |
| { | |
| if (buffer == null) | |
| throw new ArgumentNullException(nameof(buffer)); | |
| var valueSize = UnsafeUtility.SizeOf<TValue>(); | |
| if (bufferSize < valueSize) | |
| throw new ArgumentException( | |
| $"Expected buffer of at least {valueSize} bytes but got buffer with just {bufferSize} bytes", | |
| nameof(bufferSize)); | |
| var value = default(TValue); | |
| var valuePtr = UnsafeUtility.AddressOf(ref value); | |
| UnsafeUtility.MemCpy(valuePtr, buffer, valueSize); | |
| value = Process(value, control); | |
| valuePtr = UnsafeUtility.AddressOf(ref value); | |
| UnsafeUtility.MemCpy(buffer, valuePtr, valueSize); | |
| } | |
| } | |
| } | |