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 { /// /// A processor that conditions/transforms input values. /// /// /// To define a custom processor, it is usable best to derive from /// instead of from this class. Doing so will avoid having to deal with things such as the raw memory /// buffers of . /// /// 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. /// /// /// /// /// /// public abstract class InputProcessor { /// /// Process an input value, given as an object, and return the processed value as an object. /// /// A value matching the processor's value type. /// Optional control that the value originated from. Must have the same value type /// that the processor has. /// A processed value based on . /// /// 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 /// directly or to use instead and process values in raw memory buffers. /// public abstract object ProcessAsObject(object value, InputControl control); /// /// Process an input value stored in the given memory buffer. /// /// Memory buffer containing the input value. Must be at least large enough /// to hold one full value as indicated by . /// Size (in bytes) of the value inside . /// Optional control that the value originated from. Must have the same value type /// that the processor has. /// /// This method allows processing values of arbitrary size without allocating memory on the GC heap. /// public abstract unsafe void Process(void* buffer, int bufferSize, InputControl control); internal static TypeTable s_Processors; /// /// Get the value type of a processor without having to instantiate it and use . /// /// /// Value type of the given processor or null if it could not be determined statically. /// is null. /// /// This method is reliant on the processor being based on . It will return /// the TValue argument used with the class. If the processor is not based on , /// this method returns null. /// internal static Type GetValueTypeFromType(Type processorType) { if (processorType == null) throw new ArgumentNullException(nameof(processorType)); return TypeHelpers.GetGenericTypeArgumentFromHierarchy(processorType, typeof(InputProcessor<>), 0); } /// /// Caching policy regarding usage of return value from processors. /// public enum CachingPolicy { /// /// Cache result value if unprocessed value has not been changed. /// CacheResult = 0, /// /// Process value every call to even if unprocessed value has not been changed. /// EvaluateOnEveryRead = 1 } /// /// Caching policy of the processor. Override this property to provide a different value. /// public virtual CachingPolicy cachingPolicy => CachingPolicy.CacheResult; } /// /// A processor that conditions/transforms input values. /// /// Type of value to be processed. Only InputControls that use the /// same value type will be compatible with the processor. /// /// Each 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 /// field. /// /// /// /// // 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; /// } /// /// /// /// See for how to define custom parameter /// editing UIs for processors. /// /// public abstract class InputProcessor : InputProcessor where TValue : struct { /// /// Process the given value and return the result. /// /// /// The implementation of this method must not be stateful. /// /// Input value to process. /// 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 () as composites are not directly associated with controls /// but rather source their values through their child bindings. /// Processed input value. 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(); 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); } } }