A game about forced loneliness, made by TACStudios
1using System; 2using Unity.Collections.LowLevel.Unsafe; 3using UnityEngine.InputSystem.Layouts; 4using UnityEngine.InputSystem.Utilities; 5using UnityEngine.Scripting; 6 7////TODO: come up with a mechanism to allow (certain) processors to be stateful 8 9////TODO: cache processors globally; there's no need to instantiate the same processor with the same parameters multiple times 10//// (except if they do end up being stateful) 11 12namespace UnityEngine.InputSystem 13{ 14 /// <summary> 15 /// A processor that conditions/transforms input values. 16 /// </summary> 17 /// <remarks> 18 /// To define a custom processor, it is usable best to derive from <see cref="InputProcessor{TValue}"/> 19 /// instead of from this class. Doing so will avoid having to deal with things such as the raw memory 20 /// buffers of <see cref="Process"/>. 21 /// 22 /// Note, however, that if you do want to define a processor that can process more than one type of 23 /// value, you can derive directly from this class. 24 /// </remarks> 25 /// <seealso cref="InputBinding.processors"/> 26 /// <seealso cref="InputControlLayout.ControlItem.processors"/> 27 /// <seealso cref="InputSystem.RegisterProcessor{T}"/> 28 /// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/> 29 /// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> 30 public abstract class InputProcessor 31 { 32 /// <summary> 33 /// Process an input value, given as an object, and return the processed value as an object. 34 /// </summary> 35 /// <param name="value">A value matching the processor's value type.</param> 36 /// <param name="control">Optional control that the value originated from. Must have the same value type 37 /// that the processor has.</param> 38 /// <returns>A processed value based on <paramref name="value"/>.</returns> 39 /// <remarks> 40 /// This method allocates GC heap memory. To process values without allocating GC memory, it is necessary to either know 41 /// the value type of a processor at compile time and call <see cref="InputProcessor{TValue}.Process(TValue,UnityEngine.InputSystem.InputControl)"/> 42 /// directly or to use <see cref="Process"/> instead and process values in raw memory buffers. 43 /// </remarks> 44 public abstract object ProcessAsObject(object value, InputControl control); 45 46 /// <summary> 47 /// Process an input value stored in the given memory buffer. 48 /// </summary> 49 /// <param name="buffer">Memory buffer containing the input value. Must be at least large enough 50 /// to hold one full value as indicated by <paramref name="bufferSize"/>.</param> 51 /// <param name="bufferSize">Size (in bytes) of the value inside <paramref name="buffer"/>.</param> 52 /// <param name="control">Optional control that the value originated from. Must have the same value type 53 /// that the processor has.</param> 54 /// <remarks> 55 /// This method allows processing values of arbitrary size without allocating memory on the GC heap. 56 /// </remarks> 57 public abstract unsafe void Process(void* buffer, int bufferSize, InputControl control); 58 59 internal static TypeTable s_Processors; 60 61 /// <summary> 62 /// Get the value type of a processor without having to instantiate it and use <see cref="valueType"/>. 63 /// </summary> 64 /// <param name="processorType"></param> 65 /// <returns>Value type of the given processor or null if it could not be determined statically.</returns> 66 /// <exception cref="ArgumentNullException"><paramref name="processorType"/> is null.</exception> 67 /// <remarks> 68 /// This method is reliant on the processor being based on <see cref="InputProcessor{TValue}"/>. It will return 69 /// the <c>TValue</c> argument used with the class. If the processor is not based on <see cref="InputProcessor{TValue}"/>, 70 /// this method returns null. 71 /// </remarks> 72 internal static Type GetValueTypeFromType(Type processorType) 73 { 74 if (processorType == null) 75 throw new ArgumentNullException(nameof(processorType)); 76 77 return TypeHelpers.GetGenericTypeArgumentFromHierarchy(processorType, typeof(InputProcessor<>), 0); 78 } 79 80 /// <summary> 81 /// Caching policy regarding usage of return value from processors. 82 /// </summary> 83 public enum CachingPolicy 84 { 85 /// <summary> 86 /// Cache result value if unprocessed value has not been changed. 87 /// </summary> 88 CacheResult = 0, 89 90 /// <summary> 91 /// Process value every call to <see cref="InputControl{TValue}.ReadValue()"/> even if unprocessed value has not been changed. 92 /// </summary> 93 EvaluateOnEveryRead = 1 94 } 95 96 /// <summary> 97 /// Caching policy of the processor. Override this property to provide a different value. 98 /// </summary> 99 public virtual CachingPolicy cachingPolicy => CachingPolicy.CacheResult; 100 } 101 102 /// <summary> 103 /// A processor that conditions/transforms input values. 104 /// </summary> 105 /// <typeparam name="TValue">Type of value to be processed. Only InputControls that use the 106 /// same value type will be compatible with the processor.</typeparam> 107 /// <remarks> 108 /// Each <see cref="InputControl"/> can have a stack of processors assigned to it. 109 /// 110 /// Note that processors CANNOT be stateful. If you need processing that requires keeping 111 /// mutating state over time, use InputActions. All mutable state needs to be 112 /// kept in the central state buffers. 113 /// 114 /// However, processors can have configurable parameters. Every public field on a processor 115 /// object can be set using "parameters" in JSON or by supplying parameters through the 116 /// <see cref="InputControlAttribute.processors"/> field. 117 /// 118 /// <example> 119 /// <code> 120 /// public class ScalingProcessor : InputProcessor&lt;float&gt; 121 /// { 122 /// // This field can be set as a parameter. See examples below. 123 /// // If not explicitly configured, will have its default value. 124 /// public float factor = 2.0f; 125 /// 126 /// public float Process(float value, InputControl control) 127 /// { 128 /// return value * factor; 129 /// } 130 /// } 131 /// 132 /// // Use processor in JSON: 133 /// const string json = @" 134 /// { 135 /// ""name"" : ""MyDevice"", 136 /// ""controls"" : [ 137 /// { ""name"" : ""axis"", ""layout"" : ""Axis"", ""processors"" : ""scale(factor=4)"" } 138 /// ] 139 /// } 140 /// "; 141 /// 142 /// // Use processor on C# state struct: 143 /// public struct MyDeviceState : IInputStateTypeInfo 144 /// { 145 /// [InputControl(layout = "Axis", processors = "scale(factor=4)"] 146 /// public float axis; 147 /// } 148 /// </code> 149 /// </example> 150 /// 151 /// See <see cref="Editor.InputParameterEditor{T}"/> for how to define custom parameter 152 /// editing UIs for processors. 153 /// </remarks> 154 /// <seealso cref="InputSystem.RegisterProcessor"/> 155 public abstract class InputProcessor<TValue> : InputProcessor 156 where TValue : struct 157 { 158 /// <summary> 159 /// Process the given value and return the result. 160 /// </summary> 161 /// <remarks> 162 /// The implementation of this method must not be stateful. 163 /// </remarks> 164 /// <param name="value">Input value to process.</param> 165 /// <param name="control">Control that the value originally came from. This can be null if the value did 166 /// not originate from a control. This can be the case, for example, if the processor sits on a composite 167 /// binding (<see cref="InputBindingComposite"/>) as composites are not directly associated with controls 168 /// but rather source their values through their child bindings.</param> 169 /// <returns>Processed input value.</returns> 170 public abstract TValue Process(TValue value, InputControl control); 171 172 public override object ProcessAsObject(object value, InputControl control) 173 { 174 if (value == null) 175 throw new ArgumentNullException(nameof(value)); 176 177 if (!(value is TValue)) 178 throw new ArgumentException( 179 $"Expecting value of type '{typeof(TValue).Name}' but got value '{value}' of type '{value.GetType().Name}'", 180 nameof(value)); 181 182 var valueOfType = (TValue)value; 183 184 return Process(valueOfType, control); 185 } 186 187 public override unsafe void Process(void* buffer, int bufferSize, InputControl control) 188 { 189 if (buffer == null) 190 throw new ArgumentNullException(nameof(buffer)); 191 192 var valueSize = UnsafeUtility.SizeOf<TValue>(); 193 if (bufferSize < valueSize) 194 throw new ArgumentException( 195 $"Expected buffer of at least {valueSize} bytes but got buffer with just {bufferSize} bytes", 196 nameof(bufferSize)); 197 198 var value = default(TValue); 199 var valuePtr = UnsafeUtility.AddressOf(ref value); 200 UnsafeUtility.MemCpy(valuePtr, buffer, valueSize); 201 202 value = Process(value, control); 203 204 valuePtr = UnsafeUtility.AddressOf(ref value); 205 UnsafeUtility.MemCpy(buffer, valuePtr, valueSize); 206 } 207 } 208}