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", "<Keyboard>/w")
/// .With("Down", "<Keyboard>/s")
/// .With("Left", "<Keyboard>/a")
/// .With("Right", "<Keyboard>/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<float>
/// {
/// [InputControl(layout = "Button")]
/// public int button;
///
/// public float scaleFactor = 1;
///
/// public override float ReadValue(ref InputBindingComposite context)
/// {
/// return context.ReadValue<float>(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;
}
}
}
|