VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.movement/Runtime/Native/Scripts/Serialization/PlaybackFunctions.cs
IonutMocanu 48cccc22ad Main2
2025-09-08 11:13:29 +03:00

314 lines
14 KiB
C#

// Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
using System;
using System.Globalization;
using System.IO;
using Unity.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.Assertions;
using static Meta.XR.Movement.MSDKUtility;
namespace Meta.XR.Movement.Playback
{
/// <summary>
/// Contains types and functions used for deserializing data.
/// </summary>
public class PlaybackFunctions
{
/// <summary>
/// Class that contains the file stream to read to as well as its
/// associated binary reader. Its functions return deserialized data
/// obtained from the file.
/// </summary>
public class ReaderFileStream
{
/// <summary>
/// Size of snapshot's header in terms of bytes.
/// </summary>
private const int _SNAPSHOT_HEADER_SIZE_BYTES = 8;
private FileStream _stream;
private BinaryReader _reader;
/// <summary>
/// Main constructor.
/// </summary>
/// <param name="stream">File stream.</param>
/// <param name="reader">Binary writer.</param>
public ReaderFileStream(FileStream stream, BinaryReader reader)
{
_stream = stream;
_reader = reader;
}
/// <summary>
/// Parses headers of a recording, along with snapshot byte offsets.
/// Checks the recording against an expected data version.
/// </summary>
/// <param name="startHeader">The recording's start header.</param>
/// <param name="endHeader">The recording's end header.</param>
/// <param name="snapshotToByteOffsetAfterHeader">The byte offset of each snapshot
/// after the header. We can use this to find snapshot i in the file this week, by
/// doing a seek of HEADER_BYTES added to the snapshot byte offset.</param>
/// <param name="expectedDataVersion">The expected data version of the recording. An
/// error is thrown if the snapshot recording does not match it.</param>
/// <exception cref="Exception">An exception is thrown if an error occurs during
/// deserialization.</exception>
public void ParseHeadersAndOffsets(
ref StartHeader startHeader,
ref EndHeader endHeader,
ref int[] snapshotToByteOffsetAfterHeader,
double expectedDataVersion)
{
byte[] startHeaderBytes = new byte[SERIALIZATION_START_HEADER_SIZE_BYTES];
byte[] endHeaderBytes = new byte[SERIALIZATION_END_HEADER_SIZE_BYTES];
_reader.Read(startHeaderBytes, 0, startHeaderBytes.Length);
if (!DeserializeStartHeader(startHeaderBytes, out startHeader))
{
throw new Exception("Could not deserialize start header from file!");
}
var expectedDataVersionString = expectedDataVersion.ToString(CultureInfo.InvariantCulture);
if (startHeader.DataVersion != expectedDataVersionString)
{
throw new Exception($"Data version of file is {startHeader.DataVersion}, " +
$"does not match reader's version of {expectedDataVersionString}.");
}
// go through all snapshots and record byte offsets. This will allow scrubbing.
snapshotToByteOffsetAfterHeader = new int[startHeader.NumSnapshots];
int snapshotByteOffsetAfterHeader = 0;
for (int frameIndex = 0; frameIndex < startHeader.NumSnapshots; frameIndex++)
{
snapshotToByteOffsetAfterHeader[frameIndex] = snapshotByteOffsetAfterHeader;
SkipSnapshotDataFromCurrentSeek(out int numBytesSkipped);
snapshotByteOffsetAfterHeader += numBytesSkipped;
}
// process end header after skipping snapshot frames
_reader.Read(endHeaderBytes, 0, endHeaderBytes.Length);
if (!DeserializeEndHeader(endHeaderBytes, out endHeader))
{
throw new Exception("Could not deserialize start header from file!");
}
//Debug.Log($"Start header ${startHeader.ToString()}");
//Debug.Log($"End header ${endHeader.ToString()}");
}
/// <summary>
/// Cleans up unmanaged resources.
/// </summary>
public void Dispose()
{
_reader.Dispose();
_stream.Close();
_stream.Dispose();
}
private void SkipSnapshotDataFromCurrentSeek(out int numBytesSkipped)
{
int snapshotSize = _reader.ReadInt32();
int snapshotsSinceLastSync = _reader.ReadInt32();
_stream.Seek(snapshotSize, SeekOrigin.Current);
numBytesSkipped = _SNAPSHOT_HEADER_SIZE_BYTES + snapshotSize;
}
/// <summary>
/// Reads snapshot bytes.
/// </summary>
/// <param name="snapshotByteoffset">Snapshot byte offset to seek.</param>
/// <param name="snapshotsSinceLastSync">Number of snapshots since last seek (deserialized).</param>
/// <param name="snapshotBytesRead">Deserialized snaphot bytes.</param>
/// <param name="numTotalBytesDeserialized">Number of total bytes deserialized, including snapshot header bytes.</param>
public void ReadSnapshotData(
int snapshotByteoffset,
out int snapshotsSinceLastSync,
out byte[] snapshotBytesRead,
out int numTotalBytesDeserialized)
{
_stream.Seek(SERIALIZATION_START_HEADER_SIZE_BYTES
+ snapshotByteoffset, SeekOrigin.Begin);
int snapshotSize = _reader.ReadInt32();
snapshotsSinceLastSync = _reader.ReadInt32();
snapshotBytesRead = _reader.ReadBytes(snapshotSize);
numTotalBytesDeserialized = _SNAPSHOT_HEADER_SIZE_BYTES + snapshotSize;
}
/// <summary>
/// Gets the number snapshots since last sync or last full snapshot,
/// from the current snapshot offset.
/// </summary>
/// <param name="snapshotByteOffset">The snapshot byte offset.</param>
/// <returns></returns>
public int GetSnapshotsSinceLastSync(int snapshotByteOffset)
{
_stream.Seek(SERIALIZATION_START_HEADER_SIZE_BYTES
+ snapshotByteOffset, SeekOrigin.Begin);
// skip the size, then read the correct snapshot size.
var snapshotSize = _reader.ReadInt32();
return _reader.ReadInt32();
}
/// <summary>
/// Get network time for snapshot.
/// </summary>
/// <param name="snapshotByteOffset">Snapshot byte offset.</param>
/// <returns>Network time.</returns>
public double GetNetworkTimeForSnapshot(int snapshotByteOffset)
{
ReadSnapshotData(
snapshotByteOffset,
out int snapshotsSinceLastSync,
out byte[] snapshotBytesRead,
out int numTotalBytesDeserialized);
NativeArray<byte> nativeBytesArray = new NativeArray<byte>(
snapshotBytesRead.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (int i = 0; i < snapshotBytesRead.Length; i++)
{
nativeBytesArray[i] = snapshotBytesRead[i];
}
DeserializeSnapshotTimestamp(nativeBytesArray, out double readTimestamp);
return readTimestamp;
}
/// <summary>
/// Obtains the bytes represented by an initial and final snapshot range,
/// inclusive.
/// </summary>
/// <param name="firstSnapshotIndex">First snapshot of range.</param>
/// <param name="lastSnapshotIndex">Last snapshot of range.</param>
/// <param name="snapshotToByteoffsetAfterHeader">Snapshot to byte offset
/// table, useful for associating snapshot indices to byte offsets.</param>
/// <returns>Byte array representing range of snapshots, if available.</returns>
public byte[] GetSnapshotRangeBytes(
int firstSnapshotIndex,
int lastSnapshotIndex,
int[] snapshotToByteoffsetAfterHeader)
{
if (firstSnapshotIndex > lastSnapshotIndex ||
firstSnapshotIndex < 0 || lastSnapshotIndex < 0 ||
firstSnapshotIndex >= snapshotToByteoffsetAfterHeader.Length ||
lastSnapshotIndex >= snapshotToByteoffsetAfterHeader.Length)
{
Debug.LogError("Cannot get snapshot bytes due invalid range. Please " +
$"make sure it matches ({0}-{snapshotToByteoffsetAfterHeader.Length - 1})");
return null;
}
// Gather information about snapshot offset and sizes necessary.
int firstSnapshotOffset = snapshotToByteoffsetAfterHeader[firstSnapshotIndex];
int lastSnapshotOffset = snapshotToByteoffsetAfterHeader[lastSnapshotIndex];
_stream.Seek(SERIALIZATION_START_HEADER_SIZE_BYTES
+ lastSnapshotOffset, SeekOrigin.Begin);
// include last snapshot size, as well as its header size
int lastSnapshotSize = _reader.ReadInt32() + _SNAPSHOT_HEADER_SIZE_BYTES;
// Alocate snapshot bytes.
Assert.IsTrue(firstSnapshotOffset <= lastSnapshotOffset,
"First snapshot offset must be smaller than or equal to the last offset.");
int sizeToAllocate = (lastSnapshotOffset - firstSnapshotOffset) +
lastSnapshotSize;
byte[] bytesToGrab = new byte[sizeToAllocate];
// Grab the data from the stream.
_stream.Seek(SERIALIZATION_START_HEADER_SIZE_BYTES
+ firstSnapshotOffset, SeekOrigin.Begin);
bytesToGrab = _reader.ReadBytes(sizeToAllocate);
return bytesToGrab;
}
}
/// <summary>
/// Opens a file for playback.
/// </summary>
/// <param name="startHeader">Start header to create.</param>
/// <param name="endHeader">End header to create.</param>
/// <param name="snapshotToByteOffsetAfterHeader">Snapshot to byte offset after start header.</param>
/// <param name="expectedDataVersion">Expected data version.</param>
/// <param name="playbackPath">Optional path.</param>
/// <returns></returns>
public static ReaderFileStream OpenFileForPlayback(
ref StartHeader startHeader, ref EndHeader endHeader,
ref int[] snapshotToByteOffsetAfterHeader, double expectedDataVersion,
string playbackPath = null)
{
var playbackFile = playbackPath != null ? playbackPath : string.Empty;
#if UNITY_EDITOR
if (String.IsNullOrEmpty(playbackFile))
{
playbackFile = EditorUtility.OpenFilePanel("Select Recording for Playback", "", "sbn");
}
#endif
if (string.IsNullOrEmpty(playbackFile))
{
Debug.LogError("No playback file selected, bailing.");
return null;
}
ReaderFileStream readerFileStream = null;
try
{
var fileStream = new FileStream(playbackFile, FileMode.Open, FileAccess.Read, FileShare.None);
var reader = new BinaryReader(fileStream);
readerFileStream = new ReaderFileStream(fileStream, reader);
readerFileStream.ParseHeadersAndOffsets(
ref startHeader,
ref endHeader,
ref snapshotToByteOffsetAfterHeader,
expectedDataVersion);
}
catch (Exception exception)
{
Debug.LogError($"Caught exception while trying to read recording: {exception}");
// can't playback here, probably need to not play anything
return null;
}
return readerFileStream;
}
/// <summary>
/// Reads snapshot at a byte offset.
/// </summary>
/// <param name="readFileStream">File stream to use.</param>
/// <param name="currentSnapshotByteOffset">Current byte offset.</param>
/// <param name="snapshotIndex">Snapshot index.</param>
/// <param name="snapshotsSinceLastSync">Snapshots since last sync for the snapshot read.</param>
/// <param name="numSnapshotsTotal">Number of snapshots total.</param>
/// <returns></returns>
public static byte[] ReadSnapshotAtOffset(
ReaderFileStream readFileStream,
ref int currentSnapshotByteOffset,
int snapshotIndex,
ref int snapshotsSinceLastSync,
int numSnapshotsTotal)
{
byte[] snapshotBytes = null;
try
{
if (snapshotIndex < 0 || numSnapshotsTotal == snapshotIndex)
{
Debug.LogWarning($"Invalid index of {snapshotIndex}; " +
$"valid range is only (0, {numSnapshotsTotal - 1}).");
return null;
}
readFileStream.ReadSnapshotData(
currentSnapshotByteOffset,
out snapshotsSinceLastSync,
out snapshotBytes,
out int numBytesDeserialized);
currentSnapshotByteOffset += numBytesDeserialized;
}
catch (Exception exception)
{
Debug.LogError($"Caught exception while trying to read snapshot: {exception}");
return null;
}
return snapshotBytes;
}
}
}