Files
UnrealEngine/Engine/Source/Programs/ChaosUserDataPTTests/Private/ChaosUserDataPTTests.cpp
2025-05-18 13:04:45 +08:00

200 lines
7.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "TestHarness.h"
#include "ChaosUserDataPT.h"
#include "PBDRigidsSolver.h"
#include "ChaosSolversModule.h"
#include "Chaos/Box.h"
class FTestUserData : public Chaos::TUserDataManagerPT<FString> { };
// Advance a solver once with the provided DeltaTime, then wait for any async tasks to finish before continuing
void AdvanceAndWait(Chaos::FPBDRigidsSolver* InSolver, float DeltaTime)
{
if(InSolver)
{
InSolver->AdvanceAndDispatch_External(DeltaTime);
InSolver->WaitOnPendingTasks_External();
}
}
TEST_CASE("ChaosUserDataPT", "[integration]")
{
const float DeltaTime = 1.f;
const FString TestString1 = TEXT("TestData1");
const FString TestString2 = TEXT("TestData2");
// Create a solver in the solvers module
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
Chaos::FPBDRigidsSolver* Solver = Module->CreateSolver(nullptr, /*AsyncDt=*/DeltaTime, Chaos::EThreadingMode::TaskGraph);
// Create a test userdata manager in the solver
FTestUserData* TestUserData = Solver->CreateAndRegisterSimCallbackObject_External<FTestUserData>();
// Make a box geometry
auto BoxGeom = Chaos::FImplicitObjectPtr(new Chaos::TBox<Chaos::FReal, 3>(Chaos::FVec3(-1, -1, -1), Chaos::FVec3(1, 1, 1)));
// Add some proxies to the solver
Chaos::FSingleParticlePhysicsProxy* Proxy0 = Chaos::FSingleParticlePhysicsProxy::Create(Chaos::FGeometryParticle::CreateParticle());
Chaos::FSingleParticlePhysicsProxy* Proxy1 = Chaos::FSingleParticlePhysicsProxy::Create(Chaos::FGeometryParticle::CreateParticle());
Chaos::FSingleParticlePhysicsProxy* Proxy2 = Chaos::FSingleParticlePhysicsProxy::Create(Chaos::FGeometryParticle::CreateParticle());
Chaos::FRigidBodyHandle_External& HandleExternal0 = Proxy0->GetGameThreadAPI();
Chaos::FRigidBodyHandle_External& HandleExternal1 = Proxy1->GetGameThreadAPI();
Chaos::FRigidBodyHandle_External& HandleExternal2 = Proxy2->GetGameThreadAPI();
HandleExternal0.SetGeometry(BoxGeom);
HandleExternal1.SetGeometry(BoxGeom);
HandleExternal2.SetGeometry(BoxGeom);
Solver->RegisterObject(Proxy0);
Solver->RegisterObject(Proxy1);
Solver->RegisterObject(Proxy2);
// Advance the solver twice to make sure the PT handles are created and in the evolution
AdvanceAndWait(Solver, DeltaTime);
AdvanceAndWait(Solver, DeltaTime);
// Add data
SECTION("Data propagates from GT to PT")
{
// Add userdata to the particle
TestUserData->SetData_GT(HandleExternal0, TestString1);
// The first callback should show no data because the ensure will occur before
// the sim callback has occurred.
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == nullptr);
});
AdvanceAndWait(Solver, DeltaTime);
// The data should make it to the physics thread by this point
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(*TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == TestString1);
});
AdvanceAndWait(Solver, DeltaTime);
}
// Change data
SECTION("Data updates propagate from GT to PT")
{
// Add userdata to the particle
TestUserData->SetData_GT(HandleExternal0, TestString1);
AdvanceAndWait(Solver, DeltaTime);
// Set the userdata to something else
TestUserData->SetData_GT(HandleExternal0, TestString2);
AdvanceAndWait(Solver, DeltaTime);
// The data should make it to the physics thread by this point
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(*TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == TestString2);
});
AdvanceAndWait(Solver, DeltaTime);
}
// Delete data
SECTION("Data removals propagate from GT to PT")
{
// Add userdata to the particle and advance it to the physics thread
TestUserData->SetData_GT(HandleExternal0, TestString1);
AdvanceAndWait(Solver, DeltaTime);
// Delete the data
TestUserData->RemoveData_GT(HandleExternal0);
// Data should exist for one more update
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(*TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == TestString1);
});
AdvanceAndWait(Solver, DeltaTime);
// Data should be deleted at this point
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == nullptr);
});
AdvanceAndWait(Solver, DeltaTime);
}
// Delete data from particle that doesn't have it
SECTION("Removing data from a particle that never had data set on it does nothing")
{
// Delete data that isn't there
TestUserData->RemoveData_GT(HandleExternal0);
AdvanceAndWait(Solver, DeltaTime);
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == nullptr);
});
AdvanceAndWait(Solver, DeltaTime);
}
// Make sure a particle with a recycled index can't get a deleted particle's userdata
SECTION("Deleting a particle that has userdata associated with it should remove the userdata")
{
// Add data to a particle, make sure it gets to PT, then delete the particle.
const Chaos::FUniqueIdx UniqueIdx0 = HandleExternal0.UniqueIdx();
TestUserData->SetData_GT(HandleExternal0, TestString1);
AdvanceAndWait(Solver, DeltaTime);
Solver->UnregisterObject(Proxy0);
AdvanceAndWait(Solver, DeltaTime);
AdvanceAndWait(Solver, DeltaTime);
struct FMockHandle
{
FMockHandle(Chaos::FUniqueIdx InUniqueIdx) : MUniqueIdx(InUniqueIdx) { }
Chaos::FUniqueIdx UniqueIdx() const { return MUniqueIdx; }
Chaos::FUniqueIdx MUniqueIdx;
};
// Access userdata with the invalid particle handle - it should retrieve nothing
Solver->EnqueueCommandImmediate([&]()
{
const FMockHandle MockHandle0 = FMockHandle(UniqueIdx0);
REQUIRE(TestUserData->GetData_PT(MockHandle0) == nullptr);
});
AdvanceAndWait(Solver, DeltaTime);
}
SECTION("Clearing all data from a userdata manager")
{
// Add data to three particles, propagate it to the PT
TestUserData->SetData_GT(HandleExternal0, TestString1);
TestUserData->SetData_GT(HandleExternal1, TestString1);
TestUserData->SetData_GT(HandleExternal2, TestString1);
AdvanceAndWait(Solver, DeltaTime);
AdvanceAndWait(Solver, DeltaTime);
// Clear all data from the userdata manager, but after that set data back on
// particle 2 - the fact that it happened _after_ clearing should mean it is
// still there for particle 2 after the clear reaches the PT.
TestUserData->ClearData_GT();
TestUserData->SetData_GT(HandleExternal2, TestString1);
// Check to see that the data is still there at first
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(*TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == TestString1);
REQUIRE(*TestUserData->GetData_PT(*Proxy1->GetPhysicsThreadAPI()) == TestString1);
REQUIRE(*TestUserData->GetData_PT(*Proxy2->GetPhysicsThreadAPI()) == TestString1);
});
AdvanceAndWait(Solver, DeltaTime);
// Check make sure that after a couple updates, the data is cleared
Solver->EnqueueCommandImmediate([&]()
{
REQUIRE(TestUserData->GetData_PT(*Proxy0->GetPhysicsThreadAPI()) == nullptr);
REQUIRE(TestUserData->GetData_PT(*Proxy1->GetPhysicsThreadAPI()) == nullptr);
REQUIRE(*TestUserData->GetData_PT(*Proxy2->GetPhysicsThreadAPI()) == TestString1);
});
AdvanceAndWait(Solver, DeltaTime);
}
Solver->WaitOnPendingTasks_External();
Module->DestroySolver(Solver);
}