Files
UnrealEngineUWP/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCache.cpp
Josh Adams 660ab7f0c9 Copying //UE4/Dev-Platform to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2719147 on 2015/10/07 by Mark.Satterthwaite

	Allow the shader cache to perform some precompilation synchronously on load before falling back to asynchronous compilation to balance load times against total time spent precompiling. Added a stat to the group that reports how long the precompile has been running until it completes so it is easier to track.

Change 2719182 on 2015/10/07 by Mark.Satterthwaite

	Refactor the ShaderCache's internal data structures and change the way we handle recording whether a particular predraw state has been submitted to try and make it more efficient.

Change 2719185 on 2015/10/07 by Mark.Satterthwaite

	Merging CL #2717701: Try and fix random crashes on Mac when manipulating bound-shader-states caused by ShaderCache potentially providing a bogus shader state pointer on exit from predraw.

Change 2719434 on 2015/10/07 by Mark.Satterthwaite

	Make sure that Mac ensures reports have a source context and a sane callstack when sent to the crash-reports server.

Change 2724764 on 2015/10/12 by Josh.Adams

	[Initial AppleTV support]
	Merging	//depot/YakBranch/... to //UE4/Dev-Platform/...

Change 2726266 on 2015/10/13 by Lee.Clark

	PS4 - Calc reserve size required for DMA copy when using unsafe command buffers

Change 2726401 on 2015/10/13 by Mark.Satterthwaite

	Merging CL #2716418: Fix UE-15228 'Crash Report Client doesn't restart into project editor on Mac' by reporting the original command line supplied by LaunchMac, not the modified one that strips the project name. The CRC can then relaunch as expected.
	#jira UE-15228

Change 2726421 on 2015/10/13 by Lee.Clark

	PS4 - Don't try to clear invalid targets

Change 2727040 on 2015/10/13 by Michael.Trepka

	Merging CL 2724777 - Fixed splash screen rendering for images with DPI different than 72

Change 2729783 on 2015/10/15 by Keith.Judge

	Fix huge memory leak in Test/Shipping configurations, caused because I am a numpty.

Change 2729847 on 2015/10/15 by Mark.Satterthwaite

	Merging CL #2729846: On OS X unconstrain windows from the dimension of the parent display when in Windowed mode - it is OK for them to be larger in this case. They do need to be repositioned if on the Primary display so that they don't creep under the menu bar and become unmovable/unclosable and Fullscreen windows still need to be constrained to a single display. We can now take screenshots of windows that are larger than the display & not get grey bars beyond the cutoff.
	#jira UE-21992

Change 2729865 on 2015/10/15 by Keith.Judge

	Fast semantics - Finish up resource transitions, adding resource decompression where appropriate and using non-fast clears where we can't determine the resource transition.

Change 2729897 on 2015/10/15 by Keith.Judge

	Fast Semantics - Make sure all GetData() calls are made safe with GPU fences.

Change 2729972 on 2015/10/15 by Keith.Judge

	Removed the last vestiges of ID3D11DeviceContext/ID3D11DeviceContext1 from the Xbox RHI. Everything now uses ID3D11DeviceContextX directly.

	This should be marginally quicker as it stops a double call to ClearState().

Change 2731503 on 2015/10/16 by Keith.Judge

	Added _XDK_VERSION to the DDC key for textures, which should solve the issue of the tiling mode changing in August XDK (and future changes Microsoft may inflict).

Change 2731596 on 2015/10/16 by Keith.Judge

	Fast Semantics - Add deferred resource deletion queue to make deleted resources be actually deleted a number of frames later so that the GPU is definitely finished with them. Hooked up the temporary SRVs for dynamic VBs as a first step.

Change 2731928 on 2015/10/16 by Michael.Trepka

	PR #1659: Mac/Build.sh handles additional arguments (Contributed by judgeaxl)

Change 2731934 on 2015/10/16 by Michael.Trepka

	PR #1618: added clang 3.7.0 -Wshift-negative-value ignore in JpegImageWrapper.cpp (Contributed by bsekura)

Change 2732018 on 2015/10/16 by Mark.Satterthwaite

	Emit a shader code cache for each platforms requested shader formats, this is separate to the targeted formats as not all can or need to be cached.
	- The implementation extends the ShaderCache's hooks in FShaderResource's serialisation function to capture the required shaders.
	- Each target platform has its own list of cached shader formats, analogous to the list of targeted RHIs. Presently only the Mac implements this.
	- Code cached shaders are now compressed (for size) to reduce the overhead associated with keeping all the shader code around - this works esp. well for text-based formats like GLSL.

