123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- 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);
- }
- }
- }
|