using System;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Scripting;
////TODO: come up with a mechanism to allow (certain) processors to be stateful
////TODO: cache processors globally; there's no need to instantiate the same processor with the same parameters multiple times
//// (except if they do end up being stateful)
namespace UnityEngine.InputSystem
{
///
/// A processor that conditions/transforms input values.
///
///
/// To define a custom processor, it is usable best to derive from
/// instead of from this class. Doing so will avoid having to deal with things such as the raw memory
/// buffers of .
///
/// Note, however, that if you do want to define a processor that can process more than one type of
/// value, you can derive directly from this class.
///
///
///
///
///
///
public abstract class InputProcessor
{
///
/// Process an input value, given as an object, and return the processed value as an object.
///
/// A value matching the processor's value type.
/// Optional control that the value originated from. Must have the same value type
/// that the processor has.
/// A processed value based on .
///
/// This method allocates GC heap memory. To process values without allocating GC memory, it is necessary to either know
/// the value type of a processor at compile time and call
/// directly or to use instead and process values in raw memory buffers.
///
public abstract object ProcessAsObject(object value, InputControl control);
///
/// Process an input value stored in the given memory buffer.
///
/// Memory buffer containing the input value. Must be at least large enough
/// to hold one full value as indicated by .
/// Size (in bytes) of the value inside .
/// Optional control that the value originated from. Must have the same value type
/// that the processor has.
///
/// This method allows processing values of arbitrary size without allocating memory on the GC heap.
///
public abstract unsafe void Process(void* buffer, int bufferSize, InputControl control);
internal static TypeTable s_Processors;
///
/// Get the value type of a processor without having to instantiate it and use .
///
///
/// Value type of the given processor or null if it could not be determined statically.
/// is null.
///
/// This method is reliant on the processor being based on . It will return
/// the TValue argument used with the class. If the processor is not based on ,
/// this method returns null.
///
internal static Type GetValueTypeFromType(Type processorType)
{
if (processorType == null)
throw new ArgumentNullException(nameof(processorType));
return TypeHelpers.GetGenericTypeArgumentFromHierarchy(processorType, typeof(InputProcessor<>), 0);
}
///
/// Caching policy regarding usage of return value from processors.
///
public enum CachingPolicy
{
///
/// Cache result value if unprocessed value has not been changed.
///
CacheResult = 0,
///
/// Process value every call to even if unprocessed value has not been changed.
///
EvaluateOnEveryRead = 1
}
///
/// Caching policy of the processor. Override this property to provide a different value.
///
public virtual CachingPolicy cachingPolicy => CachingPolicy.CacheResult;
}
///
/// A processor that conditions/transforms input values.
///
/// Type of value to be processed. Only InputControls that use the
/// same value type will be compatible with the processor.
///
/// Each can have a stack of processors assigned to it.
///
/// Note that processors CANNOT be stateful. If you need processing that requires keeping
/// mutating state over time, use InputActions. All mutable state needs to be
/// kept in the central state buffers.
///
/// However, processors can have configurable parameters. Every public field on a processor
/// object can be set using "parameters" in JSON or by supplying parameters through the
/// field.
///
///
///
/// // To register the processor, call
/// //
/// // InputSystem.RegisterProcessor<ScalingProcessor>("scale");
/// //
/// public class ScalingProcessor : InputProcessor<float>
/// {
/// // This field can be set as a parameter. See examples below.
/// // If not explicitly configured, will have its default value.
/// public float factor = 2.0f;
///
/// public float Process(float value, InputControl control)
/// {
/// return value * factor;
/// }
/// }
///
/// // Use processor in JSON:
/// const string json = @"
/// {
/// ""name"" : ""MyDevice"",
/// ""controls"" : [
/// { ""name"" : ""axis"", ""layout"" : ""Axis"", ""processors"" : ""scale(factor=4)"" }
/// ]
/// }
/// ";
///
/// // Use processor on C# state struct:
/// public struct MyDeviceState : IInputStateTypeInfo
/// {
/// [InputControl(layout = "Axis", processors = "scale(factor=4)"]
/// public float axis;
/// }
///
///
///
/// See for how to define custom parameter
/// editing UIs for processors.
///
///
public abstract class InputProcessor : InputProcessor
where TValue : struct
{
///
/// Process the given value and return the result.
///
///
/// The implementation of this method must not be stateful.
///
/// Input value to process.
/// Control that the value originally came from. This can be null if the value did
/// not originate from a control. This can be the case, for example, if the processor sits on a composite
/// binding () as composites are not directly associated with controls
/// but rather source their values through their child bindings.
/// Processed input value.
public abstract TValue Process(TValue value, InputControl control);
public override object ProcessAsObject(object value, InputControl control)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (!(value is TValue))
throw new ArgumentException(
$"Expecting value of type '{typeof(TValue).Name}' but got value '{value}' of type '{value.GetType().Name}'",
nameof(value));
var valueOfType = (TValue)value;
return Process(valueOfType, control);
}
public override unsafe void Process(void* buffer, int bufferSize, InputControl control)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
var valueSize = UnsafeUtility.SizeOf();
if (bufferSize < valueSize)
throw new ArgumentException(
$"Expected buffer of at least {valueSize} bytes but got buffer with just {bufferSize} bytes",
nameof(bufferSize));
var value = default(TValue);
var valuePtr = UnsafeUtility.AddressOf(ref value);
UnsafeUtility.MemCpy(valuePtr, buffer, valueSize);
value = Process(value, control);
valuePtr = UnsafeUtility.AddressOf(ref value);
UnsafeUtility.MemCpy(buffer, valuePtr, valueSize);
}
}
}