diff --git a/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs b/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs index e98443475f..1cd3f4a620 100644 --- a/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs +++ b/Packages/com.unity.inputsystem/InputSystem/State/InputStateHistory.cs @@ -29,7 +29,7 @@ namespace UnityEngine.InputSystem.LowLevel /// /// The class listens to changes on the given controls by adding change monitors () /// to each control. - /// + /// /// /// /// // Track all stick controls in the system. @@ -54,7 +54,7 @@ namespace UnityEngine.InputSystem.LowLevel /// history.Dispose(); /// /// - /// + /// public class InputStateHistory : IDisposable, IEnumerable, IInputStateChangeMonitor { private const int kDefaultHistorySize = 128; @@ -62,30 +62,34 @@ public class InputStateHistory : IDisposable, IEnumerable /// Total number of state records currently captured in the history. /// - /// Number of records in the collection. /// + /// Number of records in the collection. + /// /// This will always be at most . + /// To record a change use . /// - /// - /// public int Count => m_RecordCount; /// /// Current version stamp. Every time a record is stored in the history, /// this is incremented by one. /// - /// Version stamp that indicates the number of mutations. - /// + /// + /// Version stamp that indicates the number of mutations. + /// To record a change use . + /// public uint version => m_CurrentVersion; /// /// Maximum number of records that can be recorded in the history. /// - /// Upper limit on number of records. /// is negative. /// + /// Upper limit on number of records. /// A fixed size memory block of unmanaged memory will be allocated to store history - /// records. This property determines TODO + /// records. + /// When the history is full, it will start overwriting the oldest + /// entry each time a new history record is received. /// public int historyDepth { @@ -100,6 +104,15 @@ public int historyDepth } } + /// + /// Size of additional data storage to allocate per record. + /// + /// is negative. + /// + /// Additional custom data can be stored per record up to the size of this value. + /// To retrieve a pointer to this memory use + /// Used by + /// public int extraMemoryPerRecord { get => m_ExtraMemoryPerRecord; @@ -113,6 +126,14 @@ public int extraMemoryPerRecord } } + /// + /// Specify which player loop positions the state history will be monitored for. + /// + /// When an invalid mask is provided (e.g. ). + /// + /// The state history will only be monitored for the specified player loop positions. + /// is excluded from this list + /// public InputUpdateType updateMask { get => m_UpdateMask ?? InputSystem.s_Manager.updateMask & ~InputUpdateType.Editor; @@ -124,8 +145,22 @@ public InputUpdateType updateMask } } + /// + /// List of input controls the state history will be recording for. + /// + /// + /// The list of input controls the state history will be recording for is specified on construction of the + /// public ReadOnlyArray controls => new ReadOnlyArray(m_Controls, 0, m_ControlCount); + /// + /// Returns an entry in the state history at the given index. + /// + /// Index into the array. + /// + /// Returns a entry from the state history at the given index. + /// + /// is less than 0 or greater than . public unsafe Record this[int index] { get @@ -148,9 +183,34 @@ public unsafe Record this[int index] } } + /// + /// Optional delegate to perform when a record is added to the history array. + /// + /// + /// Can be used to fill in the extra memory with custom data using + /// public Action onRecordAdded { get; set; } + + /// + /// Optional delegate to decide whether the state change should be stored in the history. + /// + /// + /// Can be used to filter out some events to focus on recording the ones you are most interested in. + /// + /// If the callback returns true, a record will be added to the history + /// If the callback returns false, the event will be ignored and not recorded. + /// public Func onShouldRecordStateChange { get; set; } + /// + /// Creates a new InputStateHistory class to record all control state changes. + /// + /// Maximum size of control state in the record entries. Controls with larger state will not be recorded. + /// + /// Creates a new InputStateHistory to record a history of control state changes. + /// + /// New controls are automatically added into the state history if their state is smaller than the threshold. + /// public InputStateHistory(int maxStateSizeInBytes) { if (maxStateSizeInBytes <= 0) @@ -160,6 +220,19 @@ public InputStateHistory(int maxStateSizeInBytes) m_StateSizeInBytes = maxStateSizeInBytes.AlignToMultipleOf(4); } + /// + /// Creates a new InputStateHistory class to record state changes for a specified control. + /// + /// Control path to identify which controls to monitor. + /// + /// Creates a new InputStateHistory to record a history of state changes for the specified controls. + /// + /// + /// + /// // Track all stick controls in the system. + /// var history = new InputStateHistory("*/<Stick>"); + /// + /// public InputStateHistory(string path) { using (var controls = InputSystem.FindControls(path)) @@ -169,6 +242,13 @@ public InputStateHistory(string path) } } + /// + /// Creates a new InputStateHistory class to record state changes for a specified control. + /// + /// Control to monitor. + /// + /// Creates a new InputStateHistory to record a history of state changes for the specified control. + /// public InputStateHistory(InputControl control) { if (control == null) @@ -178,6 +258,13 @@ public InputStateHistory(InputControl control) m_ControlCount = 1; } + /// + /// Creates a new InputStateHistory class to record state changes for a specified controls. + /// + /// Controls to monitor. + /// + /// Creates a new InputStateHistory to record a history of state changes for the specified controls. + /// public InputStateHistory(IEnumerable controls) { if (controls != null) @@ -187,11 +274,22 @@ public InputStateHistory(IEnumerable controls) } } + /// + /// InputStateHistory destructor. + /// ~InputStateHistory() { Dispose(); } + /// + /// Clear the history record. + /// + /// + /// Clear the history record. Resetting the list to empty. + /// + /// This won't clear controls that have been added on the fly. + /// public void Clear() { m_HeadIndex = 0; @@ -201,6 +299,15 @@ public void Clear() // NOTE: Won't clear controls that have been added on the fly. } + /// + /// Add a record to the input state history. + /// + /// Record to add. + /// The newly added record from the history array (as a copy is made). + /// + /// Add a record to the input state history. + /// Allocates an entry in the history array and returns this copy of the original data passed to the function. + /// public unsafe Record AddRecord(Record record) { var recordPtr = AllocateRecord(out var index); @@ -209,6 +316,21 @@ public unsafe Record AddRecord(Record record) return newRecord; } + /// + /// Start recording state history for the specified controls. + /// + /// + /// Start recording state history for the controls specified in the constructor. + /// + /// + /// + /// using (var allTouchTaps = new InputStateHistory("<Touchscreen>/touch*/tap")) + /// { + /// allTouchTaps.StartRecording(); + /// allTouchTaps.StopRecording(); + /// } + /// + /// public void StartRecording() { // We defer allocation until we actually get values on a control. @@ -217,12 +339,38 @@ public void StartRecording() InputState.AddChangeMonitor(control, this); } + /// + /// Stop recording state history for the specified controls. + /// + /// + /// Stop recording state history for the controls specified in the constructor. + /// + /// + /// + /// using (var allTouchTaps = new InputStateHistory("<Touchscreen>/touch*/tap")) + /// { + /// allTouchTaps.StartRecording(); + /// allTouchTaps.StopRecording(); + /// } + /// + /// public void StopRecording() { foreach (var control in controls) InputState.RemoveChangeMonitor(control, this); } + /// + /// Record a state change for a specific control. + /// + /// The control to record the state change for. + /// The current event data to record. + /// The newly added record. + /// + /// Record a state change for a specific control. + /// Will call the delegate after adding the record. + /// Note this does not call the delegate. + /// public unsafe Record RecordStateChange(InputControl control, InputEventPtr eventPtr) { if (eventPtr.IsA()) @@ -236,6 +384,18 @@ public unsafe Record RecordStateChange(InputControl control, InputEventPtr event return RecordStateChange(control, statePtr, eventPtr.time); } + /// + /// Record a state change for a specific control. + /// + /// The control to record the state change for. + /// The current state data to record. + /// Time stamp to apply (overriding the event timestamp) + /// The newly added record. + /// + /// Record a state change for a specific control. + /// Will call the delegate after adding the record. + /// Note this does not call the delegate. + /// public unsafe Record RecordStateChange(InputControl control, void* statePtr, double time) { var controlIndex = ArrayHelpers.IndexOfReference(m_Controls, control, m_ControlCount); @@ -277,16 +437,37 @@ public unsafe Record RecordStateChange(InputControl control, void* statePtr, dou return record; } + /// + /// Enumerate all state history records. + /// + /// An enumerator going over the state history records. + /// + /// Enumerate all state history records. + /// + /// public IEnumerator GetEnumerator() { return new Enumerator(this); } + /// + /// Enumerate all state history records. + /// + /// An enumerator going over the state history records. + /// + /// Enumerate all state history records. + /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + /// + /// Dispose of the state history records. + /// + /// + /// Stops recording and cleans up the state history + /// public void Dispose() { StopRecording(); @@ -294,6 +475,12 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + /// Destroy the state history records. + /// + /// + /// Deletes the state history records. + /// protected void Destroy() { if (m_RecordBuffer.IsCreated) @@ -325,6 +512,15 @@ private void Allocate() NativeArrayOptions.UninitializedMemory); } + /// + /// Remap a records internal index to an index from the start of the recording in the circular buffer. + /// + /// + /// Remap a records internal index, which is relative to the start of the record buffer, + /// to an index relative to the start of the recording in the circular buffer. + /// + /// Record index (from the start of the record array). + /// An index relative to the start of the recording in the circular buffer. protected internal int RecordIndexToUserIndex(int index) { if (index < m_HeadIndex) @@ -332,11 +528,30 @@ protected internal int RecordIndexToUserIndex(int index) return index - m_HeadIndex; } + /// + /// Remap an index from the start of the recording in the circular buffer to a records internal index. + /// + /// + /// Remap an index relative to the start of the recording in the circular buffer, + /// to a records internal index, which is relative to the start of the record buffer. + /// + /// An index relative to the start of the recording in the circular buffer. + /// Record index (from the start of the record array). protected internal int UserIndexToRecordIndex(int index) { return (m_HeadIndex + index) % m_HistoryDepth; } + /// + /// Retrieve a record from the input state history. + /// + /// + /// Retrieve a record from the input state history by Record index. + /// + /// Record index into the input state history records buffer. + /// The record header for the specified index + /// When the buffer is no longer valid as it has been disposed. + /// If the index is out of range of the history depth. protected internal unsafe RecordHeader* GetRecord(int index) { if (!m_RecordBuffer.IsCreated) @@ -346,11 +561,27 @@ protected internal int UserIndexToRecordIndex(int index) return GetRecordUnchecked(index); } + /// + /// Retrieve a record from the input state history, without any bounds check. + /// + /// + /// Retrieve a record from the input state history by record index, without any bounds check + /// + /// Record index into the input state history records buffer. + /// The record header for the specified index internal unsafe RecordHeader* GetRecordUnchecked(int index) { return (RecordHeader*)((byte*)m_RecordBuffer.GetUnsafePtr() + index * bytesPerRecord); } + /// + /// Allocate a new record in the input state history. + /// + /// + /// Allocate a new record in the input state history. + /// + /// The index of the newly created record + /// The header of the newly created record protected internal unsafe RecordHeader* AllocateRecord(out int index) { if (!m_RecordBuffer.IsCreated) @@ -371,6 +602,13 @@ protected internal int UserIndexToRecordIndex(int index) return (RecordHeader*)((byte*)m_RecordBuffer.GetUnsafePtr() + bytesPerRecord * index); } + /// + /// Returns value from the control in the specified record header. + /// + /// The record header to query. + /// The type of the value being read + /// The value from the record. + /// When the record is no longer value or the specified type is not present. protected unsafe TValue ReadValue(RecordHeader* data) where TValue : struct { @@ -387,6 +625,15 @@ protected unsafe TValue ReadValue(RecordHeader* data) return controlOfType.ReadValueFromState(statePtr); } + /// + /// Read the control's final, processed value from the given state and return the value as an object. + /// + /// The record header to query. + /// The value of the control associated with the record header. + /// + /// This method allocates GC memory and should not be used during normal gameplay operation. + /// + /// When the specified value is not present. protected unsafe object ReadValueAsObject(RecordHeader* data) { // Get control. If we only have a single one, the index isn't stored on the data. @@ -399,6 +646,18 @@ protected unsafe object ReadValueAsObject(RecordHeader* data) return control.ReadValueFromStateAsObject(statePtr); } + /// + /// Delegate to list to control state change notifications. + /// + /// Control that is being monitored by the state change monitor and that had its state memory changed. + /// Time on the timeline at which the control state change was received. + /// If the state change was initiated by a state event (either a + /// or ), this is the pointer to that event. Otherwise, it is pointer that is still + /// , but refers a "dummy" event that is not a or . + /// Index of the monitor as passed to + /// + /// Records a state change after checking the and the callback. + /// unsafe void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex) { @@ -416,6 +675,15 @@ unsafe void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl cont } // Unused. + /// + /// Called when a timeout set on a state change monitor has expired. + /// + /// Control on which the timeout expired. + /// Input time at which the timer expired. This is the time at which an is being + /// run whose is past the time of expiration. + /// Index of the monitor as given to . + /// Index of the timer as given to . + /// void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex) { @@ -473,16 +741,50 @@ public void Dispose() } } + /// State change record header + /// + /// Input State change record header containing the timestamp and other common record data. + /// Stored in the . + /// + /// [StructLayout(LayoutKind.Explicit)] protected internal unsafe struct RecordHeader { + /// + /// The time stamp of the input state record. + /// + /// + /// The time stamp of the input state record in the owning container. + /// + /// [FieldOffset(0)] public double time; + + /// + /// The version of the input state record. + /// + /// + /// Current version stamp. See . + /// [FieldOffset(8)] public uint version; + + /// + /// The index of the record. + /// + /// + /// The index of the record relative to the start of the buffer. + /// See to remap this record index to a user index. + /// [FieldOffset(12)] public int controlIndex; [FieldOffset(12)] private fixed byte m_StateWithoutControlIndex[1]; [FieldOffset(16)] private fixed byte m_StateWithControlIndex[1]; + /// + /// The state data including the control index. + /// + /// + /// The state data including the control index. + /// public byte* statePtrWithControlIndex { get @@ -492,6 +794,12 @@ public byte* statePtrWithControlIndex } } + /// + /// The state data excluding the control index. + /// + /// + /// The state data excluding the control index. + /// public byte* statePtrWithoutControlIndex { get @@ -501,10 +809,26 @@ public byte* statePtrWithoutControlIndex } } + /// + /// Size of the state data including the control index. + /// + /// + /// Size of the data including the control index. + /// public const int kSizeWithControlIndex = 16; + + /// + /// Size of the state data excluding the control index. + /// + /// + /// Size of the data excluding the control index. + /// public const int kSizeWithoutControlIndex = 12; } + /// State change record + /// Input State change record stored in the . + /// public unsafe struct Record : IEquatable { // We store an index rather than a direct pointer to make this struct safer to use. @@ -516,10 +840,31 @@ public unsafe struct Record : IEquatable internal int recordIndex => m_IndexPlusOne - 1; internal uint version => m_Version; + /// + /// Identifies if the record is valid. + /// + /// True if the record is a valid entry. False if invalid. + /// + /// When the history is cleared with the entries become invalid. + /// public bool valid => m_Owner != default && m_IndexPlusOne != default && header->version == m_Version; + /// + /// Identifies the owning container for the record. + /// + /// The owning container for the record. + /// + /// Identifies the owning container for the record. + /// public InputStateHistory owner => m_Owner; + /// + /// The index of the input state record in the owning container. + /// + /// + /// The index of the input state record in the owning container. + /// + /// When the record is no longer value. public int index { get @@ -529,6 +874,14 @@ public int index } } + /// + /// The time stamp of the input state record. + /// + /// + /// The time stamp of the input state record in the owning container. + /// + /// + /// When the record is no longer value. public double time { get @@ -538,6 +891,13 @@ public double time } } + /// + /// The control associated with the input state record. + /// + /// + /// The control associated with the input state record. + /// + /// When the record is no longer value. public InputControl control { get @@ -550,6 +910,13 @@ public InputControl control } } + /// + /// The next input state record in the owning container. + /// + /// + /// The next input state record in the owning container. + /// + /// When the record is no longer value. public Record next { get @@ -563,6 +930,13 @@ public Record next } } + /// + /// The previous input state record in the owning container. + /// + /// + /// The previous input state record in the owning container. + /// + /// When the record is no longer value. public Record previous { get @@ -583,6 +957,12 @@ internal Record(InputStateHistory owner, int index, RecordHeader* header) m_Version = header->version; } + /// + /// Returns value from the control in the record. + /// + /// The type of the value being read + /// Returns the value from the record. + /// When the record is no longer value or the specified type is not present. public TValue ReadValue() where TValue : struct { @@ -590,12 +970,27 @@ public TValue ReadValue() return m_Owner.ReadValue(header); } + /// + /// Read the control's final, processed value from the given state and return the value as an object. + /// + /// The value of the control associated with the record. + /// + /// This method allocates GC memory and should not be used during normal gameplay operation. + /// + /// When the specified value is not present. public object ReadValueAsObject() { CheckValid(); return m_Owner.ReadValueAsObject(header); } + /// + /// Read the state memory for the record. + /// + /// The state memory for the record. + /// + /// Read the state memory for the record. + /// public void* GetUnsafeMemoryPtr() { CheckValid(); @@ -609,6 +1004,14 @@ public object ReadValueAsObject() return header->statePtrWithControlIndex; } + /// + /// Read the extra memory for the record. + /// + /// The extra memory for the record. + /// + /// Additional date can be stored in a record in the extra memory section. + /// + /// public void* GetUnsafeExtraMemoryPtr() { CheckValid(); @@ -622,6 +1025,13 @@ public object ReadValueAsObject() return (byte*)header + m_Owner.bytesPerRecord - m_Owner.extraMemoryPerRecord; } + /// Copy data from one record to another. + /// Source record to copy from. + /// + /// Copy data from one record to another. + /// + /// When the source record history is not valid. + /// When the control is not tracked by the owning container. public void CopyFrom(Record record) { if (!record.valid) @@ -686,16 +1096,27 @@ internal void CheckValid() throw new InvalidOperationException("Record is no longer valid"); } + /// Compare two records. + /// Compare two records. + /// The record to compare with. + /// True if the records are the same, False if they differ. public bool Equals(Record other) { return ReferenceEquals(m_Owner, other.m_Owner) && m_IndexPlusOne == other.m_IndexPlusOne && m_Version == other.m_Version; } + /// Compare two records. + /// Compare two records. + /// The record to compare with. + /// True if the records are the same, False if they differ. public override bool Equals(object obj) { return obj is Record other && Equals(other); } + /// Return the hash code of the record. + /// Return the hash code of the record. + /// The hash code of the record. public override int GetHashCode() { unchecked @@ -707,6 +1128,9 @@ public override int GetHashCode() } } + /// Return the string representation of the record. + /// Includes the control, value and time of the record (or <Invalid> if not valid). + /// The string representation of the record. public override string ToString() { if (!valid) @@ -720,10 +1144,27 @@ public override string ToString() /// /// Records value changes of a given control over time. /// - /// + /// The type of the record being stored + /// + /// This class makes it easy to track input values over time. It will automatically retain input state up to a given + /// maximum history depth (). When the history is full, it will start overwriting the oldest + /// entry each time a new history record is received. + /// + /// The class listens to changes on the given controls by adding change monitors () + /// to each control. + /// public class InputStateHistory : InputStateHistory, IReadOnlyList.Record> where TValue : struct { + /// + /// Creates a new InputStateHistory class to record all control state changes. + /// + /// Maximum size of control state in the record entries. Controls with larger state will not be recorded. + /// + /// Creates a new InputStateHistory to record a history of control state changes. + /// + /// New controls are automatically added into the state history if there state is smaller than the threshold. + /// public InputStateHistory(int? maxStateSizeInBytes = null) // Using the size of the value here isn't quite correct but the value is used as an upper // bound on stored state size for which the size of the value should be a reasonable guess. @@ -733,11 +1174,31 @@ public InputStateHistory(int? maxStateSizeInBytes = null) throw new ArgumentException("Max state size cannot be smaller than sizeof(TValue)", nameof(maxStateSizeInBytes)); } + /// + /// Creates a new InputStateHistory class to record state changes for a specified control. + /// + /// Control to monitor. + /// + /// Creates a new InputStateHistory to record a history of state changes for the specified control. + /// public InputStateHistory(InputControl control) : base(control) { } + /// + /// Creates a new InputStateHistory class to record state changes for a specified control. + /// + /// Control path to identify which controls to monitor. + /// + /// Creates a new InputStateHistory to record a history of state changes for the specified controls. + /// + /// + /// + /// // Track all stick controls in the system. + /// var history = new InputStateHistory<Vector2>("*/<Stick>"); + /// + /// public InputStateHistory(string path) : base(path) { @@ -748,11 +1209,23 @@ public InputStateHistory(string path) $"Control '{control}' matched by '{path}' has value type '{TypeHelpers.GetNiceTypeName(control.valueType)}' which is incompatible with '{TypeHelpers.GetNiceTypeName(typeof(TValue))}'"); } + /// + /// InputStateHistory destructor. + /// ~InputStateHistory() { Destroy(); } + /// + /// Add a record to the input state history. + /// + /// Record to add. + /// The newly added record from the history array (as a copy is made). + /// + /// Add a record to the input state history. + /// Allocates an entry in the history array and returns this copy of the original data passed to the function. + /// public unsafe Record AddRecord(Record record) { var recordPtr = AllocateRecord(out var index); @@ -761,6 +1234,26 @@ public unsafe Record AddRecord(Record record) return newRecord; } + /// + /// Record a state change for a specific control. + /// + /// The control to record the state change for. + /// The value to record. + /// Time stamp to apply (overriding the event timestamp) + /// The newly added record. + /// + /// Record a state change for a specific control. + /// Will call the delegate after adding the record. + /// Note this does not call the delegate. + /// + /// + /// + /// using (var allTouchTaps = new InputStateHistory<float>(Gamepad.current.leftTrigger)) + /// { + /// history.RecordStateChange(Gamepad.current.leftTrigger, 0.234f); + /// } + /// + /// public unsafe Record RecordStateChange(InputControl control, TValue value, double time = -1) { using (StateEvent.From(control.device, out var eventPtr)) @@ -774,16 +1267,39 @@ public unsafe Record RecordStateChange(InputControl control, TValue valu } } + /// + /// Enumerate all state history records. + /// + /// An enumerator going over the state history records. + /// + /// Enumerate all state history records. + /// + /// public new IEnumerator GetEnumerator() { return new Enumerator(this); } + /// + /// Enumerate all state history records. + /// + /// An enumerator going over the state history records. + /// + /// Enumerate all state history records. + /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + /// + /// Returns an entry in the state history at the given index. + /// + /// Index into the array. + /// + /// Returns a entry from the state history at the given index. + /// + /// is less than 0 or greater than . public new unsafe Record this[int index] { get @@ -838,6 +1354,9 @@ public void Dispose() } } + /// State change record + /// Input State change record stored in the + /// public new unsafe struct Record : IEquatable { private readonly InputStateHistory m_Owner; @@ -847,10 +1366,31 @@ public void Dispose() internal RecordHeader* header => m_Owner.GetRecord(recordIndex); internal int recordIndex => m_IndexPlusOne - 1; + /// + /// Identifies if the record is valid. + /// + /// True if the record is a valid entry. False if invalid. + /// + /// When the history is cleared with the entries become invalid. + /// public bool valid => m_Owner != default && m_IndexPlusOne != default && header->version == m_Version; + /// + /// Identifies the owning container for the record. + /// + /// The owning container for the record. + /// + /// Identifies the owning container for the record. + /// public InputStateHistory owner => m_Owner; + /// + /// The index of the input state record in the owning container. + /// + /// + /// The index of the input state record in the owning container. + /// + /// When the record is no longer value. public int index { get @@ -860,6 +1400,14 @@ public int index } } + /// + /// The time stamp of the input state record. + /// + /// + /// The time stamp of the input state record in the owning container. + /// + /// + /// When the record is no longer value. public double time { get @@ -869,6 +1417,13 @@ public double time } } + /// + /// The control associated with the input state record. + /// + /// + /// The control associated with the input state record. + /// + /// When the record is no longer value. public InputControl control { get @@ -881,6 +1436,13 @@ public InputControl control } } + /// + /// The next input state record in the owning container. + /// + /// + /// The next input state record in the owning container. + /// + /// When the record is no longer value. public Record next { get @@ -894,6 +1456,13 @@ public Record next } } + /// + /// The previous input state record in the owning container. + /// + /// + /// The previous input state record in the owning container. + /// + /// When the record is no longer value. public Record previous { get @@ -921,12 +1490,24 @@ internal Record(InputStateHistory owner, int index) m_Version = default; } + /// + /// Returns value from the control in the Record. + /// + /// Returns value from the Record. + /// When the record is no longer value or the specified type is not present. public TValue ReadValue() { CheckValid(); return m_Owner.ReadValue(header); } + /// + /// Read the state memory for the record. + /// + /// The state memory for the record. + /// + /// Read the state memory for the record. + /// public void* GetUnsafeMemoryPtr() { CheckValid(); @@ -940,6 +1521,14 @@ public TValue ReadValue() return header->statePtrWithControlIndex; } + /// + /// Read the extra memory for the record. + /// + /// The extra memory for the record. + /// + /// Additional date can be stored in a record in the extra memory section. + /// + /// public void* GetUnsafeExtraMemoryPtr() { CheckValid(); @@ -953,6 +1542,13 @@ public TValue ReadValue() return (byte*)header + m_Owner.bytesPerRecord - m_Owner.extraMemoryPerRecord; } + /// Copy data from one record to another. + /// Source Record to copy from. + /// + /// Copy data from one record to another. + /// + /// When the source record history is not valid. + /// When the control is not tracked by the owning container. public void CopyFrom(Record record) { CheckValid(); @@ -971,16 +1567,27 @@ private void CheckValid() throw new InvalidOperationException("Record is no longer valid"); } + /// Compare two records. + /// Compare two records. + /// The record to compare with. + /// True if the records are the same, False if they differ. public bool Equals(Record other) { return ReferenceEquals(m_Owner, other.m_Owner) && m_IndexPlusOne == other.m_IndexPlusOne && m_Version == other.m_Version; } + /// Compare two records. + /// Compare two records. + /// The record to compare with. + /// True if the records are the same, False if they differ. public override bool Equals(object obj) { return obj is Record other && Equals(other); } + /// Return the hash code of the record. + /// Return the hash code of the record. + /// The hash code of the record. public override int GetHashCode() { unchecked @@ -992,6 +1599,9 @@ public override int GetHashCode() } } + /// Return the string representation of the record. + /// Includes the control, value and time of the record (or <Invalid> if not valid). + /// The string representation of the record. public override string ToString() { if (!valid)