using System; using NUnit.Framework; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.XR.ARSubsystems.Tests { [TestFixture] class TrackableChangesTestFixture { static readonly Allocator[] k_Allocators = { Allocator.Temp, Allocator.TempJob, Allocator.Persistent }; static void ForEachAllocator(Action @delegate) { foreach (var allocator in k_Allocators) { @delegate(allocator); } } /// /// This is a simulation of Trackable data that was added in a later minor revision of the package. /// struct DataAddedInALaterVersion : IEquatable { public float aFloat; public double aDouble; public char aChar; public long aLong64; public byte aByte; const double feigenbaumConstant = 4.669201609102990671853203820466; const byte largestPrimeNumberLessThan255 = 251; const long carolPrime = 18014398241046527; public static DataAddedInALaterVersion defaultValue => new DataAddedInALaterVersion { aFloat = Mathf.PI, aDouble = feigenbaumConstant, aChar = 'A', aLong64 = carolPrime, aByte = largestPrimeNumberLessThan255 }; public bool Equals(DataAddedInALaterVersion other) => aFloat.Equals(other.aFloat) && aDouble.Equals(other.aDouble) && aChar == other.aChar && aLong64 == other.aLong64 && aByte == other.aByte; public override bool Equals(object obj) => obj is DataAddedInALaterVersion other && Equals(other); // Required but not used public override int GetHashCode() => base.GetHashCode(); } /// /// Simulates some data from an , e.g., or /// struct SubsystemTrackableData : ITrackable { // Disable "Field 'field' is never assigned to, and will always have its default value 'value'" // In this test, SubsystemTrackableData is only created via a MemCpy. #pragma warning disable CS0649 TrackableId m_TrackableId; public TrackableId trackableId => m_TrackableId; Pose m_Pose; public Pose pose => m_Pose; TrackingState m_TrackingState; public TrackingState trackingState => m_TrackingState; IntPtr m_NativePtr; public IntPtr nativePtr => m_NativePtr; public int anInt32; public bool aBool; public DataAddedInALaterVersion dataAddedInALaterVersion; #pragma warning restore CS0649 public static SubsystemTrackableData defaultValue => new SubsystemTrackableData { m_Pose = Pose.identity, dataAddedInALaterVersion = DataAddedInALaterVersion.defaultValue }; public unsafe ProviderTrackableData ToProviderTrackableData() { var result = default(ProviderTrackableData); UnsafeUtility.MemCpy(&result, UnsafeUtility.AddressOf(ref this), sizeof(ProviderTrackableData)); return result; } } static unsafe TrackableId GenerateRandomId() { var guid = Guid.NewGuid(); return *(TrackableId*)&guid; } static Pose GenerateRandomPose() => new Pose(Random.insideUnitSphere * 100, Random.rotationUniform); /// /// This simulates the data a particular provider (i.e., implementation) knows about. It matches the first /// part of , but is from an "earlier" version of the API, so doesn't /// know about the . /// struct ProviderTrackableData : IEquatable { public TrackableId trackableId; public Pose pose; public TrackingState trackingState; public IntPtr nativePtr; public int anInt32; public bool aBool; public static ProviderTrackableData random => new ProviderTrackableData { trackableId = GenerateRandomId(), pose = GenerateRandomPose(), trackingState = (TrackingState)Random.Range(0, 2), nativePtr = new IntPtr(Random.Range(int.MinValue, int.MaxValue)), anInt32 = Random.Range(int.MinValue, int.MaxValue), aBool = Random.Range(0, 1) == 0, }; public bool Equals(ProviderTrackableData other) => trackableId.Equals(other.trackableId) && pose.Equals(other.pose) && trackingState == other.trackingState && nativePtr == other.nativePtr && anInt32 == other.anInt32 && aBool == other.aBool; public override bool Equals(object obj) => obj is ProviderTrackableData other && Equals(other); // Required but not used public override int GetHashCode() => base.GetHashCode(); } [Test] public unsafe void TestTrackableChangesCopiesPointers() { const int addedCount = 7; const int updatedCount = 11; const int removedCount = 13; var addedPtr = stackalloc ProviderTrackableData[addedCount]; var updatedPtr = stackalloc ProviderTrackableData[updatedCount]; var removedPtr = stackalloc TrackableId[removedCount]; for (var i = 0; i < addedCount; i++) { addedPtr[i] = ProviderTrackableData.random; } for (var i = 0; i < updatedCount; i++) { updatedPtr[i] = ProviderTrackableData.random; } for (var i = 0; i < removedCount; i++) { removedPtr[i] = GenerateRandomId(); } ForEachAllocator(allocator => { using (var trackableChanges = new TrackableChanges( addedPtr, addedCount, updatedPtr, updatedCount, removedPtr, removedCount, SubsystemTrackableData.defaultValue, UnsafeUtility.SizeOf(), allocator)) { Assert.IsTrue(trackableChanges.isCreated); Assert.AreEqual(addedCount, trackableChanges.added.Length); Assert.AreEqual(updatedCount, trackableChanges.updated.Length); Assert.AreEqual(removedCount, trackableChanges.removed.Length); for (var i = 0; i < addedCount; i++) { var added = trackableChanges.added[i]; Assert.AreEqual(addedPtr[i], added.ToProviderTrackableData()); Assert.AreEqual(DataAddedInALaterVersion.defaultValue, added.dataAddedInALaterVersion); Assert.AreEqual(0, UnsafeUtility.MemCmp(addedPtr + i, &added, sizeof(ProviderTrackableData))); } for (var i = 0; i < updatedCount; i++) { var updated = trackableChanges.updated[i]; Assert.AreEqual(updatedPtr[i], updated.ToProviderTrackableData()); Assert.AreEqual(DataAddedInALaterVersion.defaultValue, updated.dataAddedInALaterVersion); Assert.AreEqual(0, UnsafeUtility.MemCmp(updatedPtr + i, &updated, sizeof(ProviderTrackableData))); } for (var i = 0; i < removedCount; i++) { var removed = trackableChanges.removed[i]; Assert.AreEqual(removedPtr[i], removed); Assert.AreEqual(0, UnsafeUtility.MemCmp(removedPtr + i, &removed, sizeof(TrackableId))); } } }); } [Test] public unsafe void HandlesZero() { ForEachAllocator(allocator => { using (var trackableChanges = new TrackableChanges( null, 0, null, 0, null, 0, SubsystemTrackableData.defaultValue, UnsafeUtility.SizeOf(), allocator)) { Assert.AreEqual(0, trackableChanges.added.Length); Assert.AreEqual(0, trackableChanges.updated.Length); Assert.AreEqual(0, trackableChanges.removed.Length); } using (var trackableChanges = new TrackableChanges(0, 0, 0, allocator)) { Assert.AreEqual(0, trackableChanges.added.Length); Assert.AreEqual(0, trackableChanges.updated.Length); Assert.AreEqual(0, trackableChanges.removed.Length); } }); } [Test] public void IsCreatedIsFalse() { TrackableChanges changes = default; Assert.IsFalse(changes.isCreated); } [Test] public void UncreatedCanBeDisposed() { TrackableChanges changes = default; changes.Dispose(); } [Test] public void CanBeDisposedMultipleTimes() { ForEachAllocator(allocator => { var changes = new TrackableChanges(1, 1, 1, allocator); Assert.IsTrue(changes.isCreated); changes.Dispose(); Assert.IsFalse(changes.isCreated); changes.Dispose(); }); } [Test] public void AnyCountCanBeZero() { ForEachAllocator(allocator => { for (var added = 0; added <= 2; added++) { for (var updated = 0; updated <= 2; updated++) { for (var removed = 0; removed <= 2; removed++) { using (new TrackableChanges(added, updated, removed, allocator)) { // this area intentionally left blank } } } } }); } } }