// Copyright Epic Games, Inc. All Rights Reserved. #include "ChaosVehicleManager.h" #include "Engine/World.h" #include "Physics/Experimental/PhysScene_Chaos.h" #include "UObject/UObjectIterator.h" #include "PhysicalMaterials/PhysicalMaterial.h" #include "Physics/PhysicsFiltering.h" #include "Physics/PhysicsInterfaceCore.h" #include "ChaosVehicleMovementComponent.h" #include "PBDRigidsSolver.h" #include "PhysicsProxy/SingleParticlePhysicsProxy.h" DECLARE_CYCLE_STAT(TEXT("VehicleManager:ParallelUpdateVehicles"), STAT_ChaosVehicleManager_ParallelUpdateVehicles, STATGROUP_ChaosVehicleManager); DECLARE_CYCLE_STAT(TEXT("VehicleManager:Update"), STAT_ChaosVehicleManager_Update, STATGROUP_ChaosVehicleManager); DECLARE_CYCLE_STAT(TEXT("VehicleManager:ScenePreTick"), STAT_ChaosVehicleManager_ScenePreTick, STATGROUP_ChaosVehicleManager); DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumVehiclesTotal"), STAT_NumVehicles_Dynamic, STATGROUP_ChaosVehicleManager); DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumVehiclesAwake"), STAT_NumVehicles_Awake, STATGROUP_ChaosVehicleManager); DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("NumVehiclesSleeping"), STAT_NumVehicles_Sleeping, STATGROUP_ChaosVehicleManager); extern FVehicleDebugParams GVehicleDebugParams; TMap FChaosVehicleManager::SceneToVehicleManagerMap; uint32 FChaosVehicleManager::VehicleSetupTag = 0; FDelegateHandle FChaosVehicleManager::OnPostWorldInitializationHandle; FDelegateHandle FChaosVehicleManager::OnWorldCleanupHandle; bool FChaosVehicleManager::GInitialized = false; void OnPostWorldInitialize(UWorld* InWorld, const UWorld::InitializationValues) { FChaosVehicleManager* Manager = FChaosVehicleManager::GetVehicleManagerFromScene(InWorld->GetPhysicsScene()); if(Manager) { Manager->RegisterCallbacks(); } } void OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources) { FChaosVehicleManager* Manager = FChaosVehicleManager::GetVehicleManagerFromScene(InWorld->GetPhysicsScene()); if(Manager) { Manager->UnregisterCallbacks(); } } FChaosVehicleManager::FChaosVehicleManager(FPhysScene* PhysScene) : Scene(*PhysScene) , AsyncCallback(nullptr) , Timestamp(0) { check(PhysScene); if (!GInitialized) { GInitialized = true; // PhysScene->GetOwningWorld() is always null here, the world is being setup too late to be of use // therefore setup these global world delegates that will callback when everything is setup so registering // the physics solver Async Callback will succeed OnPostWorldInitializationHandle = FWorldDelegates::OnPostWorldInitialization.AddStatic(&OnPostWorldInitialize); OnWorldCleanupHandle = FWorldDelegates::OnWorldCleanup.AddStatic(&OnWorldCleanup); } ensure(FChaosVehicleManager::SceneToVehicleManagerMap.Find(PhysScene) == nullptr); //double registration with same scene, will cause a leak // Add to Scene-To-Manager map FChaosVehicleManager::SceneToVehicleManagerMap.Add(PhysScene, this); } void FChaosVehicleManager::RegisterCallbacks() { OnPhysScenePreTickHandle = Scene.OnPhysScenePreTick.AddRaw(this, &FChaosVehicleManager::Update); OnPhysScenePostTickHandle = Scene.OnPhysScenePostTick.AddRaw(this, &FChaosVehicleManager::PostUpdate); check(AsyncCallback == nullptr); AsyncCallback = Scene.GetSolver()->CreateAndRegisterSimCallbackObject_External(); } void FChaosVehicleManager::UnregisterCallbacks() { Scene.OnPhysScenePreTick.Remove(OnPhysScenePreTickHandle); Scene.OnPhysScenePostTick.Remove(OnPhysScenePostTickHandle); if (AsyncCallback) { Scene.GetSolver()->UnregisterAndFreeSimCallbackObject_External(AsyncCallback); AsyncCallback = nullptr; } } void FChaosVehicleManager::DetachFromPhysScene(FPhysScene* PhysScene) { if (AsyncCallback) { UnregisterCallbacks(); } FChaosVehicleManager::SceneToVehicleManagerMap.Remove(PhysScene); } FChaosVehicleManager::~FChaosVehicleManager() { while (Vehicles.Num() > 0) { RemoveVehicle(Vehicles.Last()); } } FChaosVehicleManager* FChaosVehicleManager::GetVehicleManagerFromScene(FPhysScene* PhysScene) { FChaosVehicleManager* Manager = nullptr; FChaosVehicleManager** ManagerPtr = SceneToVehicleManagerMap.Find(PhysScene); if (ManagerPtr != nullptr) { Manager = *ManagerPtr; } return Manager; } void FChaosVehicleManager::AddVehicle(TWeakObjectPtr Vehicle) { check(Vehicle != NULL); check(Vehicle->PhysicsVehicleOutput()); check(AsyncCallback); Vehicles.Add(Vehicle); } void FChaosVehicleManager::RemoveVehicle(TWeakObjectPtr Vehicle) { check(Vehicle != NULL); check(Vehicle->PhysicsVehicleOutput()); Vehicles.Remove(Vehicle); if (Vehicle->PhysicsVehicleOutput().IsValid()) { Vehicle->PhysicsVehicleOutput().Reset(nullptr); } } void FChaosVehicleManager::ScenePreTick(FPhysScene* PhysScene, float DeltaTime) { // inputs being set via back door, i.e. accessing PVehicle directly is a no go now, needs to go through async input system SCOPE_CYCLE_COUNTER(STAT_ChaosVehicleManager_ScenePreTick); for (int32 i = 0; i < Vehicles.Num(); ++i) { Vehicles[i]->PreTickGT(DeltaTime); } } void FChaosVehicleManager::Update(FPhysScene* PhysScene, float DeltaTime) { SCOPE_CYCLE_COUNTER(STAT_ChaosVehicleManager_Update); UWorld* World = Scene.GetOwningWorld(); SubStepCount = 0; ScenePreTick(PhysScene, DeltaTime); ParallelUpdateVehicles(DeltaTime); if (World) { FChaosVehicleManagerAsyncInput* AsyncInput = AsyncCallback->GetProducerInputData_External(); for (TWeakObjectPtr Vehicle : Vehicles) { Vehicle->Update(DeltaTime); Vehicle->FinalizeSimCallbackData(*AsyncInput); } } } void FChaosVehicleManager::PostUpdate(FChaosScene* PhysScene) { SET_DWORD_STAT(STAT_NumVehicles_Dynamic, Vehicles.Num()); int32 SleepingCount = 0; for (int32 i = 0; i < Vehicles.Num(); ++i) { if (Vehicles[i]->VehicleState.bSleeping) { SleepingCount++; } } SET_DWORD_STAT(STAT_NumVehicles_Awake, Vehicles.Num() - SleepingCount); SET_DWORD_STAT(STAT_NumVehicles_Sleeping, SleepingCount); } void FChaosVehicleManager::ParallelUpdateVehicles(float DeltaSeconds) { SCOPE_CYCLE_COUNTER(STAT_ChaosVehicleManager_ParallelUpdateVehicles); FChaosVehicleManagerAsyncInput* AsyncInput = AsyncCallback->GetProducerInputData_External(); AsyncInput->Reset(); //only want latest frame's data { // We pass pointers from TArray so this reserve is critical. Otherwise realloc happens AsyncInput->VehicleInputs.Reserve(Vehicles.Num()); AsyncInput->Timestamp = Timestamp; AsyncInput->World = Scene.GetOwningWorld(); } // Grab all outputs for processing, even future ones for interpolation. { Chaos::TSimCallbackOutputHandle AsyncOutputLatest; while ((AsyncOutputLatest = AsyncCallback->PopFutureOutputData_External())) { PendingOutputs.Emplace(MoveTemp(AsyncOutputLatest)); } } // Since we are in pre-physics, delta seconds is not accounted for in external time yet const float ResultsTime = AsyncCallback->GetSolver()->GetPhysicsResultsTime_External() + DeltaSeconds; // Find index of first non-consumable output (first one after current time) int32 LastOutputIdx = 0; for (; LastOutputIdx < PendingOutputs.Num(); ++LastOutputIdx) { if (PendingOutputs[LastOutputIdx]->InternalTime > ResultsTime) { break; } } // Process events on all outputs which occurred before current time // //for (int32 OutputIdx = 0; OutputIdx < LastOutputIdx; ++OutputIdx) //{ // for (TWeakObjectPtr Vehicle : Vehicles) // { // Vehicle->GameThread_ProcessIntermediateAsyncOutput(*PendingOutputs[OutputIdx]); // } //} // Cache the last consumed output for interpolation if (LastOutputIdx > 0) { LatestOutput = MoveTemp(PendingOutputs[LastOutputIdx - 1]); } // Remove all consumed outputs { TArray> NewPendingOutputs; for (int32 OutputIdx = LastOutputIdx; OutputIdx < PendingOutputs.Num(); ++OutputIdx) { NewPendingOutputs.Emplace(MoveTemp(PendingOutputs[OutputIdx])); } PendingOutputs = MoveTemp(NewPendingOutputs); } // It's possible we will end up multiple frames ahead of output, take the latest ready output. Chaos::TSimCallbackOutputHandle AsyncOutput; Chaos::TSimCallbackOutputHandle AsyncOutputLatest; while ((AsyncOutputLatest = AsyncCallback->PopOutputData_External())) { AsyncOutput = MoveTemp(AsyncOutputLatest); // Note: not used - left as a reminder //for (TWeakObjectPtr Vehicle : Vehicles) //{ // //Vehicle->GameThread_ProcessIntermediateAsyncOutput(*AsyncOutput); //} } if (UWorld* World = Scene.GetOwningWorld()) { int32 NumVehiclesInActiveBatch = 0; for (TWeakObjectPtr Vehicle : Vehicles) { auto NextOutput = PendingOutputs.Num() > 0 ? PendingOutputs[0].Get() : nullptr; float Alpha = 0.f; if (NextOutput && LatestOutput) { const float Denom = NextOutput->InternalTime - LatestOutput->InternalTime; if (Denom > SMALL_NUMBER) { Alpha = (ResultsTime - LatestOutput->InternalTime) / Denom; } } AsyncInput->VehicleInputs.Add(Vehicle->SetCurrentAsyncInputOutput(AsyncInput->VehicleInputs.Num(), LatestOutput.Get(), NextOutput, Alpha, Timestamp)); } } ++Timestamp; const auto& AwakeVehiclesBatch = Vehicles; // TODO: process awake only auto LambdaParallelUpdate = [DeltaSeconds, &AwakeVehiclesBatch](int32 Idx) { TWeakObjectPtr Vehicle = AwakeVehiclesBatch[Idx]; Vehicle->ParallelUpdate(DeltaSeconds); // gets output state from PT }; bool ForceSingleThread = !GVehicleDebugParams.EnableMultithreading; ParallelFor(AwakeVehiclesBatch.Num(), LambdaParallelUpdate, ForceSingleThread); }