InputBindingComposite.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using Unity.Collections.LowLevel.Unsafe;
  5. using UnityEngine.InputSystem.Layouts;
  6. using UnityEngine.InputSystem.Utilities;
  7. using UnityEngine.Scripting;
  8. ////TODO: support nested composites
  9. ////REVIEW: composites probably need a reset method, too (like interactions), so that they can be stateful
  10. ////REVIEW: isn't this about arbitrary value processing? can we open this up more and make it
  11. //// not just be about composing multiple bindings?
  12. ////REVIEW: when we get blittable type constraints, we can probably do away with the pointer-based ReadValue version
  13. namespace UnityEngine.InputSystem
  14. {
  15. ////TODO: clarify whether this can have state or not
  16. /// <summary>
  17. /// A binding that synthesizes a value from from several component bindings.
  18. /// </summary>
  19. /// <remarks>
  20. /// This is the base class for composite bindings. See <see cref="InputBindingComposite{TValue}"/>
  21. /// for more details about composites and for how to define custom composites.
  22. /// </remarks>
  23. /// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
  24. [Preserve]
  25. public abstract class InputBindingComposite
  26. {
  27. /// <summary>
  28. /// The type of value returned by the composite.
  29. /// </summary>
  30. /// <value>Type of value returned by the composite.</value>
  31. /// <remarks>
  32. /// Just like each <see cref="InputControl"/> has a specific type of value it
  33. /// will return, each composite has a specific type of value it will return.
  34. /// This is usually implicitly defined by the type parameter of <see
  35. /// cref="InputBindingComposite{TValue}"/>.
  36. /// </remarks>
  37. /// <seealso cref="InputControl.valueType"/>
  38. /// <seealso cref="InputAction.CallbackContext.valueType"/>
  39. public abstract Type valueType { get; }
  40. /// <summary>
  41. /// Size of a value read by <see cref="ReadValue"/>.
  42. /// </summary>
  43. /// <value>Size of values stored in memory buffers by <see cref="ReadValue"/>.</value>
  44. /// <remarks>
  45. /// This is usually implicitly defined by the size of values derived
  46. /// from the type argument to <see cref="InputBindingComposite{TValue}"/>. E.g.
  47. /// if the type argument is <c>Vector2</c>, this property will be 8.
  48. /// </remarks>
  49. /// <seealso cref="InputControl.valueSizeInBytes"/>
  50. /// <seealso cref="InputAction.CallbackContext.valueSizeInBytes"/>
  51. public abstract int valueSizeInBytes { get; }
  52. /// <summary>
  53. /// Read a value from the composite without having to know the value type (unlike
  54. /// <see cref="InputBindingComposite{TValue}.ReadValue(InputBindingCompositeContext)"/> and
  55. /// without allocating GC heap memory (unlike <see cref="ReadValueAsObject"/>).
  56. /// </summary>
  57. /// <param name="context">Callback context for the binding composite. Use this
  58. /// to access the values supplied by part bindings.</param>
  59. /// <param name="buffer">Buffer that receives the value read for the composite.</param>
  60. /// <param name="bufferSize">Size of the buffer allocated at <paramref name="buffer"/>.</param>
  61. /// <exception cref="ArgumentException"><paramref name="bufferSize"/> is smaller than
  62. /// <see cref="valueSizeInBytes"/>.</exception>
  63. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
  64. /// <remarks>
  65. /// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValue(void*,int)"/>
  66. /// with the action leading to the composite.
  67. ///
  68. /// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
  69. /// be implemented for you.
  70. /// </remarks>
  71. /// <seealso cref="InputAction.CallbackContext.ReadValue"/>
  72. public abstract unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize);
  73. /// <summary>
  74. /// Read the value of the composite as a boxed object. This allows reading the value
  75. /// without having to know the value type and without having to deal with raw byte buffers.
  76. /// </summary>
  77. /// <param name="context">Callback context for the binding composite. Use this
  78. /// to access the values supplied by part bindings.</param>
  79. /// <returns>The current value of the composite according to the state passed in through
  80. /// <paramref name="context"/>.</returns>
  81. /// <remarks>
  82. /// This API will be used if someone calls <see cref="InputAction.CallbackContext.ReadValueAsObject"/>
  83. /// with the action leading to the composite.
  84. ///
  85. /// By deriving from <see cref="InputBindingComposite{TValue}"/>, this will automatically
  86. /// be implemented for you.
  87. /// </remarks>
  88. public abstract object ReadValueAsObject(ref InputBindingCompositeContext context);
  89. /// <summary>
  90. /// Determine the current level of actuation of the composite.
  91. /// </summary>
  92. /// <param name="context">Callback context for the binding composite. Use this
  93. /// to access the values supplied by part bindings.</param>
  94. /// <returns></returns>
  95. /// <remarks>
  96. /// This method by default returns -1, meaning that the composite does not support
  97. /// magnitudes. You can override the method to add support for magnitudes.
  98. ///
  99. /// See <see cref="InputControl.EvaluateMagnitude()"/> for details of how magnitudes
  100. /// work.
  101. /// </remarks>
  102. /// <seealso cref="InputControl.EvaluateMagnitude()"/>
  103. public virtual float EvaluateMagnitude(ref InputBindingCompositeContext context)
  104. {
  105. return -1;
  106. }
  107. internal static TypeTable s_Composites;
  108. internal static Type GetValueType(string composite)
  109. {
  110. if (string.IsNullOrEmpty(composite))
  111. throw new ArgumentNullException(nameof(composite));
  112. var compositeType = s_Composites.LookupTypeRegistration(composite);
  113. if (compositeType == null)
  114. return null;
  115. return TypeHelpers.GetGenericTypeArgumentFromHierarchy(compositeType, typeof(InputBindingComposite<>), 0);
  116. }
  117. /// <summary>
  118. /// Return the name of the control layout that is expected for the given part (e.g. "Up") on the given
  119. /// composite (e.g. "Dpad").
  120. /// </summary>
  121. /// <param name="composite">Registration name of the composite.</param>
  122. /// <param name="part">Name of the part.</param>
  123. /// <returns>The layout name (such as "Button") expected for the given part on the composite or null if
  124. /// there is no composite with the given name or no part on the composite with the given name.</returns>
  125. /// <remarks>
  126. /// Expected control layouts can be set on composite parts by setting the <see cref="InputControlAttribute.layout"/>
  127. /// property on them.
  128. /// </remarks>
  129. /// <example>
  130. /// <code>
  131. /// InputBindingComposite.GetExpectedControlLayoutName("Dpad", "Up") // Returns "Button"
  132. ///
  133. /// // This is how Dpad communicates that:
  134. /// [InputControl(layout = "Button")] public int up;
  135. /// </code>
  136. /// </example>
  137. public static string GetExpectedControlLayoutName(string composite, string part)
  138. {
  139. if (string.IsNullOrEmpty(composite))
  140. throw new ArgumentNullException(nameof(composite));
  141. if (string.IsNullOrEmpty(part))
  142. throw new ArgumentNullException(nameof(part));
  143. var compositeType = s_Composites.LookupTypeRegistration(composite);
  144. if (compositeType == null)
  145. return null;
  146. ////TODO: allow it being properties instead of just fields
  147. var field = compositeType.GetField(part,
  148. BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
  149. if (field == null)
  150. return null;
  151. var attribute = field.GetCustomAttribute<InputControlAttribute>(false);
  152. return attribute?.layout;
  153. }
  154. internal static IEnumerable<string> GetPartNames(string composite)
  155. {
  156. if (string.IsNullOrEmpty(composite))
  157. throw new ArgumentNullException(nameof(composite));
  158. var compositeType = s_Composites.LookupTypeRegistration(composite);
  159. if (compositeType == null)
  160. yield break;
  161. foreach (var field in compositeType.GetFields(BindingFlags.Instance | BindingFlags.Public))
  162. {
  163. var controlAttribute = field.GetCustomAttribute<InputControlAttribute>();
  164. if (controlAttribute != null)
  165. yield return field.Name;
  166. }
  167. }
  168. internal static string GetDisplayFormatString(string composite)
  169. {
  170. if (string.IsNullOrEmpty(composite))
  171. throw new ArgumentNullException(nameof(composite));
  172. var compositeType = s_Composites.LookupTypeRegistration(composite);
  173. if (compositeType == null)
  174. return null;
  175. var displayFormatAttribute = compositeType.GetCustomAttribute<DisplayStringFormatAttribute>();
  176. if (displayFormatAttribute == null)
  177. return null;
  178. return displayFormatAttribute.formatString;
  179. }
  180. }
  181. /// <summary>
  182. /// A binding composite arranges several bindings such that they form a "virtual control".
  183. /// </summary>
  184. /// <typeparam name="TValue">Type of value returned by the composite. This must be a "blittable"
  185. /// type, i.e. a type whose values can simply be copied around.</typeparam>
  186. /// <remarks>
  187. /// Composite bindings are a special type of <see cref="InputBinding"/>. Whereas normally
  188. /// an input binding simply references a set of controls and returns whatever input values are
  189. /// generated by those controls, a composite binding sources input from several controls and
  190. /// derives a new value from that.
  191. ///
  192. /// A good example for that is a classic WASD keyboard binding:
  193. ///
  194. /// <example>
  195. /// <code>
  196. /// var moveAction = new InputAction(name: "move");
  197. /// moveAction.AddCompositeBinding("Vector2")
  198. /// .With("Up", "&lt;Keyboard&gt;/w")
  199. /// .With("Down", "&lt;Keyboard&gt;/s")
  200. /// .With("Left", "&lt;Keyboard&gt;/a")
  201. /// .With("Right", "&lt;Keyboard&gt;/d")
  202. /// </code>
  203. /// </example>
  204. ///
  205. /// Here, each direction is represented by a separate binding. "Up" is bound to "W", "Down"
  206. /// is bound to "S", and so on. Each direction individually returns a 0 or 1 depending
  207. /// on whether it is pressed or not.
  208. ///
  209. /// However, as a composite, the binding to the "move" action returns a combined <c>Vector2</c>
  210. /// that is computed from the state of each of the directional controls. This is what composites
  211. /// do. They take inputs from their "parts" to derive an input for the binding as a whole.
  212. ///
  213. /// Note that the properties and methods defined in <see cref="InputBindingComposite"/> and this
  214. /// class will generally be called internally by the input system and are not generally meant
  215. /// to be called directly from user land.
  216. ///
  217. /// The set of composites available in the system is extensible. While some composites are
  218. /// such as <see cref="Composites.Vector2Composite"/> and <see cref="Composites.ButtonWithOneModifier"/>
  219. /// are available out of the box, new composites can be implemented by anyone and simply be
  220. /// registered with <see cref="InputSystem.RegisterBindingComposite{T}"/>.
  221. ///
  222. /// See the "Custom Composite" sample (can be installed from package manager UI) for a detailed example
  223. /// of how to create a custom composite.
  224. /// </remarks>
  225. /// <seealso cref="InputSystem.RegisterBindingComposite{T}"/>
  226. [Preserve]
  227. public abstract class InputBindingComposite<TValue> : InputBindingComposite
  228. where TValue : struct
  229. {
  230. /// <summary>
  231. /// The type of value returned by the composite, i.e. <c>typeof(TValue)</c>.
  232. /// </summary>
  233. /// <value>Returns <c>typeof(TValue)</c>.</value>
  234. public override Type valueType => typeof(TValue);
  235. /// <summary>
  236. /// The size of values returned by the composite, i.e. <c>sizeof(TValue)</c>.
  237. /// </summary>
  238. /// <value>Returns <c>sizeof(TValue)</c>.</value>
  239. public override int valueSizeInBytes => UnsafeUtility.SizeOf<TValue>();
  240. /// <summary>
  241. /// Read a value for the composite given the supplied context.
  242. /// </summary>
  243. /// <param name="context">Callback context for the binding composite. Use this
  244. /// to access the values supplied by part bindings.</param>
  245. /// <returns>The current value of the composite according to the state made
  246. /// accessible through <paramref name="context"/>.</returns>
  247. /// <remarks>
  248. /// This is the main method to implement in custom composites.
  249. ///
  250. /// <example>
  251. /// <code>
  252. /// public class CustomComposite : InputBindingComposite&lt;float&gt;
  253. /// {
  254. /// [InputControl(layout = "Button")]
  255. /// public int button;
  256. ///
  257. /// public float scaleFactor = 1;
  258. ///
  259. /// public override float ReadValue(ref InputBindingComposite context)
  260. /// {
  261. /// return context.ReadValue&lt;float&gt;(button) * scaleFactor;
  262. /// }
  263. /// }
  264. /// </code>
  265. /// </example>
  266. ///
  267. /// The other method to consider overriding is <see cref="InputBindingComposite.EvaluateMagnitude"/>.
  268. /// </remarks>
  269. /// <seealso cref="InputAction.ReadValue{TValue}"/>
  270. /// <seealso cref="InputAction.CallbackContext.ReadValue{TValue}"/>
  271. public abstract TValue ReadValue(ref InputBindingCompositeContext context);
  272. /// <inheritdoc />
  273. public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize)
  274. {
  275. if (buffer == null)
  276. throw new ArgumentNullException(nameof(buffer));
  277. var valueSize = valueSizeInBytes;
  278. if (bufferSize < valueSize)
  279. throw new ArgumentException(
  280. $"Expected buffer of at least {valueSizeInBytes} bytes but got buffer of only {bufferSize} bytes instead",
  281. nameof(bufferSize));
  282. var value = ReadValue(ref context);
  283. var valuePtr = UnsafeUtility.AddressOf(ref value);
  284. UnsafeUtility.MemCpy(buffer, valuePtr, valueSize);
  285. }
  286. /// <inheritdoc />
  287. public override unsafe object ReadValueAsObject(ref InputBindingCompositeContext context)
  288. {
  289. var value = default(TValue);
  290. var valuePtr = UnsafeUtility.AddressOf(ref value);
  291. ReadValue(ref context, valuePtr, UnsafeUtility.SizeOf<TValue>());
  292. return value;
  293. }
  294. }
  295. }