File size: 17,114 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
using System;
using System.Collections.Generic;
using System.Reflection;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Scripting;

////TODO: support nested composites

////REVIEW: composites probably need a reset method, too (like interactions), so that they can be stateful

////REVIEW: isn't this about arbitrary value processing? can we open this up more and make it
////        not just be about composing multiple bindings?

////REVIEW: when we get blittable type constraints, we can probably do away with the pointer-based ReadValue version

namespace UnityEngine.InputSystem
{
    ////TODO: clarify whether this can have state or not
    /// <summary>
    /// A binding that synthesizes a value from from several component bindings.
    /// </summary>
    /// <remarks>
    /// This is the base class for composite bindings. See <see cref="InputBindingComposite{TValue}"/>
    /// for more details about composites and for how to define custom composites.
    /// </remarks>
    /// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
    /// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/>
    /// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
    /// <seealso cref="InputBinding.isComposite"/>
    public abstract class InputBindingComposite
    {
        /// <summary>
        /// The type of value returned by the composite.
        /// </summary>
        /// <value>Type of value returned by the composite.</value>
        /// <remarks>
        /// Just like each <see cref="InputControl"/> has a specific type of value it
        /// will return, each composite has a specific type of value it will return.
        /// This is usually implicitly defined by the type parameter of <see
        /// cref="InputBindingComposite{TValue}"/>.
        /// </remarks>
        /// <seealso cref="InputControl.valueType"/>
        /// <seealso cref="InputAction.CallbackContext.valueType"/>
        public abstract Type valueType { get; }

        /// <summary>
        /// Size of a value read by <see cref="ReadValue"/>.
        /// </summary>
        /// <value>Size of values stored in memory buffers by <see cref="ReadValue"/>.</value>
        /// <remarks>
        /// This is usually implicitly defined by the size of values derived
        /// from the type argument to <see cref="InputBindingComposite{TValue}"/>. E.g.
        /// if the type argument is <c>Vector2</c>, this property will be 8.
        /// </remarks>
        /// <seealso cref="InputControl.valueSizeInBytes"/>
        /// <seealso cref="InputAction.CallbackContext.valueSizeInBytes"/>
        public abstract int valueSizeInBytes { get; }

        /// <summary>
        /// Read a value from the composite without having to know the value type (unlike
        /// <see cref="InputBindingComposite{TValue}.ReadValue(ref InputBindingCompositeContext)"/> and
        /// without allocating GC heap memory (unlike <see cref="ReadValueAsObject"/>).
        /// </summary>
        /// <param name="context">Callback context for the binding composite. Use this
        /// to access the values supplied by part bindings.</param>
        /// <param name="buffer">Buffer that receives the value read for the composite.</param>
        /// <param name="bufferSize">Size of the buffer allocated at <paramref name="buffer"/>.</param>
        /// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than
        /// <see cref="valueSizeInBytes"/>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
        /// <remarks>
        /// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValue(void*,int)"/>
        /// with the action leading to the composite.
        ///
        /// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
        /// be implemented for you.
        /// </remarks>
        /// <seealso cref="InputAction.CallbackContext.ReadValue"/>
        public abstract unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize);

        /// <summary>
        /// Read the value of the composite as a boxed object. This allows reading the value
        /// without having to know the value type and without having to deal with raw byte buffers.
        /// </summary>
        /// <param name="context">Callback context for the binding composite. Use this
        /// to access the values supplied by part bindings.</param>
        /// <returns>The current value of the composite according to the state passed in through
        /// <paramref name="context"/>.</returns>
        /// <remarks>
        /// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValueAsObject"/>
        /// with the action leading to the composite.
        ///
        /// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
        /// be implemented for you.
        /// </remarks>
        public abstract object ReadValueAsObject(ref InputBindingCompositeContext context);

        /// <summary>
        /// Determine the current level of actuation of the composite.
        /// </summary>
        /// <param name="context">Callback context for the binding composite. Use this
        /// to access the values supplied by part bindings.</param>
        /// <returns></returns>
        /// <remarks>
        /// This method by default returns -1, meaning that the composite does not support
        /// magnitudes. You can override the method to add support for magnitudes.
        ///
        /// See <see cref="InputControl.EvaluateMagnitude()"/> for details of how magnitudes
        /// work.
        /// </remarks>
        /// <seealso cref="InputControl.EvaluateMagnitude()"/>
        public virtual float EvaluateMagnitude(ref InputBindingCompositeContext context)
        {
            return -1;
        }