Change 2732365 on 2015/10/16 by Josh.Adams

	- Packaging a TVOS .ipa now works (still haven't tried any of the Editor integration like Launch On)

Change 2733170 on 2015/10/18 by Terence.Burns

	Fix for Android IAP query not returning entire inventory.

Change 2733174 on 2015/10/18 by Terence.Burns

	Fix Movie player issue where wait for movie to finish isnt being respected.

	Seems a stray bUserCanceled event flag was causing this not to be observed.

	Added some verbose logging to apple movie player.

Change 2733488 on 2015/10/19 by Mark.Satterthwaite

	Added the ability to merge the .ushadercache files used by the ShaderCache to store shader & draw state information.
	- Fixed a bug that would cause invalid shader membership and draw state information to be logged.
	- Added a separate command-line tool to merge shader cache files, currently Mac-only but in theory should work on other platforms too.

Change 2735226 on 2015/10/20 by Mark.Satterthwaite

	Fix temporal AA rendering on GL/Mac OS X - you can't rely on EyeAdaptation values unless SM5 is available so only perform that code on SM5 & we must correctly clamp saturate(NaN) to 0 as the current hlslcc won't do that for us (& is required by the HLSL spec). The latter used to be clamped in the AA_ALPHA && AA_VELOCITY_WEIGHTING code block that was removed recently.
	#jira UE-21214
	#jira UE-19913

Change 2736722 on 2015/10/21 by Daniel.Lamb

	Improved performance of cooking stats system.

Change 2737172 on 2015/10/21 by Daniel.Lamb

	Improved cooking stats performance for ddc stats.
2015-12-10 16:56:55 -05:00

951 lines
27 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "Core.h"
#include "ModuleManager.h"
#include "DerivedDataCacheInterface.h"
#include "DerivedDataBackendInterface.h"
#include "DerivedDataPluginInterface.h"
#include "DDCCleanup.h"
DEFINE_STAT(STAT_DDC_NumGets);
DEFINE_STAT(STAT_DDC_NumPuts);
DEFINE_STAT(STAT_DDC_NumBuilds);
DEFINE_STAT(STAT_DDC_NumExist);
DEFINE_STAT(STAT_DDC_SyncGetTime);
DEFINE_STAT(STAT_DDC_ASyncWaitTime);
DEFINE_STAT(STAT_DDC_PutTime);
DEFINE_STAT(STAT_DDC_SyncBuildTime);
DEFINE_STAT(STAT_DDC_ExistTime);
/**
* Implementation of the derived data cache
* This API is fully threadsafe
**/
class FDerivedDataCache : public FDerivedDataCacheInterface
{
/**
* Async worker that checks the cache backend and if that fails, calls the deriver to build the data and then puts the results to the cache
**/
friend class FBuildAsyncWorker;
class FBuildAsyncWorker : public FNonAbandonableTask
{
public:
/**
* Constructor for async task
* @param InDataDeriver plugin to produce cache key and in the event of a miss, return the data.
* @param InCacheKey Complete cache key for this data.
**/
FBuildAsyncWorker(FDerivedDataPluginInterface* InDataDeriver, const TCHAR* InCacheKey, bool bInSynchronousForStats)
: bSuccess(false)
, bSynchronousForStats(bInSynchronousForStats)
, DataDeriver(InDataDeriver)
, CacheKey(InCacheKey)
{
}
/** Async worker that checks the cache backend and if that fails, calls the deriver to build the data and then puts the results to the cache **/
void DoWork()
{
bool bGetResult;
{
INC_DWORD_STAT(STAT_DDC_NumGets);
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
bGetResult = FDerivedDataBackend::Get().GetRoot().GetCachedData(*CacheKey, Data);
}
INC_FLOAT_STAT_BY(STAT_DDC_SyncGetTime, bSynchronousForStats ? (float)ThisTime : 0.0f);
}
if (bGetResult)
{
check(Data.Num());
bSuccess = true;
delete DataDeriver;
DataDeriver = NULL;
}
else if (DataDeriver)
{
{
INC_DWORD_STAT(STAT_DDC_NumBuilds);
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
bSuccess = DataDeriver->Build(Data);
}
INC_FLOAT_STAT_BY(STAT_DDC_SyncBuildTime, bSynchronousForStats ? (float)ThisTime : 0.0f);
}
delete DataDeriver;
DataDeriver = NULL;
if (bSuccess)
{
check(Data.Num());
INC_DWORD_STAT(STAT_DDC_NumPuts);
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
FDerivedDataBackend::Get().GetRoot().PutCachedData(*CacheKey, Data, true);
}
INC_FLOAT_STAT_BY(STAT_DDC_PutTime, bSynchronousForStats ? (float)ThisTime : 0.0f);
}
}
if (!bSuccess)
{
Data.Empty();
}
FDerivedDataBackend::Get().AddToAsyncCompletionCounter(-1);
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FBuildAsyncWorker, STATGROUP_ThreadPoolAsyncTasks);
}
/** true in the case of a cache hit, otherwise the result of the deriver build call **/
bool bSuccess;
/** true if we should record the timing **/
bool bSynchronousForStats;
/** Data dervier we are operating on **/
FDerivedDataPluginInterface* DataDeriver;
/** Cache key associated with this build **/
FString CacheKey;
/** Data to return to caller, later **/
TArray<uint8> Data;
};
public:
/** Constructor, called once to cereate a singleton **/
FDerivedDataCache()
: CurrentHandle(19248) // we will skip some potential handles to catch errors
{
FDerivedDataBackend::Get(); // we need to make sure this starts before we all us to start
}
/** Destructor, flushes all sync tasks **/
~FDerivedDataCache()
{
FScopeLock ScopeLock(&SynchronizationObject);
for (TMap<uint32,FAsyncTask<FBuildAsyncWorker>*>::TIterator It(PendingTasks); It; ++It)
{
It.Value()->EnsureCompletion();
delete It.Value();
}
PendingTasks.Empty();
}
virtual bool GetSynchronous(FDerivedDataPluginInterface* DataDeriver, TArray<uint8>& OutData) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetSynchronous);
check(DataDeriver);
FString CacheKey = FDerivedDataCache::BuildCacheKey(DataDeriver);
UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetSynchronous %s"), *CacheKey);
FAsyncTask<FBuildAsyncWorker> PendingTask(DataDeriver, *CacheKey, true);
AddToAsyncCompletionCounter(1);
PendingTask.StartSynchronousTask();
OutData = PendingTask.GetTask().Data;
return PendingTask.GetTask().bSuccess;
}
virtual uint32 GetAsynchronous(FDerivedDataPluginInterface* DataDeriver) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronous);
FScopeLock ScopeLock(&SynchronizationObject);
uint32 Handle = NextHandle();
FString CacheKey = FDerivedDataCache::BuildCacheKey(DataDeriver);
UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetAsynchronous %s"), *CacheKey);
bool bSync = !DataDeriver->IsBuildThreadsafe();
FAsyncTask<FBuildAsyncWorker>* AsyncTask = new FAsyncTask<FBuildAsyncWorker>(DataDeriver, *CacheKey, bSync);
check(!PendingTasks.Contains(Handle));
PendingTasks.Add(Handle,AsyncTask);
AddToAsyncCompletionCounter(1);
if (!bSync)
{
AsyncTask->StartBackgroundTask();
}
else
{
AsyncTask->StartSynchronousTask();
}
// Must return a valid handle
check(Handle != 0);
return Handle;
}
virtual bool PollAsynchronousCompletion(uint32 Handle) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_PollAsynchronousCompletion);
FAsyncTask<FBuildAsyncWorker>* AsyncTask = NULL;
{
FScopeLock ScopeLock(&SynchronizationObject);
AsyncTask = PendingTasks.FindRef(Handle);
}
check(AsyncTask);
return AsyncTask->IsDone();
}
virtual void WaitAsynchronousCompletion(uint32 Handle) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_WaitAsynchronousCompletion);
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
FAsyncTask<FBuildAsyncWorker>* AsyncTask = NULL;
{
FScopeLock ScopeLock(&SynchronizationObject);
AsyncTask = PendingTasks.FindRef(Handle);
}
check(AsyncTask);
AsyncTask->EnsureCompletion();
}
INC_FLOAT_STAT_BY(STAT_DDC_ASyncWaitTime,(float)ThisTime);
}
virtual bool GetAsynchronousResults(uint32 Handle, TArray<uint8>& OutData) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronousResults);
FAsyncTask<FBuildAsyncWorker>* AsyncTask = NULL;
{
FScopeLock ScopeLock(&SynchronizationObject);
PendingTasks.RemoveAndCopyValue(Handle,AsyncTask);
}
check(AsyncTask);
if (!AsyncTask->GetTask().bSuccess)
{
delete AsyncTask;
return false;
}
OutData = AsyncTask->GetTask().Data;
delete AsyncTask;
check(OutData.Num());
return true;
}
virtual IDerivedDataRollup* StartRollup() override
{
return NULL;
}
virtual void EndRollup(IDerivedDataRollup*& Rollup) override
{
check(!Rollup); // this needs to be handled by someone else, if rollups are disabled, then it should be NULL
}
virtual bool GetSynchronous(const TCHAR* CacheKey, TArray<uint8>& OutData) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetSynchronous_Data);
UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetSynchronous %s"), CacheKey);
FAsyncTask<FBuildAsyncWorker> PendingTask((FDerivedDataPluginInterface*)NULL, CacheKey, true);
AddToAsyncCompletionCounter(1);
PendingTask.StartSynchronousTask();
OutData = PendingTask.GetTask().Data;
return PendingTask.GetTask().bSuccess;
}
virtual uint32 GetAsynchronous(const TCHAR* CacheKey, IDerivedDataRollup* Rollup = NULL) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronous_Handle);
check(!Rollup); // this needs to be handled by someone else, if rollups are disabled, then it should be NULL
FScopeLock ScopeLock(&SynchronizationObject);
UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetAsynchronous %s"), CacheKey);
uint32 Handle = NextHandle();
FAsyncTask<FBuildAsyncWorker>* AsyncTask = new FAsyncTask<FBuildAsyncWorker>((FDerivedDataPluginInterface*)NULL, CacheKey, false);
check(!PendingTasks.Contains(Handle));
PendingTasks.Add(Handle, AsyncTask);
AddToAsyncCompletionCounter(1);
AsyncTask->StartBackgroundTask();
return Handle;
}
/**
* Starts the async process of checking the cache and if the item is present, retrieving the cached results (version for internal use by rollups)
* @param CacheKey Key to identify the data
* @param Rollup Rollup pointer, if this is part of a rollup
* @param Handle The handle that can be used for PollAsynchronousCompletion, WaitAsynchronousCompletion and GetAsynchronousResults
**/
void GetAsynchronousForRollup(const TCHAR* CacheKey, uint32 Handle)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronousForRollup);
FScopeLock ScopeLock(&SynchronizationObject);
UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetAsynchronous(handle) %s"), CacheKey);
FAsyncTask<FBuildAsyncWorker>* AsyncTask = new FAsyncTask<FBuildAsyncWorker>((FDerivedDataPluginInterface*)NULL, CacheKey, false);
check(!PendingTasks.Contains(Handle));
PendingTasks.Add(Handle,AsyncTask);
AddToAsyncCompletionCounter(1);
AsyncTask->StartBackgroundTask();
}
virtual void Put(const TCHAR* CacheKey, TArray<uint8>& Data, bool bPutEvenIfExists = false) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_Put);
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
FDerivedDataBackend::Get().GetRoot().PutCachedData(CacheKey, Data, bPutEvenIfExists);
}
INC_FLOAT_STAT_BY(STAT_DDC_PutTime,(float)ThisTime);
INC_DWORD_STAT(STAT_DDC_NumPuts);
}
virtual void MarkTransient(const TCHAR* CacheKey) override
{
FDerivedDataBackend::Get().GetRoot().RemoveCachedData(CacheKey, /*bTransient=*/ true);
}
virtual bool CachedDataProbablyExists(const TCHAR* CacheKey) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_CachedDataProbablyExists);
bool bResult;
INC_DWORD_STAT(STAT_DDC_NumExist);
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
bResult = FDerivedDataBackend::Get().GetRoot().CachedDataProbablyExists(CacheKey);
}
INC_FLOAT_STAT_BY(STAT_DDC_ExistTime, (float)ThisTime);
return bResult;
}
void NotifyBootComplete() override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_NotifyBootComplete);
FDerivedDataBackend::Get().NotifyBootComplete();
}
void AddToAsyncCompletionCounter(int32 Addend) override
{
FDerivedDataBackend::Get().AddToAsyncCompletionCounter(Addend);
}
void WaitForQuiescence(bool bShutdown) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_WaitForQuiescence);
FDerivedDataBackend::Get().WaitForQuiescence(bShutdown);
}
void GetDirectories(TArray<FString>& OutResults) override
{
FDerivedDataBackend::Get().GetDirectories(OutResults);
}
/** Called at ShutdownModule() time to print out status before we're cleaned up */
virtual void PrintLeaks()
{
// Used by derived classes to spit out leaked pending rollups
}
protected:
uint32 NextHandle()
{
return (uint32)CurrentHandle.Increment();
}
private:
/**
* Internal function to build a cache key out of the plugin name, versions and plugin specific info
* @param DataDeriver plugin to produce the elements of the cache key.
* @return Assembled cache key
**/
static FString BuildCacheKey(FDerivedDataPluginInterface* DataDeriver)
{
FString Result = FDerivedDataCacheInterface::BuildCacheKey(DataDeriver->GetPluginName(), DataDeriver->GetVersionString(), *DataDeriver->GetPluginSpecificCacheKeySuffix());
return Result;
}
/** Counter used to produce unique handles **/
FThreadSafeCounter CurrentHandle;
/** Object used for synchronization via a scoped lock **/
FCriticalSection SynchronizationObject;
/** Map of handle to pending task **/
TMap<uint32,FAsyncTask<FBuildAsyncWorker>*> PendingTasks;
};
//Forward reference
FDerivedDataCache& InternalSingleton();
namespace EPhase
{
enum Type
{
Adding,
AsyncRollupGet,
AsyncRollupGetSucceed,
AsyncRollupGetFailed_GettingItemsAndWaitingForPuts,
Done,
};
}
/**
* Opaque class for rollup handling
**/
class FDerivedDataRollup : public IDerivedDataRollup
{
/** Magic numbers to verify integrity and check endianness **/
enum
{
MAGIC=0x9E1B83C1,
MAGIC_SWAPPED=0xC1831B9E
};
/** Helper structure for an element of a rollup **/
struct FRollupItem
{
FRollupItem(const FString& InCacheKey, uint32 InAsyncHandle)
: CacheKey(InCacheKey)
, AsyncHandle(InAsyncHandle)
, FinishedFromThePerspectiveOfTheCaller(false)
{
}
/** Cache key for this item **/
FString CacheKey;
/** Async handle for this item, used both to return to original caller, and for calls to the actual DDC **/
uint32 AsyncHandle;
/** Payload of this item, used for both from the get of the rollup and a put to the rollup **/
TArray<uint8> Payload;
/** If true, then the caller has either asked for the results. This means we don't need to keep them any more. **/
bool FinishedFromThePerspectiveOfTheCaller;
};
/** Items in this rollup **/
TArray<FRollupItem> Items;
/** Redundant copy of the keys in this rollup **/
TSet<FString> CacheKeys;
/** Redundant copy of the async handles in this rollup **/
TSet<uint32> AsyncHandles;
/** Cache key for the rollup itself **/
FString RollupCacheKey;
/** Async handle for the rollup **/
uint32 RollupAsyncHandle;
/** Tracks the phase this rollup is in. Mostly used for check's **/
EPhase::Type CurrentPhase;
/** If true, the rollup was corrupted, so we need to force a put when we get to the put. **/
bool ForcePutForCorruption;
/** Called when the rollup is ready. This is indirectly caused by the original caller waiting for an item to be ready. **/
void GetRollupResults()
{
check(Items.Num());
check(CurrentPhase == EPhase::AsyncRollupGet);
TArray<uint8> Payload;
bool bFailed = true;
if (InternalSingleton().FDerivedDataCache::GetAsynchronousResults(RollupAsyncHandle, Payload))
{
ForcePutForCorruption = true;
if (Payload.Num() > sizeof(uint32) * 2)
{
FMemoryReader Ar(Payload);
uint32 Magic;
Ar << Magic;
if (Magic == MAGIC_SWAPPED)
{
Ar.SetByteSwapping(!Ar.ForceByteSwapping());
Magic = MAGIC;
}
if (Magic == MAGIC)
{
int32 Count;
Ar << Count;
if (Count == Items.Num())
{
bFailed = false;
for (int32 Index = 0; Index < Items.Num(); Index++)
{
FString Key;
Ar << Key;
if (Key != Items[Index].CacheKey)
{
bFailed = true;
break;
}
Ar << Items[Index].Payload;
if (!Items[Index].Payload.Num())
{
bFailed = true;
break;
}
}
}
}
}
}
if (!bFailed)
{
ForcePutForCorruption = false;
CurrentPhase = EPhase::AsyncRollupGetSucceed;
}
else
{
CurrentPhase = EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts;
for (int32 Index = 0; Index < Items.Num(); Index++)
{
Items[Index].Payload.Empty(); // we might have had partial success on a corrupted rollup; we won't accept those
InternalSingleton().FDerivedDataCache::GetAsynchronousForRollup(*Items[Index].CacheKey, Items[Index].AsyncHandle);
}
}
}
/** Tests to see if the rollup is complete and ready to be put; if it is, it packages it and puts it. **/
bool CheckForPut()
{
check(Items.Num());
check(CurrentPhase == EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts);
for (int32 Index = 0; Index < Items.Num(); Index++)
{
if (!Items[Index].Payload.Num())
{
return false; // not done yet because we don't have all of the data
}
if (!Items[Index].FinishedFromThePerspectiveOfTheCaller)
{
return false; // not done yet because the caller still hasn't retrieved their results
}
}
uint32 Magic = MAGIC;
int32 Count = Items.Num();
TArray<uint8> Buffer;
FMemoryWriter Ar(Buffer);
Ar << Magic;
Ar << Count;
for (int32 Index = 0; Index < Items.Num(); Index++)
{
Ar << Items[Index].CacheKey;
Ar << Items[Index].Payload;
}
InternalSingleton().FDerivedDataCache::Put(*RollupCacheKey, Buffer, ForcePutForCorruption);
CurrentPhase = EPhase::Done;
return true;
}
/**
* Checks to see if there is any reason for this rollup to stay alive.
* @return true when done
**/
bool CheckForDone()
{
check(Items.Num());
check(CurrentPhase == EPhase::AsyncRollupGetSucceed);
for (int32 Index = 0; Index < Items.Num(); Index++)
{
if (!Items[Index].FinishedFromThePerspectiveOfTheCaller)
{
return false; // not done yet because the caller still hasn't retrieved their results
}
if (!Items[Index].Payload.Num())
{
check(0); // what is this?
}
}
CurrentPhase = EPhase::Done;
return true;
}
/**
* Finds an item by async handle, not legal to call if this rollup does not contain this handle
* @param Handle Async handle to find the corresponding item of
* @return Item with this async handle
**/
FRollupItem& FindItem(uint32 Handle)
{
for (int32 Index = 0; Index < Items.Num(); Index++)
{
if (Items[Index].AsyncHandle == Handle)
{
return Items[Index];
}
}
check(0);
return (FRollupItem&)*(FRollupItem*)NULL;
}
/**
* Finds an item by cache keu, not legal to call if this rollup does not contain this cache key
* @param InCacheKey cache key to find the corresponding item of
* @return Item with this cache key
**/
FRollupItem& FindItem(const FString& InCacheKey)
{
for (int32 Index = 0; Index < Items.Num(); Index++)
{
if (Items[Index].CacheKey == InCacheKey)
{
return Items[Index];
}
}
check(0);
return (FRollupItem&)*(FRollupItem*)NULL;
}
public:
FDerivedDataRollup()
: RollupCacheKey(TEXT("ROLLUP_"))
, RollupAsyncHandle(0)
, CurrentPhase(EPhase::Adding)
, ForcePutForCorruption(false)
{
}
/** Return the cache key, used for error spew **/
FString GetName()
{
return RollupCacheKey;
}
/** Return true if this rollup can be deleted because it has completed its life cycle. **/
bool IsDone()
{
return CurrentPhase == EPhase::Done;
}
/** Return true if this rollup contains an item with the given cache key. **/
bool Contains(const FString& InCacheKey)
{
return CacheKeys.Contains(InCacheKey);
}
/** Return true if this rollup contains an item with the given async handle. **/
bool Contains(uint32 InAsyncHandle)
{
return AsyncHandles.Contains(InAsyncHandle);
}
/** Add a new item to this rollup with the given cache key and async handle. **/
void Add(const FString& InCacheKey, uint32 InAsyncHandle)
{
check(CurrentPhase == EPhase::Adding);
RollupCacheKey += InCacheKey;
CacheKeys.Add(InCacheKey);
AsyncHandles.Add(InAsyncHandle);
new (Items) FRollupItem(InCacheKey, InAsyncHandle);
}
/** Signifies the end of the adding phase and starts an async get of the rollup. **/
void Close()
{
check(CurrentPhase == EPhase::Adding);
if (Items.Num())
{
RollupAsyncHandle = InternalSingleton().FDerivedDataCache::GetAsynchronous(*RollupCacheKey);
CurrentPhase = EPhase::AsyncRollupGet;
}
else
{
CurrentPhase = EPhase::Done;
}
}
/**
* Handle PollAsynchronousCompletion from the calling code.
* @param Handle Async completion handle for the item
* @return true, if the calling code can request results yet
**/
bool PollAsynchronousCompletion(uint32 Handle)
{
check(Contains(Handle));
if (CurrentPhase == EPhase::AsyncRollupGet)
{
// in this phase we see if the rollup is done
if (!InternalSingleton().FDerivedDataCache::PollAsynchronousCompletion(RollupAsyncHandle))
{
return false;
}
GetRollupResults();
// fall through to handle the other cases
}
if (CurrentPhase == EPhase::AsyncRollupGetSucceed)
{
// Rollup succeeded, so the calling code can get the results
return true;
}
if (CurrentPhase == EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts)
{
// Rollup failed, so call the actual PAC for the individual item
return InternalSingleton().FDerivedDataCache::PollAsynchronousCompletion(Handle);
}
check(0); // bad phase
return false;
}
/**
* Handle WaitAsynchronousCompletion from the calling code.
* @param Handle Async completion handle for the item
**/
void WaitAsynchronousCompletion(uint32 Handle)
{
check(Contains(Handle));
if (CurrentPhase == EPhase::AsyncRollupGet)
{
// in this phase we wait for the rollup to complete, then deal with the results
InternalSingleton().FDerivedDataCache::WaitAsynchronousCompletion(RollupAsyncHandle);
GetRollupResults();
// fall through to handle the other cases
}
if (CurrentPhase == EPhase::AsyncRollupGetSucceed)
{
// Rollup succeeded, so the calling code can get the results
return;
}
if (CurrentPhase == EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts)
{
// Rollup failed, so call the actual WAC for the individual item
InternalSingleton().FDerivedDataCache::WaitAsynchronousCompletion(Handle);
return;
}
check(0); // bad phase
}
/**
* Handle GetAsynchronousResults from the calling code. If this is the last piece of data, the rollup will be put.
* @param Handle Async completion handle for the item
* @param OutData returned payload
* @return true if the payload contains data and everything is peachy
**/
bool GetAsynchronousResults(uint32 Handle, TArray<uint8>& OutData)
{
check(Contains(Handle));
FRollupItem& Item = FindItem(Handle);
Item.FinishedFromThePerspectiveOfTheCaller = true;
OutData.Empty();
if (CurrentPhase == EPhase::AsyncRollupGetSucceed)
{
OutData = Item.Payload;
CheckForDone();
return !!OutData.Num();
}
if (CurrentPhase == EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts)
{
if (InternalSingleton().FDerivedDataCache::GetAsynchronousResults(Handle, OutData))
{
Item.Payload = OutData;
CheckForPut();
}
return !!OutData.Num();
}
check(0); // bad phase
return false;
}
/**
* Handle Put from the calling code. If this is the last piece of data, the rollup will be put.
* @param CacheKey Cache key for the item
* @param Data returned payload
**/
void Put(const TCHAR* CacheKey, TArray<uint8>& Data)
{
if (CurrentPhase != EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts)
{
return;
}
check(Contains(CacheKey));
FRollupItem& Item = FindItem(CacheKey);
check(Data.Num());
Item.Payload = Data;
CheckForPut();
}
};
/**
* Implementation of the derived data cache, this layer implements rollups
**/
class FDerivedDataCacheWithRollups : public FDerivedDataCache
{
typedef FDerivedDataCache Super;
public:
~FDerivedDataCacheWithRollups()
{
}
virtual void PrintLeaks() override
{
FScopeLock ScopeLock(&SynchronizationObject);
UE_LOG(LogDerivedDataCache, Log, TEXT("Shutdown"));
for (TSet<FDerivedDataRollup*>::TIterator Iter(PendingRollups); Iter; ++Iter)
{
FString Name((*Iter)->GetName());
if (Name.Len() > 1024)
{
Name = Name.Left(1024);
Name += TEXT("...");
}
UE_LOG(LogDerivedDataCache, Warning, TEXT("Leaked Rollup! %s"), *Name);
}
//check(!PendingRollups.Num()); // leaked rollups
}
virtual bool PollAsynchronousCompletion(uint32 Handle) override
{
FScopeLock ScopeLock(&SynchronizationObject);
for (TSet<FDerivedDataRollup*>::TIterator Iter(PendingRollups); Iter; ++Iter)
{
if ((*Iter)->Contains(Handle))
{
return (*Iter)->PollAsynchronousCompletion(Handle);
}
}
return Super::PollAsynchronousCompletion(Handle);
}
virtual void WaitAsynchronousCompletion(uint32 Handle) override
{
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
FScopeLock ScopeLock(&SynchronizationObject);
for (TSet<FDerivedDataRollup*>::TIterator Iter(PendingRollups); Iter; ++Iter)
{
if ((*Iter)->Contains(Handle))
{
(*Iter)->WaitAsynchronousCompletion(Handle);
return;
}
}
}
INC_FLOAT_STAT_BY(STAT_DDC_ASyncWaitTime,(float)ThisTime);
Super::WaitAsynchronousCompletion(Handle);
}
virtual bool GetAsynchronousResults(uint32 Handle, TArray<uint8>& OutData) override
{
FScopeLock ScopeLock(&SynchronizationObject);
for (TSet<FDerivedDataRollup*>::TIterator Iter(PendingRollups); Iter; ++Iter)
{
if ((*Iter)->Contains(Handle))
{
bool Result = (*Iter)->GetAsynchronousResults(Handle, OutData);
if ((*Iter)->IsDone())
{
delete *Iter;
Iter.RemoveCurrent();
}
return Result;
}
}
return Super::GetAsynchronousResults(Handle, OutData);
}
virtual IDerivedDataRollup* StartRollup() override
{
FScopeLock ScopeLock(&SynchronizationObject);
FDerivedDataRollup* Result = new FDerivedDataRollup();
PendingRollups.Add(Result);
return Result;
}
virtual void EndRollup(IDerivedDataRollup*& InRollup) override
{
FDerivedDataRollup* Rollup = static_cast<FDerivedDataRollup*>(InRollup);
if (Rollup)
{
FScopeLock ScopeLock(&SynchronizationObject);
Rollup->Close();
if (Rollup->IsDone())
{
PendingRollups.Remove(Rollup);
delete Rollup;
}
InRollup = NULL; // set the pointer to NULL so it cannot be reused
}
}
virtual uint32 GetAsynchronous(const TCHAR* CacheKey, IDerivedDataRollup* Rollup = NULL) override
{
if (Rollup)
{
FScopeLock ScopeLock(&SynchronizationObject);
UE_LOG(LogDerivedDataCache, Verbose, TEXT("GetAsynchronous (Rollup) %s"), CacheKey);
uint32 CurrentHandle = NextHandle();
((FDerivedDataRollup*)Rollup)->Add(CacheKey, CurrentHandle);
return CurrentHandle;
}
return Super::GetAsynchronous(CacheKey, NULL);
}
virtual void Put(const TCHAR* CacheKey, TArray<uint8>& Data, bool bPutEvenIfExists = false) override
{
FScopeLock ScopeLock(&SynchronizationObject);
for (TSet<FDerivedDataRollup*>::TIterator Iter(PendingRollups); Iter; ++Iter)
{
if ((*Iter)->Contains(CacheKey))
{
(*Iter)->Put(CacheKey, Data);
if ((*Iter)->IsDone())
{
delete *Iter;
Iter.RemoveCurrent();
}
}
}
return Super::Put(CacheKey, Data, bPutEvenIfExists);
}
private:
/** Object used for synchronization via a scoped lock **/
FCriticalSection SynchronizationObject;
/** Set of rollups **/
TSet<FDerivedDataRollup*> PendingRollups;
};
/**
* Singleton used both internally, and through the module.
* We look at the commandline to check if we should disable rollups or not
*/
FDerivedDataCache& InternalSingleton()
{
static FDerivedDataCache* Singleton = NULL;
if (!Singleton)
{
if (FParse::Param(FCommandLine::Get(), TEXT("DDCNoRollups")))
{
UE_LOG(LogDerivedDataCache, Warning, TEXT("Rollups are disabled."));
static FDerivedDataCache SingletonInstance;
Singleton = &SingletonInstance;
}
else
{
static FDerivedDataCacheWithRollups SingletonInstance;
Singleton = &SingletonInstance;
}
}
return *Singleton;
}
/**
* Module for the DDC
*/
class FDerivedDataCacheModule : public IDerivedDataCacheModule
{
public:
virtual FDerivedDataCacheInterface& GetDDC() override
{
return InternalSingleton();
}
virtual void StartupModule() override
{
#if WITH_EDITOR
// Make sure the CookingStats module gets loaded on the correct thread (used by DDCStats on a background thread)
FModuleManager::Get().LoadModule(TEXT("CookingStats"));
#endif // WITH_EDITOR
}
virtual void ShutdownModule() override
{
FDDCCleanup::Shutdown();
FDerivedDataCache& DDC = static_cast< FDerivedDataCache& >( GetDDC() );
DDC.PrintLeaks();
}
};
IMPLEMENT_MODULE( FDerivedDataCacheModule, DerivedDataCache);