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