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 /// /// A binding that synthesizes a value from from several component bindings. /// /// /// This is the base class for composite bindings. See /// for more details about composites and for how to define custom composites. /// /// /// /// /// public abstract class InputBindingComposite { /// /// The type of value returned by the composite. /// /// Type of value returned by the composite. /// /// Just like each 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 . /// /// /// public abstract Type valueType { get; } /// /// Size of a value read by . /// /// Size of values stored in memory buffers by . /// /// This is usually implicitly defined by the size of values derived /// from the type argument to . E.g. /// if the type argument is Vector2, this property will be 8. /// /// /// public abstract int valueSizeInBytes { get; } /// /// Read a value from the composite without having to know the value type (unlike /// and /// without allocating GC heap memory (unlike ). /// /// Callback context for the binding composite. Use this /// to access the values supplied by part bindings. /// Buffer that receives the value read for the composite. /// Size of the buffer allocated at . /// is smaller than /// . /// is null. /// /// This API will be used if someone calls /// with the action leading to the composite. /// /// By deriving from , this will automatically /// be implemented for you. /// /// public abstract unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize); /// /// 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. /// /// Callback context for the binding composite. Use this /// to access the values supplied by part bindings. /// The current value of the composite according to the state passed in through /// . /// /// This API will be used if someone calls /// with the action leading to the composite. /// /// By deriving from , this will automatically /// be implemented for you. /// public abstract object ReadValueAsObject(ref InputBindingCompositeContext context); /// /// Determine the current level of actuation of the composite. /// /// Callback context for the binding composite. Use this /// to access the values supplied by part bindings. /// /// /// 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 for details of how magnitudes /// work. /// /// public virtual float EvaluateMagnitude(ref InputBindingCompositeContext context) { return -1; } /// /// Called after binding resolution for an is complete. /// /// /// Some composites do not have predetermine value types. Two examples of this are /// and , which /// both have a "binding" 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. /// /// /// /// [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); /// } /// /// /// 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); } /// /// Return the name of the control layout that is expected for the given part (e.g. "Up") on the given /// composite (e.g. "Dpad"). /// /// Registration name of the composite. /// Name of the part. /// 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. /// /// Expected control layouts can be set on composite parts by setting the /// property on them. /// /// /// /// InputBindingComposite.GetExpectedControlLayoutName("Dpad", "Up") // Returns "Button" /// /// // This is how Dpad communicates that: /// [InputControl(layout = "Button")] public int up; /// /// 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(false); return attribute?.layout; } internal static IEnumerable 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(); 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(); if (displayFormatAttribute == null) return null; return displayFormatAttribute.formatString; } } /// /// A binding composite arranges several bindings such that they form a "virtual control". /// /// Type of value returned by the composite. This must be a "blittable" /// type, that is, a type whose values can simply be copied around. /// /// Composite bindings are a special type of . 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: /// /// /// /// 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") /// /// /// /// 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 Vector2 /// 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 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 and /// are available out of the box, new composites can be implemented by anyone and simply be /// registered with . /// /// See the "Custom Composite" sample (can be installed from package manager UI) for a detailed example /// of how to create a custom composite. /// /// public abstract class InputBindingComposite : InputBindingComposite where TValue : struct { /// /// The type of value returned by the composite, i.e. typeof(TValue). /// /// Returns typeof(TValue). public override Type valueType => typeof(TValue); /// /// The size of values returned by the composite, i.e. sizeof(TValue). /// /// Returns sizeof(TValue). public override int valueSizeInBytes => UnsafeUtility.SizeOf(); /// /// Read a value for the composite given the supplied context. /// /// Callback context for the binding composite. Use this /// to access the values supplied by part bindings. /// The current value of the composite according to the state made /// accessible through . /// /// This is the main method to implement in custom composites. /// /// /// /// 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; /// } /// } /// /// /// /// The other method to consider overriding is . /// /// /// public abstract TValue ReadValue(ref InputBindingCompositeContext context); /// public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); var valueSize = UnsafeUtility.SizeOf(); if (bufferSize < valueSize) throw new ArgumentException( $"Expected buffer of at least {UnsafeUtility.SizeOf()} 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); } /// public override unsafe object ReadValueAsObject(ref InputBindingCompositeContext context) { var value = default(TValue); var valuePtr = UnsafeUtility.AddressOf(ref value); ReadValue(ref context, valuePtr, UnsafeUtility.SizeOf()); return value; } } }