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

232 lines
8.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HeadlessChaosTestSolverProxies.h"
#include "HeadlessChaos.h"
#include "HeadlessChaosTestUtility.h"
#include "Chaos/ChaosEngineInterface.h"
#include "Chaos/ParticleHandle.h"
#include "Chaos/ErrorReporter.h"
#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
#include "PhysicsProxy/GeometryCollectionPhysicsProxy.h"
#include "Chaos/Utilities.h"
#include "PBDRigidsSolver.h"
#include "ChaosSolversModule.h"
#include "Modules/ModuleManager.h"
namespace ChaosTest {
using namespace Chaos;
void SingleParticleProxySingleThreadTest()
{
Chaos::FImplicitObjectPtr Sphere = Chaos::FImplicitObjectPtr(new Chaos::FSphere(FVec3(0), 10));
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
// Make a solver
auto* Solver = Module->CreateSolver(nullptr, /*AsyncDt=*/-1);
// Make a particle
auto Proxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
auto& Particle = Proxy->GetGameThreadAPI();
Particle.SetGeometry(Sphere);
Particle.SetX(FVec3(0, 0, 0));
Particle.SetGravityEnabled(false);
Solver->RegisterObject(Proxy);
Particle.SetV(FVec3(0, 0, 10));
::ChaosTest::SetParticleSimDataToCollide({ Proxy->GetParticle_LowLevel() });
Solver->AdvanceAndDispatch_External(100.0f);
Solver->UpdateGameThreadStructures();
// Make sure game thread data has changed
FVec3 V = Particle.V();
EXPECT_EQ(V.X, 0.f);
EXPECT_GT(V.Z, 0.f);
FVec3 X = Particle.X();
EXPECT_EQ(X.X, 0.f);
EXPECT_GT(X.Z, 0.f);
// Throw out the proxy
Solver->UnregisterObject(Proxy);
Module->DestroySolver(Solver);
}
void SingleParticleProxyWakeEventPropagationTest()
{
using namespace Chaos;
auto Sphere = Chaos::FImplicitObjectPtr(new Chaos::FSphere(FVec3(0), 10));
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
// Make a solver
auto* Solver = Module->CreateSolver(nullptr, /*AsyncDt=*/-1);
// Make a particle
auto Proxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
auto& Particle = Proxy->GetGameThreadAPI();
Particle.SetGeometry(Sphere);
Particle.SetX(FVec3(0, 0, 220));
Particle.SetV(FVec3(0, 0, -10));
Particle.SetCCDEnabled(true);
Solver->RegisterObject(Proxy);
Solver->AddDirtyProxy(Proxy);
auto Proxy2 = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
auto& Particle2 = Proxy2->GetGameThreadAPI();
Particle2.SetGeometry(Sphere);
Particle2.SetX(FVec3(0, 0, 100));
Particle2.SetV(FVec3(0, 0, 0));
Solver->RegisterObject(Proxy2);
Particle2.SetObjectState(Chaos::EObjectStateType::Sleeping);
::ChaosTest::SetParticleSimDataToCollide({ Proxy->GetParticle_LowLevel(),Proxy2->GetParticle_LowLevel() });
// let top paticle collide and wake up second particle
int32 LoopCount = 0;
while (Particle2.GetWakeEvent() == EWakeEventEntry::None && LoopCount++ < 20)
{
Solver->AdvanceAndDispatch_External(100.0f);
Solver->UpdateGameThreadStructures();
}
// Make sure game thread data has changed
FVec3 V = Particle.V();
EXPECT_EQ(Particle.GetWakeEvent(), EWakeEventEntry::None);
EXPECT_EQ(Particle.ObjectState(), Chaos::EObjectStateType::Dynamic);
EXPECT_EQ(Particle2.GetWakeEvent(), EWakeEventEntry::Awake);
EXPECT_EQ(Particle2.ObjectState(), Chaos::EObjectStateType::Dynamic);
Particle2.ClearEvents();
EXPECT_EQ(Particle2.GetWakeEvent(), EWakeEventEntry::None);
// Throw out the proxy
Solver->UnregisterObject(Proxy);
Solver->UnregisterObject(Proxy2);
Module->DestroySolver(Solver);
}
void SingleParticleProxyNoUniqueIndexLeaks()
{
// this test make sure that we are not leaking uniqueIdx when creating and destroying particle in one frame (without PT running)
auto Sphere = Chaos::FImplicitObjectPtr(new Chaos::FSphere(FVec3(0), 10));
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
// Make a solver
auto* Solver = Module->CreateSolver(nullptr, -1);
// Make a particle
auto Proxy = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
auto& Particle = Proxy->GetGameThreadAPI();
Particle.SetGeometry(Sphere);
Particle.SetX(FVec3(0, 0, 0));
Particle.SetGravityEnabled(false);
// first register the object
Solver->RegisterObject(Proxy);
// keep track of the actual unique Idx
FUniqueIdx FirstIdx = Particle.UniqueIdx();
// unregister the object in the same GT frame
Solver->UnregisterObject(Proxy);
// run PT to make sure the callbacks are running and updating the internal pending lists
Solver->AdvanceAndDispatch_External(100.0f);
Solver->UpdateGameThreadStructures();
// unique idx should be scheduled for cleanup
EXPECT_TRUE(Solver->GetEvolution()->IsUniqueIndexPendingRelease(FirstIdx));
// run PT again so that the cleanup is done
Solver->AdvanceAndDispatch_External(100.0f);
Solver->UpdateGameThreadStructures();
Solver->AdvanceAndDispatch_External(100.0f);
Solver->UpdateGameThreadStructures();
// Unique idx should be gone from the pending lists
EXPECT_FALSE(Solver->GetEvolution()->IsUniqueIndexPendingRelease(FirstIdx));
Module->DestroySolver(Solver);
}
void JointProxySolverInitTest()
{
// Test that when we create a joint constraint, that the proxy is not added to solver proxy array by game thread.
// This array is physics thread only, it should only be added after ticking solver.
using namespace Chaos;
auto Sphere = Chaos::FImplicitObjectPtr(new Chaos::FSphere(FVec3(0), 10));
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
// Make a solver
auto* Solver = Module->CreateSolver(nullptr, /*AsyncDt=*/-1);
// Make 2 particles for our constraint
auto Proxy1 = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
auto& Particle1 = Proxy1->GetGameThreadAPI();
Particle1.SetGeometry(Sphere);
Solver->RegisterObject(Proxy1);
auto Proxy2 = FSingleParticlePhysicsProxy::Create(Chaos::FPBDRigidParticle::CreateParticle());
auto& Particle2 = Proxy2->GetGameThreadAPI();
Particle2.SetGeometry(Sphere);
Solver->RegisterObject(Proxy2);
// Confirm solver doesn't have any joint constraint proxies yet.
EXPECT_EQ(Solver->GetJointConstraintPhysicsProxies_Internal().Num(), 0);
FTransform IdentityTransform(FTransform::Identity);
FPhysicsConstraintHandle Constraint = FChaosEngineInterface::CreateConstraint(Proxy1, Proxy2, IdentityTransform, IdentityTransform);
// joint Proxy has been created, should be in dirty proxy list to be sent to physics thread.
// Note that we expect three dirty proxies, 2 for particles, 1 for joint.
EXPECT_EQ(Solver->GetDirtyProxyBucketInfoNum_External(EPhysicsProxyType::SingleParticleProxy), 2);
EXPECT_EQ(Solver->GetDirtyProxyBucketInfoNum_External(EPhysicsProxyType::JointConstraintType), 1);
// Although proxy exists, it should not have been added to physics thread only proxy array yet, as doing so from game thread code is wrong.
// Confirm game thread did not add it.
EXPECT_EQ(Solver->GetJointConstraintPhysicsProxies_Internal().Num(), 0);
// Tick physics thread, this should add proxy to solver proxy array.
Solver->AdvanceAndDispatch_External(/*Dt=*/1.0f);
EXPECT_EQ(Solver->GetJointConstraintPhysicsProxies_Internal().Num(), 1);
// Release constraint to test removal
FChaosEngineInterface::ReleaseConstraint(Constraint);
// Have only released constraint on game thread, proxy should still be in array.
EXPECT_EQ(Solver->GetJointConstraintPhysicsProxies_Internal().Num(), 1);
// Tick physics thread, this should remove proxy from array.
Solver->AdvanceAndDispatch_External(/*Dt=*/1.0f);
EXPECT_EQ(Solver->GetJointConstraintPhysicsProxies_Internal().Num(), 0);
Solver->UnregisterObject(Proxy1);
Solver->UnregisterObject(Proxy2);
Module->DestroySolver(Solver);
}
GTEST_TEST(AllTraits, SingleParticleProxyTests)
{
ChaosTest::SingleParticleProxySingleThreadTest();
ChaosTest::SingleParticleProxyWakeEventPropagationTest();
//ChaosTest::SingleParticleProxyNoUniqueIndexLeaks();
ChaosTest::JointProxySolverInitTest();
}
}