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

727 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChaosSolversModule.h"
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "ChaosStats.h"
#include "ChaosLog.h"
#include "HAL/PlatformProcess.h"
#include "Misc/CoreDelegates.h"
#include "HAL/IConsoleManager.h"
#include "PhysicsSolver.h"
#include "FramePro/FramePro.h"
#include "Chaos/BoundingVolume.h"
#include "Chaos/PBDRigidParticles.h"
#include "Chaos/UniformGrid.h"
#include "UObject/Class.h"
#include "Misc/App.h"
#include "Chaos/PhysicalMaterials.h"
TAutoConsoleVariable<int32> CVarChaosThreadEnabled(
TEXT("p.Chaos.DedicatedThreadEnabled"),
1,
TEXT("Enables a dedicated physics task/thread for Chaos tasks.")
TEXT("0: Disabled")
TEXT("1: Enabled"));
TAutoConsoleVariable<Chaos::FRealSingle> CVarDedicatedThreadDesiredHz(
TEXT("p.Chaos.Thread.DesiredHz"),
60.0f,
TEXT("Desired update rate of the dedicated physics thread in Hz/FPS (Default 60.0f)"));
TAutoConsoleVariable<int32> CVarDedicatedThreadSyncThreshold(
TEXT("p.Chaos.Thread.WaitThreshold"),
0,
TEXT("Desired wait time in ms before the game thread stops waiting to sync physics and just takes the last result. (default 16ms)")
);
namespace Chaos
{
#if !UE_BUILD_SHIPPING
CHAOS_API extern bool bPendingHierarchyDump;
#endif
FInternalDefaultSettings GDefaultChaosSettings;
}
FSolverStateStorage::FSolverStateStorage()
: Solver(nullptr)
{
}
FChaosSolversModule* FChaosSolversModule::GetModule()
{
static FChaosSolversModule* Instance = nullptr;
if(!Instance)
{
Instance = new FChaosSolversModule();
Instance->StartupModule(); //todo:remove the module api
}
return Instance;
}
FChaosSolversModule::FChaosSolversModule()
: SolverActorClassProvider(nullptr)
, SettingsProvider(nullptr)
, bPersistentTaskSpawned(false)
, PhysicsAsyncTask(nullptr)
, PhysicsInnerTask(nullptr)
, SolverActorClass(nullptr)
, SolverActorRequiredBaseClass(nullptr)
#if STATS
, AverageUpdateTime(0.0f)
, TotalAverageUpdateTime(0.0f)
, Fps(0.0f)
, EffectiveFps(0.0f)
#endif
#if WITH_EDITOR
, bPauseSolvers(false)
, SingleStepCounter(0)
#endif
, bModuleInitialized(false)
{
#if WITH_EDITOR
if(!(IsRunningDedicatedServer() || IsRunningGame()))
{
// In the editor we begin with everything paused so we don't needlessly tick
// the physics solvers until PIE begins. Delegates are bound in FPhysScene_ChaosPauseHandler
// to handle editor world transitions. In games and -game we want to just let them tick
bPauseSolvers = true;
}
#endif
}
void FChaosSolversModule::StartupModule()
{
Initialize();
}
void FChaosSolversModule::ShutdownModule()
{
Shutdown();
FCoreDelegates::OnPreExit.RemoveAll(this);
}
void FChaosSolversModule::Initialize()
{
if(!bModuleInitialized)
{
// Bind to the material manager
Chaos::FPhysicalMaterialManager& MaterialManager = Chaos::FPhysicalMaterialManager::Get();
OnCreateMaterialHandle = MaterialManager.OnMaterialCreated.Add(Chaos::FMaterialCreatedDelegate::CreateRaw(this, &FChaosSolversModule::OnCreateMaterial));
OnDestroyMaterialHandle = MaterialManager.OnMaterialDestroyed.Add(Chaos::FMaterialDestroyedDelegate::CreateRaw(this, &FChaosSolversModule::OnDestroyMaterial));
OnUpdateMaterialHandle = MaterialManager.OnMaterialUpdated.Add(Chaos::FMaterialUpdatedDelegate::CreateRaw(this, &FChaosSolversModule::OnUpdateMaterial));
OnCreateMaterialMaskHandle = MaterialManager.OnMaterialMaskCreated.Add(Chaos::FMaterialMaskCreatedDelegate::CreateRaw(this, &FChaosSolversModule::OnCreateMaterialMask));
OnDestroyMaterialMaskHandle = MaterialManager.OnMaterialMaskDestroyed.Add(Chaos::FMaterialMaskDestroyedDelegate::CreateRaw(this, &FChaosSolversModule::OnDestroyMaterialMask));
OnUpdateMaterialMaskHandle = MaterialManager.OnMaterialMaskUpdated.Add(Chaos::FMaterialMaskUpdatedDelegate::CreateRaw(this, &FChaosSolversModule::OnUpdateMaterialMask));
bModuleInitialized = true;
}
}
void FChaosSolversModule::Shutdown()
{
using namespace Chaos;
if(bModuleInitialized)
{
for(FPhysicsSolverBase* Solver : AllSolvers)
{
Solver->WaitOnPendingTasks_External();
}
// Unbind material events
FPhysicalMaterialManager& MaterialManager = FPhysicalMaterialManager::Get();
MaterialManager.OnMaterialCreated.Remove(OnCreateMaterialHandle);
MaterialManager.OnMaterialDestroyed.Remove(OnDestroyMaterialHandle);
MaterialManager.OnMaterialUpdated.Remove(OnUpdateMaterialHandle);
bModuleInitialized = false;
}
}
bool FChaosSolversModule::IsPersistentTaskEnabled() const
{
return CVarChaosThreadEnabled.GetValueOnGameThread() == 1;
}
bool FChaosSolversModule::IsPersistentTaskRunning() const
{
return bPersistentTaskSpawned;
}
Chaos::FPersistentPhysicsTask* FChaosSolversModule::GetDedicatedTask() const
{
return PhysicsInnerTask;
}
void FChaosSolversModule::SyncTask(bool bForceBlockingSync /*= false*/)
{
#if 0
// Hard lock the physics thread before syncing our data
FChaosScopedPhysicsThreadLock ScopeLock(bForceBlockingSync ? MAX_uint32 : (uint32)(CVarDedicatedThreadSyncThreshold.GetValueOnGameThread()));
// This will either get the results because physics finished, or fall back on whatever physics last gave us
// to allow the game thread to continue on without stalling.
PhysicsInnerTask->SyncProxiesFromCache(ScopeLock.DidGetLock());
// Update stats if necessary
UpdateStats();
#endif
}
Chaos::FPBDRigidsSolver* FChaosSolversModule::CreateSolver(UObject* InOwner, Chaos::FReal InAsyncDt, Chaos::EThreadingMode InThreadingMode, const FName& DebugName)
{
LLM_SCOPE(ELLMTag::Chaos);
using namespace Chaos;
FChaosScopeSolverLock SolverScopeLock;
EMultiBufferMode SolverBufferMode = InThreadingMode == EThreadingMode::SingleThread ? EMultiBufferMode::Single : EMultiBufferMode::Double;
FPBDRigidsSolver* NewSolver = new FPBDRigidsSolver(SolverBufferMode, InOwner, InAsyncDt);
AllSolvers.Add(NewSolver);
// Add The solver to the owner list
TArray<FPhysicsSolverBase*>& OwnerSolverList = SolverMap.FindOrAdd(InOwner);
OwnerSolverList.Add(NewSolver);
#if CHAOS_DEBUG_NAME
// Add solver number to solver name
const FName NewDebugName = *FString::Printf(TEXT("%s (%d)"), DebugName == NAME_None ? TEXT("Solver") : *DebugName.ToString(), AllSolvers.Num() - 1);
NewSolver->SetDebugName(NewDebugName);
#endif
// Set up the material lists on the new solver, copying from the current primary list
{
FPhysicalMaterialManager& Manager = Chaos::FPhysicalMaterialManager::Get();
FPhysicsSceneGuardScopedWrite ScopedWrite(NewSolver->GetExternalDataLock_External());
NewSolver->QueryMaterials_External = Manager.GetPrimaryMaterials_External();
NewSolver->QueryMaterialMasks_External = Manager.GetPrimaryMaterialMasks_External();
NewSolver->SimMaterials = Manager.GetPrimaryMaterials_External();
NewSolver->SimMaterialMasks = Manager.GetPrimaryMaterialMasks_External();
}
return NewSolver;
}
void FChaosSolversModule::MigrateSolver(Chaos::FPhysicsSolverBase* InSolver, const UObject* InNewOwner)
{
checkSlow(IsInGameThread());
if(InSolver->GetOwner() == InNewOwner)
{
// No migration
return;
}
if(const UObject* CurrentOwner = InSolver->GetOwner())
{
TArray<Chaos::FPhysicsSolverBase*>* OwnerList = SolverMap.Find(CurrentOwner);
ensure(OwnerList->Remove(InSolver));
}
InSolver->SetOwner(InNewOwner);
if(InNewOwner)
{
TArray<Chaos::FPhysicsSolverBase*>& OwnerList = SolverMap.FindOrAdd(InNewOwner);
OwnerList.Add(InSolver);
}
}
UClass* FChaosSolversModule::GetSolverActorClass() const
{
check(SolverActorClassProvider);
return SolverActorClassProvider->GetSolverActorClass();
}
bool FChaosSolversModule::IsValidSolverActorClass(UClass* Class) const
{
return Class->IsChildOf(SolverActorRequiredBaseClass);
}
void FChaosSolversModule::DestroySolver(Chaos::FPhysicsSolverBase* InSolver)
{
LLM_SCOPE(ELLMTag::Chaos);
FChaosScopeSolverLock SolverScopeLock;
if(AllSolvers.Remove(InSolver) > 0)
{
//should this be a find ref check?
if(TArray<Chaos::FPhysicsSolverBase*>* OwnerList = SolverMap.Find(InSolver->GetOwner()))
{
ensureMsgf(OwnerList->Remove(InSolver), TEXT("Removed a solver from the global list but not an owner list."));
}
}
else if(InSolver)
{
UE_LOG(LogChaosGeneral, Warning, TEXT("Passed valid solver state to DestroySolverState but it wasn't in the solver storage list! Make sure it was created using the Chaos module."));
}
if(InSolver)
{
Chaos::FPhysicsSolverBase::DestroySolver(*InSolver);
}
}
const TArray<Chaos::FPhysicsSolverBase*>& FChaosSolversModule::GetAllSolvers() const
{
return AllSolvers;
}
TArray<const Chaos::FPhysicsSolverBase*> FChaosSolversModule::GetSolvers(const UObject* InOwner) const
{
// Can't just return the ptr from TMap::Find here as it's likely these solver lists will be sent to
// the physics thread. In which case the solver pointers are stable but the container pointers are not
TArray<const Chaos::FPhysicsSolverBase*> Solvers;
GetSolvers(InOwner, Solvers);
return Solvers;
}
TArray<Chaos::FPhysicsSolverBase*> FChaosSolversModule::GetSolversMutable(const UObject* InOwner)
{
// Can't just return the ptr from TMap::Find here as it's likely these solver lists will be sent to
// the physics thread. In which case the solver pointers are stable but the container pointers are not
TArray<Chaos::FPhysicsSolverBase*> Solvers;
GetSolversMutable(InOwner, Solvers);
return Solvers;
}
void FChaosSolversModule::GetSolvers(const UObject* InOwner, TArray<const Chaos::FPhysicsSolverBase*>& OutSolvers) const
{
if(const TArray<Chaos::FPhysicsSolverBase*>* Solvers = SolverMap.Find(InOwner))
{
OutSolvers.Append(*Solvers);
}
}
void FChaosSolversModule::GetSolversMutable(const UObject* InOwner, TArray<Chaos::FPhysicsSolverBase*>& OutSolvers)
{
if(TArray<Chaos::FPhysicsSolverBase*>* Solvers = SolverMap.Find(InOwner))
{
OutSolvers.Append(*Solvers);
}
}
TAutoConsoleVariable<FString> DumpHier_ElementBuckets(
TEXT("p.Chaos.DumpHierElementBuckets"),
TEXT("1,4,8,16,32,64,128,256,512"),
TEXT("Distribution buckets for dump hierarchy stats command"));
void FChaosSolversModule::DumpHierarchyStats(int32* OutOptMaxCellElements)
{
TArray<FString> BucketStrings;
DumpHier_ElementBuckets.GetValueOnGameThread().ParseIntoArray(BucketStrings, TEXT(","));
// 2 extra for the 0 bucket at the start and the larger bucket at the end
const int32 NumBuckets = BucketStrings.Num() + 2;
TArray<int32> BucketSizes;
BucketSizes.AddZeroed(NumBuckets);
BucketSizes.Last() = MAX_int32;
for(int32 BucketIndex = 1; BucketIndex < NumBuckets - 1; ++BucketIndex)
{
BucketSizes[BucketIndex] = FCString::Atoi(*BucketStrings[BucketIndex - 1]);
}
BucketSizes.Sort();
TArray<int32> BucketCounts;
BucketCounts.AddZeroed(NumBuckets);
const int32 NumSolvers = AllSolvers.Num();
for(int32 SolverIndex = 0; SolverIndex < NumSolvers; ++SolverIndex)
{
Chaos::FPhysicsSolverBase* Solver = AllSolvers[SolverIndex];
#if TODO_REIMPLEMENT_SPATIAL_ACCELERATION_ACCESS
if(const Chaos::ISpatialAcceleration<Chaos::FReal,3>* SpatialAcceleration = Solver->GetSpatialAcceleration())
{
#if !UE_BUILD_SHIPPING
SpatialAcceleration->DumpStats();
#endif
Solver->ReleaseSpatialAcceleration();
}
#endif
#if 0
const TArray<Chaos::FAABB3>& Boxes = Hierarchy->GetWorldSpaceBoxes();
if(Boxes.Num() > 0)
{
FString OutputString = TEXT("\n\n");
OutputString += FString::Printf(TEXT("Solver %d - Hierarchy Stats\n"));
const Chaos::TUniformGrid<Chaos::FReal, 3>& Grid = Hierarchy->GetGrid();
const int32 NumCells = Grid.GetNumCells();
const FVector Min = Grid.MinCorner();
const FVector Max = Grid.MaxCorner();
const FVector Extent = Max - Min;
OutputString += FString::Printf(TEXT("Grid:\n\tCells: [%d, %d, %d] (%d)\n\tMin: %s\n\tMax: %s\n\tExtent: %s\n"),
Grid.Counts()[0],
Grid.Counts()[1],
Grid.Counts()[2],
NumCells,
*Min.ToString(),
*Max.ToString(),
*Extent.ToString()
);
int32 CellsL0 = 0;
int32 TotalElems = 0;
int32 MaxElements = 0;
const int32 NumHeirElems = Hierarchy->GetElements().Num();
for(int32 ElemIndex = 0; ElemIndex < NumHeirElems; ++ElemIndex)
{
const TArray<int32>& CellElems = Hierarchy->GetElements()[ElemIndex];
const int32 NumCellEntries = CellElems.Num();
if(NumCellEntries > 0)
{
++CellsL0;
}
if(NumCellEntries > MaxElements)
{
MaxElements = NumCellEntries;
}
TotalElems += NumCellEntries;
for(int32 BucketIndex = 1; BucketIndex < NumBuckets; ++BucketIndex)
{
if(NumCellEntries >= BucketSizes[BucketIndex - 1] && NumCellEntries < BucketSizes[BucketIndex])
{
BucketCounts[BucketIndex]++;
break;
}
}
}
if(OutOptMaxCellElements)
{
(*OutOptMaxCellElements) = MaxElements;
}
const Chaos::FReal AveragePopulatedCount = (Chaos::FReal)TotalElems / (Chaos::FReal)CellsL0;
OutputString += FString::Printf(TEXT("\n\tL0: %d\n\tAvg elements per populated cell: %.5f\n\tTotal elems: %d"),
CellsL0,
AveragePopulatedCount,
TotalElems);
int32 MaxBucketCount = 0;
for(int32 Count : BucketCounts)
{
if(Count > MaxBucketCount)
{
MaxBucketCount = Count;
}
}
const int32 MaxChars = 20;
const Chaos::FReal CountPerCharacter = (Chaos::FReal)MaxBucketCount / (Chaos::FReal)MaxChars;
OutputString += TEXT("\n\nElement Count Distribution:\n");
for(int32 BucketIndex = 1; BucketIndex < NumBuckets; ++BucketIndex)
{
const int32 NumChars = (Chaos::FReal)BucketCounts[BucketIndex] / (Chaos::FReal)CountPerCharacter;
if(BucketIndex < (NumBuckets - 1))
{
OutputString += FString::Printf(TEXT("\t[%4d - %4d) (%4d) |"), BucketSizes[BucketIndex - 1], BucketSizes[BucketIndex], BucketCounts[BucketIndex]);
}
else
{
OutputString += FString::Printf(TEXT("\t[%4d - inf) (%4d) |"), BucketSizes[BucketIndex - 1], BucketCounts[BucketIndex]);
}
for(int32 CharIndex = 0; CharIndex < NumChars; ++CharIndex)
{
OutputString += TEXT("-");
}
OutputString += TEXT("\n");
}
OutputString += TEXT("\n--------------------------------------------------");
UE_LOG(LogChaos, Warning, TEXT("%s"), *OutputString);
}
#endif
#if TODO_REIMPLEMENT_SPATIAL_ACCELERATION_ACCESS
Solver->ReleaseSpatialAcceleration();
#endif
#if !UE_BUILD_SHIPPING
Chaos::bPendingHierarchyDump = true; //mark solver pending dump to get more info
#endif
}
}
DECLARE_CYCLE_STAT(TEXT("PhysicsDedicatedStats"), STAT_PhysicsDedicatedStats, STATGROUP_ChaosDedicated); //this is a hack, needed to make stat group turn on
DECLARE_FLOAT_COUNTER_STAT(TEXT("PhysicsThreadTotalTime(ms)"), STAT_PhysicsThreadTotalTime, STATGROUP_ChaosDedicated);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumActiveConstraints"), STAT_NumActiveConstraintsDedicated, STATGROUP_ChaosDedicated);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumActiveParticles"), STAT_NumActiveParticlesDedicated, STATGROUP_ChaosDedicated);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumActiveCollisionPoints"), STAT_NumActiveCollisionPointsDedicated, STATGROUP_ChaosDedicated);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumActiveShapes"), STAT_NumActiveShapesDedicated, STATGROUP_ChaosDedicated);
#if 0
void FChaosSolversModule::UpdateStats()
{
#if STATS
SCOPE_CYCLE_COUNTER(STAT_PhysicsStatUpdate);
SCOPE_CYCLE_COUNTER(STAT_PhysicsDedicatedStats);
Chaos::FPersistentPhysicsTaskStatistics PhysStats = PhysicsInnerTask->GetNextThreadStatistics_GameThread();
if(PhysStats.NumUpdates > 0)
{
AverageUpdateTime = PhysStats.AccumulatedTime / (FReal)PhysStats.NumUpdates;
TotalAverageUpdateTime = PhysStats.ActualAccumulatedTime / (FReal)PhysStats.NumUpdates;
Fps = 1.0f / AverageUpdateTime;
EffectiveFps = 1.0f / TotalAverageUpdateTime;
}
// Only set the stats if something is actually running
if(Fps != 0.0f)
{
SET_FLOAT_STAT(STAT_PhysicsThreadTime, AverageUpdateTime * 1000.0f);
SET_FLOAT_STAT(STAT_PhysicsThreadTimeEff, TotalAverageUpdateTime * 1000.0f);
SET_FLOAT_STAT(STAT_PhysicsThreadFps, Fps);
SET_FLOAT_STAT(STAT_PhysicsThreadFpsEff, EffectiveFps);
if (Fps != 0.0f && PhysStats.SolverStats.Num() > 0)
{
PerSolverStats = PhysStats.AccumulateSolverStats();
}
SET_FLOAT_STAT(STAT_PhysicsThreadTotalTime, AverageUpdateTime * 1000.0f);
SET_DWORD_STAT(STAT_NumActiveConstraintsDedicated, PerSolverStats.NumActiveConstraints);
SET_DWORD_STAT(STAT_NumActiveParticlesDedicated, PerSolverStats.NumActiveParticles);
SET_DWORD_STAT(STAT_NumActiveCollisionPointsDedicated, PerSolverStats.EvolutionStats.ActiveCollisionPoints);
SET_DWORD_STAT(STAT_NumActiveShapesDedicated, PerSolverStats.EvolutionStats.ActiveShapes);
}
#if FRAMEPRO_ENABLED
// Custom framepro stats for graphs
const Chaos::FRealSingle AvgUpdateMs = AverageUpdateTime * 1000.f;
const Chaos::FRealSingle AvgEffectiveUpdateMs = TotalAverageUpdateTime * 1000.0f;
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_Fps", Fps, "ChaosThread", "FPS", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_EffectiveFps", EffectiveFps, "ChaosThread", "FPS", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_Time", AvgUpdateMs, "ChaosThread", "ms", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_EffectiveTime", AvgEffectiveUpdateMs, "ChaosThread", "ms", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_NumActiveParticles", PerSolverStats.NumActiveParticles, "ChaosThread", "Particles", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_NumConstraints", PerSolverStats.NumActiveConstraints, "ChaosThread", "Constraints", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_NumAllocatedParticles", PerSolverStats.NumAllocatedParticles, "ChaosThread", "Particles", FRAMEPRO_COLOUR(255,255,255));
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_NumPaticleIslands", PerSolverStats.NumParticleIslands, "ChaosThread", "Islands", FRAMEPRO_COLOUR(255,255,255));
const int32 NumSolvers = AllSolvers.Num();
#if 0
for(int32 SolverIndex = 0; SolverIndex < NumSolvers; ++SolverIndex)
{
Chaos::PBDRigidsSolver* Solver = AllSolvers[SolverIndex];
const Chaos::TBoundingVolume<Chaos::FPBDRigidParticles>* Hierarchy = Solver->GetSpatialAcceleration();
if(Hierarchy)
{
FRAMEPRO_CUSTOM_STAT("Chaos_Thread_Hierarchy_NumObjects", Hierarchy->GlobalObjects().Num(), "ChaosThread", "Objects");
}
Solver->ReleaseSpatialAcceleration();
}
#endif
#endif
#endif
}
#endif
#if WITH_EDITOR
void FChaosSolversModule::PauseSolvers()
{
bPauseSolvers = true;
UE_LOG(LogChaosDebug, Verbose, TEXT("Pausing solvers."));
// Sync physics to allow last minute updates
if(IsPersistentTaskRunning())
{
SyncTask(true);
}
}
void FChaosSolversModule::ResumeSolvers()
{
bPauseSolvers = false;
UE_LOG(LogChaosDebug, Verbose, TEXT("Resuming solvers."));
}
void FChaosSolversModule::SingleStepSolvers()
{
bPauseSolvers = true;
SingleStepCounter.Increment();
UE_LOG(LogChaosDebug, Verbose, TEXT("Single-stepping solvers."));
// Sync physics to allow last minute updates
if(IsPersistentTaskRunning())
{
SyncTask(true);
}
}
bool FChaosSolversModule::ShouldStepSolver(int32& InOutSingleStepCounter) const
{
const int32 counter = SingleStepCounter.GetValue();
const bool bShouldStepSolver = !(bPauseSolvers && InOutSingleStepCounter == counter);
InOutSingleStepCounter = counter;
return bShouldStepSolver;
}
#endif // #if WITH_EDITOR
void FChaosSolversModule::OnUpdateMaterial(Chaos::FMaterialHandle InHandle)
{
// Grab the material
Chaos::FChaosPhysicsMaterial* Material = InHandle.Get();
if(ensure(Material))
{
for(Chaos::FPhysicsSolverBase* Solver : AllSolvers)
{
// Send a copy of the material to each solver
Solver->EnqueueCommandImmediate([InHandle, MaterialCopy = *Material, Solver]()
{
Solver->CastHelper([InHandle, &MaterialCopy](auto& Concrete)
{
Concrete.UpdateMaterial(InHandle,MaterialCopy);
});
});
}
}
}
void FChaosSolversModule::OnCreateMaterial(Chaos::FMaterialHandle InHandle)
{
// Grab the material
Chaos::FChaosPhysicsMaterial* Material = InHandle.Get();
if(ensure(Material))
{
for(Chaos::FPhysicsSolverBase* Solver : AllSolvers)
{
// Send a copy of the material to each solver
Solver->EnqueueCommandImmediate([InHandle, MaterialCopy = *Material, Solver]()
{
Solver->CastHelper([InHandle, &MaterialCopy](auto& Concrete)
{
Concrete.CreateMaterial(InHandle,MaterialCopy);
});
});
}
}
}
void FChaosSolversModule::OnDestroyMaterial(Chaos::FMaterialHandle InHandle)
{
// Grab the material
Chaos::FChaosPhysicsMaterial* Material = InHandle.Get();
if(ensure(Material))
{
for(Chaos::FPhysicsSolverBase* Solver : AllSolvers)
{
// Notify each solver
Solver->EnqueueCommandImmediate([InHandle, Solver]()
{
Solver->CastHelper([InHandle](auto& Concrete)
{
Concrete.DestroyMaterial(InHandle);
});
});
}
}
}
void FChaosSolversModule::OnUpdateMaterialMask(Chaos::FMaterialMaskHandle InHandle)
{
// Grab the material
Chaos::FChaosPhysicsMaterialMask* MaterialMask = InHandle.Get();
if (ensure(MaterialMask))
{
for (Chaos::FPhysicsSolverBase* Solver : AllSolvers)
{
// Send a copy of the material to each solver
Solver->EnqueueCommandImmediate([InHandle, MaterialMaskCopy = *MaterialMask, Solver]()
{
Solver->CastHelper([InHandle,&MaterialMaskCopy](auto& Concrete)
{
Concrete.UpdateMaterialMask(InHandle,MaterialMaskCopy);
});
});
}
}
}
void FChaosSolversModule::OnCreateMaterialMask(Chaos::FMaterialMaskHandle InHandle)
{
// Grab the material
Chaos::FChaosPhysicsMaterialMask* MaterialMask = InHandle.Get();
if (ensure(MaterialMask))
{
for (Chaos::FPhysicsSolverBase* Solver : AllSolvers)
{
// Send a copy of the material to each solver
Solver->EnqueueCommandImmediate([InHandle, MaterialMaskCopy = *MaterialMask, Solver]()
{
Solver->CastHelper([InHandle,&MaterialMaskCopy](auto& Concrete)
{
Concrete.CreateMaterialMask(InHandle,MaterialMaskCopy);
});
});
}
}
}
void FChaosSolversModule::OnDestroyMaterialMask(Chaos::FMaterialMaskHandle InHandle)
{
// Grab the material
Chaos::FChaosPhysicsMaterialMask* MaterialMask = InHandle.Get();
if (ensure(MaterialMask))
{
for (Chaos::FPhysicsSolverBase* Solver : AllSolvers)
{
// Notify each solver
Solver->EnqueueCommandImmediate([InHandle, Solver]()
{
Solver->CastHelper([InHandle](auto& Concrete)
{
Concrete.DestroyMaterialMask(InHandle);
});
});
}
}
}
const IChaosSettingsProvider& FChaosSolversModule::GetSettingsProvider() const
{
return SettingsProvider ? *SettingsProvider : Chaos::GDefaultChaosSettings;
}