123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Runtime.InteropServices;
- using System.Text;
- using Unity.Collections.LowLevel.Unsafe;
- using UnityEngine.InputSystem.LowLevel;
-
- ////REVIEW: why not switch to this being the default mechanism? seems like this could allow us to also solve
- //// the actions-update-when-not-expected problem; plus give us access to easy polling
-
- ////REVIEW: should this automatically unsubscribe itself on disposal?
-
- ////TODO: make it possible to persist this same way that it should be possible to persist InputEventTrace
-
- ////TODO: make this one thread-safe
-
- ////TODO: add random access capability
-
- ////TODO: protect traces against controls changing configuration (if state layouts change, we're affected)
-
- namespace UnityEngine.InputSystem.Utilities
- {
- /// <summary>
- /// Records the triggering of actions into a sequence of events that can be replayed at will.
- /// </summary>
- /// <remarks>
- /// This is an alternate way to the callback-based responses (such as <see cref="InputAction.performed"/>)
- /// of <see cref="InputAction">input actions</see>. Instead of executing response code right away whenever
- /// an action triggers, an <see cref="RecordAction">event is recorded</see> which can then be queried on demand.
- ///
- /// The recorded data will stay valid even if the bindings on the actions are changed (e.g. by enabling a different
- /// set of bindings through altering <see cref="InputAction.bindingMask"/> or <see cref="InputActionMap.devices"/> or
- /// when modifying the paths of bindings altogether). Note, however, that when this happens, a trace will have
- /// to make a private copy of the data that stores the binding resolution state. This means that there can be
- /// GC allocation spike when reconfiguring actions that have recorded data in traces.
- ///
- /// <example>
- /// <code>
- /// var trace = new InputActionTrace();
- ///
- /// // Subscribe trace to single action.
- /// // (Use UnsubscribeFrom to unsubscribe)
- /// trace.SubscribeTo(myAction);
- ///
- /// // Subscribe trace to entire action map.
- /// // (Use UnsubscribeFrom to unsubscribe)
- /// trace.SubscribeTo(myActionMap);
- ///
- /// // Subscribe trace to all actions in the system.
- /// trace.SubscribeToAll();
- ///
- /// // Record a single triggering of an action.
- /// myAction.performed +=
- /// ctx =>
- /// {
- /// if (ctx.ReadValue<float>() > 0.5f)
- /// trace.RecordAction(ctx);
- /// };
- ///
- /// // Output trace to console.
- /// Debug.Log(string.Join(",\n", trace));
- ///
- /// // Walk through all recorded actions and then clear trace.
- /// foreach (var record in trace)
- /// {
- /// Debug.Log($"{record.action} was {record.phase} by control {record.control} at {record.time}");
- ///
- /// // To read out the value, you either have to know the value type or read the
- /// // value out as a generic byte buffer. Here we assume that the value type is
- /// // float.
- ///
- /// Debug.Log("Value: " + record.ReadValue<float>());
- ///
- /// // An alternative is read the value as an object. In this case, you don't have
- /// // to know the value type but there will be a boxed object allocation.
- /// Debug.Log("Value: " + record.ReadValueAsObject());
- /// }
- /// trace.Clear();
- ///
- /// // Unsubscribe trace from everything.
- /// trace.UnsubscribeFromAll();
- ///
- /// // Release memory held by trace.
- /// trace.Dispose();
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="InputAction.started"/>
- /// <seealso cref="InputAction.performed"/>
- /// <seealso cref="InputAction.canceled"/>
- /// <seealso cref="InputSystem.onActionChange"/>
- public sealed class InputActionTrace : IEnumerable<InputActionTrace.ActionEventPtr>, IDisposable
- {
- ////REVIEW: this is of limited use without having access to ActionEvent
- /// <summary>
- /// Directly access the underlying raw memory queue.
- /// </summary>
- public InputEventBuffer buffer => m_EventBuffer;
-
- /// <summary>
- /// Returns the number of events in the associated event buffer.
- /// </summary>
- public int count => m_EventBuffer.eventCount;
-
- /// <summary>
- /// Constructs a new default initialized <c>InputActionTrace</c>.
- /// </summary>
- /// <remarks>
- /// When you use this constructor, the new InputActionTrace object does not start recording any actions.
- /// To record actions, you must explicitly set them up after creating the object.
- /// Alternatively, you can use one of the other constructor overloads which begin recording actions immediately.
- /// </remarks>
- /// <seealso cref="SubscribeTo(InputAction)"/>
- /// <seealso cref="SubscribeTo(InputActionMap)"/>
- /// <seealso cref="SubscribeToAll"/>
- public InputActionTrace()
- {
- }
-
- /// <summary>
- /// Constructs a new <c>InputActionTrace</c> that records <paramref name="action"/>.
- /// </summary>
- /// <param name="action">The action to be recorded.</param>
- /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="action"/> is <c>null</c>.</exception>
- public InputActionTrace(InputAction action)
- {
- if (action == null)
- throw new ArgumentNullException(nameof(action));
- SubscribeTo(action);
- }
-
- /// <summary>
- /// Constructs a new <c>InputActionTrace</c> that records all actions in <paramref name="actionMap"/>.
- /// </summary>
- /// <param name="actionMap">The action-map containing actions to be recorded.</param>
- /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="action"/> is <c>null</c>.</exception>
- public InputActionTrace(InputActionMap actionMap)
- {
- if (actionMap == null)
- throw new ArgumentNullException(nameof(actionMap));
- SubscribeTo(actionMap);
- }
-
- /// <summary>
- /// Record any action getting triggered anywhere.
- /// </summary>
- /// <remarks>
- /// This does not require the trace to actually hook into every single action or action map in the system.
- /// Instead, the trace will listen to <see cref="InputSystem.onActionChange"/> and automatically record
- /// every triggered action.
- /// </remarks>
- /// <seealso cref="SubscribeTo(InputAction)"/>
- /// <seealso cref="SubscribeTo(InputActionMap)"/>
- public void SubscribeToAll()
- {
- if (m_SubscribedToAll)
- return;
-
- HookOnActionChange();
- m_SubscribedToAll = true;
-
- // Remove manually created subscriptions.
- while (m_SubscribedActions.length > 0)
- UnsubscribeFrom(m_SubscribedActions[m_SubscribedActions.length - 1]);
- while (m_SubscribedActionMaps.length > 0)
- UnsubscribeFrom(m_SubscribedActionMaps[m_SubscribedActionMaps.length - 1]);
- }
-
- /// <summary>
- /// Unsubscribes from all actions currently being recorded.
- /// </summary>
- /// <seealso cref="UnsubscribeFrom(InputAction)"/>
- /// <seealso cref="UnsubscribeFrom(InputActionMap)"/>
- public void UnsubscribeFromAll()
- {
- // Only unhook from OnActionChange if we don't have any recorded actions. If we do have
- // any, we still need the callback to be notified about when binding data changes.
- if (count == 0)
- UnhookOnActionChange();
-
- m_SubscribedToAll = false;
-
- while (m_SubscribedActions.length > 0)
- UnsubscribeFrom(m_SubscribedActions[m_SubscribedActions.length - 1]);
- while (m_SubscribedActionMaps.length > 0)
- UnsubscribeFrom(m_SubscribedActionMaps[m_SubscribedActionMaps.length - 1]);
- }
-
- /// <summary>
- /// Subscribes to <paramref name="action"/>.
- /// </summary>
- /// <param name="action">The action to be recorded.</param>
- /// <remarks>
- /// **Note:** This method does not prevent you from subscribing to the same action multiple times.
- /// If you subscribe to the same action multiple times, your event buffer will contain duplicate entries.
- /// </remarks>
- /// <exception cref="ArgumentNullException">If <paramref name="action"/> is <c>null</c>.</exception>
- /// <seealso cref="SubscribeTo(InputActionMap)"/>
- /// <seealso cref="SubscribeToAll"/>
- public void SubscribeTo(InputAction action)
- {
- if (action == null)
- throw new ArgumentNullException(nameof(action));
-
- if (m_CallbackDelegate == null)
- m_CallbackDelegate = RecordAction;
-
- action.performed += m_CallbackDelegate;
- action.started += m_CallbackDelegate;
- action.canceled += m_CallbackDelegate;
-
- m_SubscribedActions.AppendWithCapacity(action);
- }
-
- /// <summary>
- /// Subscribes to all actions contained within <paramref name="actionMap"/>.
- /// </summary>
- /// <param name="actionMap">The action-map containing all actions to be recorded.</param>
- /// <remarks>
- /// **Note:** This method does not prevent you from subscribing to the same action multiple times.
- /// If you subscribe to the same action multiple times, your event buffer will contain duplicate entries.
- /// </remarks>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="actionMap"/> is null.</exception>
- /// <seealso cref="SubscribeTo(InputAction)"/>
- /// <seealso cref="SubscribeToAll"/>
- public void SubscribeTo(InputActionMap actionMap)
- {
- if (actionMap == null)
- throw new ArgumentNullException(nameof(actionMap));
-
- if (m_CallbackDelegate == null)
- m_CallbackDelegate = RecordAction;
-
- actionMap.actionTriggered += m_CallbackDelegate;
-
- m_SubscribedActionMaps.AppendWithCapacity(actionMap);
- }
-
- /// <summary>
- /// Unsubscribes from an action, if that action was previously subscribed to.
- /// </summary>
- /// <param name="action">The action to unsubscribe from.</param>
- /// <remarks>
- /// **Note:** This method has no side effects if you attempt to unsubscribe from an action that you have not previously subscribed to.
- /// </remarks>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is <c>null</c>.</exception>
- /// <seealso cref="UnsubscribeFrom(InputActionMap)"/>
- /// <seealso cref="UnsubscribeFromAll"/>
- public void UnsubscribeFrom(InputAction action)
- {
- if (action == null)
- throw new ArgumentNullException(nameof(action));
-
- if (m_CallbackDelegate == null)
- return;
-
- action.performed -= m_CallbackDelegate;
- action.started -= m_CallbackDelegate;
- action.canceled -= m_CallbackDelegate;
-
- var index = m_SubscribedActions.IndexOfReference(action);
- if (index != -1)
- m_SubscribedActions.RemoveAtWithCapacity(index);
- }
-
- /// <summary>
- /// Unsubscribes from all actions included in <paramref name="actionMap"/>.
- /// </summary>
- /// <param name="actionMap">The action-map containing actions to unsubscribe from.</param>
- /// <remarks>
- /// **Note:** This method has no side effects if you attempt to unsubscribe from an action-map that you have not previously subscribed to.
- /// </remarks>
- /// <exception cref="ArgumentNullException">Thrown if <paramref name="actionMap"/> is <c>null</c>.</exception>
- /// <seealso cref="UnsubscribeFrom(InputAction)"/>
- /// <seealso cref="UnsubscribeFromAll"/>
- public void UnsubscribeFrom(InputActionMap actionMap)
- {
- if (actionMap == null)
- throw new ArgumentNullException(nameof(actionMap));
-
- if (m_CallbackDelegate == null)
- return;
-
- actionMap.actionTriggered -= m_CallbackDelegate;
-
- var index = m_SubscribedActionMaps.IndexOfReference(actionMap);
- if (index != -1)
- m_SubscribedActionMaps.RemoveAtWithCapacity(index);
- }
-
- /// <summary>
- /// Record the triggering of an action as an <see cref="ActionEventPtr">action event</see>.
- /// </summary>
- /// <param name="context"></param>
- /// <see cref="InputAction.performed"/>
- /// <see cref="InputAction.started"/>
- /// <see cref="InputAction.canceled"/>
- /// <see cref="InputActionMap.actionTriggered"/>
- public unsafe void RecordAction(InputAction.CallbackContext context)
- {
- // Find/add state.
- var stateIndex = m_ActionMapStates.IndexOfReference(context.m_State);
- if (stateIndex == -1)
- stateIndex = m_ActionMapStates.AppendWithCapacity(context.m_State);
-
- // Make sure we get notified if there's a change to binding setups.
- HookOnActionChange();
-
- // Allocate event.
- var valueSizeInBytes = context.valueSizeInBytes;
- var eventPtr =
- (ActionEvent*)m_EventBuffer.AllocateEvent(ActionEvent.GetEventSizeWithValueSize(valueSizeInBytes));
-
- // Initialize event.
- ref var triggerState = ref context.m_State.actionStates[context.m_ActionIndex];
- eventPtr->baseEvent.type = ActionEvent.Type;
- eventPtr->baseEvent.time = triggerState.time;
- eventPtr->stateIndex = stateIndex;
- eventPtr->controlIndex = triggerState.controlIndex;
- eventPtr->bindingIndex = triggerState.bindingIndex;
- eventPtr->interactionIndex = triggerState.interactionIndex;
- eventPtr->startTime = triggerState.startTime;
- eventPtr->phase = triggerState.phase;
-
- // Store value.
- // NOTE: If the action triggered from a composite, this stores the value as
- // read from the composite.
- // NOTE: Also, the value we store is a fully processed value.
- var valueBuffer = eventPtr->valueData;
- context.ReadValue(valueBuffer, valueSizeInBytes);
- }
-
- /// <summary>
- /// Clears all recorded data.
- /// </summary>
- /// <remarks>
- /// **Note:** This method does not unsubscribe any actions that the instance is listening to, so after clearing the recorded data, new input on those subscribed actions will continue to be recorded.
- /// </remarks>
- public void Clear()
- {
- m_EventBuffer.Reset();
- m_ActionMapStates.ClearWithCapacity();
- }
-
- ~InputActionTrace()
- {
- DisposeInternal();
- }
-
- /// <inheritdoc/>
- public override string ToString()
- {
- if (count == 0)
- return "[]";
-
- var str = new StringBuilder();
- str.Append('[');
- var isFirst = true;
- foreach (var eventPtr in this)
- {
- if (!isFirst)
- str.Append(",\n");
- str.Append(eventPtr.ToString());
- isFirst = false;
- }
- str.Append(']');
- return str.ToString();
- }
-
- /// <inheritdoc/>
- public void Dispose()
- {
- UnsubscribeFromAll();
- DisposeInternal();
- }
-
- private void DisposeInternal()
- {
- // Nuke clones we made of InputActionMapStates.
- for (var i = 0; i < m_ActionMapStateClones.length; ++i)
- m_ActionMapStateClones[i].Dispose();
-
- m_EventBuffer.Dispose();
- m_ActionMapStates.Clear();
- m_ActionMapStateClones.Clear();
-
- if (m_ActionChangeDelegate != null)
- {
- InputSystem.onActionChange -= m_ActionChangeDelegate;
- m_ActionChangeDelegate = null;
- }
- }
-
- /// <summary>
- /// Returns an enumerator that enumerates all action events recorded for this instance.
- /// </summary>
- /// <returns>Enumerator instance, never <c>null</c>.</returns>
- /// <seealso cref="ActionEventPtr"/>
- public IEnumerator<ActionEventPtr> GetEnumerator()
- {
- return new Enumerator(this);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- private bool m_SubscribedToAll;
- private bool m_OnActionChangeHooked;
- private InlinedArray<InputAction> m_SubscribedActions;
- private InlinedArray<InputActionMap> m_SubscribedActionMaps;
- private InputEventBuffer m_EventBuffer;
- private InlinedArray<InputActionState> m_ActionMapStates;
- private InlinedArray<InputActionState> m_ActionMapStateClones;
- private Action<InputAction.CallbackContext> m_CallbackDelegate;
- private Action<object, InputActionChange> m_ActionChangeDelegate;
-
- private void HookOnActionChange()
- {
- if (m_OnActionChangeHooked)
- return;
-
- if (m_ActionChangeDelegate == null)
- m_ActionChangeDelegate = OnActionChange;
-
- InputSystem.onActionChange += m_ActionChangeDelegate;
- m_OnActionChangeHooked = true;
- }
-
- private void UnhookOnActionChange()
- {
- if (!m_OnActionChangeHooked)
- return;
-
- InputSystem.onActionChange -= m_ActionChangeDelegate;
- m_OnActionChangeHooked = false;
- }
-
- private void OnActionChange(object actionOrMapOrAsset, InputActionChange change)
- {
- // If we're subscribed to all actions, check if an action got triggered.
- if (m_SubscribedToAll)
- {
- switch (change)
- {
- case InputActionChange.ActionStarted:
- case InputActionChange.ActionPerformed:
- case InputActionChange.ActionCanceled:
- Debug.Assert(actionOrMapOrAsset is InputAction, "Expected an action");
- var triggeredAction = (InputAction)actionOrMapOrAsset;
- var actionIndex = triggeredAction.m_ActionIndexInState;
- var stateForAction = triggeredAction.m_ActionMap.m_State;
-
- var context = new InputAction.CallbackContext
- {
- m_State = stateForAction,
- m_ActionIndex = actionIndex,
- };
-
- RecordAction(context);
-
- return;
- }
- }
-
- // We're only interested in changes to the binding resolution state of actions.
- if (change != InputActionChange.BoundControlsAboutToChange)
- return;
-
- // Grab the associated action map(s).
- if (actionOrMapOrAsset is InputAction action)
- CloneActionStateBeforeBindingsChange(action.m_ActionMap);
- else if (actionOrMapOrAsset is InputActionMap actionMap)
- CloneActionStateBeforeBindingsChange(actionMap);
- else if (actionOrMapOrAsset is InputActionAsset actionAsset)
- foreach (var actionMapInAsset in actionAsset.actionMaps)
- CloneActionStateBeforeBindingsChange(actionMapInAsset);
- else
- Debug.Assert(false, "Expected InputAction, InputActionMap or InputActionAsset");
- }
-
- private void CloneActionStateBeforeBindingsChange(InputActionMap actionMap)
- {
- // Grab the state.
- var state = actionMap.m_State;
- if (state == null)
- {
- // Bindings have not been resolved yet for this action map. We shouldn't even be
- // on the notification list in this case, but just in case, ignore.
- return;
- }
-
- // See if we're using the given state.
- var stateIndex = m_ActionMapStates.IndexOfReference(state);
- if (stateIndex == -1)
- return;
-
- // Yes, we are so make our own private copy of its current state.
- // NOTE: We do not put these local InputActionMapStates on the global list.
- var clone = state.Clone();
- m_ActionMapStateClones.Append(clone);
- m_ActionMapStates[stateIndex] = clone;
- }
-
- /// <summary>
- /// A wrapper around <see cref="ActionEvent"/> that automatically translates all the
- /// information in events into their high-level representations.
- /// </summary>
- /// <remarks>
- /// For example, instead of returning <see cref="ActionEvent.controlIndex">control indices</see>,
- /// it automatically resolves and returns the respective <see cref="InputControl">controls</see>.
- /// </remarks>
- public unsafe struct ActionEventPtr
- {
- internal InputActionState m_State;
- internal ActionEvent* m_Ptr;
-
- /// <summary>
- /// The <see cref="InputAction"/> associated with this action event.
- /// </summary>
- public InputAction action => m_State.GetActionOrNull(m_Ptr->bindingIndex);
-
- /// <summary>
- /// The <see cref="InputActionPhase"/> associated with this action event.
- /// </summary>
- /// <seealso cref="InputAction.phase"/>
- /// <seealso cref="InputAction.CallbackContext.phase"/>
- public InputActionPhase phase => m_Ptr->phase;
-
- /// <summary>
- /// The <see cref="InputControl"/> instance associated with this action event.
- /// </summary>
- public InputControl control => m_State.controls[m_Ptr->controlIndex];
-
- /// <summary>
- /// The <see cref="IInputInteraction"/> instance associated with this action event if applicable, or <c>null</c> if the action event is not associated with an input interaction.
- /// </summary>
- public IInputInteraction interaction
- {
- get
- {
- var index = m_Ptr->interactionIndex;
- if (index == InputActionState.kInvalidIndex)
- return null;
-
- return m_State.interactions[index];
- }
- }
-
- /// <summary>
- /// The time, in seconds since your game or app started, that the event occurred.
- /// </summary>
- /// <remarks>
- /// Times are in seconds and progress linearly in real-time. The timeline is the same as for <see cref="Time.realtimeSinceStartup"/>.
- /// </remarks>
- public double time => m_Ptr->baseEvent.time;
-
- /// <summary>
- /// The time, in seconds since your game or app started, that the <see cref="phase"/> transitioned into <see cref="InputActionPhase.Started"/>.
- /// </summary>
- public double startTime => m_Ptr->startTime;
-
- /// <summary>
- /// The duration, in seconds, that has elapsed between when this event was generated and when the
- /// action <see cref="phase"/> transitioned to <see cref="InputActionPhase.Started"/> and has remained active.
- /// </summary>
- public double duration => time - startTime;
-
- /// <summary>
- /// The size, in bytes, of the value associated with this action event.
- /// </summary>
- public int valueSizeInBytes => m_Ptr->valueSizeInBytes;
-
- /// <summary>
- /// Reads the value associated with this event as an <c>object</c>.
- /// </summary>
- /// <returns><c>object</c> representing the value of this action event.</returns>
- /// <seealso cref="ReadOnlyArray{TValue}"/>
- /// <seealso cref="ReadValue(void*, int)"/>
- public object ReadValueAsObject()
- {
- if (m_Ptr == null)
- throw new InvalidOperationException("ActionEventPtr is invalid");
-
- var valuePtr = m_Ptr->valueData;
-
- // Check if the value came from a composite.
- var bindingIndex = m_Ptr->bindingIndex;
- if (m_State.bindingStates[bindingIndex].isPartOfComposite)
- {
- // Yes, so have to put the value/struct data we read into a boxed
- // object based on the value type of the composite.
-
- var compositeBindingIndex = m_State.bindingStates[bindingIndex].compositeOrCompositeBindingIndex;
- var compositeIndex = m_State.bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex;
- var composite = m_State.composites[compositeIndex];
- Debug.Assert(composite != null, "NULL composite instance");
-
- var valueType = composite.valueType;
- if (valueType == null)
- throw new InvalidOperationException($"Cannot read value from Composite '{composite}' which does not have a valueType set");
-
- return Marshal.PtrToStructure(new IntPtr(valuePtr), valueType);
- }
-
- // Expecting action to only trigger from part bindings or bindings outside of composites.
- Debug.Assert(!m_State.bindingStates[bindingIndex].isComposite, "Action should not have triggered directly from a composite binding");
-
- // Read value through InputControl.
- var valueSizeInBytes = m_Ptr->valueSizeInBytes;
- return control.ReadValueFromBufferAsObject(valuePtr, valueSizeInBytes);
- }
-
- /// <summary>
- /// Reads the value associated with this event into the contiguous memory buffer defined by <c>[buffer, buffer + bufferSize)</c>.
- /// </summary>
- /// <param name="buffer">Pointer to the contiguous memory buffer to write value data to.</param>
- /// <param name="bufferSize">The size, in bytes, of the contiguous buffer pointed to by <paramref name="buffer"/>.</param>
- /// <exception cref="NullReferenceException">If <paramref name="buffer"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentException">If the given <paramref name="bufferSize"/> is less than the number of bytes required to write the event value to <paramref name="buffer"/>.</exception>
- /// <seealso cref="ReadValueAsObject"/>
- /// <seealso cref="ReadValue{TValue}"/>
- public void ReadValue(void* buffer, int bufferSize)
- {
- var valueSizeInBytes = m_Ptr->valueSizeInBytes;
-
- ////REVIEW: do we want more checking than this?
- if (bufferSize < valueSizeInBytes)
- throw new ArgumentException(
- $"Expected buffer of at least {valueSizeInBytes} bytes but got buffer of just {bufferSize} bytes instead",
- nameof(bufferSize));
-
- UnsafeUtility.MemCpy(buffer, m_Ptr->valueData, valueSizeInBytes);
- }
-
- /// <summary>
- /// Reads the value associated with this event as an object of type <typeparamref name="TValue"/>.
- /// </summary>
- /// <typeparam name="TValue">The event value type to be used.</typeparam>
- /// <returns>Object of type <typeparamref name="TValue"/>.</returns>
- /// <exception cref="InvalidOperationException">In case the size of <typeparamref name="TValue"/> does not match the size of the value associated with this event.</exception>
- public TValue ReadValue<TValue>()
- where TValue : struct
- {
- var valueSizeInBytes = m_Ptr->valueSizeInBytes;
-
- ////REVIEW: do we want more checking than this?
- if (UnsafeUtility.SizeOf<TValue>() != valueSizeInBytes)
- throw new InvalidOperationException(
- $"Cannot read a value of type '{typeof(TValue).Name}' with size {UnsafeUtility.SizeOf<TValue>()} from event on action '{action}' with value size {valueSizeInBytes}");
-
- var result = new TValue();
- var resultPtr = UnsafeUtility.AddressOf(ref result);
- UnsafeUtility.MemCpy(resultPtr, m_Ptr->valueData, valueSizeInBytes);
-
- return result;
- }
-
- /// <inheritdoc/>
- public override string ToString()
- {
- if (m_Ptr == null)
- return "<null>";
-
- var actionName = action.actionMap != null ? $"{action.actionMap.name}/{action.name}" : action.name;
- return $"{{ action={actionName} phase={phase} time={time} control={control} value={ReadValueAsObject()} interaction={interaction} duration={duration} }}";
- }
- }
-
- private unsafe struct Enumerator : IEnumerator<ActionEventPtr>
- {
- private readonly InputActionTrace m_Trace;
- private readonly ActionEvent* m_Buffer;
- private readonly int m_EventCount;
- private ActionEvent* m_CurrentEvent;
- private int m_CurrentIndex;
-
- public Enumerator(InputActionTrace trace)
- {
- m_Trace = trace;
- m_Buffer = (ActionEvent*)trace.m_EventBuffer.bufferPtr.data;
- m_EventCount = trace.m_EventBuffer.eventCount;
- m_CurrentEvent = null;
- m_CurrentIndex = 0;
- }
-
- public bool MoveNext()
- {
- if (m_CurrentIndex == m_EventCount)
- return false;
-
- if (m_CurrentEvent == null)
- {
- m_CurrentEvent = m_Buffer;
- return m_CurrentEvent != null;
- }
-
- Debug.Assert(m_CurrentEvent != null);
-
- ++m_CurrentIndex;
- if (m_CurrentIndex == m_EventCount)
- return false;
-
- m_CurrentEvent = (ActionEvent*)InputEvent.GetNextInMemory((InputEvent*)m_CurrentEvent);
- return true;
- }
-
- public void Reset()
- {
- m_CurrentEvent = null;
- m_CurrentIndex = 0;
- }
-
- public void Dispose()
- {
- }
-
- public ActionEventPtr Current
- {
- get
- {
- var state = m_Trace.m_ActionMapStates[m_CurrentEvent->stateIndex];
- return new ActionEventPtr
- {
- m_State = state,
- m_Ptr = m_CurrentEvent,
- };
- }
- }
-
- object IEnumerator.Current => Current;
- }
- }
- }
|