// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #pragma once #include "DDCStatsHelper.h" /** * A backend wrapper that implements a cache hierarchy of backends. **/ class FHierarchicalDerivedDataBackend : public FDerivedDataBackendInterface { public: /** * Constructor * @param InInnerBackends Backends to call into for actual storage of the cache, first item is the "fastest cache" */ FHierarchicalDerivedDataBackend(const TArray& InInnerBackends) : InnerBackends(InInnerBackends) , bIsWritable(false) { check(InnerBackends.Num() > 1); // if it is just one, then you don't need this wrapper for (int32 CacheIndex = 0; CacheIndex < InnerBackends.Num(); CacheIndex++) { if (InnerBackends[CacheIndex]->IsWritable()) { bIsWritable = true; } } if (bIsWritable) { for (int32 CacheIndex = 0; CacheIndex < InnerBackends.Num(); CacheIndex++) { // async puts to allow us to fill all levels without holding up the engine new (AsyncPutInnerBackends) TScopedPointer(new FDerivedDataBackendAsyncPutWrapper(InnerBackends[CacheIndex], false)); } } } /** Adds inner backend. */ void AddInnerBackend(FDerivedDataBackendInterface* InInner) { InnerBackends.Add(InInner); } /** Removes inner backend. */ bool RemoveInnerBackend(FDerivedDataBackendInterface* InInner) { const int32 NumRemoved = InnerBackends.Remove(InInner); return NumRemoved != 0; } /** return true if this cache is writable **/ virtual bool IsWritable() override { return bIsWritable; } /** * Synchronous test for the existence of a cache item * * @param CacheKey Alphanumeric+underscore key of this cache item * @return true if the data probably will be found, this can't be guaranteed because of concurrency in the backends, corruption, etc */ virtual bool CachedDataProbablyExists(const TCHAR* CacheKey) override { static FName NAME_CachedDataProbablyExists(TEXT("CachedDataProbablyExists")); FDDCScopeStatHelper Stat(CacheKey, NAME_CachedDataProbablyExists); const static FName NAME_HierarchicalDDC = FName(TEXT("HierarchicalDDC")); Stat.AddTag(NAME_HierarchicalDDC, FString()); for (int32 CacheIndex = 0; CacheIndex < InnerBackends.Num(); CacheIndex++) { if (InnerBackends[CacheIndex]->CachedDataProbablyExists(CacheKey)) { return true; } } return false; } /** * Synchronous retrieve of a cache item * * @param CacheKey Alphanumeric+underscore key of this cache item * @param OutData Buffer to receive the results, if any were found * @return true if any data was found, and in this case OutData is non-empty */ virtual bool GetCachedData(const TCHAR* CacheKey, TArray& OutData, FCacheStatRecord* Stats) override { const static FName NAME_GetCachedData = FName(TEXT("GetCachedData")); const static FName NAME_HierarchicalDDC = FName(TEXT("HierarchicalDDC")); const static FName NAME_DataSize = FName(TEXT("DataSize")); static FName NAME_Retrieved(TEXT("Retrieved")); FDDCScopeStatHelper Stat(CacheKey, NAME_GetCachedData); Stat.AddTag(NAME_HierarchicalDDC, FString()); Stats->StartTime = FPlatformTime::Seconds(); for (int32 CacheIndex = 0; CacheIndex < InnerBackends.Num(); CacheIndex++) { if (InnerBackends[CacheIndex]->CachedDataProbablyExists(CacheKey) && InnerBackends[CacheIndex]->GetCachedData(CacheKey, OutData, Stats)) { Stats->GetDuration = FPlatformTime::Seconds() - Stats->StartTime; Stats->DataSize = OutData.Num(); double StartTime = FPlatformTime::Seconds(); if (bIsWritable) { // fill in the higher level caches for (int32 PutCacheIndex = CacheIndex - 1; PutCacheIndex >= 0; PutCacheIndex--) { if (InnerBackends[PutCacheIndex]->IsWritable()) { if (InnerBackends[PutCacheIndex]->BackfillLowerCacheLevels() && InnerBackends[PutCacheIndex]->CachedDataProbablyExists(CacheKey)) { InnerBackends[PutCacheIndex]->RemoveCachedData(CacheKey, /*bTransient=*/ false); // it apparently failed, so lets delete what is there AsyncPutInnerBackends[PutCacheIndex]->PutCachedData(CacheKey, OutData, true, Stats); // we force a put here because it must have failed Stats->CacheGet++; } else { AsyncPutInnerBackends[PutCacheIndex]->PutCachedData(CacheKey, OutData, false, Stats); Stats->CacheGet++; } } } if (InnerBackends[CacheIndex]->BackfillLowerCacheLevels()) { // fill in the lower level caches for (int32 PutCacheIndex = CacheIndex + 1; PutCacheIndex < AsyncPutInnerBackends.Num(); PutCacheIndex++) { if (!InnerBackends[PutCacheIndex]->IsWritable() && !InnerBackends[PutCacheIndex]->BackfillLowerCacheLevels() && InnerBackends[PutCacheIndex]->CachedDataProbablyExists(CacheKey)) { break; //do not write things that are already in the read only pak file } if (InnerBackends[PutCacheIndex]->IsWritable()) { AsyncPutInnerBackends[PutCacheIndex]->PutCachedData(CacheKey, OutData, false, Stats); // we do not need to force a put here Stats->CacheGet++; } } } } Stat.AddTag(NAME_DataSize, FString::Printf(TEXT("%d bytes"), OutData.Num())); Stat.AddTag(NAME_Retrieved, TEXT("true")); Stats->EndTime = FPlatformTime::Seconds(); Stats->PutDuration = Stats->EndTime - StartTime; return true; } } Stats->EndTime = FPlatformTime::Seconds(); Stats->GetDuration = FPlatformTime::Seconds() - Stats->StartTime; Stats->PutDuration = 0.; Stat.AddTag(NAME_Retrieved, TEXT("false")); return false; } /** * Asynchronous, fire-and-forget placement of a cache item * * @param CacheKey Alphanumeric+underscore key of this cache item * @param InData Buffer containing the data to cache, can be destroyed after the call returns, immediately * @param bPutEvenIfExists If true, then do not attempt skip the put even if CachedDataProbablyExists returns true */ virtual void PutCachedData(const TCHAR* CacheKey, TArray& InData, bool bPutEvenIfExists, FCacheStatRecord* Stats) override { const static FName NAME_PutCachedData = FName(TEXT("PutCachedData")); const static FName NAME_HierarchicalDDC = FName(TEXT("HierarchicalDDC")); const static FName NAME_DataSize = FName(TEXT("DataSize")); FDDCScopeStatHelper Stat(CacheKey, NAME_PutCachedData); Stat.AddTag(NAME_HierarchicalDDC, FString()); Stat.AddTag(NAME_DataSize, FString::Printf(TEXT("%d bytes"), InData.Num())); if (!bIsWritable) { return; // no point in continuing down the chain } bool bSynchronousPutPeformed = false; // we must do at least one synchronous put to a writable cache before we return for (int32 PutCacheIndex = 0; PutCacheIndex < InnerBackends.Num(); PutCacheIndex++) { if (!InnerBackends[PutCacheIndex]->IsWritable() && !InnerBackends[PutCacheIndex]->BackfillLowerCacheLevels() && InnerBackends[PutCacheIndex]->CachedDataProbablyExists(CacheKey)) { break; //do not write things that are already in the read only pak file } if (InnerBackends[PutCacheIndex]->IsWritable()) { if (!bSynchronousPutPeformed) { InnerBackends[PutCacheIndex]->PutCachedData(CacheKey, InData, bPutEvenIfExists, Stats); bSynchronousPutPeformed = true; } else { AsyncPutInnerBackends[PutCacheIndex]->PutCachedData(CacheKey, InData, bPutEvenIfExists, Stats); } } } } virtual void RemoveCachedData(const TCHAR* CacheKey, bool bTransient) override { if (!bIsWritable) { return; // no point in continuing down the chain } for (int32 PutCacheIndex = 0; PutCacheIndex < InnerBackends.Num(); PutCacheIndex++) { InnerBackends[PutCacheIndex]->RemoveCachedData(CacheKey, bTransient); } } private: /** Array of backends forming the hierarchical cache...the first element is the fastest cache. **/ TArray InnerBackends; /** Each of the backends wrapped with an async put **/ TArray > AsyncPutInnerBackends; /** As an optimization, we check our writable status at contruction **/ bool bIsWritable; };