        /// <summary>
        /// Called after binding resolution for an <see cref="InputActionMap"/> is complete.
        /// </summary>
        /// <remarks>
        /// Some composites do not have predetermine value types. Two examples of this are
        /// <see cref="Composites.OneModifierComposite"/> and <see cref="Composites.TwoModifiersComposite"/>, which
        /// both have a <c>"binding"</c> part that can be bound to arbitrary controls. This means that the
        /// value type of these bindings can only be determined at runtime.
        ///
        /// Overriding this method allows accessing the actual controls bound to each part
        /// at runtime.
        ///
        /// <example>
        /// <code>
        /// [InputControl] public int binding;
        ///
        /// protected override void FinishSetup(ref InputBindingContext context)
        /// {
        ///     // Get all controls bound to the 'binding' part.
        ///     var controls = context.controls
        ///         .Where(x => x.part == binding)
        ///         .Select(x => x.control);
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        protected virtual void FinishSetup(ref InputBindingCompositeContext context)
        {
        }

        // Avoid having to expose internal modifier.
        internal void CallFinishSetup(ref InputBindingCompositeContext context)
        {
            FinishSetup(ref context);
        }

        internal static TypeTable s_Composites;

        internal static Type GetValueType(string composite)
        {
            if (string.IsNullOrEmpty(composite))
                throw new ArgumentNullException(nameof(composite));

            var compositeType = s_Composites.LookupTypeRegistration(composite);
            if (compositeType == null)
                return null;

            return TypeHelpers.GetGenericTypeArgumentFromHierarchy(compositeType, typeof(InputBindingComposite<>), 0);
        }

        /// <summary>
        /// Return the name of the control layout that is expected for the given part (e.g. "Up") on the given
        /// composite (e.g. "Dpad").
        /// </summary>
        /// <param name="composite">Registration name of the composite.</param>
        /// <param name="part">Name of the part.</param>
        /// <returns>The layout name (such as "Button") expected for the given part on the composite or null if
        /// there is no composite with the given name or no part on the composite with the given name.</returns>
        /// <remarks>
        /// Expected control layouts can be set on composite parts by setting the <see cref="InputControlAttribute.layout"/>
        /// property on them.
        /// </remarks>
        /// <example>
        /// <code>
        /// InputBindingComposite.GetExpectedControlLayoutName("Dpad", "Up") // Returns "Button"
        ///
        /// // This is how Dpad communicates that:
        /// [InputControl(layout = "Button")] public int up;
        /// </code>
        /// </example>
        public static string GetExpectedControlLayoutName(string composite, string part)
        {
            if (string.IsNullOrEmpty(composite))
                throw new ArgumentNullException(nameof(composite));
            if (string.IsNullOrEmpty(part))
                throw new ArgumentNullException(nameof(part));

            var compositeType = s_Composites.LookupTypeRegistration(composite);
            if (compositeType == null)
                return null;

            ////TODO: allow it being properties instead of just fields
            var field = compositeType.GetField(part,
                BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
            if (field == null)
                return null;

            var attribute = field.GetCustomAttribute<InputControlAttribute>(false);
            return attribute?.layout;
        }

        internal static IEnumerable<string> GetPartNames(string composite)
        {
            if (string.IsNullOrEmpty(composite))
                throw new ArgumentNullException(nameof(composite));

            var compositeType = s_Composites.LookupTypeRegistration(composite);
            if (compositeType == null)
                yield break;

            foreach (var field in compositeType.GetFields(BindingFlags.Instance | BindingFlags.Public))
            {
                var controlAttribute = field.GetCustomAttribute<InputControlAttribute>();
                if (controlAttribute != null)
                    yield return field.Name;
            }
        }

        internal static string GetDisplayFormatString(string composite)
        {
            if (string.IsNullOrEmpty(composite))
                throw new ArgumentNullException(nameof(composite));

            var compositeType = s_Composites.LookupTypeRegistration(composite);
            if (compositeType == null)
                return null;

            var displayFormatAttribute = compositeType.GetCustomAttribute<DisplayStringFormatAttribute>();
            if (displayFormatAttribute == null)
                return null;

            return displayFormatAttribute.formatString;
        }
    }

