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<float>
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}