A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.IO; 5using UnityEngine.InputSystem.Utilities; 6using Unity.Collections; 7using Unity.Collections.LowLevel.Unsafe; 8using UnityEngine.InputSystem.Layouts; 9using Unity.Profiling; 10 11namespace UnityEngine.InputSystem.LowLevel 12{ 13 /// <summary> 14 /// InputEventTrace lets you record input events for later processing. It also has features for writing traces 15 /// to disk, for loading them from disk, and for playing back previously recorded traces. 16 /// </summary> 17 /// <remarks> 18 /// InputEventTrace lets you record input events into a buffer for either a specific device, or for all events 19 /// received by the input system. This is useful for testing purposes or for replaying recorded input. 20 /// 21 /// Note that event traces <em>must</em> be disposed of (by calling <see cref="Dispose"/>) after use or they 22 /// will leak memory on the unmanaged (C++) memory heap. 23 /// 24 /// Event traces are serializable such that they can survive domain reloads in the editor. 25 /// </remarks> 26 [Serializable] 27 public sealed unsafe class InputEventTrace : IDisposable, IEnumerable<InputEventPtr> 28 { 29 private const int kDefaultBufferSize = 1024 * 1024; 30 private static readonly ProfilerMarker k_InputEvenTraceMarker = new ProfilerMarker("InputEventTrace"); 31 32 /// <summary> 33 /// If <see name="recordFrameMarkers"/> is enabled, an <see cref="InputEvent"/> with this <see cref="FourCC"/> 34 /// code in its <see cref="InputEvent.type"/> is recorded whenever the input system starts a new update, i.e. 35 /// whenever <see cref="InputSystem.onBeforeUpdate"/> is triggered. This is useful for replaying events in such 36 /// a way that they are correctly spaced out over frames. 37 /// </summary> 38 public static FourCC FrameMarkerEvent => new FourCC('F', 'R', 'M', 'E'); 39 40 /// <summary> 41 /// Set device to record events for. Set to <see cref="InputDevice.InvalidDeviceId"/> by default 42 /// in which case events from all devices are recorded. 43 /// </summary> 44 public int deviceId 45 { 46 get => m_DeviceId; 47 set => m_DeviceId = value; 48 } 49 50 /// <summary> 51 /// Whether the trace is currently recording input. 52 /// </summary> 53 /// <value>True if the trace is currently recording events.</value> 54 /// <seealso cref="Enable"/> 55 /// <seealso cref="Disable"/> 56 public bool enabled => m_Enabled; 57 58 /// <summary> 59 /// If true, input update boundaries will be recorded as events. By default, this is off. 60 /// </summary> 61 /// <value>Whether frame boundaries should be recorded in the trace.</value> 62 /// <remarks> 63 /// When recording with this off, all events are written one after the other for as long 64 /// as the recording is active. This means that when a recording runs over multiple frames, 65 /// it is no longer possible for the trace to tell which events happened in distinct frames. 66 /// 67 /// By turning this feature on, frame marker events (i.e. <see cref="InputEvent"/> instances 68 /// with <see cref="InputEvent.type"/> set to <see cref="FrameMarkerEvent"/>) will be written 69 /// to the trace every time an input update occurs. When playing such a trace back via <see 70 /// cref="ReplayController.PlayAllFramesOneByOne"/>, events will get spaced out over frames corresponding 71 /// to how they were spaced out when input was initially recorded. 72 /// 73 /// Note that having this feature enabled will fill up traces much quicker. Instead of being 74 /// filled up only when there is input, TODO 75 /// </remarks> 76 /// <seealso cref="ReplayController.PlayAllFramesOneByOne"/> 77 /// <seealso cref="FrameMarkerEvent"/> 78 public bool recordFrameMarkers 79 { 80 get => m_RecordFrameMarkers; 81 set 82 { 83 if (m_RecordFrameMarkers == value) 84 return; 85 m_RecordFrameMarkers = value; 86 if (m_Enabled) 87 { 88 if (value) 89 InputSystem.onBeforeUpdate += OnBeforeUpdate; 90 else 91 InputSystem.onBeforeUpdate -= OnBeforeUpdate; 92 } 93 } 94 } 95 96 /// <summary> 97 /// Total number of events currently in the trace. 98 /// </summary> 99 /// <value>Number of events recorded in the trace.</value> 100 public long eventCount => m_EventCount; 101 102 /// <summary> 103 /// The amount of memory consumed by all events combined that are currently 104 /// stored in the trace. 105 /// </summary> 106 /// <value>Total size of event data currently in trace.</value> 107 public long totalEventSizeInBytes => m_EventSizeInBytes; 108 109 /// <summary> 110 /// Total size of memory buffer (in bytes) currently allocated. 111 /// </summary> 112 /// <value>Size of memory currently allocated.</value> 113 /// <remarks> 114 /// The buffer is allocated on the unmanaged heap. 115 /// </remarks> 116 public long allocatedSizeInBytes => m_EventBuffer != default ? m_EventBufferSize : 0; 117 118 /// <summary> 119 /// Largest size (in bytes) that the memory buffer is allowed to grow to. By default, this is 120 /// the same as <see cref="allocatedSizeInBytes"/> meaning that the buffer is not allowed to grow but will 121 /// rather wrap around when full. 122 /// </summary> 123 /// <value>Largest size the memory buffer is allowed to grow to.</value> 124 public long maxSizeInBytes => m_MaxEventBufferSize; 125 126 /// <summary> 127 /// Information about all devices for which events have been recorded in the trace. 128 /// </summary> 129 /// <value>Record of devices recorded in the trace.</value> 130 public ReadOnlyArray<DeviceInfo> deviceInfos => m_DeviceInfos; 131 132 /// <summary> 133 /// Optional delegate to decide whether an input should be stored in a trace. Null by default. 134 /// </summary> 135 /// <value>Delegate to accept or reject individual events.</value> 136 /// <remarks> 137 /// When this is set, the callback will be invoked on every event that would otherwise be stored 138 /// directly in the trace. If the callback returns <c>true</c>, the trace will continue to record 139 /// the event. If the callback returns <c>false</c>, the event will be ignored and not recorded. 140 /// 141 /// The callback should generally mutate the event. If you do so, note that this will impact 142 /// event processing in general, not just recording of the event in the trace. 143 /// </remarks> 144 public Func<InputEventPtr, InputDevice, bool> onFilterEvent 145 { 146 get => m_OnFilterEvent; 147 set => m_OnFilterEvent = value; 148 } 149 150 /// <summary> 151 /// Event that is triggered every time an event has been recorded in the trace. 152 /// </summary> 153 public event Action<InputEventPtr> onEvent 154 { 155 add => m_EventListeners.AddCallback(value); 156 remove => m_EventListeners.RemoveCallback(value); 157 } 158 159 public InputEventTrace(InputDevice device, long bufferSizeInBytes = kDefaultBufferSize, bool growBuffer = false, 160 long maxBufferSizeInBytes = -1, long growIncrementSizeInBytes = -1) 161 : this(bufferSizeInBytes, growBuffer, maxBufferSizeInBytes, growIncrementSizeInBytes) 162 { 163 if (device == null) 164 throw new ArgumentNullException(nameof(device)); 165 166 m_DeviceId = device.deviceId; 167 } 168 169 /// <summary> 170 /// Create a disabled event trace that does not perform any allocation yet. An event trace only starts consuming resources 171 /// the first time it is enabled. 172 /// </summary> 173 /// <param name="bufferSizeInBytes">Size of buffer that will be allocated on first event captured by trace. Defaults to 1MB.</param> 174 /// <param name="growBuffer">If true, the event buffer will be grown automatically when it reaches capacity, up to a maximum 175 /// size of <paramref name="maxBufferSizeInBytes"/>. This is off by default.</param> 176 /// <param name="maxBufferSizeInBytes">If <paramref name="growBuffer"/> is true, this is the maximum size that the buffer should 177 /// be grown to. If the maximum size is reached, old events are being overwritten.</param> 178 public InputEventTrace(long bufferSizeInBytes = kDefaultBufferSize, bool growBuffer = false, long maxBufferSizeInBytes = -1, long growIncrementSizeInBytes = -1) 179 { 180 m_EventBufferSize = (uint)bufferSizeInBytes; 181 182 if (growBuffer) 183 { 184 if (maxBufferSizeInBytes < 0) 185 m_MaxEventBufferSize = 256 * kDefaultBufferSize; 186 else 187 m_MaxEventBufferSize = maxBufferSizeInBytes; 188 189 if (growIncrementSizeInBytes < 0) 190 m_GrowIncrementSize = kDefaultBufferSize; 191 else 192 m_GrowIncrementSize = growIncrementSizeInBytes; 193 } 194 else 195 { 196 m_MaxEventBufferSize = m_EventBufferSize; 197 } 198 } 199 200 /// <summary> 201 /// Write the contents of the event trace to a file. 202 /// </summary> 203 /// <param name="filePath">Path of the file to write.</param> 204 /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is <c>null</c> or empty.</exception> 205 /// <exception cref="FileNotFoundException"><paramref name="filePath"/> is invalid.</exception> 206 /// <exception cref="DirectoryNotFoundException">A directory in <paramref name="filePath"/> is invalid.</exception> 207 /// <exception cref="UnauthorizedAccessException"><paramref name="filePath"/> cannot be accessed.</exception> 208 /// <seealso cref="ReadFrom(string)"/> 209 public void WriteTo(string filePath) 210 { 211 if (string.IsNullOrEmpty(filePath)) 212 throw new ArgumentNullException(nameof(filePath)); 213 214 using (var stream = File.OpenWrite(filePath)) 215 WriteTo(stream); 216 } 217 218 /// <summary> 219 /// Write the contents of the event trace to the given stream. 220 /// </summary> 221 /// <param name="stream">Stream to write the data to. Must support seeking (i.e. <c>Stream.canSeek</c> must be true).</param> 222 /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception> 223 /// <exception cref="ArgumentException"><paramref name="stream"/> does not support seeking.</exception> 224 /// <exception cref="IOException">An error occurred trying to write to <paramref name="stream"/>.</exception> 225 public void WriteTo(Stream stream) 226 { 227 if (stream == null) 228 throw new ArgumentNullException(nameof(stream)); 229 if (!stream.CanSeek) 230 throw new ArgumentException("Stream does not support seeking", nameof(stream)); 231 232 var writer = new BinaryWriter(stream); 233 234 var flags = default(FileFlags); 235 if (InputSystem.settings.updateMode == InputSettings.UpdateMode.ProcessEventsInFixedUpdate) 236 flags |= FileFlags.FixedUpdate; 237 238 // Write header. 239 writer.Write(kFileFormat); 240 writer.Write(kFileVersion); 241 writer.Write((int)flags); 242 writer.Write((int)Application.platform); 243 writer.Write((ulong)m_EventCount); 244 writer.Write((ulong)m_EventSizeInBytes); 245 246 // Write events. 247 foreach (var eventPtr in this) 248 { 249 ////TODO: find way to directly write a byte* buffer to the stream instead of copying to a temp byte[] 250 251 var sizeInBytes = eventPtr.sizeInBytes; 252 var buffer = new byte[sizeInBytes]; 253 fixed(byte* bufferPtr = buffer) 254 { 255 UnsafeUtility.MemCpy(bufferPtr, eventPtr.data, sizeInBytes); 256 writer.Write(buffer); 257 } 258 } 259 260 // Write devices. 261 writer.Flush(); 262 var positionOfDeviceList = stream.Position; 263 var deviceCount = m_DeviceInfos.LengthSafe(); 264 writer.Write(deviceCount); 265 for (var i = 0; i < deviceCount; ++i) 266 { 267 ref var device = ref m_DeviceInfos[i]; 268 writer.Write(device.deviceId); 269 writer.Write(device.layout); 270 writer.Write(device.stateFormat); 271 writer.Write(device.stateSizeInBytes); 272 writer.Write(device.m_FullLayoutJson ?? string.Empty); 273 } 274 275 // Write offset of device list. 276 writer.Flush(); 277 var offsetOfDeviceList = stream.Position - positionOfDeviceList; 278 writer.Write(offsetOfDeviceList); 279 } 280 281 /// <summary> 282 /// Read the contents of an input event trace stored in the given file. 283 /// </summary> 284 /// <param name="filePath">Path to a file.</param> 285 /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is <c>null</c> or empty.</exception> 286 /// <exception cref="FileNotFoundException"><paramref name="filePath"/> is invalid.</exception> 287 /// <exception cref="DirectoryNotFoundException">A directory in <paramref name="filePath"/> is invalid.</exception> 288 /// <exception cref="UnauthorizedAccessException"><paramref name="filePath"/> cannot be accessed.</exception> 289 /// <remarks> 290 /// This method replaces the contents of the trace with those read from the given file. 291 /// </remarks> 292 /// <seealso cref="WriteTo(string)"/> 293 public void ReadFrom(string filePath) 294 { 295 if (string.IsNullOrEmpty(filePath)) 296 throw new ArgumentNullException(nameof(filePath)); 297 298 using (var stream = File.OpenRead(filePath)) 299 ReadFrom(stream); 300 } 301 302 /// <summary> 303 /// Read the contents of an input event trace from the given stream. 304 /// </summary> 305 /// <param name="stream">A stream of binary data containing a recorded event trace as written out with <see cref="WriteTo(Stream)"/>. 306 /// Must support reading.</param> 307 /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception> 308 /// <exception cref="ArgumentException"><paramref name="stream"/> does not support reading.</exception> 309 /// <exception cref="IOException">An error occurred trying to read from <paramref name="stream"/>.</exception> 310 /// <remarks> 311 /// This method replaces the contents of the event trace with those read from the stream. It does not append 312 /// to the existing trace. 313 /// </remarks> 314 /// <seealso cref="WriteTo(Stream)"/> 315 public void ReadFrom(Stream stream) 316 { 317 if (stream == null) 318 throw new ArgumentNullException(nameof(stream)); 319 if (!stream.CanRead) 320 throw new ArgumentException("Stream does not support reading", nameof(stream)); 321 322 var reader = new BinaryReader(stream); 323 324 // Read header. 325 if (reader.ReadInt32() != kFileFormat) 326 throw new IOException($"Stream does not appear to be an InputEventTrace (no '{kFileFormat}' code)"); 327 if (reader.ReadInt32() > kFileVersion) 328 throw new IOException($"Stream is an InputEventTrace but a newer version (expected version {kFileVersion} or below)"); 329 reader.ReadInt32(); // Flags; ignored for now. 330 reader.ReadInt32(); // Platform; for now we're not doing anything with it. 331 var eventCount = reader.ReadUInt64(); 332 var totalEventSizeInBytes = reader.ReadUInt64(); 333 var oldBuffer = m_EventBuffer; 334 335 if (eventCount > 0 && totalEventSizeInBytes > 0) 336 { 337 // Allocate buffer, if need be. 338 byte* buffer; 339 if (m_EventBuffer != null && m_EventBufferSize >= (long)totalEventSizeInBytes) 340 { 341 // Existing buffer is large enough. 342 buffer = m_EventBuffer; 343 } 344 else 345 { 346 buffer = (byte*)UnsafeUtility.Malloc((long)totalEventSizeInBytes, InputEvent.kAlignment, Allocator.Persistent); 347 m_EventBufferSize = (long)totalEventSizeInBytes; 348 } 349 try 350 { 351 // Read events. 352 var tailPtr = buffer; 353 var endPtr = tailPtr + totalEventSizeInBytes; 354 var totalEventSize = 0L; 355 for (var i = 0ul; i < eventCount; ++i) 356 { 357 var eventType = reader.ReadInt32(); 358 var eventSizeInBytes = (uint)reader.ReadUInt16(); 359 var eventDeviceId = (uint)reader.ReadUInt16(); 360 361 if (eventSizeInBytes > endPtr - tailPtr) 362 break; 363 364 *(int*)tailPtr = eventType; 365 tailPtr += 4; 366 *(ushort*)tailPtr = (ushort)eventSizeInBytes; 367 tailPtr += 2; 368 *(ushort*)tailPtr = (ushort)eventDeviceId; 369 tailPtr += 2; 370 371 ////TODO: find way to directly read from stream into a byte* pointer 372 var remainingSize = (int)eventSizeInBytes - sizeof(int) - sizeof(short) - sizeof(short); 373 var tempBuffer = reader.ReadBytes(remainingSize); 374 fixed(byte* tempBufferPtr = tempBuffer) 375 UnsafeUtility.MemCpy(tailPtr, tempBufferPtr, remainingSize); 376 377 tailPtr += remainingSize.AlignToMultipleOf(InputEvent.kAlignment); 378 totalEventSize += eventSizeInBytes.AlignToMultipleOf(InputEvent.kAlignment); 379 380 if (tailPtr >= endPtr) 381 break; 382 } 383 384 // Read device infos. 385 var deviceCount = reader.ReadInt32(); 386 var deviceInfos = new DeviceInfo[deviceCount]; 387 for (var i = 0; i < deviceCount; ++i) 388 { 389 deviceInfos[i] = new DeviceInfo 390 { 391 deviceId = reader.ReadInt32(), 392 layout = reader.ReadString(), 393 stateFormat = reader.ReadInt32(), 394 stateSizeInBytes = reader.ReadInt32(), 395 m_FullLayoutJson = reader.ReadString() 396 }; 397 } 398 399 // Install buffer. 400 m_EventBuffer = buffer; 401 m_EventBufferHead = m_EventBuffer; 402 m_EventBufferTail = endPtr; 403 m_EventCount = (long)eventCount; 404 m_EventSizeInBytes = totalEventSize; 405 m_DeviceInfos = deviceInfos; 406 } 407 catch 408 { 409 if (buffer != oldBuffer) 410 UnsafeUtility.Free(buffer, Allocator.Persistent); 411 throw; 412 } 413 } 414 else 415 { 416 m_EventBuffer = default; 417 m_EventBufferHead = default; 418 m_EventBufferTail = default; 419 } 420 421 // Release old buffer, if we've switched to a new one. 422 if (m_EventBuffer != oldBuffer && oldBuffer != null) 423 UnsafeUtility.Free(oldBuffer, Allocator.Persistent); 424 425 ++m_ChangeCounter; 426 } 427 428 /// <summary> 429 /// Load an input event trace from the given file. 430 /// </summary> 431 /// <param name="filePath">Path to a file.</param> 432 /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is <c>null</c> or empty.</exception> 433 /// <exception cref="FileNotFoundException"><paramref name="filePath"/> is invalid.</exception> 434 /// <exception cref="DirectoryNotFoundException">A directory in <paramref name="filePath"/> is invalid.</exception> 435 /// <exception cref="UnauthorizedAccessException"><paramref name="filePath"/> cannot be accessed.</exception> 436 /// <seealso cref="WriteTo(string)"/> 437 /// <seealso cref="ReadFrom(string)"/> 438 public static InputEventTrace LoadFrom(string filePath) 439 { 440 if (string.IsNullOrEmpty(filePath)) 441 throw new ArgumentNullException(nameof(filePath)); 442 443 using (var stream = File.OpenRead(filePath)) 444 return LoadFrom(stream); 445 } 446 447 /// <summary> 448 /// Load an event trace from a previously captured event stream. 449 /// </summary> 450 /// <param name="stream">A stream as written by <see cref="WriteTo(Stream)"/>. Must support reading.</param> 451 /// <returns>The loaded event trace.</returns> 452 /// <exception cref="ArgumentException"><paramref name="stream"/> is not readable.</exception> 453 /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception> 454 /// <exception cref="IOException">The stream cannot be loaded (e.g. wrong format; details in the exception).</exception> 455 /// <seealso cref="WriteTo(Stream)"/> 456 public static InputEventTrace LoadFrom(Stream stream) 457 { 458 if (stream == null) 459 throw new ArgumentNullException(nameof(stream)); 460 if (!stream.CanRead) 461 throw new ArgumentException("Stream must be readable", nameof(stream)); 462 463 var trace = new InputEventTrace(); 464 trace.ReadFrom(stream); 465 466 return trace; 467 } 468 469 /// <summary> 470 /// Start a replay of the events in the trace. 471 /// </summary> 472 /// <returns>An object that controls playback.</returns> 473 /// <remarks> 474 /// Calling this method implicitly turns off recording, if currently enabled (i.e. it calls <see cref="Disable"/>), 475 /// as replaying an event trace cannot be done while it is also concurrently modified. 476 /// </remarks> 477 public ReplayController Replay() 478 { 479 Disable(); 480 return new ReplayController(this); 481 } 482 483 /// <summary> 484 /// Resize the current event memory buffer to the specified size. 485 /// </summary> 486 /// <param name="newBufferSize">Size to allocate for the buffer.</param> 487 /// <param name="newMaxBufferSize">Optional parameter to specifying the mark up to which the buffer is allowed to grow. By default, 488 /// this is negative which indicates the buffer should not grow. In this case, <see cref="maxSizeInBytes"/> will be set 489 /// to <paramref name="newBufferSize"/>. If this parameter is a non-negative number, it must be greater than or equal to 490 /// <paramref name="newBufferSize"/> and will become the new value for <see cref="maxSizeInBytes"/>.</param> 491 /// <returns>True if the new buffer was successfully allocated.</returns> 492 /// <exception cref="ArgumentException"><paramref name="newBufferSize"/> is negative.</exception> 493 public bool Resize(long newBufferSize, long newMaxBufferSize = -1) 494 { 495 if (newBufferSize <= 0) 496 throw new ArgumentException("Size must be positive", nameof(newBufferSize)); 497 498 if (m_EventBufferSize == newBufferSize) 499 return true; 500 501 if (newMaxBufferSize < newBufferSize) 502 newMaxBufferSize = newBufferSize; 503 504 // Allocate. 505 var newEventBuffer = (byte*)UnsafeUtility.Malloc(newBufferSize, InputEvent.kAlignment, Allocator.Persistent); 506 if (newEventBuffer == default) 507 return false; 508 509 // If we have existing contents, migrate them. 510 if (m_EventCount > 0) 511 { 512 // If we're shrinking the buffer or have a buffer that has already wrapped around, 513 // migrate events one by one. 514 if (newBufferSize < m_EventBufferSize || m_HasWrapped) 515 { 516 var fromPtr = new InputEventPtr((InputEvent*)m_EventBufferHead); 517 var toPtr = (InputEvent*)newEventBuffer; 518 var newEventCount = 0; 519 var newEventSizeInBytes = 0; 520 var remainingEventBytes = m_EventSizeInBytes; 521 522 for (var i = 0; i < m_EventCount; ++i) 523 { 524 var eventSizeInBytes = fromPtr.sizeInBytes; 525 var alignedEventSizeInBytes = eventSizeInBytes.AlignToMultipleOf(InputEvent.kAlignment); 526 527 // We only start copying once we know that the remaining events we have fit in the new buffer. 528 // This way we get the newest events and not the oldest ones. 529 if (remainingEventBytes <= newBufferSize) 530 { 531 UnsafeUtility.MemCpy(toPtr, fromPtr.ToPointer(), eventSizeInBytes); 532 toPtr = InputEvent.GetNextInMemory(toPtr); 533 newEventSizeInBytes += (int)alignedEventSizeInBytes; 534 ++newEventCount; 535 } 536 537 remainingEventBytes -= alignedEventSizeInBytes; 538 if (!GetNextEvent(ref fromPtr)) 539 break; 540 } 541 542 m_HasWrapped = false; 543 m_EventCount = newEventCount; 544 m_EventSizeInBytes = newEventSizeInBytes; 545 } 546 else 547 { 548 // Simple case of just having to copy everything between head and tail. 549 UnsafeUtility.MemCpy(newEventBuffer, 550 m_EventBufferHead, 551 m_EventSizeInBytes); 552 } 553 } 554 555 if (m_EventBuffer != null) 556 UnsafeUtility.Free(m_EventBuffer, Allocator.Persistent); 557 558 m_EventBufferSize = newBufferSize; 559 m_EventBuffer = newEventBuffer; 560 m_EventBufferHead = newEventBuffer; 561 m_EventBufferTail = m_EventBuffer + m_EventSizeInBytes; 562 m_MaxEventBufferSize = newMaxBufferSize; 563 564 ++m_ChangeCounter; 565 566 return true; 567 } 568 569 /// <summary> 570 /// Reset the trace. Clears all recorded events. 571 /// </summary> 572 public void Clear() 573 { 574 m_EventBufferHead = m_EventBufferTail = default; 575 m_EventCount = 0; 576 m_EventSizeInBytes = 0; 577 ++m_ChangeCounter; 578 m_DeviceInfos = null; 579 } 580 581 /// <summary> 582 /// Start recording events. 583 /// </summary> 584 /// <seealso cref="Disable"/> 585 public void Enable() 586 { 587 if (m_Enabled) 588 return; 589 590 if (m_EventBuffer == default) 591 Allocate(); 592 593 InputSystem.onEvent += OnInputEvent; 594 if (m_RecordFrameMarkers) 595 InputSystem.onBeforeUpdate += OnBeforeUpdate; 596 597 m_Enabled = true; 598 } 599 600 /// <summary> 601 /// Stop recording events. 602 /// </summary> 603 /// <seealso cref="Enable"/> 604 public void Disable() 605 { 606 if (!m_Enabled) 607 return; 608 609 InputSystem.onEvent -= OnInputEvent; 610 InputSystem.onBeforeUpdate -= OnBeforeUpdate; 611 612 m_Enabled = false; 613 } 614 615 /// <summary> 616 /// Based on the given event pointer, return a pointer to the next event in the trace. 617 /// </summary> 618 /// <param name="current">A pointer to an event in the trace or a <c>default(InputEventTrace)</c>. In the former case, 619 /// the pointer will be updated to the next event, if there is one. In the latter case, the pointer will be updated 620 /// to the first event in the trace, if there is one.</param> 621 /// <returns>True if <c>current</c> has been set to the next event, false otherwise.</returns> 622 /// <remarks> 623 /// Event storage in memory may be circular if the event buffer is fixed in size or has reached maximum 624 /// size and new events start overwriting old events. This method will automatically start with the first 625 /// event when the given <paramref name="current"/> event is null. Any subsequent call with then loop over 626 /// the remaining events until no more events are available. 627 /// 628 /// Note that it is VERY IMPORTANT that the buffer is not modified while iterating over events this way. 629 /// If this is not ensured, invalid memory accesses may result. 630 /// 631 /// <example> 632 /// <code> 633 /// // Loop over all events in the InputEventTrace in the `trace` variable. 634 /// var current = default(InputEventPtr); 635 /// while (trace.GetNextEvent(ref current)) 636 /// { 637 /// Debug.Log(current); 638 /// } 639 /// </code> 640 /// </example> 641 /// </remarks> 642 public bool GetNextEvent(ref InputEventPtr current) 643 { 644 if (m_EventBuffer == default) 645 return false; 646 647 // If head is null, tail is too and it means there's nothing in the 648 // buffer yet. 649 if (m_EventBufferHead == default) 650 return false; 651 652 // If current is null, start iterating at head. 653 if (!current.valid) 654 { 655 current = new InputEventPtr((InputEvent*)m_EventBufferHead); 656 return true; 657 } 658 659 // Otherwise feel our way forward. 660 661 var nextEvent = (byte*)current.Next().data; 662 var endOfBuffer = m_EventBuffer + m_EventBufferSize; 663 664 // If we've run into our tail, there's no more events. 665 if (nextEvent == m_EventBufferTail) 666 return false; 667 668 // If we've reached blank space at the end of the buffer, wrap 669 // around to the beginning. In this scenario there must be an event 670 // at the beginning of the buffer; tail won't position itself at 671 // m_EventBuffer. 672 if (endOfBuffer - nextEvent < InputEvent.kBaseEventSize || 673 ((InputEvent*)nextEvent)->sizeInBytes == 0) 674 { 675 nextEvent = m_EventBuffer; 676 if (nextEvent == current.ToPointer()) 677 return false; // There's only a single event in the buffer. 678 } 679 680 // We're good. There's still space between us and our tail. 681 current = new InputEventPtr((InputEvent*)nextEvent); 682 return true; 683 } 684 685 public IEnumerator<InputEventPtr> GetEnumerator() 686 { 687 return new Enumerator(this); 688 } 689 690 IEnumerator IEnumerable.GetEnumerator() 691 { 692 return GetEnumerator(); 693 } 694 695 /// <summary> 696 /// Stop recording, if necessary, and clear the trace such that it released unmanaged 697 /// memory which might be allocated. 698 /// </summary> 699 /// <remarks> 700 /// For any trace that has recorded events, calling this method is crucial in order to not 701 /// leak memory on the unmanaged (C++) memory heap. 702 /// </remarks> 703 public void Dispose() 704 { 705 Disable(); 706 Release(); 707 } 708 709 // We want to make sure that it's not possible to iterate with an enumerable over 710 // a trace that is being changed so we bump this counter every time we modify the 711 // buffer and check in the enumerator that the counts match. 712 [NonSerialized] private int m_ChangeCounter; 713 [NonSerialized] private bool m_Enabled; 714 [NonSerialized] private Func<InputEventPtr, InputDevice, bool> m_OnFilterEvent; 715 716 [SerializeField] private int m_DeviceId = InputDevice.InvalidDeviceId; 717 [NonSerialized] private CallbackArray<Action<InputEventPtr>> m_EventListeners; 718 719 // Buffer for storing event trace. Allocated in native so that we can survive a 720 // domain reload without losing event traces. 721 // NOTE: Ideally this would simply use InputEventBuffer but we can't serialize that one because 722 // of the NativeArray it has inside. Also, due to the wrap-around nature, storage of 723 // events in the buffer may not be linear. 724 [SerializeField] private long m_EventBufferSize; 725 [SerializeField] private long m_MaxEventBufferSize; 726 [SerializeField] private long m_GrowIncrementSize; 727 [SerializeField] private long m_EventCount; 728 [SerializeField] private long m_EventSizeInBytes; 729 // These are ulongs for the sake of Unity serialization which can't handle pointers or IntPtrs. 730 [SerializeField] private ulong m_EventBufferStorage; 731 [SerializeField] private ulong m_EventBufferHeadStorage; 732 [SerializeField] private ulong m_EventBufferTailStorage; 733 [SerializeField] private bool m_HasWrapped; 734 [SerializeField] private bool m_RecordFrameMarkers; 735 [SerializeField] private DeviceInfo[] m_DeviceInfos; 736 737 private byte* m_EventBuffer 738 { 739 get => (byte*)m_EventBufferStorage; 740 set => m_EventBufferStorage = (ulong)value; 741 } 742 743 private byte* m_EventBufferHead 744 { 745 get => (byte*)m_EventBufferHeadStorage; 746 set => m_EventBufferHeadStorage = (ulong)value; 747 } 748 749 private byte* m_EventBufferTail 750 { 751 get => (byte*)m_EventBufferTailStorage; 752 set => m_EventBufferTailStorage = (ulong)value; 753 } 754 755 private void Allocate() 756 { 757 m_EventBuffer = (byte*)UnsafeUtility.Malloc(m_EventBufferSize, InputEvent.kAlignment, Allocator.Persistent); 758 } 759 760 private void Release() 761 { 762 Clear(); 763 764 if (m_EventBuffer != default) 765 { 766 UnsafeUtility.Free(m_EventBuffer, Allocator.Persistent); 767 m_EventBuffer = default; 768 } 769 } 770 771 private void OnBeforeUpdate() 772 { 773 ////TODO: make this work correctly with the different update types 774 775 if (m_RecordFrameMarkers) 776 { 777 // Record frame marker event. 778 // NOTE: ATM these events don't get valid event IDs. Might be this is even useful but is more a side-effect 779 // of there not being a method to obtain an ID except by actually queuing an event. 780 var frameMarkerEvent = new InputEvent 781 { 782 type = FrameMarkerEvent, 783 internalTime = InputRuntime.s_Instance.currentTime, 784 sizeInBytes = (uint)UnsafeUtility.SizeOf<InputEvent>() 785 }; 786 787 OnInputEvent(new InputEventPtr((InputEvent*)UnsafeUtility.AddressOf(ref frameMarkerEvent)), null); 788 } 789 } 790 791 private void OnInputEvent(InputEventPtr inputEvent, InputDevice device) 792 { 793 // Ignore events that are already marked as handled. 794 if (inputEvent.handled) 795 return; 796 797 // Ignore if the event isn't for our device (except if it's a frame marker). 798 if (m_DeviceId != InputDevice.InvalidDeviceId && inputEvent.deviceId != m_DeviceId && inputEvent.type != FrameMarkerEvent) 799 return; 800 801 // Give callback a chance to filter event. 802 if (m_OnFilterEvent != null && !m_OnFilterEvent(inputEvent, device)) 803 return; 804 805 // This shouldn't happen but ignore the event if we're not tracing. 806 if (m_EventBuffer == default) 807 return; 808 809 var bytesNeeded = inputEvent.sizeInBytes.AlignToMultipleOf(InputEvent.kAlignment); 810 811 // Make sure we can fit the event at all. 812 if (bytesNeeded > m_MaxEventBufferSize) 813 return; 814 815 k_InputEvenTraceMarker.Begin(); 816 817 if (m_EventBufferTail == default) 818 { 819 // First event in buffer. 820 m_EventBufferHead = m_EventBuffer; 821 m_EventBufferTail = m_EventBuffer; 822 } 823 824 var newTail = m_EventBufferTail + bytesNeeded; 825 var newTailOvertakesHead = newTail > m_EventBufferHead && m_EventBufferHead != m_EventBuffer; 826 827 // If tail goes out of bounds, enlarge the buffer or wrap around to the beginning. 828 var newTailGoesPastEndOfBuffer = newTail > m_EventBuffer + m_EventBufferSize; 829 if (newTailGoesPastEndOfBuffer) 830 { 831 // If we haven't reached the max size yet, grow the buffer. 832 if (m_EventBufferSize < m_MaxEventBufferSize && !m_HasWrapped) 833 { 834 var increment = Math.Max(m_GrowIncrementSize, bytesNeeded.AlignToMultipleOf(InputEvent.kAlignment)); 835 var newBufferSize = m_EventBufferSize + increment; 836 if (newBufferSize > m_MaxEventBufferSize) 837 newBufferSize = m_MaxEventBufferSize; 838 839 if (newBufferSize < bytesNeeded) 840 { 841 k_InputEvenTraceMarker.End(); 842 return; 843 } 844 845 Resize(newBufferSize); 846 847 newTail = m_EventBufferTail + bytesNeeded; 848 } 849 850 // See if we fit. 851 var spaceLeft = m_EventBufferSize - (m_EventBufferTail - m_EventBuffer); 852 if (spaceLeft < bytesNeeded) 853 { 854 // No, so wrap around. 855 m_HasWrapped = true; 856 857 // Make sure head isn't trying to advance into gap we may be leaving at the end of the 858 // buffer by wiping the space if it could fit an event. 859 if (spaceLeft >= InputEvent.kBaseEventSize) 860 UnsafeUtility.MemClear(m_EventBufferTail, InputEvent.kBaseEventSize); 861 862 m_EventBufferTail = m_EventBuffer; 863 newTail = m_EventBuffer + bytesNeeded; 864 865 // If the tail overtook both the head and the end of the buffer, 866 // we need to make sure the head is wrapped around as well. 867 if (newTailOvertakesHead) 868 m_EventBufferHead = m_EventBuffer; 869 870 // Recheck whether we're overtaking head. 871 newTailOvertakesHead = newTail > m_EventBufferHead; 872 } 873 } 874 875 // If the new tail runs into head, bump head as many times as we need to 876 // make room for the event. Head may itself wrap around here. 877 if (newTailOvertakesHead) 878 { 879 var newHead = m_EventBufferHead; 880 var endOfBufferMinusOneEvent = 881 m_EventBuffer + m_EventBufferSize - InputEvent.kBaseEventSize; 882 883 while (newHead < newTail) 884 { 885 var numBytes = ((InputEvent*)newHead)->sizeInBytes; 886 newHead += numBytes; 887 --m_EventCount; 888 m_EventSizeInBytes -= numBytes; 889 if (newHead > endOfBufferMinusOneEvent || ((InputEvent*)newHead)->sizeInBytes == 0) 890 { 891 newHead = m_EventBuffer; 892 break; 893 } 894 } 895 896 m_EventBufferHead = newHead; 897 } 898 899 var buffer = m_EventBufferTail; 900 m_EventBufferTail = newTail; 901 902 // Copy data to buffer. 903 UnsafeUtility.MemCpy(buffer, inputEvent.data, inputEvent.sizeInBytes); 904 ++m_ChangeCounter; 905 ++m_EventCount; 906 m_EventSizeInBytes += bytesNeeded; 907 908 // Make sure we have a record for the device. 909 if (device != null) 910 { 911 var haveRecord = false; 912 if (m_DeviceInfos != null) 913 for (var i = 0; i < m_DeviceInfos.Length; ++i) 914 if (m_DeviceInfos[i].deviceId == device.deviceId) 915 { 916 haveRecord = true; 917 break; 918 } 919 if (!haveRecord) 920 ArrayHelpers.Append(ref m_DeviceInfos, new DeviceInfo 921 { 922 m_DeviceId = device.deviceId, 923 m_Layout = device.layout, 924 m_StateFormat = device.stateBlock.format, 925 m_StateSizeInBytes = (int)device.stateBlock.alignedSizeInBytes, 926 927 // If it's a generated layout, store the full layout JSON in the device info. We do this so that 928 // when saving traces for this kind of input, we can recreate the device. 929 m_FullLayoutJson = InputControlLayout.s_Layouts.IsGeneratedLayout(device.m_Layout) 930 ? InputSystem.LoadLayout(device.layout).ToJson() 931 : null 932 }); 933 } 934 935 // Notify listeners. 936 if (m_EventListeners.length > 0) 937 DelegateHelpers.InvokeCallbacksSafe(ref m_EventListeners, new InputEventPtr((InputEvent*)buffer), 938 "InputEventTrace.onEvent"); 939 940 k_InputEvenTraceMarker.End(); 941 } 942 943 private class Enumerator : IEnumerator<InputEventPtr> 944 { 945 private InputEventTrace m_Trace; 946 private int m_ChangeCounter; 947 internal InputEventPtr m_Current; 948 949 public Enumerator(InputEventTrace trace) 950 { 951 m_Trace = trace; 952 m_ChangeCounter = trace.m_ChangeCounter; 953 } 954 955 public void Dispose() 956 { 957 m_Trace = null; 958 m_Current = new InputEventPtr(); 959 } 960 961 public bool MoveNext() 962 { 963 if (m_Trace == null) 964 throw new ObjectDisposedException(ToString()); 965 if (m_Trace.m_ChangeCounter != m_ChangeCounter) 966 throw new InvalidOperationException("Trace has been modified while enumerating!"); 967 968 return m_Trace.GetNextEvent(ref m_Current); 969 } 970 971 public void Reset() 972 { 973 m_Current = default; 974 m_ChangeCounter = m_Trace.m_ChangeCounter; 975 } 976 977 public InputEventPtr Current => m_Current; 978 object IEnumerator.Current => Current; 979 } 980 981 private static FourCC kFileFormat => new FourCC('I', 'E', 'V', 'T'); 982 private static int kFileVersion = 1; 983 984 [Flags] 985 private enum FileFlags 986 { 987 FixedUpdate = 1 << 0, // Events were recorded with system being in fixed-update mode. 988 } 989 990 /// <summary> 991 /// Controls replaying of events recorded in an <see cref="InputEventTrace"/>. 992 /// </summary> 993 /// <remarks> 994 /// Playback can be controlled either on a per-event or a per-frame basis. Note that playing back events 995 /// frame by frame requires frame markers to be present in the trace (see <see cref="recordFrameMarkers"/>). 996 /// 997 /// By default, events will be queued as is except for their timestamps which will be set to the current 998 /// time that each event is queued at. 999 /// 1000 /// What this means is that events replay with the same device ID (see <see cref="InputEvent.deviceId"/>) 1001 /// they were captured on. If the trace is replayed in the same session that it was recorded in, this means 1002 /// that the events will replay on the same device (if it still exists). 1003 /// 1004 /// To map recorded events to a different device, you can either call <see cref="WithDeviceMappedFromTo(int,int)"/> to 1005 /// map an arbitrary device ID to a new one or call <see cref="WithAllDevicesMappedToNewInstances"/> to create 1006 /// new (temporary) devices for the duration of playback. 1007 /// 1008 /// <example> 1009 /// <code> 1010 /// var trace = new InputEventTrace(myDevice); 1011 /// trace.Enable(); 1012 /// 1013 /// // ... run one or more frames ... 1014 /// 1015 /// trace.Replay().OneFrame(); 1016 /// </code> 1017 /// </example> 1018 /// </remarks> 1019 /// <seealso cref="InputEventTrace.Replay"/> 1020 public class ReplayController : IDisposable 1021 { 1022 /// <summary> 1023 /// The event trace associated with the replay controller. 1024 /// </summary> 1025 /// <value>Trace from which events are replayed.</value> 1026 public InputEventTrace trace => m_EventTrace; 1027 1028 /// <summary> 1029 /// Whether replay has finished. 1030 /// </summary> 1031 /// <value>True if replay has finished or is not in progress.</value> 1032 /// <seealso cref="PlayAllFramesOneByOne"/> 1033 /// <seealso cref="PlayAllEvents"/> 1034 public bool finished { get; private set; } 1035 1036 /// <summary> 1037 /// Whether replay is paused. 1038 /// </summary> 1039 /// <value>True if replay is currently paused.</value> 1040 public bool paused { get; set; } 1041 1042 /// <summary> 1043 /// Current position in the event stream. 1044 /// </summary> 1045 /// <value>Index of current event in trace.</value> 1046 public int position { get; private set; } 1047 1048 /// <summary> 1049 /// List of devices created by the replay controller. 1050 /// </summary> 1051 /// <value>Devices created by the replay controller.</value> 1052 /// <remarks> 1053 /// By default, a replay controller will queue events as is, i.e. with <see cref="InputEvent.deviceId"/> of 1054 /// each event left as is. This means that the events will target existing devices (if any) that have the 1055 /// respective ID. 1056 /// 1057 /// Using <see cref="WithAllDevicesMappedToNewInstances"/>, a replay controller can be instructed to create 1058 /// new, temporary devices instead for each unique <see cref="InputEvent.deviceId"/> encountered in the stream. 1059 /// All devices created by the controller this way will be put on this list. 1060 /// </remarks> 1061 /// <seealso cref="WithAllDevicesMappedToNewInstances"/> 1062 public IEnumerable<InputDevice> createdDevices => m_CreatedDevices; 1063 1064 private InputEventTrace m_EventTrace; 1065 private Enumerator m_Enumerator; 1066 private InlinedArray<KeyValuePair<int, int>> m_DeviceIDMappings; 1067 private bool m_CreateNewDevices; 1068 private InlinedArray<InputDevice> m_CreatedDevices; 1069 private Action m_OnFinished; 1070 private Action<InputEventPtr> m_OnEvent; 1071 private double m_StartTimeAsPerFirstEvent; 1072 private double m_StartTimeAsPerRuntime; 1073 private int m_AllEventsByTimeIndex = 0; 1074 private List<InputEventPtr> m_AllEventsByTime; 1075 1076 internal ReplayController(InputEventTrace trace) 1077 { 1078 if (trace == null) 1079 throw new ArgumentNullException(nameof(trace)); 1080 1081 m_EventTrace = trace; 1082 } 1083 1084 /// <summary> 1085 /// Removes devices created by the controller when using <see cref="WithAllDevicesMappedToNewInstances"/>. 1086 /// </summary> 1087 public void Dispose() 1088 { 1089 InputSystem.onBeforeUpdate -= OnBeginFrame; 1090 finished = true; 1091 1092 foreach (var device in m_CreatedDevices) 1093 InputSystem.RemoveDevice(device); 1094 m_CreatedDevices = default; 1095 } 1096 1097 /// <summary> 1098 /// Replay events recorded from <paramref name="recordedDevice"/> on device <paramref name="playbackDevice"/>. 1099 /// </summary> 1100 /// <param name="recordedDevice">Device events have been recorded from.</param> 1101 /// <param name="playbackDevice">Device events should be played back on.</param> 1102 /// <returns>The same ReplayController instance.</returns> 1103 /// <exception cref="ArgumentNullException"><paramref name="recordedDevice"/> is <c>null</c> -or- 1104 /// <paramref name="playbackDevice"/> is <c>null</c>.</exception> 1105 /// <remarks> 1106 /// This method causes all events with a device ID (see <see cref="InputDevice.deviceId"/> and <see cref="InputEvent.deviceId"/>) 1107 /// corresponding to the one of <paramref cref="recordedDevice"/> to be queued with the device ID of <paramref name="playbackDevice"/>. 1108 /// </remarks> 1109 public ReplayController WithDeviceMappedFromTo(InputDevice recordedDevice, InputDevice playbackDevice) 1110 { 1111 if (recordedDevice == null) 1112 throw new ArgumentNullException(nameof(recordedDevice)); 1113 if (playbackDevice == null) 1114 throw new ArgumentNullException(nameof(playbackDevice)); 1115 1116 WithDeviceMappedFromTo(recordedDevice.deviceId, playbackDevice.deviceId); 1117 return this; 1118 } 1119 1120 /// <summary> 1121 /// Replace <see cref="InputEvent.deviceId"/> values of events that are equal to <paramref name="recordedDeviceId"/> 1122 /// with device ID <paramref name="playbackDeviceId"/>. 1123 /// </summary> 1124 /// <param name="recordedDeviceId"><see cref="InputDevice.deviceId"/> to map from.</param> 1125 /// <param name="playbackDeviceId"><see cref="InputDevice.deviceId"/> to map to.</param> 1126 /// <returns>The same ReplayController instance.</returns> 1127 public ReplayController WithDeviceMappedFromTo(int recordedDeviceId, int playbackDeviceId) 1128 { 1129 // If there's an existing mapping entry for the device, update it. 1130 for (var i = 0; i < m_DeviceIDMappings.length; ++i) 1131 { 1132 if (m_DeviceIDMappings[i].Key != recordedDeviceId) 1133 continue; 1134 1135 if (recordedDeviceId == playbackDeviceId) // Device mapped back to itself. 1136 m_DeviceIDMappings.RemoveAtWithCapacity(i); 1137 else 1138 m_DeviceIDMappings[i] = new KeyValuePair<int, int>(recordedDeviceId, playbackDeviceId); 1139 1140 return this; 1141 } 1142 1143 // Ignore if mapped to itself. 1144 if (recordedDeviceId == playbackDeviceId) 1145 return this; 1146 1147 // Record mapping. 1148 m_DeviceIDMappings.AppendWithCapacity(new KeyValuePair<int, int>(recordedDeviceId, playbackDeviceId)); 1149 return this; 1150 } 1151 1152 /// <summary> 1153 /// For all events, create new devices to replay the events on instead of replaying the events on existing devices. 1154 /// </summary> 1155 /// <returns>The same ReplayController instance.</returns> 1156 /// <remarks> 1157 /// Note that devices created by the <c>ReplayController</c> will stick around for as long as the replay 1158 /// controller is not disposed of. This means that multiple successive replays using the same <c>ReplayController</c> 1159 /// will replay the events on the same devices that were created on the first replay. It also means that in order 1160 /// to do away with the created devices, it is necessary to call <see cref="Dispose"/>. 1161 /// </remarks> 1162 /// <seealso cref="Dispose"/> 1163 /// <seealso cref="createdDevices"/> 1164 public ReplayController WithAllDevicesMappedToNewInstances() 1165 { 1166 m_CreateNewDevices = true; 1167 return this; 1168 } 1169 1170 /// <summary> 1171 /// Invoke the given callback when playback finishes. 1172 /// </summary> 1173 /// <param name="action">A callback to invoke when playback finishes.</param> 1174 /// <returns>The same ReplayController instance.</returns> 1175 public ReplayController OnFinished(Action action) 1176 { 1177 m_OnFinished = action; 1178 return this; 1179 } 1180 1181 /// <summary> 1182 /// Invoke the given callback when an event is about to be queued. 1183 /// </summary> 1184 /// <param name="action">A callback to invoke when an event is getting queued.</param> 1185 /// <returns>The same ReplayController instance.</returns> 1186 public ReplayController OnEvent(Action<InputEventPtr> action) 1187 { 1188 m_OnEvent = action; 1189 return this; 1190 } 1191 1192 /// <summary> 1193 /// Takes the next event from the trace and queues it. 1194 /// </summary> 1195 /// <returns>The same ReplayController instance.</returns> 1196 /// <exception cref="InvalidOperationException">There are no more events in the <see cref="trace"/> -or- the only 1197 /// events left are frame marker events (see <see cref="InputEventTrace.FrameMarkerEvent"/>).</exception> 1198 /// <remarks> 1199 /// This method takes the next event at the current read position and queues it using <see cref="InputSystem.QueueEvent"/>. 1200 /// The read position is advanced past the taken event. 1201 /// 1202 /// Frame marker events (see <see cref="InputEventTrace.FrameMarkerEvent"/>) are skipped. 1203 /// </remarks> 1204 public ReplayController PlayOneEvent() 1205 { 1206 // Skip events until we hit something that isn't a frame marker. 1207 if (!MoveNext(true, out var eventPtr)) 1208 throw new InvalidOperationException("No more events"); 1209 1210 QueueEvent(eventPtr); 1211 1212 return this; 1213 } 1214 1215 ////TODO: OneFrame 1216 ////TODO: RewindOneEvent 1217 ////TODO: RewindOneFrame 1218 ////TODO: Stop 1219 1220 /// <summary> 1221 /// Rewind playback all the way to the beginning of the event trace. 1222 /// </summary> 1223 /// <returns>The same ReplayController instance.</returns> 1224 public ReplayController Rewind() 1225 { 1226 m_Enumerator = default; 1227 m_AllEventsByTime = null; 1228 m_AllEventsByTimeIndex = -1; 1229 position = 0; 1230 return this; 1231 } 1232 1233 /// <summary> 1234 /// Replay all frames one by one from the current playback position. 1235 /// </summary> 1236 /// <returns>The same ReplayController instance.</returns> 1237 /// <remarks> 1238 /// Events will be fed to the input system from within <see cref="InputSystem.onBeforeUpdate"/>. Each update 1239 /// will receive events for one frame. 1240 /// 1241 /// Note that for this method to correctly space out events and distribute them to frames, frame markers 1242 /// must be present in the trace (see <see cref="recordFrameMarkers"/>). If not present, all events will 1243 /// be fed into first frame. 1244 /// </remarks> 1245 /// <seealso cref="recordFrameMarkers"/> 1246 /// <seealso cref="InputSystem.onBeforeUpdate"/> 1247 /// <seealso cref="PlayAllEvents"/> 1248 /// <seealso cref="PlayAllEventsAccordingToTimestamps"/> 1249 public ReplayController PlayAllFramesOneByOne() 1250 { 1251 finished = false; 1252 InputSystem.onBeforeUpdate += OnBeginFrame; 1253 return this; 1254 } 1255 1256 /// <summary> 1257 /// Go through all remaining event in the trace starting at the current read position and queue them using 1258 /// <see cref="InputSystem.QueueEvent"/>. 1259 /// </summary> 1260 /// <returns>The same ReplayController instance.</returns> 1261 /// <remarks> 1262 /// Unlike methods such as <see cref="PlayAllFramesOneByOne"/>, this method immediately queues events and immediately 1263 /// completes playback upon return from the method. 1264 /// </remarks> 1265 /// <seealso cref="PlayAllFramesOneByOne"/> 1266 /// <seealso cref="PlayAllEventsAccordingToTimestamps"/> 1267 public ReplayController PlayAllEvents() 1268 { 1269 finished = false; 1270 try 1271 { 1272 while (MoveNext(true, out var eventPtr)) 1273 QueueEvent(eventPtr); 1274 } 1275 finally 1276 { 1277 Finished(); 1278 } 1279 return this; 1280 } 1281 1282 /// <summary> 1283 /// Replay events in a way that tries to preserve the original timing sequence. 1284 /// </summary> 1285 /// <returns>The same ReplayController instance.</returns> 1286 /// <remarks> 1287 /// This method will take the current time as the starting time to which make all events 1288 /// relative to. Based on this time, it will try to correlate the original event timing 1289 /// with the timing of input updates as they happen. When successful, this will compensate 1290 /// for differences in frame timings compared to when input was recorded and instead queue 1291 /// input in frames that are closer to the original timing. 1292 /// 1293 /// Note that this method will perform one initial scan of the trace to determine a linear 1294 /// ordering of the events by time (the input system does not require any such ordering on the 1295 /// events in its queue and thus events in a trace, especially if there are multiple devices 1296 /// involved, may be out of order). 1297 /// </remarks> 1298 /// <seealso cref="PlayAllFramesOneByOne"/> 1299 /// <seealso cref="PlayAllEvents"/> 1300 public ReplayController PlayAllEventsAccordingToTimestamps() 1301 { 1302 // Sort remaining events by time. 1303 var eventsByTime = new List<InputEventPtr>(); 1304 while (MoveNext(true, out var eventPtr)) 1305 eventsByTime.Add(eventPtr); 1306 eventsByTime.Sort((a, b) => a.time.CompareTo(b.time)); 1307 m_Enumerator.Dispose(); 1308 m_Enumerator = null; 1309 m_AllEventsByTime = eventsByTime; 1310 position = 0; 1311 1312 // Start playback. 1313 finished = false; 1314 m_StartTimeAsPerFirstEvent = -1; 1315 m_AllEventsByTimeIndex = -1; 1316 InputSystem.onBeforeUpdate += OnBeginFrame; 1317 return this; 1318 } 1319 1320 private void OnBeginFrame() 1321 { 1322 if (paused) 1323 return; 1324 1325 if (!MoveNext(false, out var currentEventPtr)) 1326 { 1327 if (m_AllEventsByTime == null || m_AllEventsByTimeIndex >= m_AllEventsByTime.Count) 1328 Finished(); 1329 return; 1330 } 1331 1332 // Check for empty frame (note: when playing back events by time, we won't see frame marker events 1333 // returned from MoveNext). 1334 if (currentEventPtr.type == FrameMarkerEvent) 1335 { 1336 if (!MoveNext(false, out var nextEvent)) 1337 { 1338 // Last frame. 1339 Finished(); 1340 return; 1341 } 1342 1343 // Check for empty frame. 1344 if (nextEvent.type == FrameMarkerEvent) 1345 { 1346 --position; 1347 m_Enumerator.m_Current = currentEventPtr; 1348 return; 1349 } 1350 1351 currentEventPtr = nextEvent; 1352 } 1353 1354 // Inject our events into the frame. 1355 while (true) 1356 { 1357 QueueEvent(currentEventPtr); 1358 1359 // Stop if we reach the end of the stream. 1360 if (!MoveNext(false, out var nextEvent)) 1361 { 1362 if (m_AllEventsByTime == null || m_AllEventsByTimeIndex >= m_AllEventsByTime.Count) 1363 Finished(); 1364 break; 1365 } 1366 1367 // Stop if we've reached the next frame (won't happen if we're playing events by time). 1368 if (nextEvent.type == FrameMarkerEvent) 1369 { 1370 // Back up one event. 1371 m_Enumerator.m_Current = currentEventPtr; 1372 --position; 1373 break; 1374 } 1375 1376 currentEventPtr = nextEvent; 1377 } 1378 } 1379 1380 private void Finished() 1381 { 1382 finished = true; 1383 InputSystem.onBeforeUpdate -= OnBeginFrame; 1384 m_OnFinished?.Invoke(); 1385 } 1386 1387 private void QueueEvent(InputEventPtr eventPtr) 1388 { 1389 // Shift time on event. 1390 var originalTimestamp = eventPtr.internalTime; 1391 if (m_AllEventsByTime != null) 1392 eventPtr.internalTime = m_StartTimeAsPerRuntime + (eventPtr.internalTime - m_StartTimeAsPerFirstEvent); 1393 else 1394 eventPtr.internalTime = InputRuntime.s_Instance.currentTime; 1395 1396 // Remember original event ID. QueueEvent will automatically update the event ID 1397 // and actually do so in place. 1398 var originalEventId = eventPtr.id; 1399 1400 // Map device ID. 1401 var originalDeviceId = eventPtr.deviceId; 1402 eventPtr.deviceId = ApplyDeviceMapping(originalDeviceId); 1403 1404 // Notify. 1405 m_OnEvent?.Invoke(eventPtr); 1406 1407 // Queue event. 1408 try 1409 { 1410 InputSystem.QueueEvent(eventPtr); 1411 } 1412 finally 1413 { 1414 // Restore modification we made to the event buffer. 1415 eventPtr.internalTime = originalTimestamp; 1416 eventPtr.id = originalEventId; 1417 eventPtr.deviceId = originalDeviceId; 1418 } 1419 } 1420 1421 private bool MoveNext(bool skipFrameEvents, out InputEventPtr eventPtr) 1422 { 1423 eventPtr = default; 1424 1425 if (m_AllEventsByTime != null) 1426 { 1427 if (m_AllEventsByTimeIndex + 1 >= m_AllEventsByTime.Count) 1428 { 1429 position = m_AllEventsByTime.Count; 1430 m_AllEventsByTimeIndex = m_AllEventsByTime.Count; 1431 return false; 1432 } 1433 1434 if (m_AllEventsByTimeIndex < 0) 1435 { 1436 m_StartTimeAsPerFirstEvent = m_AllEventsByTime[0].internalTime; 1437 m_StartTimeAsPerRuntime = InputRuntime.s_Instance.currentTime; 1438 } 1439 else if (m_AllEventsByTimeIndex < m_AllEventsByTime.Count - 1 && 1440 m_AllEventsByTime[m_AllEventsByTimeIndex + 1].internalTime > m_StartTimeAsPerFirstEvent + (InputRuntime.s_Instance.currentTime - m_StartTimeAsPerRuntime)) 1441 { 1442 // We're queuing by original time and the next event isn't up yet, 1443 // so early out. 1444 return false; 1445 } 1446 1447 ++m_AllEventsByTimeIndex; 1448 ++position; 1449 eventPtr = m_AllEventsByTime[m_AllEventsByTimeIndex]; 1450 } 1451 else 1452 { 1453 if (m_Enumerator == null) 1454 m_Enumerator = new Enumerator(m_EventTrace); 1455 1456 do 1457 { 1458 if (!m_Enumerator.MoveNext()) 1459 return false; 1460 1461 ++position; 1462 eventPtr = m_Enumerator.Current; 1463 } 1464 while (skipFrameEvents && eventPtr.type == FrameMarkerEvent); 1465 } 1466 1467 return true; 1468 } 1469 1470 private int ApplyDeviceMapping(int originalDeviceId) 1471 { 1472 // Look up in mappings. 1473 for (var i = 0; i < m_DeviceIDMappings.length; ++i) 1474 { 1475 var entry = m_DeviceIDMappings[i]; 1476 if (entry.Key == originalDeviceId) 1477 return entry.Value; 1478 } 1479 1480 // Create device, if needed. 1481 if (m_CreateNewDevices) 1482 { 1483 try 1484 { 1485 // Find device info. 1486 var deviceIndex = m_EventTrace.deviceInfos.IndexOf(x => x.deviceId == originalDeviceId); 1487 if (deviceIndex != -1) 1488 { 1489 var deviceInfo = m_EventTrace.deviceInfos[deviceIndex]; 1490 1491 // If we don't have the layout, try to add it from the persisted layout info. 1492 var layoutName = new InternedString(deviceInfo.layout); 1493 if (!InputControlLayout.s_Layouts.HasLayout(layoutName)) 1494 { 1495 if (string.IsNullOrEmpty(deviceInfo.m_FullLayoutJson)) 1496 return originalDeviceId; 1497 1498 InputSystem.RegisterLayout(deviceInfo.m_FullLayoutJson); 1499 } 1500 1501 // Create device. 1502 var device = InputSystem.AddDevice(layoutName); 1503 WithDeviceMappedFromTo(originalDeviceId, device.deviceId); 1504 m_CreatedDevices.AppendWithCapacity(device); 1505 return device.deviceId; 1506 } 1507 } 1508 catch 1509 { 1510 // Swallow and just return originalDeviceId. 1511 } 1512 } 1513 1514 return originalDeviceId; 1515 } 1516 } 1517 1518 /// <summary> 1519 /// Information about a device whose input has been captured in an <see cref="InputEventTrace"/> 1520 /// </summary> 1521 /// <seealso cref="InputEventTrace.deviceInfos"/> 1522 [Serializable] 1523 public struct DeviceInfo 1524 { 1525 /// <summary> 1526 /// Id of the device as stored in the events for the device. 1527 /// </summary> 1528 /// <seealso cref="InputDevice.deviceId"/> 1529 public int deviceId 1530 { 1531 get => m_DeviceId; 1532 set => m_DeviceId = value; 1533 } 1534 1535 /// <summary> 1536 /// Name of the layout used by the device. 1537 /// </summary> 1538 /// <seealso cref="InputControl.layout"/> 1539 public string layout 1540 { 1541 get => m_Layout; 1542 set => m_Layout = value; 1543 } 1544 1545 /// <summary> 1546 /// Tag for the format in which state for the device is stored. 1547 /// </summary> 1548 /// <seealso cref="InputControl.stateBlock"/> 1549 /// <seealso cref="InputStateBlock.format"/> 1550 public FourCC stateFormat 1551 { 1552 get => m_StateFormat; 1553 set => m_StateFormat = value; 1554 } 1555 1556 /// <summary> 1557 /// Size of a full state snapshot of the device. 1558 /// </summary> 1559 public int stateSizeInBytes 1560 { 1561 get => m_StateSizeInBytes; 1562 set => m_StateSizeInBytes = value; 1563 } 1564 1565 [SerializeField] internal int m_DeviceId; 1566 [SerializeField] internal string m_Layout; 1567 [SerializeField] internal FourCC m_StateFormat; 1568 [SerializeField] internal int m_StateSizeInBytes; 1569 [SerializeField] internal string m_FullLayoutJson; 1570 } 1571 } 1572}