/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // Contains: // high-level loading of characters #ifndef CRYINCLUDE_CRYANIMATION_CHARACTERMANAGER_H #define CRYINCLUDE_CRYANIMATION_CHARACTERMANAGER_H #pragma once #include <AzCore/std/containers/set.h> #include <AzCore/std/parallel/mutex.h> #include <AzCore/std/smart_ptr/unique_ptr.h> #include <AzFramework/Asset/AssetCatalogBus.h> #include <ICryAnimation.h> #include "ParamLoader.h" #include "AttachmentVCloth.h" #include "ManualResetEvent.h" #include "UniqueManualEvent.h" class CSkin; //default skinning class CAttachmentSKIN; //skin-instance class CAttachmentVCLOTH; //cloth-instance class CDefaultSkeleton; //default skeleton class CCharInstance; //skel-instance class CAttachmentManager; //skel-instance class CClothManager; class CFacialAnimation; struct IAnimationSet; #ifdef ENABLE_RUNTIME_POSE_MODIFIERS class CPoseModifierSetup; DECLARE_SMART_POINTERS(CPoseModifierSetup); #endif extern float g_YLine; struct DebugInstances { _smart_ptr<ICharacterInstance> m_pInst; QuatTS m_GridLocation; f32 m_fExtrapolation; ColorF m_AmbientColor; }; struct CDefaultSkeletonReferences { CDefaultSkeleton* m_pDefaultSkeleton; DynArray<CCharInstance*> m_RefByInstances; CDefaultSkeletonReferences() { m_pDefaultSkeleton = 0; m_RefByInstances.reserve(0x10); } void GetMemoryUsage(ICrySizer* pSizer) const {} }; struct CDefaultSkinningReferences { CSkin* m_pDefaultSkinning; DynArray<CAttachmentSKIN*> m_RefByInstances; DynArray<CAttachmentVCLOTH*> m_RefByInstancesVCloth; CDefaultSkinningReferences() { m_pDefaultSkinning = 0; m_RefByInstances.reserve(0x10); } void GetMemoryUsage(ICrySizer* pSizer) const {} }; //! Allows returning a CDefaultSkinningReferences pointer in a thread-safe way by holding onto a lock struct ScopedDefaultSkinningReferences { public: ScopedDefaultSkinningReferences(CDefaultSkinningReferences* defaultSkinningReferences, AZStd::unique_lock<AZStd::recursive_mutex>&& lock) : m_defaultSkinningReferences(defaultSkinningReferences), m_lock(std::move(lock)) {} CDefaultSkinningReferences* operator->() const { return m_defaultSkinningReferences; } bool operator ==(AZStd::nullptr_t nullPtr) const { return m_defaultSkinningReferences == nullptr; } bool operator !=(AZStd::nullptr_t nullPtr) const { return m_defaultSkinningReferences != nullptr; } explicit operator bool() const { return m_defaultSkinningReferences != nullptr; } private: CDefaultSkinningReferences* m_defaultSkinningReferences; AZStd::unique_lock<AZStd::recursive_mutex> m_lock; }; struct CharacterAttachment { CharacterAttachment() : m_relRotation(false) , m_relPosition(false) , m_RelativeDefault(IDENTITY) , m_AbsoluteDefault(IDENTITY) , m_Type(0xDeadBeef) , m_AttFlags(0) , m_ProxyParams(0, 0, 0, 0) , m_ProxyPurpose(0) { memset(&m_AttPhysInfo, 0, sizeof(m_AttPhysInfo)); } void GetMemoryUsage(ICrySizer* pSizer) const { pSizer->AddObject(m_strAttachmentName); pSizer->AddObject(m_strJointName); pSizer->AddObject(m_strBindingPath); pSizer->AddObject(m_pMaterial); pSizer->AddObject(m_parrMaterial); pSizer->AddObject(m_pStaticObject); } string m_strAttachmentName; uint32 m_Type; uint32 m_AttFlags; string m_strJointName; bool m_relRotation; bool m_relPosition; QuatT m_RelativeDefault; QuatT m_AbsoluteDefault; Vec4 m_ProxyParams; uint32 m_ProxyPurpose; SimulationParams ap; string m_strRowJointName; RowSimulationParams rowap; SVClothParams clothParams; string m_strBindingPath; string m_strSimBindingPath; _smart_ptr<IMaterial> m_pMaterial; //this is the shared material for all LODs _smart_ptr<IMaterial> m_parrMaterial[g_nMaxGeomLodLevels]; //some LODs can have an individual material _smart_ptr<IStatObj> m_pStaticObject; CryBonePhysics m_AttPhysInfo[2]; }; struct CharacterDefinition { virtual ~CharacterDefinition(){} CharacterDefinition() { m_nRefCounter = 0; m_nKeepInMemory = 0; m_nKeepModelsInMemory = -1; } virtual void AddRef() { ++m_nRefCounter; } void GetMemoryUsage(ICrySizer* pSizer) const { pSizer->AddObject(m_strBaseModelFilePath); pSizer->AddObject(m_pBaseModelMaterial); pSizer->AddObject(m_arrAttachments); } AZStd::string m_strFilePath; string m_strBaseModelFilePath; _smart_ptr<IMaterial> m_pBaseModelMaterial; int m_nKeepModelsInMemory; // Reference count. AZStd::atomic<int> m_nRefCounter; // Reference count. int m_nKeepInMemory; DynArray<CharacterAttachment> m_arrAttachments; #ifdef ENABLE_RUNTIME_POSE_MODIFIERS CPoseModifierSetupPtr m_pPoseModifierSetup; #endif }; ////////////////////////////////////////////////////////////////////// /// This class contains a list of character bodies and list of character instances. /// On attempt to create same character second time only new instance will be created. /// Only this class can create actual characters. class CharacterManager : public ICharacterManager , public AzFramework::LegacyAssetEventBus::MultiHandler { public: static bool s_bPaused; static uint32 s_renderFrameIdLocal; static uint32 GetRendererThreadId(); static uint32 GetRendererMainThreadId(); friend class CAnimationManager; friend class CAttachmentManager; friend class CAttachmentSKIN; friend class CAttachmentVCLOTH; friend class CClothPiece; friend class CCharInstance; friend class CDefaultSkeleton; friend class CSkin; friend class CParamLoader; #if BLENDSPACE_VISUALIZATION friend class CSkeletonAnim; friend struct SParametricSamplerInternal; #endif static const char* c_AnimationEditorFileLocation; CharacterManager (); ~CharacterManager (); virtual void Release(); // Deletes itself virtual void PostInit(); virtual void SyncAllAnimations(); virtual void AddAnimationToSyncQueue(ICharacterInstance* pCharInstance); virtual void RemoveAnimationToSyncQueue(ICharacterInstance* pCharInstance); void ClearPoseModifiersFromSynchQueue(); void StopAnimationsOnAllInstances(); void UpdateRendererFrame(); // should be called every frame void Update(bool bPause); void UpdateStreaming(int nFullUpdateRoundId, int nFastUpdateRoundId); void DatabaseUnloading(); CAnimationSet* GetAnimationSetUsedInCharEdit(); void DummyUpdate(); // can be called instead of Update() for UI purposes (such as in preview viewports, etc). ////////////////////////////////////////////////////////////////////////// IFacialAnimation* GetIFacialAnimation(); const IFacialAnimation* GetIFacialAnimation() const; IChrParamsParser* GetIChrParamParser(){ return &m_ParamLoader; }; IAnimEvents* GetIAnimEvents() { return &g_AnimationManager; }; const IAnimEvents* GetIAnimEvents() const { return &g_AnimationManager; }; CAnimationManager& GetAnimationManager() { return m_AnimationManager; }; const CAnimationManager& GetAnimationManager() const { return m_AnimationManager; }; CParamLoader& GetParamLoader() { return m_ParamLoader; }; CFacialAnimation* GetFacialAnimation() {return m_pFacialAnimation; } const CFacialAnimation* GetFacialAnimation() const {return m_pFacialAnimation; } //a list with model-names that use "ForceSkeletonUpdates" std::vector<string> m_arrSkeletonUpdates; std::vector<uint32> m_arrAnimPlaying; std::vector<uint32> m_arrForceSkeletonUpdates; std::vector<uint32> m_arrVisible; uint32 m_nUpdateCounter; uint32 m_AllowStartOfAnimation; uint32 g_SkeletonUpdates; uint32 g_AnimationUpdates; IAnimationStreamingListener* m_pStreamingListener; //methods to handle animation assets void SetAnimMemoryTracker(const SAnimMemoryTracker& amt) { g_AnimationManager.m_AnimMemoryTracker.m_nMemTracker = amt.m_nMemTracker; g_AnimationManager.m_AnimMemoryTracker.m_nAnimsCurrent = amt.m_nAnimsCurrent; g_AnimationManager.m_AnimMemoryTracker.m_nAnimsMax = amt.m_nAnimsMax; g_AnimationManager.m_AnimMemoryTracker.m_nAnimsAdd = amt.m_nAnimsAdd; g_AnimationManager.m_AnimMemoryTracker.m_nAnimsCounter = amt.m_nAnimsCounter; } SAnimMemoryTracker GetAnimMemoryTracker() const { return g_AnimationManager.m_AnimMemoryTracker; } bool DBA_PreLoad(const char* filepath, ICharacterManager::EStreamingDBAPriority priority); bool DBA_LockStatus(const char* filepath, uint32 status, ICharacterManager::EStreamingDBAPriority priority); bool DBA_Unload(const char* filepath); bool DBA_Unload_All(); virtual bool CAF_AddRef(uint32 filePathCRC); virtual bool CAF_IsLoaded(uint32 filePathCRC) const; virtual bool CAF_Release(uint32 filePathCRC); virtual bool CAF_LoadSynchronously(uint32 filePathCRC); virtual bool LMG_LoadSynchronously(uint32 filePathCRC, const IAnimationSet* pAnimationSet); virtual bool CAF_AddRefByGlobalId(int globalID); virtual bool CAF_ReleaseByGlobalId(int globalID); EReloadCAFResult ReloadCAF(const char* szFilePathCAF) { return g_AnimationManager.ReloadCAF(szFilePathCAF); }; int ReloadLMG(const char* szFilePathLMG) { return g_AnimationManager.ReloadLMG(szFilePathLMG); }; CDefaultSkeleton* GetModelByAimPoseID(uint32 nGlobalIDAimPose); const char* GetDBAFilePathByGlobalID(int32 globalID) const; virtual void SetStreamingListener(IAnimationStreamingListener* pListener) { m_pStreamingListener = pListener; }; // light profiler functions virtual void AddFrameTicks(uint64 nTicks) { m_nFrameTicks += nTicks; } virtual void AddFrameSyncTicks(uint64 nTicks) { m_nFrameSyncTicks += nTicks; } virtual void ResetFrameTicks() { m_nFrameTicks = 0; m_nFrameSyncTicks = 0; } virtual uint64 NumFrameTicks() const { return m_nFrameTicks; } virtual uint64 NumFrameSyncTicks() const { return m_nFrameSyncTicks; } virtual uint32 NumCharacters() const { return m_nActiveCharactersLastFrame; } void UpdateDatabaseUnloadTimeStamp(); uint32 GetDatabaseUnloadTimeDelta() const; ICharacterInstance* LoadCharacterDefinition(const AZStd::string& pathname, uint32 nLoadingFlags = 0); UniqueManualEvent SafeGetOrLoadCDF(const AZStd::string& pathname, CharacterDefinition*& characterDefinition); CharacterDefinition* LoadCDF(const char* pathname); CharacterDefinition* LoadCDFFromXML(XmlNodeRef root, const char* pathname); void ReleaseCDF(const char* pathname); uint32 GetCDFId(const AZStd::string& pathname); CharacterDefinition* GetOrLoadCDF(const AZStd::string& pathname, bool addRef = false); bool StreamKeepCDFResident(const char* szFilePath, int nLod, int nRefAdj, bool bUrgent); //! Return value will hold a lock on m_cacheSkinMutex until it goes out of scope ScopedDefaultSkinningReferences GetDefaultSkinningReferences(CSkin* pDefaultSkinning); // override from AssetCatalogEventBus::Handler void OnFileChanged(AZStd::string assetPath) override; void OnFileRemoved(AZStd::string assetPath) override; void MarkForGC(CDefaultSkeleton* pSkeleton); void MarkForGC(CSkin* pSkin); void UnmarkForGC(CDefaultSkeleton* pSkeleton); void UnmarkForGC(CSkin* pSkin); void RunGarbageCollection(); private: template<typename T, void(CharacterManager::* TUnregisterFunc)(T*)> void RunGarbageSet(AZStd::unordered_set<T*>& pendingGarbageList, AZStd::recursive_mutex& cacheMutex); uint32 m_StartGAH_Iterator; void LoadAnimationImageFile(const char* filenameCAF, const char* filenameAIM); bool LoadAnimationImageFileCAF(const char* filenameCAF, IChunkFile* pChunkFile); bool LoadAnimationImageFileAIM(const char* filenameAIM, IChunkFile* pChunkFile); uint32 IsInitializedByIMG() { return m_InitializedByIMG; }; uint32 m_InitializedByIMG; void DumpAssetStatistics(); f32 GetAverageFrameTime(f32 sec, f32 FrameTime, f32 TimeScale, f32 LastAverageFrameTime); //methods to manage instances virtual ICharacterInstance* CreateInstance(const char* szFilePath, uint32 nLoadingFlags = 0); virtual void CreateInstanceAsync(const LoadInstanceAsyncResult& callback, const char* szFileName, uint32 nLoadingFlags = 0); ICharacterInstance* CreateCGAInstance(const char* szFilePath, uint32 nLoadingFlags = 0); ICharacterInstance* CreateSKELInstance(const char* szFilePath, uint32 nLoadingFlags); void RegisterInstanceSkel(CDefaultSkeleton* pDefaultSkeleton, CCharInstance* pInstance); void UnregisterInstanceSkel(CDefaultSkeleton* pDefaultSkeleton, CCharInstance* pInstance); void RegisterInstanceSkin(CSkin* pDefaultSkinning, CAttachmentSKIN* pInstance); void UnregisterInstanceSkin(CSkin* pDefaultSkinning, CAttachmentSKIN* pInstance); void RegisterInstanceVCloth(CSkin* pDefaultSkinning, CAttachmentVCLOTH* pInstance); void UnregisterInstanceVCloth(CSkin* pDefaultSkinning, CAttachmentVCLOTH* pInstance); virtual uint32 GetNumInstancesPerModel(const IDefaultSkeleton& rIDefaultSkeleton) const; virtual ICharacterInstance* GetICharInstanceFromModel(const IDefaultSkeleton& rIDefaultSkeleton, uint32 num) const; void GetCharacterInstancesSize(class ICrySizer* pSizer) const; //methods to manage skels and skins virtual IDefaultSkeleton* LoadModelSKELUnsafeManualRef(const char* szFilePath, uint32 nLoadingFlags); virtual _smart_ptr<ISkin> LoadModelSKINAutoRef(const char* szFilePath, uint32 nLoadingFlags); virtual ISkin* LoadModelSKINUnsafeManualRef(const char* szFilePath, uint32 nLoadingFlags); CDefaultSkeleton* FetchModelSKELUnsafeManualRef(const char* szFilePath, uint32 nLoadingFlags); _smart_ptr<CDefaultSkeleton> FetchModelSKELAutoRef(const char* szFilePath, uint32 nLoadingFlags); _smart_ptr<CDefaultSkeleton> FetchModelSKELForGCAAutoRef(const char* szFilePath, uint32 nLoadingFlags); CDefaultSkeleton* FetchModelSKELForGCAUnsafeManualRef(const char* szFilePath, uint32 nLoadingFlags); CSkin* FetchModelSKINUnsafeManualRef(const char* szFilePath, uint32 nLoadingFlags); _smart_ptr<CSkin> FetchModelSKINAutoRef(const char* szFilePath, uint32 nLoadingFlags); void PreloadModelsCDF(); void PreloadModelsCHR(); void PreloadModelsCGA(); void RegisterModelSKEL(CDefaultSkeleton* pModelSKEL, uint32 nLoadingFlags); void RegisterModelSKIN(CSkin* pModelSKIN, uint32 nLoadingFlags); void UnregisterModelSKEL(CDefaultSkeleton* pModelSKEL); void UnregisterModelSKIN(CSkin* pModelSKIN); void SkelExtension(CCharInstance* pCharInstance, const char* pFilepathSKEL, const CharacterDefinition& characterDef, const uint32 nLoadingFlags); uint32 CompatibilityTest(CDefaultSkeleton* pDefaultSkeleton, CSkin* pCSkin); CDefaultSkeleton* CheckIfModelSKELLoadedUnsafeManualRef(const string& strFileName, uint32 nLoadingFlags); _smart_ptr<CDefaultSkeleton> CheckIfModelSKELLoadedAutoRef(const string& strFileName, uint32 nLoadingFlags); CDefaultSkeleton* CreateExtendedSkel(CCharInstance* pCharInstance, CDefaultSkeleton* pDefaultSkeleton, uint64 nExtendedCRC64, const DynArray<const char*>& arrNotMatchingSkins, const uint32 nLoadingFlags); #ifdef EDITOR_PCDEBUGCODE virtual void ClearAllKeepInMemFlags(); #endif CSkin* CheckIfModelSKINLoadedUnsafeManualRef(const string& strFileName, uint32 nLoadingFlags); _smart_ptr<CSkin> CheckIfModelSKINLoadedAutoRef(const string& strFileName, uint32 nLoadingFlags); CDefaultSkeleton* CheckIfModelExtSKELCreated (const uint64 nCRC64, uint32 nLoadingFlags); void UpdateStreaming_SKEL(std::vector<CDefaultSkeletonReferences>& skels, uint32 nRenderFrameId, const uint32* nRoundIds); void UpdateStreaming_SKIN(std::vector<CDefaultSkinningReferences>& skins, uint32 nRenderFrameId, const uint32* nRoundIds); virtual bool LoadAndLockResources(const char* szFilePath, uint32 nLoadingFlags); virtual void StreamKeepCharacterResourcesResident(const char* szFilePath, int nLod, bool bKeep, bool bUrgent = false); virtual bool StreamHasCharacterResources(const char* szFilePath, int nLod); virtual void GetLoadedModels(IDefaultSkeleton** pIDefaultSkeleton, uint32& nCount) const; virtual void ReloadAllModels(); virtual void ReloadAllCHRPARAMS(); virtual void PreloadLevelModels(); void GetModelCacheSize() const; void DebugModelCache(uint32 printtxt, std::vector<CDefaultSkeletonReferences>& parrModelCache, std::vector<CDefaultSkinningReferences>& parrModelCacheSKIN); virtual void ClearResources(bool bForceCleanup); //! Cleans up all resources - currently deletes all bodies and characters (even if there are references on them) void CleanupModelCache(bool bForceCleanup); // deletes all registered bodies; the character instances got deleted even if they're still referenced void GetStatistics(Statistics& rStats) const; // returns statistics about this instance of character animation manager don't call this too frequently void GetMemoryUsage(ICrySizer* pSizer) const; //puts the size of the whole subsystem into this sizer object, classified, according to the flags set in the sizer void TrackMemoryOfModels(); std::vector<CDefaultSkeletonReferences> m_arrModelCacheSKEL; std::vector<CDefaultSkinningReferences> m_arrModelCacheSKIN; uint32 GetCDFIdUnsafe(const AZStd::string& pathname); #if BLENDSPACE_VISUALIZATION void CreateDebugInstances(const char* szCharacterFileName); void DeleteDebugInstances(); void RenderDebugInstances(const SRenderingPassInfo& passInfo); void RenderBlendSpace(const SRenderingPassInfo& passInfo, ICharacterInstance* pCharacterInstance, float fCharacterScale, unsigned int flags); bool HasAnyDebugInstancesCreated() const; bool HasDebugInstancesCreated(const char* szCharacterFileName) const; DynArray<DebugInstances> m_arrCharacterBase; #endif #ifdef EDITOR_PCDEBUGCODE virtual void GetMotionParameterDetails(SMotionParameterDetails& outDetails, EMotionParamID paramId) const; virtual bool InjectCDF(const char* pathname, const char* content, size_t contentLength); virtual void ClearCDFCache() { m_arrCacheForCDF.clear(); m_pendingCDFLoads.clear(); } //deactivate the cache in Editor-Mode, or we can't load the same CDF after we changed & saved it virtual void InjectBSPACE(const char* pathname, const char* content, size_t contentLength); virtual void ClearBSPACECache(); std::vector<CDefaultSkeletonReferences> m_arrModelCacheSKEL_CharEdit; std::vector<CDefaultSkinningReferences> m_arrModelCacheSKIN_CharEdit; #endif void ReloadQueuedAssets(); bool IsAnimationCurrentlyBeingUsedByAnimationSet(int globalAnimationID); void LoadAnimationToValidSets(const AZStd::string& assetPath); void RemoveAnimationFromValidSets(const AZStd::string& assetPath); bool LiveReloadSkin(const AZStd::string& assetPath, std::vector<CDefaultSkinningReferences>& modelCacheSKIN, uint32 loadingFlags); bool CreateAndBindNewSkin(const AZStd::string& assetPath, DynArray<CAttachmentSKIN*>& attachmentInstances, uint32 loadingFlags, bool keepInMemory); bool RemoveSkin(const AZStd::string& assetPath, DynArray<CAttachmentSKIN*>& attachmentInstances); bool LiveReloadSkeletonSet(const AZStd::string& assetPath, std::vector<CDefaultSkeletonReferences>& modelCacheSkeleton, uint32 loadingFlags); bool UpdateSkeletonCharacterInstances(_smart_ptr<CDefaultSkeleton> baseDefaultSkeleton, std::vector<CDefaultSkeletonReferences>& skeletonList, const uint32 nLoadingFlags); void ReloadCharacterInstanceSkeleton(CCharInstance* pCharInstance, _smart_ptr<CDefaultSkeleton> pDefaultSkeleton, const uint32 nLoadingFlags); bool LiveReloadSkeletonSetCHRPARAMS(const AZStd::string& assetPath, std::vector<CDefaultSkeletonReferences>& skeletonList, uint32 loadingFlags); void ReloadSkeletonCHRPARAMS(const AZStd::string& assetPath, CDefaultSkeletonReferences* skeletonReferences, const uint32 nLoadingFlags); AZStd::set<AZStd::string> m_queuedReloadSet; static const float s_reloadTimeout; float m_reloadRequestTimeStamp; CAnimationManager m_AnimationManager; CFacialAnimation* m_pFacialAnimation; CParamLoader m_ParamLoader; std::vector<f32> m_arrFrameTimes; AZStd::vector<AZStd::unique_ptr<CharacterDefinition>> m_arrCacheForCDF; struct PendingLoad { PendingLoad() {} PendingLoad(AZStd::unique_ptr<ManualResetEvent> manualResetEvent) : m_refCount(0) , m_resetEvent(std::move(manualResetEvent)) { } PendingLoad(const PendingLoad&) = delete; PendingLoad(PendingLoad&& rhs) : m_refCount(rhs.m_refCount) , m_resetEvent(AZStd::move(rhs.m_resetEvent)) { rhs.m_refCount = 0; } int m_refCount; AZStd::unique_ptr<ManualResetEvent> m_resetEvent; }; AZStd::unordered_map<AZStd::string, PendingLoad> m_pendingCDFLoads; std::vector<ICharacterInstance*> m_AnimationSyncQueue; // queue to remember all animations which were started and need a sync void ProcessAsyncLoadRequests(); AZStd::mutex m_asyncLoadQueueLock; // Lock for m_asyncLoadRequests std::queue<InstanceAsyncLoadRequest> m_asyncLoadRequests; uint64 m_nFrameTicks; // number of ticks spend in animations function during the last frame uint64 m_nFrameSyncTicks; // number of ticks spend in animations sync function during the last frame uint32 m_nActiveCharactersLastFrame; // number of characters for which ForwardKinematics was called uint32 m_nStreamUpdateRoundId[MAX_STREAM_PREDICTION_ZONES]; uint32 m_lastDatabaseUnloadTimeStamp; // geometry data cache for VCloth SClothGeometry* LoadVClothGeometry(const CAttachmentVCLOTH& pRendAtt, _smart_ptr<IRenderMesh> pRenderMeshes[]); typedef std::map<uint64, SClothGeometry> TClothGeomCache; TClothGeomCache m_clothGeometries; AZStd::unordered_set<CDefaultSkeleton*> m_defaultSkelGarbageSet; AZStd::unordered_set<CSkin*> m_skinGarbageSet; mutable AZStd::mutex m_cdfCacheMutex; //! Must be taken before m_garbageMutex if taking both mutable AZStd::recursive_mutex m_cacheSkinMutex; //! Must be taken before m_garbageMutex if taking both mutable AZStd::recursive_mutex m_cacheSkelMutex; //! Must be taken after m_cacheSkelMutex/m_cacheSkinMutex if taking both mutable AZStd::mutex m_garbageMutex; }; #endif // CRYINCLUDE_CRYANIMATION_CHARACTERMANAGER_H