// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Misc/DisplayClusterTickableGameObject.h" #include "Misc/SecureHash.h" /** * Cache template for named data with automatic deletion of unused items after timeout. */ template class TDisplayClusterDataCache { public: /** Data type of stored objects * * The following methods must be implemented in DataType: * * // Return DataCache timeout in frames. A negative value disables the timeout. * static int32 GetDataCacheTimeOutInFrames(); * * // Return true if DataCache is enabled. * static bool IsDataCacheEnabled(); * * // Method for releasing a cached data item, called before its destructor * void ReleaseDataCacheItem(); * * // Returns the unique name of this texture for DataCache. * inline const FString& GetDataCacheName() const; */ typedef InDataType DataType; virtual ~TDisplayClusterDataCache() { Release(); } /** Helper to perform the very common case of hashing an FString into a hex representation. */ static inline FString HashString(const FString& InString) { return FMD5::HashAnsiString(*InString.ToLower()); } /** Retrieve an existing object by a unique name. */ TSharedPtr Find(const FString& InUniqueDataName) { if (DataType::IsDataCacheEnabled()) { if (FCachedObject* ExistCachedObject = CachedObjects.FindByPredicate([InUniqueDataName](const FCachedObject& CachedObjectIt) { return CachedObjectIt.IsNameEqual(InUniqueDataName); })) { return ExistCachedObject->DataRef; } } return nullptr; } /** Register a new object. */ void Add(const TSharedPtr& InDataRef) { if (DataType::IsDataCacheEnabled()) { CachedObjects.Add(FCachedObject(InDataRef)); UpdateTickableGameObject(); } } protected: /** * Release the cache */ void Release() { CachedObjects.Empty(); UpdateTickableGameObject(); } /** * When the number of DataRef references drops to 1, these items will be removed after a timeout. */ void Tick(float DeltaTime) { // If the cache is disabled, clear it if (!DataType::IsDataCacheEnabled()) { Release(); return; } const int32 TimeOutInFrames = DataType::GetDataCacheTimeOutInFrames(); // Updating the time for all objects in use for (int32 Index = 0; Index < CachedObjects.Num(); Index++) { if (CachedObjects[Index].DataRef.GetSharedReferenceCount() >1 || TimeOutInFrames < 0) { // When this object used, reset this counter CachedObjects[Index].FramesInCachedState = 0; } else { // After the data ref counter remains 1, this counter will be incremented every frame CachedObjects[Index].FramesInCachedState++; // Remove after timout if (CachedObjects[Index].FramesInCachedState > TimeOutInFrames) { // Delete the current element, and reuse the current array index, // because the next element will move to the index of the current CachedObjects.RemoveAt(Index--); } } } UpdateTickableGameObject(); } /** Create or remove tickable game object. */ inline void UpdateTickableGameObject() { if (CachedObjects.IsEmpty()) { // Unregister tick event if (TickableGameObject.IsValid() && TickHandle.IsValid()) { TickableGameObject->OnTick().Remove(TickHandle); } TickableGameObject.Reset(); } else { if (!TickableGameObject.IsValid()) { TickableGameObject = MakeUnique(); TickHandle = TickableGameObject->OnTick().AddRaw(this, &TDisplayClusterDataCache::Tick); } } } private: struct FCachedObject { FCachedObject(const TSharedPtr& InDataRef) : DataRef(InDataRef) { } ~FCachedObject() { Release(); } /** Release referenced data. */ void Release() { if (DataRef.IsValid()) { // Calls the special release function before the destructor DataRef->ReleaseDataCacheItem(); DataRef.Reset(); } FramesInCachedState = 0; } inline bool IsNameEqual(const FString& InName) const { return DataRef.IsValid() && DataRef->GetDataCacheName() == InName; } // reference to data TSharedPtr DataRef; // After the data ref counter remains 1, this counter will be incremented every frame int32 FramesInCachedState = 0; }; // Cached objects map TArray CachedObjects; // When CachedObjects is not empty, this ticking object will be created. // Also, this object will be deleted when CachedObjects becomes empty. TUniquePtr TickableGameObject; // Event delegate container of the Tickable object FDelegateHandle TickHandle; };