Files
UnrealEngine/Engine/Source/Programs/LowLevelTests/StateStreamTests/Tests/StateStreamTests.cpp
2025-05-18 13:04:45 +08:00

591 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TestCommon/Initialization.h"
#include "StateStreamCreator.h"
#include "TestHarness.h"
#include "MockStateStreamImpl.h"
#include "StateStreamManagerImpl.h"
#include "TransformStateStreamImpl.h"
TEST_CASE("FPow2ChunkedArray", "[StateStream]")
{
// 0 - 15 Bucket 0 (16 entries)
// 16 - 47 Bucket 1 (32 entries)
// 48 - 111 Bucket 2 (64 entries)
// 112 - 239 Bucket 3 (128 entries)
// 240 - 495 Bucket 4 (256 entries)
FPow2ChunkedArray<int> Store;
CHECK_EQUAL(Store.SkipCount, 4);
CHECK_EQUAL(Store.BucketCount, 21);
CHECK_EQUAL(Store.GetBucketIndex(0), 0);
CHECK_EQUAL(Store.GetBucketIndex(15), 0);
CHECK_EQUAL(Store.GetBucketIndex(16), 1);
CHECK_EQUAL(Store.GetBucketIndex(47), 1);
CHECK_EQUAL(Store.GetBucketIndex(48), 2);
CHECK_EQUAL(Store.GetBucketIndex(111), 2);
CHECK_EQUAL(Store.GetBucketIndex(112), 3);
CHECK_EQUAL(Store.GetBucketIndex(16777215), Store.BucketCount - 1);
CHECK_EQUAL(Store.GetBucketSize(0), 16);
CHECK_EQUAL(Store.GetBucketSize(1), 32);
CHECK_EQUAL(Store.GetBucketSize(2), 64);
CHECK_EQUAL(Store.GetBucketSize(3), 128);
CHECK_EQUAL(Store.GetBucketStart(0), 0);
CHECK_EQUAL(Store.GetBucketStart(1), 16);
CHECK_EQUAL(Store.GetBucketStart(2), 48);
CHECK_EQUAL(Store.GetBucketStart(3), 112);
CHECK_EQUAL(Store.GetBucketStart(4), 240);
for (int i=0;i!=1000;++i)
{
Store.Add(i);
}
for (int i=0;i!=1000;++i)
{
CHECK_EQUAL(Store[i], i);
}
}
TEST_CASE("StateStream::Store", "[StateStream]")
{
TStateStreamStore<int> Store;
for (int i=0;i!=1000;++i)
{
Store.Add(i);
}
CHECK_EQUAL(Store.GetUsedCount(), 1000);
for (int i=0;i!=1000;++i)
{
CHECK_EQUAL(Store[i], i);
}
Store.Remove(100);
Store.Remove(101);
CHECK_EQUAL(Store.GetUsedCount(), 998);
CHECK_EQUAL(Store.Add(1234), 101);
CHECK_EQUAL(Store.Add(1235), 100);
CHECK_EQUAL(Store.GetUsedCount(), 1000);
CHECK_EQUAL(Store[100], 1235);
CHECK_EQUAL(Store[101], 1234);
CHECK_EQUAL(Store.Add(1235), 1000);
CHECK_EQUAL(Store.GetUsedCount(), 1001);
CHECK_EQUAL(Store[1000], 1235);
}
TEST_CASE("StateStream::SingleInstance", "[StateStream]")
{
// Initialization (needs to be done centrally/renderside before gameplay starts) (These instances are not visible to GT)
FMockStateStreamImpl MockStateStreamImpl;
FStateStreamManagerImpl ManagerImpl;
ManagerImpl.Render_Register(MockStateStreamImpl, false);
// Game thread populating ticks
{
IStateStreamManager& Manager = ManagerImpl;
IMockStateStream& Stream = Manager.Game_Get<IMockStateStream>();
// Tick 1, create an instance
Manager.Game_BeginTick();
FMockHandle Handle = Stream.Game_CreateInstance(FMockStaticState(), FMockDynamicState{0});
Manager.Game_EndTick(100);
// Tick 2, update the instance. Set Value to 100
Manager.Game_BeginTick();
Handle.Update(FMockDynamicState{100, true});
Handle.Update(FMockDynamicState{100});
Manager.Game_EndTick(200);
// Tick 3, update the instance again. Set Value to 200
Manager.Game_BeginTick();
Handle.Update(FMockDynamicState{200, false});
Manager.Game_EndTick(300);
// Tick 4, destroy the instance
Manager.Game_BeginTick();
Handle = {};
Manager.Game_EndTick(400);
// Tick 5, empty
Manager.Game_BeginTick();
Manager.Game_EndTick(500);
}
// Render thread consuming ticks
FStateStreamManagerImpl& Manager = ManagerImpl;
FMockStateStreamImpl& Stream = MockStateStreamImpl;
SECTION("Rend not crossing ticks")
{
Manager.Render_Update(10); // We start consuming Tick 1
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 0);
CHECK_EQUAL(Stream.Instances[0]->Bit, false);
Manager.Render_Update(50); // Update from Tick 1 (still no interpolation, instance was created in this tick)
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 0);
CHECK_EQUAL(Stream.Instances[0]->Bit, false);
Manager.Render_Update(100); // Values should be exactly like 1
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 0);
CHECK_EQUAL(Stream.Instances[0]->Bit, false);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 0);
// Moving into Tick 2
Manager.Render_Update(110); // 10 units into Tick 2.. Values should start interpolating from Tick 1 against Tick 2.
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 10);
CHECK_EQUAL(Stream.Instances[0]->Bit, true);
Manager.Render_Update(150); // 50 units into Tick 2
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 50);
CHECK_EQUAL(Stream.Instances[0]->Bit, true);
Manager.Render_Update(200); // Full Tick 2.. Value should be like Tick 2
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 100);
CHECK_EQUAL(Stream.Instances[0]->Bit, true);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 3);
// Moving into Tick 3
Manager.Render_Update(210); // 10 units into Tick 3..
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 110);
CHECK_EQUAL(Stream.Instances[0]->Bit, false);
Manager.Render_Update(280); // 80 units into Tick 3..
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 180);
Manager.Render_Update(300); // Full Tick 3..
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 200);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 6);
// Moving into Tick 4
Manager.Render_Update(310); // 10 units into Tick 4
CHECK_EQUAL(Stream.Instances.Num(), 0);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 1);
CHECK_EQUAL(Stream.UpdateCount, 6);
}
SECTION("Rend crossing single ticks")
{
// Consuming entire Tick 1 and moving into Tick 2
Manager.Render_Update(110);
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 10);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 0);
// Consuming rest of Tick 2 and moving into Tick 3
Manager.Render_Update(210);
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 110);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 1);
// Consuming rest of Tick 3 and moving into Tick 4
Manager.Render_Update(310);
CHECK_EQUAL(Stream.Instances.Num(), 0);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 1);
CHECK_EQUAL(Stream.UpdateCount, 1);
}
SECTION("Rend crossing multiple ticks")
{
// Consuming entire Tick 1 and 2 and moving into Tick 3
Manager.Render_Update(210);
CHECK_EQUAL(Stream.Instances.Num(), 1);
CHECK_EQUAL(Stream.Instances[0]->Value, 110);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 0);
// Consuming rest of Tick 3, entire 4 and entering 5
Manager.Render_Update(410);
CHECK_EQUAL(Stream.Instances.Num(), 0);
CHECK_EQUAL(Stream.CreateCount, 1);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 0);
CHECK_EQUAL(Stream.DestroyCount, 1);
CHECK_EQUAL(Stream.UpdateCount, 0);
}
SECTION("Rend crossing all ticks")
{
// Consuming entire Tick 1 to 5
Manager.Render_Update(500);
CHECK_EQUAL(Stream.Instances.Num(), 0);
CHECK_EQUAL(Stream.CreateCount, 0);
CHECK_EQUAL(Stream.CreateAndDestroyCount, 1);
CHECK_EQUAL(Stream.DestroyCount, 0);
CHECK_EQUAL(Stream.UpdateCount, 0);
}
}
TEST_CASE("StateStream::MultipleInstances", "[StateStream]")
{
FMockStateStreamImpl MockStateStreamImpl;
FStateStreamManagerImpl ManagerImpl;
ManagerImpl.Render_Register(MockStateStreamImpl, false);
{
IStateStreamManager& Manager = ManagerImpl;
IMockStateStream& Stream = Manager.Game_Get<IMockStateStream>();
// Tick 1
Manager.Game_BeginTick();
FMockHandle Handle1 = Stream.Game_CreateInstance(FMockStaticState(), FMockDynamicState{0});
FMockHandle Handle2 = Stream.Game_CreateInstance(FMockStaticState(), FMockDynamicState{0});
Manager.Game_EndTick(100);
// Tick 2
Manager.Game_BeginTick();
Handle1 = {};
Manager.Game_EndTick(200);
// Tick 3
Manager.Game_BeginTick();
Handle2 = {};
Manager.Game_EndTick(300);
// Tick 4
Manager.Game_BeginTick();
Manager.Game_EndTick(400);
}
FStateStreamManagerImpl& Manager = ManagerImpl;
FMockStateStreamImpl& Stream = MockStateStreamImpl;
SECTION("Rend not crossing ticks")
{
Manager.Render_Update(10); // We start consuming Tick 1
CHECK_EQUAL(Stream.Instances.Num(), 2);
Manager.Render_Update(100); // Consume rest of Tick 1
CHECK_EQUAL(Stream.Instances.Num(), 2);
Manager.Render_Update(110); // Moving into Tick 2
CHECK_EQUAL(Stream.Instances.Num(), 1);
Manager.Render_Update(200); // Consume rest of Tick 2
CHECK_EQUAL(Stream.Instances.Num(), 1);
Manager.Render_Update(210); // Moving into Tick 3
CHECK_EQUAL(Stream.Instances.Num(), 0);
Manager.Render_Update(300); // Consume rest of Tick 3
CHECK_EQUAL(Stream.Instances.Num(), 0);
Manager.Render_Update(400); // Consume rest of Tick 4
CHECK_EQUAL(Stream.Instances.Num(), 0);
}
}
TEST_CASE("StateStream::GarbageCollect", "[StateStream]")
{
FMockStateStreamImpl MockStateStreamImpl;
FStateStreamManagerImpl ManagerImpl;
ManagerImpl.Render_Register(MockStateStreamImpl, false);
{
IStateStreamManager& Manager = ManagerImpl;
IMockStateStream& Stream = Manager.Game_Get<IMockStateStream>();
// Tick 1
Manager.Game_BeginTick();
FMockHandle Handle1 = Stream.Game_CreateInstance(FMockStaticState(), FMockDynamicState{0});
FMockHandle Handle2 = Stream.Game_CreateInstance(FMockStaticState(), FMockDynamicState{0});
Manager.Game_EndTick(100);
CHECK_EQUAL(MockStateStreamImpl.GetUsedInstancesCount(), 2);
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 2);
// Tick 2
Manager.Game_BeginTick();
Handle1 = {};
Handle2 = {};
Manager.Game_EndTick(200);
// Tick 3
Manager.Game_BeginTick();
Manager.Game_EndTick(300);
CHECK_EQUAL(MockStateStreamImpl.GetUsedInstancesCount(), 2);
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 2);
}
FStateStreamManagerImpl& Manager = ManagerImpl;
FMockStateStreamImpl& Stream = MockStateStreamImpl;
{
Manager.Render_Update(10);
Manager.Render_GarbageCollect();
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 2);
Manager.Render_Update(100);
Manager.Render_GarbageCollect();
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 2);
Manager.Render_Update(110);
Manager.Render_GarbageCollect();
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 2);
Manager.Render_Update(200);
Manager.Render_GarbageCollect();
Manager.Render_Update(210);
Manager.Render_GarbageCollect();
CHECK_EQUAL(Stream.GetUsedInstancesCount(), 2);
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 2);
Manager.Render_Update(300);
Manager.Render_GarbageCollect();
CHECK_EQUAL(Stream.GetUsedInstancesCount(), 0);
CHECK_EQUAL(MockStateStreamImpl.GetUsedDynamicstatesCount(), 0);
}
}
TEST_CASE("StateStream::InternalDependencies", "[StateStream]")
{
FTransformStateStreamImpl TransformStreamImpl;
FStateStreamManagerImpl ManagerImpl;
ManagerImpl.Render_Register(TransformStreamImpl, false);
{
IStateStreamManager& Manager = ManagerImpl;
ITransformStateStream& TransformStream = Manager.Game_Get<ITransformStateStream>();
FTransformHandle TransformHandle;
// Tick 1
Manager.Game_BeginTick();
{
FTransformDynamicState Ds;
Ds.SetParent(TransformStream.Game_CreateInstance(FTransformStaticState(), FTransformDynamicState{}));
TransformHandle = TransformStream.Game_CreateInstance(FTransformStaticState(), Ds);
}
Manager.Game_EndTick(100);
CHECK_EQUAL(TransformStreamImpl.GetUsedInstancesCount(), 2);
CHECK_EQUAL(TransformStreamImpl.GetUsedDynamicstatesCount(), 2);
// Tick 2
Manager.Game_BeginTick();
{
FTransformDynamicState Ds;
Ds.SetParent(TransformStream.Game_CreateInstance(FTransformStaticState(), FTransformDynamicState{}));
TransformHandle.Update(Ds);
}
Manager.Game_EndTick(200);
// Tick 3
Manager.Game_BeginTick();
TransformHandle = {};
Manager.Game_EndTick(300);
Manager.Game_Exit();
}
FStateStreamManagerImpl& Manager = ManagerImpl;
FTransformStateStreamImpl& TransformStream = TransformStreamImpl;
SECTION("Rend not crossing ticks")
{
// Frame 1 consuming tick 1
Manager.Render_Update(100);
// Frame 2 consuming tick 2
Manager.Render_Update(200);
// Frame 3 consuming tick 3
Manager.Render_Update(300);
}
SECTION("Rend crossing ticks")
{
// Frame 1 consuming tick 1, 2 and 3
Manager.Render_Update(300);
}
}
TEST_CASE("StateStream::ExternalDependencies", "[StateStream]")
{
FTransformStateStreamImpl TransformStreamImpl;
FMockStateStreamImpl MockStreamImpl;
FStateStreamManagerImpl ManagerImpl;
ManagerImpl.Render_Register(TransformStreamImpl, false);
ManagerImpl.Render_Register(MockStreamImpl, false);
{
IStateStreamManager& Manager = ManagerImpl;
ITransformStateStream& TransformStream = Manager.Game_Get<ITransformStateStream>();
IMockStateStream& MockStream = Manager.Game_Get<IMockStateStream>();
FMockHandle MockHandle;
// Tick 1
Manager.Game_BeginTick();
{
FMockDynamicState Ds;
Ds.SetTransform(TransformStream.Game_CreateInstance(FTransformStaticState(), FTransformDynamicState{}));
MockHandle = MockStream.Game_CreateInstance(FMockStaticState(), Ds);
}
Manager.Game_EndTick(100);
CHECK_EQUAL(TransformStreamImpl.GetUsedInstancesCount(), 1);
CHECK_EQUAL(TransformStreamImpl.GetUsedDynamicstatesCount(), 1);
CHECK_EQUAL(MockStreamImpl.GetUsedInstancesCount(), 1);
CHECK_EQUAL(MockStreamImpl.GetUsedDynamicstatesCount(), 1);
// Tick 2
Manager.Game_BeginTick();
{
FMockDynamicState Ds;
Ds.SetTransform(TransformStream.Game_CreateInstance(FTransformStaticState(), FTransformDynamicState{}));
MockHandle.Update(Ds);
}
Manager.Game_EndTick(200);
// Tick 3
Manager.Game_BeginTick();
MockHandle = {};
Manager.Game_EndTick(300);
Manager.Game_Exit();
}
FStateStreamManagerImpl& Manager = ManagerImpl;
FTransformStateStreamImpl& TransformStream = TransformStreamImpl;
FMockStateStreamImpl& MockStream = MockStreamImpl;
SECTION("Rend not crossing ticks")
{
// Frame 1 consuming tick 1
Manager.Render_Update(100);
CHECK_EQUAL(MockStream.Instances.Num(), 1);
//CHECK_EQUAL(MockStream.Instances[0]->Value, 110);
// Frame 2 consuming tick 2
Manager.Render_Update(200);
// Frame 3 consuming tick 3
Manager.Render_Update(300);
}
SECTION("Rend crossing ticks")
{
// Frame 1 consuming tick 1, 2 and 3
Manager.Render_Update(300);
}
}
TEST_CASE("StateStream::Interpolation", "[StateStream]")
{
FTransformStateStreamImpl TransformStreamImpl;
FStateStreamManagerImpl ManagerImpl;
ManagerImpl.Render_Register(TransformStreamImpl, false);
{
IStateStreamManager& Manager = ManagerImpl;
ITransformStateStream& Stream = Manager.Game_Get<ITransformStateStream>();
// Tick 1
Manager.Game_BeginTick();
FTransformHandle Handle = Stream.Game_CreateInstance(FTransformStaticState(), FTransformDynamicState());
Manager.Game_EndTick(0);
// Tick 2
Manager.Game_BeginTick();
FTransformDynamicState newState;
newState.SetLocalTransform(FTransform(FQuat::Identity, FVector(100, 100, 100)));
Handle.Update(newState);
Manager.Game_EndTick(100);
// Tick 3
Manager.Game_BeginTick();
Handle = {};
Manager.Game_EndTick(200);
}
FStateStreamManagerImpl& Manager = ManagerImpl;
FTransformStateStreamImpl& Stream = TransformStreamImpl;
SECTION("Interpolation of translation")
{
Manager.Render_Update(10); // We start consuming Tick 2
FTransformObject* Object = (FTransformObject*)Stream.Render_GetUserData(1u);
CHECK_EQUAL((FMath::Abs(Object->GetInfo().WorldTransform.GetTranslation().X - 10.0f) < 0.001f), true);
}
}
TEST_CASE("StateStreamCreator", "[StateStream]")
{
uint32 Counter = 0;
FStateStreamCreator Creator1(1, [&](const FStateStreamRegisterContext&){ CHECK_EQUAL(Counter++, 1); }, [&](const FStateStreamUnregisterContext&){ --Counter; });
FStateStreamCreator Creator2(3, [&](const FStateStreamRegisterContext&){ CHECK_EQUAL(Counter++, 3); }, [&](const FStateStreamUnregisterContext&){ --Counter; });
FStateStreamCreator Creator3(2, [&](const FStateStreamRegisterContext&){ CHECK_EQUAL(Counter++, 2); }, [&](const FStateStreamUnregisterContext&){ --Counter; });
FStateStreamCreator Creator4(0, [&](const FStateStreamRegisterContext&){ CHECK_EQUAL(Counter++, 0); }, [&](const FStateStreamUnregisterContext&){ --Counter; });
FStateStreamCreator Creator5(4, [&](const FStateStreamRegisterContext&){ CHECK_EQUAL(Counter++, 4); }, [&](const FStateStreamUnregisterContext&){ --Counter; });
FStateStreamManagerImpl ManagerImpl;
FStateStreamCreator::RegisterStateStreams(FStateStreamRegisterContext{ManagerImpl});
CHECK_EQUAL(Counter, 5);
FStateStreamCreator::UnregisterStateStreams(FStateStreamUnregisterContext{ManagerImpl});
CHECK_EQUAL(Counter, 0);
}