Files
UnrealEngine/Engine/Source/Runtime/PhysicsCore/Private/ChaosScene.cpp
2025-05-18 13:04:45 +08:00

605 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Chaos/ChaosScene.h"
#include "Async/AsyncWork.h"
#include "Async/ParallelFor.h"
#include "Misc/CoreDelegates.h"
#include "Misc/ScopeLock.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "ChaosSolversModule.h"
#include "ChaosLog.h"
#include "ChaosStats.h"
#if WITH_CHAOS_VISUAL_DEBUGGER
#include "ChaosVDRuntimeModule.h"
#endif
#include "Field/FieldSystem.h"
#include "PhysicsProxy/PerSolverFieldSystem.h"
#include "PhysicsProxy/GeometryCollectionPhysicsProxy.h"
#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
#include "PhysicsProxy/SkeletalMeshPhysicsProxy.h"
#include "PhysicsProxy/StaticMeshPhysicsProxy.h"
#include "Chaos/UniformGrid.h"
#include "Chaos/BoundingVolume.h"
#include "Chaos/Framework/DebugSubstep.h"
#include "Chaos/PerParticleGravity.h"
#include "PBDRigidActiveParticlesBuffer.h"
#include "Chaos/GeometryParticlesfwd.h"
#include "Chaos/Box.h"
#include "EventsData.h"
#include "EventManager.h"
#include "RewindData.h"
#include "PhysicsSettingsCore.h"
#include "Chaos/PhysicsSolverBaseImpl.h"
#include "Chaos/AsyncInitBodyHelper.h"
#include "ChaosVisualDebugger/ChaosVisualDebuggerTrace.h"
#include "ChaosDebugDraw/ChaosDDScene.h"
#include "ChaosDebugDraw/ChaosDDTimeline.h"
DECLARE_CYCLE_STAT(TEXT("Update Kinematics On Deferred SkelMeshes"), STAT_UpdateKinematicsOnDeferredSkelMeshesChaos, STATGROUP_Physics);
CSV_DEFINE_CATEGORY(ChaosPhysics,true);
CSV_DEFINE_CATEGORY(AABBTreeExpensiveStats, false);
// Stat Counters
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumDirtyAABBTreeElements"), STAT_ChaosCounter_NumDirtyAABBTreeElements, STATGROUP_ChaosCounters);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumDirtyGridOverflowElements"), STAT_ChaosCounter_NumDirtyGridOverflowElements, STATGROUP_ChaosCounters);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumDirtyElementsTooLargeForGrid"), STAT_ChaosCounter_NumDirtyElementsTooLargeForGrid, STATGROUP_ChaosCounters);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumDirtyNonEmptyCellsInGrid"), STAT_ChaosCounter_NumDirtyNonEmptyCellsInGrid, STATGROUP_ChaosCounters);
TAutoConsoleVariable<int32> CVar_ChaosSimulationEnable(TEXT("P.Chaos.Simulation.Enable"),1,TEXT("Enable / disable chaos simulation. If disabled, physics will not tick."));
TAutoConsoleVariable<int32> CVar_ApplyProjectSettings(TEXT("p.Chaos.Simulation.ApplySolverProjectSettings"), 1, TEXT("Whether to apply the solver project settings on spawning a solver"));
FChaosScene::FChaosScene(
UObject* OwnerPtr
, Chaos::FReal InAsyncDt
, const FName& DebugName
)
: SolverAccelerationStructure(nullptr)
, ChaosModule(nullptr)
, SceneSolver(nullptr)
, Owner(OwnerPtr)
{
LLM_SCOPE(ELLMTag::ChaosScene);
ChaosModule = FChaosSolversModule::GetModule();
check(ChaosModule);
const bool bForceSingleThread = !(FApp::ShouldUseThreadingForPerformance() || FForkProcessHelper::SupportsMultithreadingPostFork());
Chaos::EThreadingMode ThreadingMode = bForceSingleThread ? Chaos::EThreadingMode::SingleThread : Chaos::EThreadingMode::TaskGraph;
SceneSolver = ChaosModule->CreateSolver(OwnerPtr, InAsyncDt, ThreadingMode, DebugName);
check(SceneSolver);
#if WITH_CHAOS_VISUAL_DEBUGGER
SceneSolver->GetChaosVDContextData().OwnerID = GetChaosVDContextData().Id;
SceneSolver->GetChaosVDContextData().Id = FChaosVDRuntimeModule::Get().GenerateUniqueID();
SceneSolver->GetChaosVDContextData().Type = static_cast<int32>(EChaosVDContextType::Solver);
#endif
SceneSolver->PhysSceneHack = this;
SimCallback = SceneSolver->CreateAndRegisterSimCallbackObject_External<FChaosSceneSimCallback>();
// Apply project settings to the solver
if (CVar_ApplyProjectSettings.GetValueOnAnyThread() != 0)
{
UPhysicsSettingsCore* Settings = UPhysicsSettingsCore::Get();
SceneSolver->ApplyConfig(Settings->SolverOptions);
SceneSolver->SetIsDeterministic(Settings->bEnableEnhancedDeterminism);
}
// Make sure we have initialized structure on game thread, evolution has already initialized structure, just need to copy.
CopySolverAccelerationStructure();
}
FChaosScene::~FChaosScene()
{
if(ensure(SceneSolver))
{
Chaos::FEventManager* EventManager = SceneSolver->GetEventManager();
EventManager->UnregisterHandler(Chaos::EEventType::Collision,this);
SceneSolver->UnregisterAndFreeSimCallbackObject_External(SimCallback);
}
if(ensure(ChaosModule))
{
// Destroy our solver
ChaosModule->DestroySolver(GetSolver());
}
SimCallback = nullptr;
ChaosModule = nullptr;
SceneSolver = nullptr;
}
#if WITH_ENGINE
void FChaosScene::AddReferencedObjects(FReferenceCollector& Collector)
{
#if WITH_EDITOR
for(auto& Obj : PieModifiedObjects)
{
Collector.AddReferencedObject(Obj);
}
#endif
}
#endif
#if WITH_EDITOR
void FChaosScene::AddPieModifiedObject(UObject* InObj)
{
if(GIsPlayInEditorWorld)
{
PieModifiedObjects.AddUnique(ObjectPtrWrap(InObj));
}
}
#endif
const Chaos::ISpatialAcceleration<Chaos::FAccelerationStructureHandle, Chaos::FReal, 3>* FChaosScene::GetSpacialAcceleration() const
{
return SolverAccelerationStructure;
}
Chaos::ISpatialAcceleration<Chaos::FAccelerationStructureHandle, Chaos::FReal, 3>* FChaosScene::GetSpacialAcceleration()
{
return SolverAccelerationStructure;
}
void FChaosScene::CopySolverAccelerationStructure()
{
using namespace Chaos;
if(SceneSolver)
{
FPhysicsSceneGuardScopedWrite ScopedWrite(SceneSolver->GetExternalDataLock_External());
SceneSolver->UpdateExternalAccelerationStructure_External(SolverAccelerationStructure);
}
}
void FChaosScene::Flush()
{
check(IsInGameThread());
Chaos::FPBDRigidsSolver* Solver = GetSolver();
if(Solver)
{
//Make sure any dirty proxy data is pushed
Solver->AdvanceAndDispatch_External(0); //force commands through
Solver->WaitOnPendingTasks_External();
// Populate the spacial acceleration
Chaos::FPBDRigidsSolver::FPBDRigidsEvolution* Evolution = Solver->GetEvolution();
if(Evolution)
{
Evolution->FlushSpatialAcceleration();
}
}
CopySolverAccelerationStructure();
}
void FChaosScene::RemoveActorFromAccelerationStructure(FPhysicsActorHandle Actor)
{
using namespace Chaos;
RemoveActorFromAccelerationStructureImp(Actor->GetParticle_LowLevel());
}
void FChaosScene::RemoveActorFromAccelerationStructureImp(Chaos::FGeometryParticle* Particle)
{
using namespace Chaos;
if (GetSpacialAcceleration() && Particle->UniqueIdx().IsValid())
{
FPhysicsSceneGuardScopedWrite ScopedWrite(SceneSolver->GetExternalDataLock_External());
Chaos::FAccelerationStructureHandle AccelerationHandle(Particle);
GetSpacialAcceleration()->RemoveElementFrom(AccelerationHandle, Particle->SpatialIdx());
}
}
void FChaosScene::UpdateActorInAccelerationStructure(const FPhysicsActorHandle& Actor)
{
using namespace Chaos;
if(GetSpacialAcceleration())
{
FPhysicsSceneGuardScopedWrite ScopedWrite(SceneSolver->GetExternalDataLock_External());
auto SpatialAcceleration = GetSpacialAcceleration();
const Chaos::FRigidBodyHandle_External& Body_External = Actor->GetGameThreadAPI();
if(SpatialAcceleration)
{
FAABB3 WorldBounds;
const bool bHasBounds = Body_External.GetGeometry()->HasBoundingBox();
if(bHasBounds)
{
WorldBounds = Body_External.GetGeometry()->BoundingBox().TransformedAABB(FRigidTransform3(Body_External.X(), Body_External.R()));
}
Chaos::FAccelerationStructureHandle AccelerationHandle(Actor->GetParticle_LowLevel());
SpatialAcceleration->UpdateElementIn(AccelerationHandle,WorldBounds,bHasBounds, Body_External.SpatialIdx());
}
GetSolver()->UpdateParticleInAccelerationStructure_External(Actor->GetParticle_LowLevel(), EPendingSpatialDataOperation::Update);
}
}
void FChaosScene::UpdateActorsInAccelerationStructure(const TArrayView<FPhysicsActorHandle>& Actors)
{
using namespace Chaos;
if(GetSpacialAcceleration())
{
FPhysicsSceneGuardScopedWrite ScopedWrite(SceneSolver->GetExternalDataLock_External());
auto SpatialAcceleration = GetSpacialAcceleration();
if(SpatialAcceleration)
{
int32 NumActors = Actors.Num();
for(int32 ActorIndex = 0; ActorIndex < NumActors; ++ActorIndex)
{
const FPhysicsActorHandle& Actor = Actors[ActorIndex];
if(Actor)
{
const Chaos::FRigidBodyHandle_External& Body_External = Actor->GetGameThreadAPI();
// @todo(chaos): dedupe code in UpdateActorInAccelerationStructure
FAABB3 WorldBounds;
const bool bHasBounds = Body_External.GetGeometry()->HasBoundingBox();
if(bHasBounds)
{
WorldBounds = Body_External.GetGeometry()->BoundingBox().TransformedAABB(FRigidTransform3(Body_External.X(), Body_External.R()));
}
Chaos::FAccelerationStructureHandle AccelerationHandle(Actor->GetParticle_LowLevel());
SpatialAcceleration->UpdateElementIn(AccelerationHandle,WorldBounds,bHasBounds, Body_External.SpatialIdx());
}
}
}
for(int32 ActorIndex = 0; ActorIndex < Actors.Num(); ++ActorIndex)
{
const FPhysicsActorHandle& Actor = Actors[ActorIndex];
if(Actor)
{
GetSolver()->UpdateParticleInAccelerationStructure_External(Actor->GetParticle_LowLevel(), EPendingSpatialDataOperation::Update);
}
}
}
}
void FChaosScene::AddActorsToScene_AssumesLocked(TArray<FPhysicsActorHandle>& InHandles,const bool bImmediate)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FChaosScene::AddActorsToScene_AssumesLocked)
Chaos::FPhysicsSolver* Solver = GetSolver();
UE_CHAOS_ASYNC_INITBODY_WRITESCOPELOCK(Solver->GetExternalDataLock_External());
Chaos::ISpatialAcceleration<Chaos::FAccelerationStructureHandle,Chaos::FReal,3>* SpatialAcceleration = GetSpacialAcceleration();
for(FPhysicsActorHandle& Handle : InHandles)
{
FChaosEngineInterface::AddActorToSolver(Handle,Solver);
// Optionally add this to the game-thread acceleration structure immediately
if(bImmediate && SpatialAcceleration)
{
const Chaos::FRigidBodyHandle_External& Body_External = Handle->GetGameThreadAPI();
// Get the bounding box for the particle if it has one
bool bHasBounds = Body_External.GetGeometry()->HasBoundingBox();
Chaos::FAABB3 WorldBounds;
if(bHasBounds)
{
const Chaos::FAABB3 LocalBounds = Body_External.GetGeometry()->BoundingBox();
WorldBounds = LocalBounds.TransformedAABB(Chaos::FRigidTransform3(Body_External.X(), Body_External.R()));
}
// Insert the particle
Chaos::FAccelerationStructureHandle AccelerationHandle(Handle->GetParticle_LowLevel());
SpatialAcceleration->UpdateElementIn(AccelerationHandle,WorldBounds,bHasBounds, Body_External.SpatialIdx());
}
}
}
void FChaosSceneSimCallback::OnPreSimulate_Internal()
{
if(const FChaosSceneCallbackInput* Input = GetConsumerInput_Internal())
{
// the "main" gravity is index 0
static_cast<Chaos::FPBDRigidsSolver*>(GetSolver())->GetEvolution()->GetGravityForces().SetAcceleration(Input->Gravity, 0);
}
}
FName FChaosSceneSimCallback::GetFNameForStatId() const
{
const static FLazyName StaticName("FChaosSceneSimCallback");
return StaticName;
}
void FChaosScene::SetGravity(const Chaos::FVec3& Acceleration)
{
SimCallback->GetProducerInputData_External()->Gravity = Acceleration;
}
void FChaosScene::SetUpForFrame(const FVector* NewGrav,float InDeltaSeconds /*= 0.0f*/,float InMinPhysicsDeltaTime /*= 0.0f*/,float InMaxPhysicsDeltaTime /*= 0.0f*/,float InMaxSubstepDeltaTime /*= 0.0f*/,int32 InMaxSubsteps,bool bSubstepping)
{
using namespace Chaos;
SetGravity(*NewGrav);
InDeltaSeconds *= MNetworkDeltaTimeScale;
if(bSubstepping)
{
MDeltaTime = InMaxSubstepDeltaTime > 0.f ? FMath::Min(InDeltaSeconds, InMaxSubsteps * InMaxSubstepDeltaTime) : InDeltaSeconds;
}
else
{
MDeltaTime = InMaxPhysicsDeltaTime > 0.f ? FMath::Min(InDeltaSeconds, InMaxPhysicsDeltaTime) : InDeltaSeconds;
}
if(FPhysicsSolver* Solver = GetSolver())
{
if(bSubstepping)
{
Solver->SetMaxDeltaTime_External(InMaxSubstepDeltaTime);
Solver->SetMaxSubSteps_External(InMaxSubsteps);
}
else
{
Solver->SetMaxDeltaTime_External(InMaxPhysicsDeltaTime);
Solver->SetMaxSubSteps_External(1);
}
Solver->SetMinDeltaTime_External(InMinPhysicsDeltaTime);
}
}
void FChaosScene::StartFrame()
{
using namespace Chaos;
SCOPE_CYCLE_COUNTER(STAT_Scene_StartFrame);
if(CVar_ChaosSimulationEnable.GetValueOnGameThread() == 0)
{
return;
}
const float UseDeltaTime = OnStartFrame(MDeltaTime);
TArray<FPhysicsSolverBase*> SolverList = GetPhysicsSolvers();
for(FPhysicsSolverBase* Solver : SolverList)
{
if(FGraphEventRef SolverEvent = Solver->AdvanceAndDispatch_External(UseDeltaTime))
{
if(SolverEvent.IsValid())
{
CompletionEvents.Add(SolverEvent);
}
}
}
}
void FChaosScene::OnSyncBodies(Chaos::FPhysicsSolverBase* Solver)
{
struct FDispatcher {} Dispatcher;
Solver->PullPhysicsStateForEachDirtyProxy_External(Dispatcher);
}
void FChaosScene::KillSafeAsyncTasks()
{
Chaos::FPBDRigidsSolver* Solver = GetSolver();
if (Solver )
{
Solver->KillSafeAsyncTasks();
}
}
void FChaosScene::WaitSolverTasks()
{
Chaos::FPBDRigidsSolver* Solver = GetSolver();
if(Solver)
{
Solver->WaitOnPendingTasks_External();
}
}
bool FChaosScene::AreAnyTasksPending() const
{
const Chaos::FPBDRigidsSolver* Solver = GetSolver();
if (Solver && Solver->AreAnyTasksPending())
{
return true;
}
return false;
}
void FChaosScene::BeginDestroy()
{
Chaos::FPBDRigidsSolver* Solver = GetSolver();
if (Solver)
{
Solver->BeginDestroy();
}
}
bool FChaosScene::IsCompletionEventComplete() const
{
for (FGraphEventRef Event : CompletionEvents)
{
if (Event && !Event->IsComplete())
{
return false;
}
}
return true;
}
template <typename TSolver>
void FChaosScene::SyncBodies(TSolver* Solver)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("SyncBodies"),STAT_SyncBodies,STATGROUP_Physics);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(SyncBodies);
OnSyncBodies(Solver);
}
// Accumulate all the AABBTree stats
void GetAABBTreeStats(Chaos::ISpatialAccelerationCollection<Chaos::FAccelerationStructureHandle, Chaos::FReal, 3>& Collection, Chaos::AABBTreeStatistics& OutAABBTreeStatistics, Chaos::AABBTreeExpensiveStatistics& OutAABBTreeExpensiveStatistics)
{
CSV_SCOPED_TIMING_STAT(AABBTreeExpensiveStats, GetAABBTreeStats);
using namespace Chaos;
OutAABBTreeStatistics.Reset();
TArray<FSpatialAccelerationIdx> SpatialIndices = Collection.GetAllSpatialIndices();
for (const FSpatialAccelerationIdx SpatialIndex : SpatialIndices)
{
auto SubStructure = Collection.GetSubstructure(SpatialIndex);
if (const auto AABBTree = SubStructure->template As<TAABBTree<FAccelerationStructureHandle, TAABBTreeLeafArray<FAccelerationStructureHandle>>>())
{
OutAABBTreeStatistics.MergeStatistics(AABBTree->GetAABBTreeStatistics());
#if CSV_PROFILER_STATS
if (FCsvProfiler::Get()->IsCapturing() && FCsvProfiler::Get()->IsCategoryEnabled(CSV_CATEGORY_INDEX(AABBTreeExpensiveStats)))
{
OutAABBTreeExpensiveStatistics.MergeStatistics(AABBTree->GetAABBTreeExpensiveStatistics());
}
#endif
}
else if (const auto AABBTreeBV = SubStructure->template As<TAABBTree<FAccelerationStructureHandle, TBoundingVolume<FAccelerationStructureHandle>>>())
{
OutAABBTreeStatistics.MergeStatistics(AABBTreeBV->GetAABBTreeStatistics());
}
}
}
TArray<Chaos::FPhysicsSolverBase*> FChaosScene::GetPhysicsSolvers() const
{
// Make a list of solvers to process. This is a list of all solvers registered to our world
// And our internal base scene solver.
TArray<Chaos::FPhysicsSolverBase*> SolverList;
if(const Chaos::FPhysicsSolver* Solver = GetSolver())
{
if(!Solver->IsStandaloneSolver())
{
// Get all the solvers with the same owner
ChaosModule->GetSolversMutable(Owner,SolverList);
}
// Make sure our solver is in the list
SolverList.AddUnique(GetSolver());
}
return SolverList;
}
void FChaosScene::EndFrame()
{
using namespace Chaos;
using SpatialAccelerationCollection = ISpatialAccelerationCollection<FAccelerationStructureHandle,FReal,3>;
SCOPE_CYCLE_COUNTER(STAT_Scene_EndFrame);
if(CVar_ChaosSimulationEnable.GetValueOnGameThread() == 0 || GetSolver() == nullptr)
{
return;
}
CVD_TRACE_ACCELERATION_STRUCTURES(SolverAccelerationStructure, Chaos::FPhysicsSolver, *SceneSolver, CVDDC_AccelerationStructures);
#if !UE_BUILD_SHIPPING
{
Chaos::AABBTreeStatistics TreeStats;
Chaos::AABBTreeExpensiveStatistics TreeExpensiveStats;
GetAABBTreeStats(GetSpacialAcceleration()->AsChecked<SpatialAccelerationCollection>(), TreeStats, TreeExpensiveStats);
CSV_CUSTOM_STAT(ChaosPhysics, AABBTreeDirtyElementCount, TreeStats.StatNumDirtyElements, ECsvCustomStatOp::Set);
SET_DWORD_STAT(STAT_ChaosCounter_NumDirtyAABBTreeElements, TreeStats.StatNumDirtyElements);
CSV_CUSTOM_STAT(ChaosPhysics, AABBTreeDirtyGridOverflowCount, TreeStats.StatNumGridOverflowElements, ECsvCustomStatOp::Set);
SET_DWORD_STAT(STAT_ChaosCounter_NumDirtyGridOverflowElements, TreeStats.StatNumGridOverflowElements);
CSV_CUSTOM_STAT(ChaosPhysics, AABBTreeDirtyElementTooLargeCount, TreeStats.StatNumElementsTooLargeForGrid, ECsvCustomStatOp::Set);
SET_DWORD_STAT(STAT_ChaosCounter_NumDirtyElementsTooLargeForGrid, TreeStats.StatNumElementsTooLargeForGrid);
CSV_CUSTOM_STAT(ChaosPhysics, AABBTreeDirtyElementNonEmptyCellCount, TreeStats.StatNumNonEmptyCellsInGrid, ECsvCustomStatOp::Set);
SET_DWORD_STAT(STAT_ChaosCounter_NumDirtyNonEmptyCellsInGrid, TreeStats.StatNumNonEmptyCellsInGrid);
#if CSV_PROFILER_STATS
if (FCsvProfiler::Get()->IsCapturing() && FCsvProfiler::Get()->IsCategoryEnabled(CSV_CATEGORY_INDEX(AABBTreeExpensiveStats)))
{
CSV_CUSTOM_STAT(AABBTreeExpensiveStats, AABBTreeMaxNumLeaves, TreeExpensiveStats.StatMaxNumLeaves, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(AABBTreeExpensiveStats, AABBTreeMaxDirtyElements, TreeExpensiveStats.StatMaxDirtyElements, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(AABBTreeExpensiveStats, AABBTreeMaxTreeDepth, TreeExpensiveStats.StatMaxTreeDepth, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(AABBTreeExpensiveStats, AABBTreeMaxLeafSize, TreeExpensiveStats.StatMaxLeafSize, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(AABBTreeExpensiveStats, AABBTreeGlobalPayloadsSize, TreeExpensiveStats.StatGlobalPayloadsSize, ECsvCustomStatOp::Set);
}
#endif // CSV_PROFILER_STATS
}
#endif // UE_BUILD_SHIPPING
check(IsCompletionEventComplete())
//check(PhysicsTickTask->IsComplete());
CompletionEvents.Reset();
// Make a list of solvers to process. This is a list of all solvers registered to our world
// And our internal base scene solver.
TArray<FPhysicsSolverBase*> SolverList = GetPhysicsSolvers();
// Flip the buffers over to the game thread and sync
{
SCOPE_CYCLE_COUNTER(STAT_FlipResults);
//update external SQ structure
//for now just copy the whole thing, stomping any changes that came from GT
CopySolverAccelerationStructure();
for(FPhysicsSolverBase* Solver : SolverList)
{
Solver->CastHelper([&SolverList, Solver, this](auto& Concrete)
{
SyncBodies(&Concrete);
Solver->FlipEventManagerBuffer();
Concrete.SyncEvents_GameThread();
{
SCOPE_CYCLE_COUNTER(STAT_SqUpdateMaterials);
Concrete.SyncQueryMaterials_External();
}
});
}
}
OnPhysScenePostTick.Broadcast(this);
}
void FChaosScene::WaitPhysScenes()
{
if(!IsCompletionEventComplete())
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FPhysScene_WaitPhysScenes);
FTaskGraphInterface::Get().WaitUntilTasksComplete(CompletionEvents,ENamedThreads::GameThread);
}
}
FGraphEventArray FChaosScene::GetCompletionEvents()
{
return CompletionEvents;
}
#if CHAOS_DEBUG_DRAW
void FChaosScene::SetDebugDrawScene(const ChaosDD::Private::FChaosDDScenePtr& InCDDScene)
{
CDDScene = InCDDScene;
SceneSolver->EnqueueCommandImmediate(
[this]()
{
SceneSolver->SetDebugDrawScene(CDDScene);
});
}
#endif