    /// <summary>
    /// A binding composite arranges several bindings such that they form a "virtual control".
    /// </summary>
    /// <typeparam name="TValue">Type of value returned by the composite. This must be a "blittable"
    /// type, that is, a type whose values can simply be copied around.</typeparam>
    /// <remarks>
    /// Composite bindings are a special type of <see cref="InputBinding"/>. Whereas normally
    /// an input binding simply references a set of controls and returns whatever input values are
    /// generated by those controls, a composite binding sources input from several controls and
    /// derives a new value from that.
    ///
    /// A good example for that is a classic WASD keyboard binding:
    ///
    /// <example>
    /// <code>
    /// var moveAction = new InputAction(name: "move");
    /// moveAction.AddCompositeBinding("Vector2")
    ///     .With("Up", "&lt;Keyboard&gt;/w")
    ///     .With("Down", "&lt;Keyboard&gt;/s")
    ///     .With("Left", "&lt;Keyboard&gt;/a")
    ///     .With("Right", "&lt;Keyboard&gt;/d")
    /// </code>
    /// </example>
    ///
    /// Here, each direction is represented by a separate binding. "Up" is bound to "W", "Down"
    /// is bound to "S", and so on. Each direction individually returns a 0 or 1 depending
    /// on whether it is pressed or not.
    ///
    /// However, as a composite, the binding to the "move" action returns a combined <c>Vector2</c>
    /// that is computed from the state of each of the directional controls. This is what composites
    /// do. They take inputs from their "parts" to derive an input for the binding as a whole.
    ///
    /// Note that the properties and methods defined in <see cref="InputBindingComposite"/> and this
    /// class will generally be called internally by the input system and are not generally meant
    /// to be called directly from user land.
    ///
    /// The set of composites available in the system is extensible. While some composites are
    /// such as <see cref="Composites.Vector2Composite"/> and <see cref="Composites.ButtonWithOneModifier"/>
    /// are available out of the box, new composites can be implemented by anyone and simply be
    /// registered with <see cref="InputSystem.RegisterBindingComposite{T}"/>.
    ///
    /// See the "Custom Composite" sample (can be installed from package manager UI) for a detailed example
    /// of how to create a custom composite.
    /// </remarks>
    /// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
    public abstract class InputBindingComposite<TValue> : InputBindingComposite
        where TValue : struct
    {
        /// <summary>
        /// The type of value returned by the composite, i.e. <c>typeof(TValue)</c>.
        /// </summary>
        /// <value>Returns <c>typeof(TValue)</c>.</value>
        public override Type valueType => typeof(TValue);

        /// <summary>
        /// The size of values returned by the composite, i.e. <c>sizeof(TValue)</c>.
        /// </summary>
        /// <value>Returns <c>sizeof(TValue)</c>.</value>
        public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>();

        /// <summary>
        /// Read a value for the composite given the supplied context.
        /// </summary>
        /// <param name="context">Callback context for the binding composite. Use this
        /// to access the values supplied by part bindings.</param>
        /// <returns>The current value of the composite according to the state made
        /// accessible through <paramref name="context"/>.</returns>
        /// <remarks>
        /// This is the main method to implement in custom composites.
        ///
        /// <example>
        /// <code>
        /// public class CustomComposite : InputBindingComposite&lt;float&gt;
        /// {
        ///     [InputControl(layout = "Button")]
        ///     public int button;
        ///
        ///     public float scaleFactor = 1;
        ///
        ///     public override float ReadValue(ref InputBindingComposite context)
        ///     {
        ///         return context.ReadValue&lt;float&gt;(button) * scaleFactor;
        ///     }
        /// }
        /// </code>
        /// </example>
        ///
        /// The other method to consider overriding is <see cref="InputBindingComposite.EvaluateMagnitude"/>.
        /// </remarks>
        /// <seealso cref="InputAction.ReadValue{TValue}"/>
        /// <seealso cref="InputAction.CallbackContext.ReadValue{TValue}"/>
        public abstract TValue ReadValue(ref InputBindingCompositeContext context);

        /// <inheritdoc />
        public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
        {
            if (buffer == null)
                throw new ArgumentNullException(nameof(buffer));

            var valueSize = UnsafeUtility.SizeOf<TValue>();
            if (bufferSize < valueSize)
                throw new ArgumentException(
                    $"Expected buffer of at least {UnsafeUtility.SizeOf<TValue>()} bytes but got buffer of only {bufferSize} bytes instead",
                    nameof(bufferSize));

            var value = ReadValue(ref context);
            var valuePtr = UnsafeUtility.AddressOf(ref value);

            UnsafeUtility.MemCpy(buffer, valuePtr, valueSize);
        }

        /// <inheritdoc />
        public override unsafe object ReadValueAsObject(ref InputBindingCompositeContext context)
        {
            var value = default(TValue);
            var valuePtr = UnsafeUtility.AddressOf(ref value);

            ReadValue(ref context, valuePtr, UnsafeUtility.SizeOf<TValue>());

            return value;
        }
    }
}