// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "HAL/Event.h" #include "HAL/IConsoleManager.h" #include "HAL/ThreadSafeBool.h" #include "Modules/ModuleInterface.h" #include "Modules/ModuleManager.h" #include "Async/AsyncWork.h" #include "UObject/ObjectMacros.h" #include "Framework/Threading.h" #include "PhysicsCoreTypes.h" #include "Chaos/Framework/MultiBufferResource.h" #include "Chaos/Declares.h" #include "Chaos/PhysicalMaterials.h" #include "Chaos/Defines.h" #include "Async/TaskGraphInterfaces.h" /** Classes that want to set the solver actor class can implement this. */ class IChaosSolverActorClassProvider { public: virtual UClass* GetSolverActorClass() const = 0; }; /** Class that external users can implement and set on the module to provide various settings to the system (see FChaosSolversModule::SetSettingsProvider) */ class IChaosSettingsProvider { public: virtual ~IChaosSettingsProvider() {} virtual float GetMinDeltaVelocityForHitEvents() const { return 0.f; } virtual bool GetPhysicsPredictionEnabled() const { return false; } UE_DEPRECATED(5.5, "GetResimulationErrorThreshold has been renamed, please use GetResimulationErrorPositionThreshold.") virtual float GetResimulationErrorThreshold() const { return GetResimulationErrorPositionThreshold(); } virtual bool GetResimulationErrorPositionThresholdEnabled() const { return false; } virtual float GetResimulationErrorPositionThreshold() const { return 0; } virtual bool GetResimulationErrorRotationThresholdEnabled() const { return false; } virtual float GetResimulationErrorRotationThreshold() const { return 0; } virtual bool GetResimulationErrorLinearVelocityThresholdEnabled() const { return false; } virtual float GetResimulationErrorLinearVelocityThreshold() const { return 0; } virtual bool GetResimulationErrorAngularVelocityThresholdEnabled() const { return false; } virtual float GetResimulationErrorAngularVelocityThreshold() const { return 0; } virtual float GetPhysicsHistoryTimeLength() const { return 0; } virtual int32 GetPhysicsHistoryCount() const { return 0; } }; namespace Chaos { class FPersistentPhysicsTask; class FPhysicsProxy; class FPhysicsSolverBase; } // Default settings implementation namespace Chaos { class FInternalDefaultSettings : public IChaosSettingsProvider { public: }; extern CHAOS_API FInternalDefaultSettings GDefaultChaosSettings; } struct FSolverStateStorage { friend class FChaosSolversModule; Chaos::FPhysicsSolver* Solver; TArray ActiveProxies; TArray ActiveProxies_GameThread; private: // Private so only the module can actually make these so they can be tracked FSolverStateStorage(); FSolverStateStorage(const FSolverStateStorage& InCopy) = default; FSolverStateStorage(FSolverStateStorage&& InSteal) = default; FSolverStateStorage& operator =(const FSolverStateStorage& InCopy) = default; FSolverStateStorage& operator =(FSolverStateStorage&& InSteal) = default; }; enum class ESolverFlags : uint8 { None = 0, Standalone = 1 << 0 }; ENUM_CLASS_FLAGS(ESolverFlags); class FChaosSolversModule { public: static CHAOS_API FChaosSolversModule* GetModule(); CHAOS_API FChaosSolversModule(); CHAOS_API void StartupModule(); CHAOS_API void ShutdownModule(); CHAOS_API void Initialize(); CHAOS_API void Shutdown(); /** * Queries for multithreaded configurations */ CHAOS_API bool IsPersistentTaskEnabled() const; CHAOS_API bool IsPersistentTaskRunning() const; /** * Gets the inner physics thread task if it has been spawned. Care must be taken when * using methods and members that the calling context can safely access those fields * as the task will be running on its own thread. */ CHAOS_API Chaos::FPersistentPhysicsTask* GetDedicatedTask() const; /** * Called to request a sync between the game thread and the currently running physics task * @param bForceBlockingSync forces this */ CHAOS_API void SyncTask(bool bForceBlockingSync = false); /** * Create a new solver state storage object to contain a solver and proxy storage object. Intended * to be used by the physics scene to create a common storage object that can be passed to a dedicated * thread when it is enabled without having to link Engine from Chaos. * * Should be called from the game thread to create a new solver. After creation, non-standalone solvers * are dispatched to the physics thread automatically if it is available * * @param InOwner Ptr to some owning UObject. The module doesn't use this but allows calling code to organize solver ownership * @param ThreadingMode The desired threading mode the solver will use * @param bStandalone Whether the solver is standalone (not sent to physics thread - updating left to caller) */ CHAOS_API Chaos::FPBDRigidsSolver* CreateSolver(UObject* InOwner, Chaos::FReal InAsyncDt, Chaos::EThreadingMode ThreadingMode = Chaos::EThreadingMode::SingleThread, const FName& DebugName = NAME_None); CHAOS_API void MigrateSolver(Chaos::FPhysicsSolverBase* InSolver, const UObject* InNewOwner); /** * Sets the actor type which should be AChaosSolverActor::StaticClass() but that is not accessible from engine. */ void SetSolverActorClass(UClass* InActorClass, UClass* InActorRequiredBaseClass) { SolverActorClass = InActorClass; SolverActorRequiredBaseClass = InActorRequiredBaseClass; check(IsValidSolverActorClass(SolverActorClass)); } CHAOS_API UClass* GetSolverActorClass() const; CHAOS_API bool IsValidSolverActorClass(UClass* Class) const; /** * Shuts down and destroys a solver state * * Should be called on whichever thread currently owns the solver state */ CHAOS_API void DestroySolver(Chaos::FPhysicsSolverBase* InState); /** Retrieve the list of all extant solvers. This contains all owned, unowned and standalone solvers */ CHAOS_API const TArray& GetAllSolvers() const; /** * Read access to the current solver-state objects, be aware which thread owns this data when * attempting to use this. Physics thread will query when spinning up to get current world state */ CHAOS_API TArray GetSolvers(const UObject* InOwner) const; CHAOS_API void GetSolvers(const UObject* InOwner, TArray& OutSolvers) const; /** Non-const access to the solver array is private - only the module should ever modify the storage lists */ CHAOS_API TArray GetSolversMutable(const UObject* InOwner); CHAOS_API void GetSolversMutable(const UObject* InOwner, TArray& OutSolvers); /** * Outputs statistics for the solver hierarchies. Currently engine calls into this * from a console command on demand. */ CHAOS_API void DumpHierarchyStats(int32* OutOptMaxCellElements = nullptr); #if WITH_EDITOR /** * Pause solvers. Thread safe. * This is typically called from a playing editor to pause all solvers. * @note Game pause must use a different per solver mechanics. */ CHAOS_API void PauseSolvers(); /** * Resume solvers. Thread safe. * This is typically called from a paused editor to resume all solvers. * @note Game resume must use a different per solver mechanics. */ CHAOS_API void ResumeSolvers(); /** * Single-step advance solvers. Thread safe. * This is typically called from a paused editor to single step all solvers. */ CHAOS_API void SingleStepSolvers(); /** * Query whether a particular solver should advance and update its single-step counter. Thread safe. */ CHAOS_API bool ShouldStepSolver(int32& InOutSingleStepCounter) const; #endif // #if WITH_EDITOR void RegisterSolverActorClassProvider(IChaosSolverActorClassProvider* Provider) { SolverActorClassProvider = Provider; }; /** Safe method for always getting a settings provider (from the external caller or an internal default) */ CHAOS_API const IChaosSettingsProvider& GetSettingsProvider() const; void SetSettingsProvider(IChaosSettingsProvider* InProvider) { SettingsProvider = InProvider; } void LockSolvers() { SolverLock.Lock(); } void UnlockSolvers() { SolverLock.Unlock(); } /** Events that the material manager will emit for us to update our threaded data */ CHAOS_API void OnUpdateMaterial(Chaos::FMaterialHandle InHandle); CHAOS_API void OnCreateMaterial(Chaos::FMaterialHandle InHandle); CHAOS_API void OnDestroyMaterial(Chaos::FMaterialHandle InHandle); CHAOS_API void OnUpdateMaterialMask(Chaos::FMaterialMaskHandle InHandle); CHAOS_API void OnCreateMaterialMask(Chaos::FMaterialMaskHandle InHandle); CHAOS_API void OnDestroyMaterialMask(Chaos::FMaterialMaskHandle InHandle); /** Gets a list of pending prerequisites required before proceeding with a solver update */ CHAOS_API void GetSolverUpdatePrerequisites(FGraphEventArray& InPrerequisiteContainer); private: /** Object that contains implementation of GetSolverActorClass() */ IChaosSolverActorClassProvider* SolverActorClassProvider; /** Settings provider from external user */ IChaosSettingsProvider* SettingsProvider; // Whether we actually spawned a physics task (distinct from whether we _should_ spawn it) bool bPersistentTaskSpawned; // The actually running tasks if running in a multi threaded configuration. FAsyncTask* PhysicsAsyncTask; Chaos::FPersistentPhysicsTask* PhysicsInnerTask; // Core delegate signaling app shutdown, clean up and spin down threads before exit. FDelegateHandle PreExitHandle; // Flat list of every solver the module has created. TArray AllSolvers; // Map of solver owners to the solvers they own. Nullptr is a valid 'owner' in that it's a map // key for unowned, standalone solvers TMap> SolverMap; // Lock for the above list to ensure we don't delete solvers out from underneath other threads // or mess up the solvers array during use. mutable FCriticalSection SolverLock; /** Store the ChaosSolverActor type */ UClass* SolverActorClass; /** SolverActorClass is require to be this class or a child thereof */ UClass* SolverActorRequiredBaseClass; /** Reference to an in progress or completed task to run global and thread commands (a prerequisite for any solver ticking) */ FGraphEventRef GlobalCommandTaskEventRef; #if STATS // Stored stats from the physics thread float AverageUpdateTime; float TotalAverageUpdateTime; float Fps; float EffectiveFps; #endif #if WITH_EDITOR // Pause/Resume/Single-step thread safe booleans. FThreadSafeBool bPauseSolvers; FThreadSafeCounter SingleStepCounter; // Counter that increments its value each time a single step is instructed. #endif // #if WITH_EDITOR // Whether we're initialized, gates work in Initialize() and Shutdown() bool bModuleInitialized; /** Event binding handles */ FDelegateHandle OnCreateMaterialHandle; FDelegateHandle OnDestroyMaterialHandle; FDelegateHandle OnUpdateMaterialHandle; FDelegateHandle OnCreateMaterialMaskHandle; FDelegateHandle OnDestroyMaterialMaskHandle; FDelegateHandle OnUpdateMaterialMaskHandle; }; struct FChaosScopeSolverLock { FChaosScopeSolverLock() { FChaosSolversModule::GetModule()->LockSolvers(); } ~FChaosScopeSolverLock() { FChaosSolversModule::GetModule()->UnlockSolvers(); } };