Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,37 @@
using NUnit.Framework;
using Unity.Collections;
using Unity.Mathematics;
using static Rukhanka.AnimationProcessSystem;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
public class AnimationProcessSystemTests
{
[Test]
public void MuscleRangeToRadiansTest()
{
var r0 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-60, -30, -100), new float3(40, 60, 120), new float3(-0.5f, 0.0f, 0.5f));
Assert.IsTrue(math.all(r0 == new float3(-30, 0, 60)));
var r1 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-60, -30, -100), new float3(40, 60, 120), new float3(0, 0, 0));
Assert.IsTrue(math.all(r1 == new float3(0, 0, 0)));
var r2 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-91, -92, -93), new float3(94, 95, 96), new float3(1, 1, 1));
Assert.IsTrue(math.all(r2 == new float3(94, 95, 96)));
var r3 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-91, -92, -93), new float3(94, 95, 96), new float3(-1, -1, -1));
Assert.IsTrue(math.all(r3 == new float3(-91, -92, -93)));
var r4 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-60, -30, -100), new float3(40, 60, 120), new float3(-0.9f, 0.1f, 0.9f));
Assert.IsTrue(math.all(r4 == new float3(-54, 6, 108)));
var r5 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-60, -30, -100), new float3(40, 60, 120), new float3(0.9f, -0.1f, -0.2f));
Assert.IsTrue(math.all(r5 == new float3(36, -3, -20)));
var r6 = ComputeBoneAnimationJob.MuscleRangeToRadians(new float3(-12, -13, -14), new float3(12, 13, 14), new float3(-2, 3, -5.5f));
Assert.IsTrue(math.all(r6 == new float3(-24, 39, -77)));
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b8972ff72b5f18f438eb941e6ac95b51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/AnimationProcessSystemTests.cs
uploadId: 897522
@@ -0,0 +1,79 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using static Rukhanka.AnimatorControllerSystemJobs;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
public class AnimatorControllerTest
{
[Test]
public void LoopAwareTransitionTimeTest()
{
var fn = TestToolbox.GetStaticPrivateMethod<StateMachineProcessJob>("GetLoopAwareTransitionExitTime");
var testData = new List<float2>();
testData.Add(new float2(0, 0));
testData.Add(new float2(1, 0));
testData.Add(new float2(0, 1));
testData.Add(new float2(1, 1));
testData.Add(new float2(0, 2));
testData.Add(new float2(1, 2));
testData.Add(new float2(0, 3));
testData.Add(new float2(1, 3));
testData.Add(new float2(0, 3.5f));
testData.Add(new float2(1, 3.5f));
testData.Add(new float2(0, 6.99f));
testData.Add(new float2(1, 6.99f));
testData.Add(new float2(0, 7.01f));
testData.Add(new float2(1, 7.01f));
UnityEngine.Random.InitState(DateTime.Now.Millisecond);
for (var i = 0; i < 100; ++i)
{
var exitTime = UnityEngine.Random.value;
var normalizedStateTime = (UnityEngine.Random.value * 2 - 1) * 20;
testData.Add(new float2(exitTime, normalizedStateTime));
}
// Test for exit time [0..1]
foreach (var td in testData)
{
var exitTime = td.x;
var normalizedStateTime = td.y;
var t = StateMachineProcessJob.GetLoopAwareTransitionExitTime(exitTime, normalizedStateTime, math.sign(normalizedStateTime));
var dt = t - normalizedStateTime;
//Debug.Log($"ET: {exitTime}, NT: {normalizedStateTime}, Transition Time: {t}");
if (math.sign(normalizedStateTime) > 0)
{
Assert.IsTrue(dt >= 0);
Assert.IsTrue(dt <= 1);
}
else
{
Assert.IsTrue(dt <= 0);
Assert.IsTrue(dt >= -1);
}
}
// Test for exit time >= 1
for (var i = 0; i < 20; ++i)
{
var exitTime = UnityEngine.Random.value * 10 + 1;
var normalizedStateTime = (UnityEngine.Random.value * 2 - 1) * 20;
var sgn = math.sign(normalizedStateTime);
var t = StateMachineProcessJob.GetLoopAwareTransitionExitTime(exitTime, normalizedStateTime, sgn);
Assert.IsTrue(t == exitTime * sgn);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 06dc952934a081a44afa544c8bd18772
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/AnimatorControllerTest.cs
uploadId: 897522
@@ -0,0 +1,142 @@
using NUnit.Framework;
using Rukhanka.Hybrid;
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Random = Unity.Mathematics.Random;
#if RUKHANKA_WITH_TEST_PERFORMANCE
using Unity.PerformanceTesting;
#endif
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
[BurstCompile]
public class AnimatorParameterTest
{
#if RUKHANKA_WITH_TEST_PERFORMANCE
[BurstCompile]
static void ParameterAccessByLinearSearch(ref NativeSlice<AnimatorControllerParameterComponent> paramArr, int iterationCount, out int dummyResult)
{
var l = paramArr.Length;
dummyResult = 0;
for (int i = 0; i < iterationCount; ++i)
{
var idx = i % l;
var p = paramArr[idx];
bool found = false;
int k = 0;
for (; k < paramArr.Length && !found; ++k)
{
var candidat = paramArr[k];
found = candidat.hash == p.hash;
}
dummyResult += paramArr[k - 1].value.intValue;
}
}
/////////////////////////////////////////////////////////////////////////////////
NativeArray<AnimatorControllerParameterComponent> CreateTestParamArray(int len)
{
var paramArr = new NativeArray<AnimatorControllerParameterComponent>(len, Allocator.Temp);
var rng = new Random((uint)DateTime.Now.Millisecond + 1);
for (var i = 0; i < paramArr.Length; ++i)
{
var randomHash = rng.NextUInt();
var acpc = new AnimatorControllerParameterComponent()
{
hash = randomHash,
type = ControllerParameterType.Int,
value = default
};
paramArr[i] = acpc;
}
return paramArr;
}
/////////////////////////////////////////////////////////////////////////////////
[Test, Performance]
public void LinearSearchParameterAccess()
{
// Create test data
var paramArr = CreateTestParamArray(200);
var iterationsCount = 1000000;
var testGroupSize = new int[] {2, 5, 10, 20, 50, 100};
foreach (var paramCount in testGroupSize)
{
var paramArrForTest = new NativeSlice<AnimatorControllerParameterComponent>(paramArr, 0, paramCount);
Measure.Method(() =>
{
ParameterAccessByLinearSearch(ref paramArrForTest, iterationsCount, out var dummyResult);
})
.MeasurementCount(10)
.SampleGroup($"Linear Parameter Access. Parameter count: {paramArrForTest.Length}, Searches count: {iterationsCount}")
.Run();
}
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static unsafe void ParameterAccessHashLookup(ref NativeSlice<AnimatorControllerParameterComponent> paramArr, ref BlobAssetReference<PerfectHashTableBlob> pht, int iterationCount, out int dummyResult)
{
dummyResult = 0;
var l = paramArr.Length;
for (int i = 0; i < iterationCount; ++i)
{
var idx = i % l;
var p = paramArr[idx];
var ps = new ReadOnlySpan<AnimatorControllerParameterComponent>(paramArr.GetUnsafePtr(), paramArr.Length);
var pi = FastAnimatorParameter.GetRuntimeParameterIndex(p.hash, pht, ps);
dummyResult += paramArr[pi].value.intValue;
}
}
/////////////////////////////////////////////////////////////////////////////////
[Test, Performance]
public void HashTableParameterAccess()
{
// Create test data
var paramArr = CreateTestParamArray(200);
var iterationsCount = 1000000;
var testGroupSize = new int[] {2, 5, 10, 20, 50, 100};
foreach (var paramCount in testGroupSize)
{
var paramArrSlice = new NativeSlice<AnimatorControllerParameterComponent>(paramArr, 0, paramCount);
var hashesArr = new NativeArray<uint>(paramCount, Allocator.Temp);
for (int i = 0; i < hashesArr.Length; ++i)
{
hashesArr[i] = paramArr[i].hash;
}
var perfectHashTableBlob = AnimatorControllerBaker.CreateParametersPerfectHashTableBlobInternal(hashesArr);
Measure.Method(() =>
{
ParameterAccessHashLookup(ref paramArrSlice, ref perfectHashTableBlob, iterationsCount, out _);
})
.MeasurementCount(10)
.SampleGroup($"Hash Table Parameter Access. Parameter count: {paramArrSlice.Length}, Searches count: {iterationsCount}")
.Run();
perfectHashTableBlob.Dispose();
}
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e01a62303845fcc4cb476467f1d8d7ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/AnimatorParameterTest.cs
uploadId: 897522
@@ -0,0 +1,36 @@
using NUnit.Framework;
using Rukhanka.Toolbox;
using Unity.Collections;
using UnityEngine;
using Random = Unity.Mathematics.Random;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
public class BitFieldNTest
{
[Test]
public unsafe void FunctionalityTest()
{
var rng = new Random((uint)(Time.time * 1000));
var maxBitfieldSize = rng.NextInt(300, 1000);
var bitFieldMem = stackalloc uint[maxBitfieldSize];
var numTests = rng.NextUInt(100);
for (var i = 0; i < numTests; ++i)
{
var sz = rng.NextInt(1, maxBitfieldSize);
var bf = new BitFieldN(bitFieldMem, sz);
var i0 = rng.NextInt(0, bf.Length);
bf.Set(i0, true);
Assert.IsTrue(bf.IsSet(i0));
Assert.IsTrue(bf.TestAny());
bf.Set(i0, false);
Assert.IsFalse(bf.IsSet(i0));
Assert.IsFalse(bf.TestAny());
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5c5ef6a217643ec4b90bf176a3c2fe96
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/BitFieldNTest.cs
uploadId: 897522
@@ -0,0 +1,148 @@
using NUnit.Framework;
using Unity.Collections;
using Rukhanka.Toolbox;
using Unity.Mathematics;
using UnityEngine;
using Random = Unity.Mathematics.Random;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
public class ComputeBufferToolsTests
{
[Test]
public void ComputeBufferClearTest()
{
var bufferSize = 0xffff;
var readbackData = new int[bufferSize];
var srcData = new NativeArray<int>(bufferSize, Allocator.Temp);
using var buf = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, bufferSize, sizeof(int));
var rng = new Random((uint)bufferSize);
// Fill buffer with random data
for (var k = 0; k < bufferSize; ++k)
{
srcData[k] = rng.NextInt();
}
var numIterations = 10;
for (var i = 0; i < numIterations; ++i)
{
buf.SetData(srcData);
// Now clear portion of buffer and check result
var startIndex = rng.NextUInt((uint)bufferSize - 1);
var count = rng.NextUInt((uint)bufferSize - startIndex);
var clearValue = rng.NextInt();
ComputeBufferTools.Clear(buf, startIndex * 4, count * 4, (uint)clearValue);
buf.GetData(readbackData);
// Verify correctness
for (var k = 0; k < bufferSize; ++k)
{
var v = readbackData[k];
var srcV = srcData[k];
if (k < startIndex || k >= startIndex + count)
{
Assert.IsTrue(v == srcV);
}
else
{
Assert.IsTrue(v == clearValue);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
[Test]
public void ComputeBufferCopyTest()
{
var bufferSize = 0xffff;
using var srcBuf = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, bufferSize, sizeof(int));
var srcData = new NativeArray<int>(srcBuf.count, Allocator.Temp);
using var dstBuf = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, bufferSize * 2, sizeof(int));
var dstData = new NativeArray<int>(dstBuf.count, Allocator.Temp);
var readbackData = new int[dstBuf.count];
var rng = new Random((uint)bufferSize);
// Fill buffer with random data
for (var k = 0; k < srcData.Length; ++k)
srcData[k] = rng.NextInt();
for (var k = 0; k < dstData.Length; ++k)
dstData[k] = rng.NextInt();
var numIterations = 100;
for (var i = 0; i < numIterations; ++i)
{
srcBuf.SetData(srcData);
dstBuf.SetData(dstData);
var srcIndex = rng.NextUInt((uint)srcData.Length - 1);
var copyCount = rng.NextUInt((uint)srcData.Length - srcIndex);
var dstIndex = rng.NextUInt((uint)dstData.Length - copyCount);
ComputeBufferTools.Copy(srcBuf, dstBuf, srcIndex * 4, dstIndex * 4, copyCount * 4);
dstBuf.GetData(readbackData);
// Verify correctness
for (var k = 0; k < readbackData.Length; ++k)
{
var v = readbackData[k];
var si = k - dstIndex;
if (si < 0 || si >= copyCount)
{
var sv = dstData[k];
Assert.IsTrue(v == sv);
}
else
{
var sv = srcData[(int)(si + srcIndex)];
Assert.IsTrue(v == sv);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
[Test]
public void ComputeBufferResizeTest()
{
var srcBufferSize = 0xffff;
var dstBufferSize = srcBufferSize * 2;
var srcData = new NativeArray<int>(srcBufferSize, Allocator.Temp);
var rng = new Random((uint)srcBufferSize);
// Fill buffer with random data
for (var k = 0; k < srcData.Length; ++k)
srcData[k] = rng.NextInt();
var numIterations = 10;
for (var i = 0; i < numIterations; ++i)
{
var srcBuf = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, srcBufferSize, sizeof(int));
srcBuf.SetData(srcData);
var newSize = rng.NextInt(dstBufferSize);
using var newBuf = ComputeBufferTools.Resize(srcBuf, newSize);
srcBuf.Release();
var readbackBuf = new int[newSize];
newBuf.GetData(readbackBuf);
var sz = math.min(newSize, srcData.Length);
for (var k = 0; k < sz; ++k)
{
Assert.IsTrue(readbackBuf[k] == srcData[k]);
}
newBuf.Release();
}
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 32a45c080cb803f418a4a8111296eb72
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/ComputeBufferToolsTest.cs
uploadId: 897522
@@ -0,0 +1,45 @@
using NUnit.Framework;
using Unity.Collections;
using Rukhanka.Toolbox;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
public class MathUtilsTest
{
[Test]
public void ShuffleListTest()
{
var sz = 100;
var srcArr = new NativeList<int>(128, Allocator.Temp);
var refArr = new NativeList<int>(128, Allocator.Temp);
var rr = new Unity.Mathematics.Random(123);
for (int i = 0; i < sz; ++i)
{
var v = rr.NextInt() % sz * 2;
srcArr.Add(v);
refArr.Add(v);
}
var indexList = new NativeList<int>(srcArr.Length, Allocator.Temp);
var shuffleList = new NativeList<int>(srcArr.Length, Allocator.Temp);
for (int i = 0; i < sz; ++i) indexList.Add(i);
while (!indexList.IsEmpty)
{
var index = (int)(rr.NextUInt() % indexList.Length);
shuffleList.Add(indexList[index]);
indexList.RemoveAt(index);
}
MathUtils.ShuffleArray(srcArr.AsArray().AsSpan(), shuffleList.AsArray());
for (int i = 0; i < sz; ++i)
{
Assert.IsTrue(srcArr[i] == refArr[shuffleList[i]]);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: abf96d770202cf64a98f590eca32fef9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/MathUtilsTest.cs
uploadId: 897522
@@ -0,0 +1,168 @@
using System.Runtime.CompilerServices;
using NUnit.Framework;
using Unity.Collections;
using Unity.Mathematics;
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.PerformanceTesting;
using UnityEngine;
using Random = Unity.Mathematics.Random;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public class PerfectHashTest
{
[Test]
public void PerfectHashValidationTest()
{
var numTests = 100;
var seed = 123124u;
var rng = new Random(seed);
for (var i = 0; i < numTests; ++i)
{
InternalHashFuncTest(rng.NextUInt(), math.abs(rng.NextInt() % 1000));
}
}
/////////////////////////////////////////////////////////////////////////////////
unsafe void InternalHashFuncTest(uint rngSeed, int numHashedValues)
{
var rng = new Random(rngSeed);
var hashArr = new NativeList<uint>(Allocator.Temp);
for (int i = 0; i < numHashedValues; ++i)
{
hashArr.Add(rng.NextUInt());
};
Perfect2HashTable.Build(hashArr.AsArray(), out var pht, out var seed);
for (int i = 0; i < hashArr.Length; ++i)
{
var iHash = hashArr[i];
var l = Perfect2HashTable.Query(iHash, seed, (uint2*)pht.GetUnsafePtr(), pht.Length);
Assert.IsTrue(l == i);
}
}
/////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.NoInlining)]
static void DummyUse(int s)
{
s += 1;
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static void HashSetQueryFunc(in NativeHashSet<uint> inSet, in NativeArray<uint> srcValues, int numIterations)
{
var sum = 0;
for (int i = 0; i < numIterations; ++i)
{
var l = i * 12 % srcValues.Length;
var k = inSet.Contains(srcValues[l]);
sum += k ? 1 : 0;
//BurstAssert.IsTrue(l == shuffleArr[k], "Does not match");
}
// Need to use sum somehow to keep query function body from stripping
DummyUse(sum);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static unsafe void PHT2QueryFunc(in NativeArray<uint2> pht, in NativeArray<uint> srcValues, uint seed, int numIterations)
{
var sum = 0;
for (int i = 0; i < numIterations; ++i)
{
var l = i * 12 % srcValues.Length;
var k = Perfect2HashTable.Query(srcValues[l], seed, (uint2*)pht.GetUnsafeReadOnlyPtr(), pht.Length);
sum += (k >= 0) ? 1 : 0;
//BurstAssert.IsTrue(k >= 0, "Does not match");
}
// Need to use sum somehow to keep query function body from stripping
DummyUse(sum);
}
/////////////////////////////////////////////////////////////////////////////////
void NativeHashMapPerformanceSingleTest(NativeHashSet<uint> inSet, NativeArray<uint> srcValues, int numIterations)
{
Measure.Method(() =>
{
HashSetQueryFunc(inSet, srcValues, numIterations);
})
.MeasurementCount(10)
.SampleGroup($"Native hash set query. Table size: {inSet.Count}, Queries count: {numIterations}")
.Run();
}
/////////////////////////////////////////////////////////////////////////////////
void PHT2_SingleTest(NativeArray<uint2> pht, NativeArray<uint> srcValues, uint seed, int numIterations)
{
Measure.Method(() =>
{
PHT2QueryFunc(pht, srcValues, seed, numIterations);
})
.MeasurementCount(10)
.SampleGroup($"PHT2 query. Table size: {pht.Length}, Queries count: {numIterations}")
.Run();
}
/////////////////////////////////////////////////////////////////////////////////
[Test, Performance]
public void PerfectHashTableQueryPerformance()
{
var iterationsCount = 1000000;
var rng = new Random((uint)(Time.time * 1000));
var testArraySizes = new [] { 10, 20, 50, 100, 200, 300 };
var maxElements = testArraySizes[^1];
var hashArr = new NativeArray<uint>(maxElements, Allocator.Temp);
var hashUniqCheck = new NativeHashSet<uint>(maxElements, Allocator.Temp);
for (int i = 0; i < maxElements; ++i)
{
var v = rng.NextUInt();
if (hashUniqCheck.Add(v))
hashArr[i] = v;
else
{
Debug.LogWarning("Generator collision!");
i--;
}
};
for (var i = 0; i < testArraySizes.Length; ++i)
{
var h0 = hashArr.Slice(0, testArraySizes[i]).AsArray();
Perfect2HashTable.Build(h0, out var pht, out var seed);
PHT2_SingleTest(pht, h0, seed, iterationsCount);
}
for (var i = 0; i < testArraySizes.Length; ++i)
{
var h0 = hashArr.Slice(0, testArraySizes[i]).AsArray();
var hs = new NativeHashSet<uint>(h0.Length, Allocator.Temp);
foreach (var v in h0)
{
hs.Add(v);
}
NativeHashMapPerformanceSingleTest(hs, h0, iterationsCount);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2ff7f209bd6dd8f4f93c411f13e86b20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/PerfectHashTest.cs
uploadId: 897522
@@ -0,0 +1,36 @@
{
"name": "Rukhanka.Tests",
"rootNamespace": "",
"references": [
"GUID:01e5f7c036964124aa192bf5e2d021ba",
"GUID:69653bcfe095d1841bfa14a815f86dad",
"GUID:3bd69b8470189e543a7984590f770554",
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:0acc523941302664db1f4e527237feb3",
"GUID:734d92eba21c94caba915361bd5ac177",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:c0dd0d10738d4ad4a9de57c559d0ca1b",
"GUID:2665a8d13d1b3f18800f46e256720795",
"GUID:8819f35a0fc84499b990e90a4ca1911f"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.test-framework.performance",
"expression": "3.0.0-pre.2",
"define": "RUKHANKA_WITH_TEST_PERFORMANCE"
}
],
"noEngineReferences": false
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: af79188302ef8ac4694408b7b04e6709
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/Rukhanka.Tests.asmdef
uploadId: 897522
@@ -0,0 +1,173 @@
using NUnit.Framework;
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.PerformanceTesting;
using UnityEngine.TestTools.Utils;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
[BurstCompile]
public class SampleAnimationCurveTest
{
struct TestAnimationCurveBlob
{
public BlobArray<KeyFrame> animationCurve;
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static unsafe void SampleAnimationCurveLinearSearch(ref NativeArray<float> accumulator, in NativeArray<float> sampleTimes, in BlobAssetReference<TestAnimationCurveBlob> ba)
{
for (int i = 0; i < accumulator.Length; ++i)
{
var sum = 0.0f;
for (int j = 0; j < sampleTimes.Length; ++j)
{
var arr = new ReadOnlySpan<KeyFrame>(ba.Value.animationCurve.GetUnsafePtr(), ba.Value.animationCurve.Length);
var f = BlobCurve.SampleAnimationCurveLinearSearch(arr, sampleTimes[j]);
sum += f;
}
accumulator[i] = sum;
}
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
static unsafe void SampleAnimationCurveBinarySearch(ref NativeArray<float> accumulator, in NativeArray<float> sampleTimes, in BlobAssetReference<TestAnimationCurveBlob> ba)
{
for (int i = 0; i < accumulator.Length; ++i)
{
var sum = 0.0f;
for (int j = 0; j < sampleTimes.Length; ++j)
{
var arr = new ReadOnlySpan<KeyFrame>(ba.Value.animationCurve.GetUnsafePtr(), ba.Value.animationCurve.Length);
var f = BlobCurve.SampleAnimationCurveBinarySearch(arr, sampleTimes[j]);
sum += f;
}
accumulator[i] = sum;
}
}
/////////////////////////////////////////////////////////////////////////////////
BlobAssetReference<TestAnimationCurveBlob> CreateTestBlob(int sz)
{
var bb = new BlobBuilder(Allocator.Temp);
ref var cb = ref bb.ConstructRoot<TestAnimationCurveBlob>();
var keyFramesArr = bb.Allocate(ref cb.animationCurve, sz);
var dt = 1.0f / keyFramesArr.Length;
for (int i = 0; i < keyFramesArr.Length; ++i)
{
var kf = new KeyFrame()
{
time = dt * i,
v = dt * i,
inTan = 0.1f,
outTan = 0.1f
};
keyFramesArr[i] = kf;
}
var rv = bb.CreateBlobAssetReference<TestAnimationCurveBlob>(Allocator.Temp);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
[Test, Performance]
public void SampleAnimationCurvePerformanceTests()
{
var acc = new NativeArray<float>(1000, Allocator.Temp);
var sampleTimes = new NativeArray<float>(100, Allocator.Temp);
var rr = new Unity.Mathematics.Random(123);
for (int i = 0; i < sampleTimes.Length; ++i)
{
sampleTimes[i] = rr.NextFloat();
}
var testBlobAsset = CreateTestBlob(100);
Measure.Method(() =>
{
SampleAnimationCurveLinearSearch(ref acc, sampleTimes, testBlobAsset);
})
.MeasurementCount(10)
.SampleGroup($"Linear key search")
.Run();
Measure.Method(() =>
{
SampleAnimationCurveBinarySearch(ref acc, sampleTimes, testBlobAsset);
})
.MeasurementCount(10)
.SampleGroup($"Binary key search")
.Run();
}
/////////////////////////////////////////////////////////////////////////////////
unsafe void ValidationTestBinarySearch(ref BlobArray<KeyFrame> ac)
{
var comparer = new FloatEqualityComparer(10e-6f);
var clip = new ReadOnlySpan<KeyFrame>(ac.GetUnsafePtr(), ac.Length);
var f = BlobCurve.SampleAnimationCurveBinarySearch(clip, 0);
var refV = ac[0].v;
Assert.That(f, Is.EqualTo(refV).Using(comparer));
f = BlobCurve.SampleAnimationCurveBinarySearch(clip, 1);
refV = ac[ac.Length - 1].v;
Assert.That(f, Is.EqualTo(refV).Using(comparer));
f = BlobCurve.SampleAnimationCurveBinarySearch(clip, -100);
refV = ac[0].v;
Assert.That(f, Is.EqualTo(refV).Using(comparer));
f = BlobCurve.SampleAnimationCurveBinarySearch(clip, 100);
refV = ac[ac.Length - 1].v;
Assert.That(f, Is.EqualTo(refV).Using(comparer));
var rr = new Unity.Mathematics.Random((uint)ac.Length);
var delta = 1.0f / ac.Length;
for (int i = 0; i < 100; ++i)
{
var time = rr.NextFloat();
f = BlobCurve.SampleAnimationCurveBinarySearch(clip, time);
Assert.IsTrue(math.abs(f - time) < delta);
}
}
/////////////////////////////////////////////////////////////////////////////////
[Test]
public void SampleAnimationCurveValidationTests()
{
var blob10 = CreateTestBlob(10);
var blob2 = CreateTestBlob(2);
var blob3 = CreateTestBlob(3);
var blob7 = CreateTestBlob(7);
var blob123 = CreateTestBlob(123);
var blob321 = CreateTestBlob(321);
ValidationTestBinarySearch(ref blob10.Value.animationCurve);
ValidationTestBinarySearch(ref blob2.Value.animationCurve);
ValidationTestBinarySearch(ref blob3.Value.animationCurve);
ValidationTestBinarySearch(ref blob7.Value.animationCurve);
ValidationTestBinarySearch(ref blob123.Value.animationCurve);
ValidationTestBinarySearch(ref blob321.Value.animationCurve);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 15fba35ea76ae0b42a8d183f293ea08b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/SampleAnimationCurveTests.cs
uploadId: 897522
@@ -0,0 +1,24 @@
using NUnit.Framework;
using System.Reflection;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Tests
{
public static class TestToolbox
{
public static MethodInfo GetStaticPrivateMethod<T>(string fnName)
{
if (string.IsNullOrWhiteSpace(fnName))
Assert.Fail("Function name cannot be empty");
var classType = typeof(T);
MethodInfo rv = classType.GetMethod(fnName, BindingFlags.Static | BindingFlags.NonPublic);
if (rv == null)
Assert.Fail(string.Format("{0} method not found", fnName));
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5dd31b777f2b6fc48a80550a59ff997d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Tests/TestToolbox.cs
uploadId: 897522