暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputStateHistory.cs 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using Unity.Collections;
  7. using Unity.Collections.LowLevel.Unsafe;
  8. using UnityEngine.InputSystem;
  9. using UnityEngine.InputSystem.LowLevel;
  10. using UnityEngine.InputSystem.Utilities;
  11. ////REVIEW: should this enumerate *backwards* in time rather than *forwards*?
  12. ////TODO: allow correlating history to frames/updates
  13. ////TODO: add ability to grow such that you can set it to e.g. record up to 4 seconds of history and it will automatically keep the buffer size bounded
  14. ////REVIEW: should we align the extra memory on a 4 byte boundary?
  15. namespace UnityEngine.InputSystem.LowLevel
  16. {
  17. /// <summary>
  18. /// Record a history of state changes applied to one or more controls.
  19. /// </summary>
  20. /// <remarks>
  21. /// This class makes it easy to track input values over time. It will automatically retain input state up to a given
  22. /// maximum history depth (<see cref="historyDepth"/>). When the history is full, it will start overwriting the oldest
  23. /// entry each time a new history record is received.
  24. ///
  25. /// The class listens to changes on the given controls by adding change monitors (<see cref="IInputStateChangeMonitor"/>)
  26. /// to each control.
  27. ///
  28. /// <example>
  29. /// <code>
  30. /// // Track all stick controls in the system.
  31. /// var history = new InputStateHistory&lt;Vector2&gt;("*/&lt;Stick&gt;");
  32. /// foreach (var control in history.controls)
  33. /// Debug.Log("Capturing input on " + control);
  34. ///
  35. /// // Start capturing.
  36. /// history.StartRecording();
  37. ///
  38. /// // Perform a couple artificial value changes.
  39. /// Gamepad.current.leftStick.QueueValueChange(new Vector2(0.123f, 0.234f));
  40. /// Gamepad.current.leftStick.QueueValueChange(new Vector2(0.234f, 0.345f));
  41. /// Gamepad.current.leftStick.QueueValueChange(new Vector2(0.345f, 0.456f));
  42. /// InputSystem.Update();
  43. ///
  44. /// // Every value change will be visible in the history.
  45. /// foreach (var record in history)
  46. /// Debug.Log($"{record.control} changed value to {record.ReadValue()}");
  47. ///
  48. /// // Histories allocate unmanaged memory and must be disposed of in order to not leak.
  49. /// history.Dispose();
  50. /// </code>
  51. /// </example>
  52. /// </remarks>
  53. public class InputStateHistory : IDisposable, IEnumerable<InputStateHistory.Record>, IInputStateChangeMonitor
  54. {
  55. private const int kDefaultHistorySize = 128;
  56. /// <summary>
  57. /// Total number of state records currently captured in the history.
  58. /// </summary>
  59. /// <value>Number of records in the collection.</value>
  60. /// <remarks>
  61. /// This will always be at most <see cref="historyDepth"/>.
  62. /// </remarks>
  63. /// <seealso cref="historyDepth"/>
  64. /// <seealso cref="RecordStateChange(InputControl,InputEventPtr)"/>
  65. public int Count => m_RecordCount;
  66. /// <summary>
  67. /// Current version stamp. Every time a record is stored in the history,
  68. /// this is incremented by one.
  69. /// </summary>
  70. /// <value>Version stamp that indicates the number of mutations.</value>
  71. /// <seealso cref="RecordStateChange(InputControl,InputEventPtr)"/>
  72. public uint version => m_CurrentVersion;
  73. /// <summary>
  74. /// Maximum number of records that can be recorded in the history.
  75. /// </summary>
  76. /// <value>Upper limit on number of records.</value>
  77. /// <exception cref="ArgumentException"><paramref name="value"/> is negative.</exception>
  78. /// <remarks>
  79. /// A fixed size memory block of unmanaged memory will be allocated to store history
  80. /// records. This property determines TODO
  81. /// </remarks>
  82. public int historyDepth
  83. {
  84. get => m_HistoryDepth;
  85. set
  86. {
  87. if (value < 0)
  88. throw new ArgumentException("History depth cannot be negative", nameof(value));
  89. if (m_RecordBuffer.IsCreated)
  90. throw new NotImplementedException();
  91. m_HistoryDepth = value;
  92. }
  93. }
  94. public int extraMemoryPerRecord
  95. {
  96. get => m_ExtraMemoryPerRecord;
  97. set
  98. {
  99. if (value < 0)
  100. throw new ArgumentException("Memory size cannot be negative", nameof(value));
  101. if (m_RecordBuffer.IsCreated)
  102. throw new NotImplementedException();
  103. m_ExtraMemoryPerRecord = value;
  104. }
  105. }
  106. public InputUpdateType updateMask
  107. {
  108. get => m_UpdateMask ?? InputSystem.s_Manager.updateMask & ~InputUpdateType.Editor;
  109. set
  110. {
  111. if (value == InputUpdateType.None)
  112. throw new ArgumentException("'InputUpdateType.None' is not a valid update mask", nameof(value));
  113. m_UpdateMask = value;
  114. }
  115. }
  116. public ReadOnlyArray<InputControl> controls => new ReadOnlyArray<InputControl>(m_Controls, 0, m_ControlCount);
  117. public unsafe Record this[int index]
  118. {
  119. get
  120. {
  121. if (index < 0 || index >= m_RecordCount)
  122. throw new ArgumentOutOfRangeException(
  123. $"Index {index} is out of range for history with {m_RecordCount} entries", nameof(index));
  124. var recordIndex = UserIndexToRecordIndex(index);
  125. return new Record(this, recordIndex, GetRecord(recordIndex));
  126. }
  127. set
  128. {
  129. if (index < 0 || index >= m_RecordCount)
  130. throw new ArgumentOutOfRangeException(
  131. $"Index {index} is out of range for history with {m_RecordCount} entries", nameof(index));
  132. var recordIndex = UserIndexToRecordIndex(index);
  133. new Record(this, recordIndex, GetRecord(recordIndex)).CopyFrom(value);
  134. }
  135. }
  136. public Action<Record> onRecordAdded { get; set; }
  137. public Func<InputControl, double, InputEventPtr, bool> onShouldRecordStateChange { get; set; }
  138. public InputStateHistory(int maxStateSizeInBytes)
  139. {
  140. if (maxStateSizeInBytes <= 0)
  141. throw new ArgumentException("State size must be >= 0", nameof(maxStateSizeInBytes));
  142. m_AddNewControls = true;
  143. m_StateSizeInBytes = maxStateSizeInBytes.AlignToMultipleOf(4);
  144. }
  145. public InputStateHistory(string path)
  146. {
  147. using (var controls = InputSystem.FindControls(path))
  148. {
  149. m_Controls = controls.ToArray();
  150. m_ControlCount = m_Controls.Length;
  151. }
  152. }
  153. public InputStateHistory(InputControl control)
  154. {
  155. if (control == null)
  156. throw new ArgumentNullException(nameof(control));
  157. m_Controls = new[] {control};
  158. m_ControlCount = 1;
  159. }
  160. public InputStateHistory(IEnumerable<InputControl> controls)
  161. {
  162. if (controls != null)
  163. {
  164. m_Controls = controls.ToArray();
  165. m_ControlCount = m_Controls.Length;
  166. }
  167. }
  168. ~InputStateHistory()
  169. {
  170. Dispose();
  171. }
  172. public void Clear()
  173. {
  174. m_HeadIndex = 0;
  175. m_RecordCount = 0;
  176. ++m_CurrentVersion;
  177. // NOTE: Won't clear controls that have been added on the fly.
  178. }
  179. public unsafe Record AddRecord(Record record)
  180. {
  181. var recordPtr = AllocateRecord(out var index);
  182. var newRecord = new Record(this, index, recordPtr);
  183. newRecord.CopyFrom(record);
  184. return newRecord;
  185. }
  186. public void StartRecording()
  187. {
  188. // We defer allocation until we actually get values on a control.
  189. foreach (var control in controls)
  190. InputState.AddChangeMonitor(control, this);
  191. }
  192. public void StopRecording()
  193. {
  194. foreach (var control in controls)
  195. InputState.RemoveChangeMonitor(control, this);
  196. }
  197. public unsafe Record RecordStateChange(InputControl control, InputEventPtr eventPtr)
  198. {
  199. if (eventPtr.IsA<DeltaStateEvent>())
  200. throw new NotImplementedException();
  201. if (!eventPtr.IsA<StateEvent>())
  202. throw new ArgumentException($"Event must be a state event but is '{eventPtr}' instead",
  203. nameof(eventPtr));
  204. var statePtr = (byte*)StateEvent.From(eventPtr)->state - control.device.stateBlock.byteOffset;
  205. return RecordStateChange(control, statePtr, eventPtr.time);
  206. }
  207. public unsafe Record RecordStateChange(InputControl control, void* statePtr, double time)
  208. {
  209. var controlIndex = ArrayHelpers.IndexOfReference(m_Controls, control, m_ControlCount);
  210. if (controlIndex == -1)
  211. {
  212. if (m_AddNewControls)
  213. {
  214. if (control.stateBlock.alignedSizeInBytes > m_StateSizeInBytes)
  215. throw new InvalidOperationException(
  216. $"Cannot add control '{control}' with state larger than {m_StateSizeInBytes} bytes");
  217. controlIndex = ArrayHelpers.AppendWithCapacity(ref m_Controls, ref m_ControlCount, control);
  218. }
  219. else
  220. throw new ArgumentException($"Control '{control}' is not part of InputStateHistory",
  221. nameof(control));
  222. }
  223. var recordPtr = AllocateRecord(out var index);
  224. recordPtr->time = time;
  225. recordPtr->version = ++m_CurrentVersion;
  226. var stateBufferPtr = recordPtr->statePtrWithoutControlIndex;
  227. if (m_ControlCount > 1 || m_AddNewControls)
  228. {
  229. // If there's multiple controls, write index of control to which the state change
  230. // pertains as an int before the state memory contents following it.
  231. recordPtr->controlIndex = controlIndex;
  232. stateBufferPtr = recordPtr->statePtrWithControlIndex;
  233. }
  234. var stateSize = control.stateBlock.alignedSizeInBytes;
  235. var stateOffset = control.stateBlock.byteOffset;
  236. UnsafeUtility.MemCpy(stateBufferPtr, (byte*)statePtr + stateOffset, stateSize);
  237. // Trigger callback.
  238. var record = new Record(this, index, recordPtr);
  239. onRecordAdded?.Invoke(record);
  240. return record;
  241. }
  242. public IEnumerator<Record> GetEnumerator()
  243. {
  244. return new Enumerator(this);
  245. }
  246. IEnumerator IEnumerable.GetEnumerator()
  247. {
  248. return GetEnumerator();
  249. }
  250. public void Dispose()
  251. {
  252. StopRecording();
  253. Destroy();
  254. GC.SuppressFinalize(this);
  255. }
  256. protected void Destroy()
  257. {
  258. if (m_RecordBuffer.IsCreated)
  259. {
  260. m_RecordBuffer.Dispose();
  261. m_RecordBuffer = new NativeArray<byte>();
  262. }
  263. }
  264. private void Allocate()
  265. {
  266. // Find max size of state.
  267. if (!m_AddNewControls)
  268. {
  269. m_StateSizeInBytes = 0;
  270. foreach (var control in controls)
  271. m_StateSizeInBytes = (int)Math.Max((uint)m_StateSizeInBytes, control.stateBlock.alignedSizeInBytes);
  272. }
  273. else
  274. {
  275. Debug.Assert(m_StateSizeInBytes > 0, "State size must be have initialized!");
  276. }
  277. // Allocate historyDepth times state blocks of the given max size. For each one
  278. // add space for the RecordHeader header.
  279. // NOTE: If we only have a single control, we omit storing the integer control index.
  280. var totalSizeOfBuffer = bytesPerRecord * m_HistoryDepth;
  281. m_RecordBuffer = new NativeArray<byte>(totalSizeOfBuffer, Allocator.Persistent,
  282. NativeArrayOptions.UninitializedMemory);
  283. }
  284. protected internal int RecordIndexToUserIndex(int index)
  285. {
  286. if (index < m_HeadIndex)
  287. return m_HistoryDepth - m_HeadIndex + index;
  288. return index - m_HeadIndex;
  289. }
  290. protected internal int UserIndexToRecordIndex(int index)
  291. {
  292. return (m_HeadIndex + index) % m_HistoryDepth;
  293. }
  294. protected internal unsafe RecordHeader* GetRecord(int index)
  295. {
  296. if (!m_RecordBuffer.IsCreated)
  297. throw new InvalidOperationException("History buffer has been disposed");
  298. if (index < 0 || index >= m_HistoryDepth)
  299. throw new ArgumentOutOfRangeException(nameof(index));
  300. return GetRecordUnchecked(index);
  301. }
  302. internal unsafe RecordHeader* GetRecordUnchecked(int index)
  303. {
  304. return (RecordHeader*)((byte*)m_RecordBuffer.GetUnsafePtr() + index * bytesPerRecord);
  305. }
  306. protected internal unsafe RecordHeader* AllocateRecord(out int index)
  307. {
  308. if (!m_RecordBuffer.IsCreated)
  309. Allocate();
  310. index = (m_HeadIndex + m_RecordCount) % m_HistoryDepth;
  311. // If we're full, advance head to make room.
  312. if (m_RecordCount == m_HistoryDepth)
  313. m_HeadIndex = (m_HeadIndex + 1) % m_HistoryDepth;
  314. else
  315. {
  316. // We have a fixed max size given by the history depth and will start overwriting
  317. // older entries once we reached max size.
  318. ++m_RecordCount;
  319. }
  320. return (RecordHeader*)((byte*)m_RecordBuffer.GetUnsafePtr() + bytesPerRecord * index);
  321. }
  322. protected unsafe TValue ReadValue<TValue>(RecordHeader* data)
  323. where TValue : struct
  324. {
  325. // Get control. If we only have a single one, the index isn't stored on the data.
  326. var haveSingleControl = m_ControlCount == 1 && !m_AddNewControls;
  327. var control = haveSingleControl ? controls[0] : controls[data->controlIndex];
  328. if (!(control is InputControl<TValue> controlOfType))
  329. throw new InvalidOperationException(
  330. $"Cannot read value of type '{TypeHelpers.GetNiceTypeName(typeof(TValue))}' from control '{control}' with value type '{TypeHelpers.GetNiceTypeName(control.valueType)}'");
  331. // Grab state memory.
  332. var statePtr = haveSingleControl ? data->statePtrWithoutControlIndex : data->statePtrWithControlIndex;
  333. statePtr -= control.stateBlock.byteOffset;
  334. return controlOfType.ReadValueFromState(statePtr);
  335. }
  336. protected unsafe object ReadValueAsObject(RecordHeader* data)
  337. {
  338. // Get control. If we only have a single one, the index isn't stored on the data.
  339. var haveSingleControl = m_ControlCount == 1 && !m_AddNewControls;
  340. var control = haveSingleControl ? controls[0] : controls[data->controlIndex];
  341. // Grab state memory.
  342. var statePtr = haveSingleControl ? data->statePtrWithoutControlIndex : data->statePtrWithControlIndex;
  343. statePtr -= control.stateBlock.byteOffset;
  344. return control.ReadValueFromStateAsObject(statePtr);
  345. }
  346. unsafe void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl control, double time,
  347. InputEventPtr eventPtr, long monitorIndex)
  348. {
  349. // Ignore state change if it's in an input update we're not interested in.
  350. var currentUpdateType = InputState.currentUpdateType;
  351. var updateTypeMask = updateMask;
  352. if ((currentUpdateType & updateTypeMask) == 0)
  353. return;
  354. // Ignore state change if we have a filter and the state change doesn't pass the check.
  355. if (onShouldRecordStateChange != null && !onShouldRecordStateChange(control, time, eventPtr))
  356. return;
  357. RecordStateChange(control, control.currentStatePtr, time);
  358. }
  359. // Unused.
  360. void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double time, long monitorIndex,
  361. int timerIndex)
  362. {
  363. }
  364. internal InputControl[] m_Controls;
  365. internal int m_ControlCount;
  366. private NativeArray<byte> m_RecordBuffer;
  367. private int m_StateSizeInBytes;
  368. private int m_RecordCount;
  369. private int m_HistoryDepth = kDefaultHistorySize;
  370. private int m_ExtraMemoryPerRecord;
  371. internal int m_HeadIndex;
  372. internal uint m_CurrentVersion;
  373. private InputUpdateType? m_UpdateMask;
  374. internal readonly bool m_AddNewControls;
  375. internal int bytesPerRecord =>
  376. (m_StateSizeInBytes +
  377. m_ExtraMemoryPerRecord +
  378. (m_ControlCount == 1 && !m_AddNewControls
  379. ? RecordHeader.kSizeWithoutControlIndex
  380. : RecordHeader.kSizeWithControlIndex)).AlignToMultipleOf(4);
  381. private struct Enumerator : IEnumerator<Record>
  382. {
  383. private readonly InputStateHistory m_History;
  384. private int m_Index;
  385. public Enumerator(InputStateHistory history)
  386. {
  387. m_History = history;
  388. m_Index = -1;
  389. }
  390. public bool MoveNext()
  391. {
  392. if (m_Index + 1 >= m_History.Count)
  393. return false;
  394. ++m_Index;
  395. return true;
  396. }
  397. public void Reset()
  398. {
  399. m_Index = -1;
  400. }
  401. public Record Current => m_History[m_Index];
  402. object IEnumerator.Current => Current;
  403. public void Dispose()
  404. {
  405. }
  406. }
  407. [StructLayout(LayoutKind.Explicit)]
  408. protected internal unsafe struct RecordHeader
  409. {
  410. [FieldOffset(0)] public double time;
  411. [FieldOffset(8)] public uint version;
  412. [FieldOffset(12)] public int controlIndex;
  413. [FieldOffset(12)] private fixed byte m_StateWithoutControlIndex[1];
  414. [FieldOffset(16)] private fixed byte m_StateWithControlIndex[1];
  415. public byte* statePtrWithControlIndex
  416. {
  417. get
  418. {
  419. fixed(byte* ptr = m_StateWithControlIndex)
  420. return ptr;
  421. }
  422. }
  423. public byte* statePtrWithoutControlIndex
  424. {
  425. get
  426. {
  427. fixed(byte* ptr = m_StateWithoutControlIndex)
  428. return ptr;
  429. }
  430. }
  431. public const int kSizeWithControlIndex = 16;
  432. public const int kSizeWithoutControlIndex = 12;
  433. }
  434. public unsafe struct Record : IEquatable<Record>
  435. {
  436. // We store an index rather than a direct pointer to make this struct safer to use.
  437. private readonly InputStateHistory m_Owner;
  438. private readonly int m_IndexPlusOne; // Plus one so that default(int) works for us.
  439. private uint m_Version;
  440. internal RecordHeader* header => m_Owner.GetRecord(recordIndex);
  441. internal int recordIndex => m_IndexPlusOne - 1;
  442. internal uint version => m_Version;
  443. public bool valid => m_Owner != default && m_IndexPlusOne != default && header->version == m_Version;
  444. public InputStateHistory owner => m_Owner;
  445. public int index
  446. {
  447. get
  448. {
  449. CheckValid();
  450. return m_Owner.RecordIndexToUserIndex(recordIndex);
  451. }
  452. }
  453. public double time
  454. {
  455. get
  456. {
  457. CheckValid();
  458. return header->time;
  459. }
  460. }
  461. public InputControl control
  462. {
  463. get
  464. {
  465. CheckValid();
  466. var controls = m_Owner.controls;
  467. if (controls.Count == 1 && !m_Owner.m_AddNewControls)
  468. return controls[0];
  469. return controls[header->controlIndex];
  470. }
  471. }
  472. public Record next
  473. {
  474. get
  475. {
  476. CheckValid();
  477. var userIndex = m_Owner.RecordIndexToUserIndex(this.recordIndex);
  478. if (userIndex + 1 >= m_Owner.Count)
  479. return default;
  480. var recordIndex = m_Owner.UserIndexToRecordIndex(userIndex + 1);
  481. return new Record(m_Owner, recordIndex, m_Owner.GetRecord(recordIndex));
  482. }
  483. }
  484. public Record previous
  485. {
  486. get
  487. {
  488. CheckValid();
  489. var userIndex = m_Owner.RecordIndexToUserIndex(this.recordIndex);
  490. if (userIndex - 1 < 0)
  491. return default;
  492. var recordIndex = m_Owner.UserIndexToRecordIndex(userIndex - 1);
  493. return new Record(m_Owner, recordIndex, m_Owner.GetRecord(recordIndex));
  494. }
  495. }
  496. internal Record(InputStateHistory owner, int index, RecordHeader* header)
  497. {
  498. m_Owner = owner;
  499. m_IndexPlusOne = index + 1;
  500. m_Version = header->version;
  501. }
  502. public TValue ReadValue<TValue>()
  503. where TValue : struct
  504. {
  505. CheckValid();
  506. return m_Owner.ReadValue<TValue>(header);
  507. }
  508. public object ReadValueAsObject()
  509. {
  510. CheckValid();
  511. return m_Owner.ReadValueAsObject(header);
  512. }
  513. public void* GetUnsafeMemoryPtr()
  514. {
  515. CheckValid();
  516. return GetUnsafeMemoryPtrUnchecked();
  517. }
  518. internal void* GetUnsafeMemoryPtrUnchecked()
  519. {
  520. if (m_Owner.controls.Count == 1 && !m_Owner.m_AddNewControls)
  521. return header->statePtrWithoutControlIndex;
  522. return header->statePtrWithControlIndex;
  523. }
  524. public void* GetUnsafeExtraMemoryPtr()
  525. {
  526. CheckValid();
  527. return GetUnsafeExtraMemoryPtrUnchecked();
  528. }
  529. internal void* GetUnsafeExtraMemoryPtrUnchecked()
  530. {
  531. if (m_Owner.extraMemoryPerRecord == 0)
  532. throw new InvalidOperationException("No extra memory has been set up for history records; set extraMemoryPerRecord");
  533. return (byte*)header + m_Owner.bytesPerRecord - m_Owner.extraMemoryPerRecord;
  534. }
  535. public void CopyFrom(Record record)
  536. {
  537. if (!record.valid)
  538. throw new ArgumentException("Given history record is not valid", nameof(record));
  539. CheckValid();
  540. // Find control.
  541. var control = record.control;
  542. var controlIndex = m_Owner.controls.IndexOfReference(control);
  543. if (controlIndex == -1)
  544. {
  545. // We haven't found it. Throw if we can't add it.
  546. if (!m_Owner.m_AddNewControls)
  547. throw new InvalidOperationException($"Control '{record.control}' is not tracked by target history");
  548. controlIndex =
  549. ArrayHelpers.AppendWithCapacity(ref m_Owner.m_Controls, ref m_Owner.m_ControlCount, control);
  550. }
  551. // Make sure memory sizes match.
  552. var numBytesForState = m_Owner.m_StateSizeInBytes;
  553. if (numBytesForState != record.m_Owner.m_StateSizeInBytes)
  554. throw new InvalidOperationException(
  555. $"Cannot copy record from owner with state size '{record.m_Owner.m_StateSizeInBytes}' to owner with state size '{numBytesForState}'");
  556. // Copy and update header.
  557. var thisRecordPtr = header;
  558. var otherRecordPtr = record.header;
  559. UnsafeUtility.MemCpy(thisRecordPtr, otherRecordPtr, RecordHeader.kSizeWithoutControlIndex);
  560. thisRecordPtr->version = ++m_Owner.m_CurrentVersion;
  561. m_Version = thisRecordPtr->version;
  562. // Copy state.
  563. var dstPtr = thisRecordPtr->statePtrWithoutControlIndex;
  564. if (m_Owner.controls.Count > 1 || m_Owner.m_AddNewControls)
  565. {
  566. thisRecordPtr->controlIndex = controlIndex;
  567. dstPtr = thisRecordPtr->statePtrWithControlIndex;
  568. }
  569. var srcPtr = record.m_Owner.m_ControlCount > 1 || record.m_Owner.m_AddNewControls
  570. ? otherRecordPtr->statePtrWithControlIndex
  571. : otherRecordPtr->statePtrWithoutControlIndex;
  572. UnsafeUtility.MemCpy(dstPtr, srcPtr, numBytesForState);
  573. // Copy extra memory, but only if the size in the source and target
  574. // history are identical.
  575. var numBytesExtraMemory = m_Owner.m_ExtraMemoryPerRecord;
  576. if (numBytesExtraMemory > 0 && numBytesExtraMemory == record.m_Owner.m_ExtraMemoryPerRecord)
  577. UnsafeUtility.MemCpy(GetUnsafeExtraMemoryPtr(), record.GetUnsafeExtraMemoryPtr(),
  578. numBytesExtraMemory);
  579. // Notify.
  580. m_Owner.onRecordAdded?.Invoke(this);
  581. }
  582. internal void CheckValid()
  583. {
  584. if (m_Owner == default || m_IndexPlusOne == default)
  585. throw new InvalidOperationException("Value not initialized");
  586. ////TODO: need to check whether memory has been disposed
  587. if (header->version != m_Version)
  588. throw new InvalidOperationException("Record is no longer valid");
  589. }
  590. public bool Equals(Record other)
  591. {
  592. return ReferenceEquals(m_Owner, other.m_Owner) && m_IndexPlusOne == other.m_IndexPlusOne && m_Version == other.m_Version;
  593. }
  594. public override bool Equals(object obj)
  595. {
  596. return obj is Record other && Equals(other);
  597. }
  598. public override int GetHashCode()
  599. {
  600. unchecked
  601. {
  602. var hashCode = m_Owner != null ? m_Owner.GetHashCode() : 0;
  603. hashCode = (hashCode * 397) ^ m_IndexPlusOne;
  604. hashCode = (hashCode * 397) ^ (int)m_Version;
  605. return hashCode;
  606. }
  607. }
  608. public override string ToString()
  609. {
  610. if (!valid)
  611. return "<Invalid>";
  612. return $"{{ control={control} value={ReadValueAsObject()} time={time} }}";
  613. }
  614. }
  615. }
  616. /// <summary>
  617. /// Records value changes of a given control over time.
  618. /// </summary>
  619. /// <typeparam name="TValue"></typeparam>
  620. public class InputStateHistory<TValue> : InputStateHistory, IReadOnlyList<InputStateHistory<TValue>.Record>
  621. where TValue : struct
  622. {
  623. public InputStateHistory(int? maxStateSizeInBytes = null)
  624. // Using the size of the value here isn't quite correct but the value is used as an upper
  625. // bound on stored state size for which the size of the value should be a reasonable guess.
  626. : base(maxStateSizeInBytes ?? UnsafeUtility.SizeOf<TValue>())
  627. {
  628. if (maxStateSizeInBytes < UnsafeUtility.SizeOf<TValue>())
  629. throw new ArgumentException("Max state size cannot be smaller than sizeof(TValue)", nameof(maxStateSizeInBytes));
  630. }
  631. public InputStateHistory(InputControl<TValue> control)
  632. : base(control)
  633. {
  634. }
  635. public InputStateHistory(string path)
  636. : base(path)
  637. {
  638. // Make sure that the value type of all matched controls is compatible with TValue.
  639. foreach (var control in controls)
  640. if (!typeof(TValue).IsAssignableFrom(control.valueType))
  641. throw new ArgumentException(
  642. $"Control '{control}' matched by '{path}' has value type '{TypeHelpers.GetNiceTypeName(control.valueType)}' which is incompatible with '{TypeHelpers.GetNiceTypeName(typeof(TValue))}'");
  643. }
  644. ~InputStateHistory()
  645. {
  646. Destroy();
  647. }
  648. public unsafe Record AddRecord(Record record)
  649. {
  650. var recordPtr = AllocateRecord(out var index);
  651. var newRecord = new Record(this, index, recordPtr);
  652. newRecord.CopyFrom(record);
  653. return newRecord;
  654. }
  655. public unsafe Record RecordStateChange(InputControl<TValue> control, TValue value, double time = -1)
  656. {
  657. using (StateEvent.From(control.device, out var eventPtr))
  658. {
  659. var statePtr = (byte*)StateEvent.From(eventPtr)->state - control.device.stateBlock.byteOffset;
  660. control.WriteValueIntoState(value, statePtr);
  661. if (time >= 0)
  662. eventPtr.time = time;
  663. var record = RecordStateChange(control, eventPtr);
  664. return new Record(this, record.recordIndex, record.header);
  665. }
  666. }
  667. public new IEnumerator<Record> GetEnumerator()
  668. {
  669. return new Enumerator(this);
  670. }
  671. IEnumerator IEnumerable.GetEnumerator()
  672. {
  673. return GetEnumerator();
  674. }
  675. public new unsafe Record this[int index]
  676. {
  677. get
  678. {
  679. if (index < 0 || index >= Count)
  680. throw new ArgumentOutOfRangeException(
  681. $"Index {index} is out of range for history with {Count} entries", nameof(index));
  682. var recordIndex = UserIndexToRecordIndex(index);
  683. return new Record(this, recordIndex, GetRecord(recordIndex));
  684. }
  685. set
  686. {
  687. if (index < 0 || index >= Count)
  688. throw new ArgumentOutOfRangeException(
  689. $"Index {index} is out of range for history with {Count} entries", nameof(index));
  690. var recordIndex = UserIndexToRecordIndex(index);
  691. new Record(this, recordIndex, GetRecord(recordIndex)).CopyFrom(value);
  692. }
  693. }
  694. private struct Enumerator : IEnumerator<Record>
  695. {
  696. private readonly InputStateHistory<TValue> m_History;
  697. private int m_Index;
  698. public Enumerator(InputStateHistory<TValue> history)
  699. {
  700. m_History = history;
  701. m_Index = -1;
  702. }
  703. public bool MoveNext()
  704. {
  705. if (m_Index + 1 >= m_History.Count)
  706. return false;
  707. ++m_Index;
  708. return true;
  709. }
  710. public void Reset()
  711. {
  712. m_Index = -1;
  713. }
  714. public Record Current => m_History[m_Index];
  715. object IEnumerator.Current => Current;
  716. public void Dispose()
  717. {
  718. }
  719. }
  720. public new unsafe struct Record : IEquatable<Record>
  721. {
  722. private readonly InputStateHistory<TValue> m_Owner;
  723. private readonly int m_IndexPlusOne;
  724. private uint m_Version;
  725. internal RecordHeader* header => m_Owner.GetRecord(recordIndex);
  726. internal int recordIndex => m_IndexPlusOne - 1;
  727. public bool valid => m_Owner != default && m_IndexPlusOne != default && header->version == m_Version;
  728. public InputStateHistory<TValue> owner => m_Owner;
  729. public int index
  730. {
  731. get
  732. {
  733. CheckValid();
  734. return m_Owner.RecordIndexToUserIndex(recordIndex);
  735. }
  736. }
  737. public double time
  738. {
  739. get
  740. {
  741. CheckValid();
  742. return header->time;
  743. }
  744. }
  745. public InputControl<TValue> control
  746. {
  747. get
  748. {
  749. CheckValid();
  750. var controls = m_Owner.controls;
  751. if (controls.Count == 1 && !m_Owner.m_AddNewControls)
  752. return (InputControl<TValue>)controls[0];
  753. return (InputControl<TValue>)controls[header->controlIndex];
  754. }
  755. }
  756. public Record next
  757. {
  758. get
  759. {
  760. CheckValid();
  761. var userIndex = m_Owner.RecordIndexToUserIndex(this.recordIndex);
  762. if (userIndex + 1 >= m_Owner.Count)
  763. return default;
  764. var recordIndex = m_Owner.UserIndexToRecordIndex(userIndex + 1);
  765. return new Record(m_Owner, recordIndex, m_Owner.GetRecord(recordIndex));
  766. }
  767. }
  768. public Record previous
  769. {
  770. get
  771. {
  772. CheckValid();
  773. var userIndex = m_Owner.RecordIndexToUserIndex(this.recordIndex);
  774. if (userIndex - 1 < 0)
  775. return default;
  776. var recordIndex = m_Owner.UserIndexToRecordIndex(userIndex - 1);
  777. return new Record(m_Owner, recordIndex, m_Owner.GetRecord(recordIndex));
  778. }
  779. }
  780. internal Record(InputStateHistory<TValue> owner, int index, RecordHeader* header)
  781. {
  782. m_Owner = owner;
  783. m_IndexPlusOne = index + 1;
  784. m_Version = header->version;
  785. }
  786. internal Record(InputStateHistory<TValue> owner, int index)
  787. {
  788. m_Owner = owner;
  789. m_IndexPlusOne = index + 1;
  790. m_Version = default;
  791. }
  792. public TValue ReadValue()
  793. {
  794. CheckValid();
  795. return m_Owner.ReadValue<TValue>(header);
  796. }
  797. public void* GetUnsafeMemoryPtr()
  798. {
  799. CheckValid();
  800. return GetUnsafeMemoryPtrUnchecked();
  801. }
  802. internal void* GetUnsafeMemoryPtrUnchecked()
  803. {
  804. if (m_Owner.controls.Count == 1 && !m_Owner.m_AddNewControls)
  805. return header->statePtrWithoutControlIndex;
  806. return header->statePtrWithControlIndex;
  807. }
  808. public void* GetUnsafeExtraMemoryPtr()
  809. {
  810. CheckValid();
  811. return GetUnsafeExtraMemoryPtrUnchecked();
  812. }
  813. internal void* GetUnsafeExtraMemoryPtrUnchecked()
  814. {
  815. if (m_Owner.extraMemoryPerRecord == 0)
  816. throw new InvalidOperationException("No extra memory has been set up for history records; set extraMemoryPerRecord");
  817. return (byte*)header + m_Owner.bytesPerRecord - m_Owner.extraMemoryPerRecord;
  818. }
  819. public void CopyFrom(Record record)
  820. {
  821. CheckValid();
  822. if (!record.valid)
  823. throw new ArgumentException("Given history record is not valid", nameof(record));
  824. var temp = new InputStateHistory.Record(m_Owner, recordIndex, header);
  825. temp.CopyFrom(new InputStateHistory.Record(record.m_Owner, record.recordIndex, record.header));
  826. m_Version = temp.version;
  827. }
  828. private void CheckValid()
  829. {
  830. if (m_Owner == default || m_IndexPlusOne == default)
  831. throw new InvalidOperationException("Value not initialized");
  832. if (header->version != m_Version)
  833. throw new InvalidOperationException("Record is no longer valid");
  834. }
  835. public bool Equals(Record other)
  836. {
  837. return ReferenceEquals(m_Owner, other.m_Owner) && m_IndexPlusOne == other.m_IndexPlusOne && m_Version == other.m_Version;
  838. }
  839. public override bool Equals(object obj)
  840. {
  841. return obj is Record other && Equals(other);
  842. }
  843. public override int GetHashCode()
  844. {
  845. unchecked
  846. {
  847. var hashCode = m_Owner != null ? m_Owner.GetHashCode() : 0;
  848. hashCode = (hashCode * 397) ^ m_IndexPlusOne;
  849. hashCode = (hashCode * 397) ^ (int)m_Version;
  850. return hashCode;
  851. }
  852. }
  853. public override string ToString()
  854. {
  855. if (!valid)
  856. return "<Invalid>";
  857. return $"{{ control={control} value={ReadValue()} time={time} }}";
  858. }
  859. }
  860. }
  861. }