Files
UnrealEngineUWP/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCache.cpp
Thomas Sarkanen 40cfc29928 Copying //UE4/Dev-AnimPhys to //UE4/Dev-Main (Source: //UE4/Dev-AnimPhys @ 3537446)
#lockdown Nick.Penwarden

=====================================
 MAJOR FEATURES + CHANGES
=====================================

Change 3491514 by Jonathan.Poncelet

	Added new functions AddTorqueDegrees and AddAngularImpulseDegrees to UPrimitiveComponent

	Provided automated tests ensure that the angular velocity is consistent for each of the new functions by comparing it with an equivalent call to the original function.

	#jira UE-39757 Torque and angular velocity are inconsistent

	#automation Tests verify that AddTorque/AddTorqueDegrees and AddAngularImpulse/AddAngularImpulseDegrees both produce the correct angular velocity, when passed the same value in different units.

Change 3495025 by Jonathan.Poncelet

	Back out changelist 3491514

	"Added new functions AddTorqueDegrees and AddAngularImpulseDegrees to UPrimitiveComponent

	Provided automated tests ensure that the angular velocity is consistent for each of the new functions by comparing it with an equivalent call to the original function.

	#jira UE-39757 Torque and angular velocity are inconsistent

	#automation Tests verify that AddTorque/AddTorqueDegrees and AddAngularImpulse/AddAngularImpulseDegrees both produce the correct angular velocity, when passed the same value in different units."

Change 3505086 by Danny.Bouimad

	Updating test content in TM-AnimPhys and TM-TangentNormals

Change 3505375 by James.Cobbett

	Automating Settle test map

Change 3505714 by Lina.Halper

	Add more descriptive pin name and node text for constraint node

	#jira: UE-45895
	#rb: Ori.Cohen

Change 3505731 by Lina.Halper

	1. Renamed FTargetReference to FBoneSocketTarget

	 - this allows users to choose either bone or socket as a target.

	2. Two Bone IK refactor to use FBoneSocketTarget

	- Effector Target and Joint Target are converted to use FBoneSocketTarget, so you can use socket or bone

	- Effector Location and Joint Target Location is used as offset from target location, so you can use as a combination of FBoneSocketTarget

	- Editor code now uses runtime node instead of Graph Node, will have more discussion with Tom on this.

	3. FABRIK refactor to use FBoneSocketTarget

	#code review: Laurent.Delayen, Martin.Wilson, Thomas.Sarkanen
	#rb: Laurent.Delayen

Change 3505770 by Lina.Halper

	IK automation test

	#jira: UE-46250

Change 3506369 by Lina.Halper

	Fix initialization order issue

	#rb:none
	#rnx

Change 3506697 by Martin.Wilson

	Fix root motion when using ForceAnimRate's of more than 1

	#jira UE-39021

Change 3506765 by Lina.Halper

	It's confusing to see the same name multiple times. So fixed so that this utility functions show up later, and then added function instead.

	#jira: UE-45871
	#rb: Martin.Wilson

Change 3506787 by Ori.Cohen

	Added single threaded physx tasks stats using "stat PhysXTasks"

Change 3506803 by Ori.Cohen

	Turn off debug code which was submitted by accident

Change 3506840 by Jurre.deBaare

	Fix for automation vertex-color warning

Change 3506917 by Danny.Bouimad

	Checking in Edits made to AnimBP Constraint Content

Change 3507045 by James.Cobbett

	Submitting final Settle test map updates

Change 3509208 by Danny.Bouimad

	Checking in content changes for TM-SuspendCloth

Change 3509235 by James.Cobbett

	Deleting Settle test map from QAGame - now lives in EngineTest

Change 3509935 by Lina.Halper

	One customization tree for supporting Bone and Socket
	 : you can just use FBoneSocketTarget and that will allow displaying sockets also

	#jira: UE-45778
	#rb: Thomas.Sarkanen
	#code review:Thomas.Sarkanen

Change 3511250 by Martin.Wilson

	Fix crash when performing drag operations in the notify track window

	#jira UE-46420

Change 3511397 by Thomas.Sarkanen

	Asset reloading now defers re-opening asset editors until post-GC phase

	This prevents an issue in some asset editors (like Persona) which may reference other assets in their UI.

	#jira UE-46442 - Crash when opening skeletal mesh editor window after reloading asset

Change 3512849 by Aaron.McLeran

	#jira UE-46576 Fixing granulator loading multiple sound waves

Change 3513414 by James.Cobbett

	Fixing destructible test map

Change 3513588 by Benn.Gallagher

	Clothing LOD improvements
	 - Added full pipeline for adding LODs to clothing assets in editor
	 - Added methods for mapping parameters between masks on meshes with differing topology
	 - Fixed a few UI bugs

Change 3513599 by Benn.Gallagher

	Missed files from last checkin

Change 3513920 by Martin.Wilson

	Move Live Link Retarget Asset to live link plugin and remove engine dependency from LiveLinkInterface (fixes maya live link compiling)

Change 3515400 by Aaron.McLeran

	#jira UE-46299 Added a fade in function to audio device so audio can resume after fading out in main audio device.

Change 3515495 by Joe.Conley

	Had reports some AnimationBP deterministic cooking errors were being caused by FBakedAnimationState::bAlwaysResetOnEntry not being initialized in the constructor explicitly.  Changing that to be explicitly initialized to false in the constructor, as well as UAnimStateNode::bAlwaysResetOnEntry.

Change 3515641 by Benn.Gallagher

	CIS fix for game builds

Change 3516817 by Aaron.McLeran

	Moving opus lib to subfolder Windows instead of win32 to fix UGS game sync issues.

Change 3516853 by Aaron.McLeran

	Slight optimization in converting proc audio buffer to 16 bit PCM.

Change 3517525 by Jonathan.Poncelet

	Fix comment for FName operator!=

Change 3517826 by James.Cobbett

	Test files for bug UE-46719

Change 3518049 by James.Cobbett

	Updating Settle automated test map to include settling on convex floors. Also added step to save actor starting location in Ground Truth, and reset actors to that location at the end of the test.

Change 3518185 by Ori.Cohen

	Fix merge error as reported by NVIDIA

Change 3518711 by Ethan.Geller

	Integrating fix for switch crash on load level.

Change 3518720 by Ethan.Geller

	Back out changelist 3518711

Change 3519040 by Aaron.McLeran

	Simple feature to add attack/decay interpolation times for focus feature to avoid fast focus/out-of-focus volume scaling.

Change 3519972 by James.Golding

	Fix constructor order for FSoundAttenuationSettings to fix CIS

Change 3520141 by Martin.Wilson

	Make retarget assets are no longer assets but blueprints instead.
	Add blueprint function for remap asset to allow blueprints to transform bone names

	#jira UEAP-235

Change 3520568 by Martin.Wilson

	CIS fix

Change 3520677 by Benn.Gallagher

	Added ability to rename clothing assets after creation

Change 3520727 by Benn.Gallagher

	Removed unecessary header for asset list

Change 3520791 by Martin.Wilson

	Fix multiple calls to FinalizeBoneTransforms when calling RefreshBoneTransforms outside of tick

Change 3521069 by Jurre.deBaare

	Merging an actor with recompute normal and Overlapping UVs causes the normals generate incorrectly
	#fix old code path was causing normals to be recompute when it wasn't required causing the smooth normals on the issueing asset
	#jira UE-46806

Change 3521070 by Jurre.deBaare

	Ensure occurs when performing a Bake Out Material on Cube
	#fix Make sure that we update the Material data used for texture streaming when adding/changing materials during material baking
	#jira UE-46807

Change 3521142 by Jurre.deBaare

	Bake Material large Texture size crash
	#fix Added clamping to baked out material texture sizes in all occurences (GetMax2DTextureDimension())
	#jira UE-46808

Change 3523294 by Aaron.McLeran

	Resetting available byte count when resetting the procedural sound wave

Change 3523297 by Aaron.McLeran

	Adding thread safe mode for plugin interface shared ptrs.

Change 3524153 by Jurre.deBaare

	Issue where new blend space samples list in detailsview would not regenerate blendspace sampling
	#fix Unified what happens when you change the grid sample value (this issue also caused undo/redo not to work with these numeric boxes)

Change 3524154 by Jurre.deBaare

	Advanced preview tool tip in BlendSpace editor has different behaviour when grid doesn't have focus, and broke the sample dragging functionality.
	#fix Undid some added state cleanup code which actually was invalid to do, and made sure the CTRL down isn't a toggle but a constant state

Change 3524282 by Thomas.Sarkanen

	Fixed OculusAudio in line with new API

	Post-Main merge fixup

Change 3524348 by Thomas.Sarkanen

	Merging using Dev-Physics-Upgrade_PhysX3_To_Dev-AnimPhys_PhysX

	Original CL 3521358:

	[From trunk] 22410436 [Px-1090]PCm sphere convex jittering in UE4 [Reviewer: Kier]

	p4rmerge of Change 22415420 by sschirm
	from e:\P4\dev1\sw\physx\Releases\distro_mirrors\PhysX_3.4_APEX_1.4\Mirror_scripts\patch/cl-22415420.p4r
	moved from //sw/physx/Releases/distro_mirrors/PhysX_3.4_APEX_1.4/Mirror/ to //UE4/Dev-Physics-Upgrade/Engine/Source/ThirdParty/PhysX3/

	#jira UE-46668 - PCM still has stability issues

Change 3524541 by Jurre.deBaare

	Disabled material baking automated tests for now
	#jira UE-46510

Change 3524684 by Jurre.deBaare

	If you paste a invalid name into Bones to Remove and hit apply the editor will crash
	#fix did not check for INDEX_NONE or NAME_NONE bones when retrieving bone indices
	#jira UE-46830

Change 3525244 by Ori.Cohen

	Added the ability to verify the DDC content is not stale. Systems have to opt in (-VerifyDDC)

Change 3525248 by Ori.Cohen

	Physx DDC will now run with verify ddc. Also fixed bad key which was missing the complexity type of body setup

Change 3525263 by Ori.Cohen

	Fix typo with printf

Change 3525279 by Ori.Cohen

	Fix CIS

Change 3525478 by Ethan.Geller

	Adding memory aligned audio buffer support

Change 3525688 by Aaron.McLeran

	Removing unnecessary code

Change 3526391 by Benn.Gallagher

	Clothing optimization pass, mainly removing allocations and precaching some skin information. 10% Overall non-gamethread time reduction, gamethread sync completion task time halved.
	#jira UEAP-197

Change 3526454 by Benn.Gallagher

	CIS fix

Change 3526919 by Chad.Garyet

	adding verifyddc flag to automated tests

Change 3527006 by Lina.Halper

	Fix crash with blendspace sample value change

	- Matt also fixed undo transaction, queuing every move vs only final value
	- Matt fixed property changed to send interactive or not paramger, so that it doens't call object changed for every single move

	#jira: UE-46929
	#rb: Matt.Kuhlenschmidt
	#code review: Jurre.DeBaare, Matt.Kuhlenschmidt

Change 3528684 by Benn.Gallagher

	Static analysis fix, excessive statement left to signal reasons clothing assets would invalidate their caches triggered SA fail.

Change 3528687 by Benn.Gallagher

	CIS Fix, method definition outside of declaration #if block.

Change 3528890 by Ori.Cohen

	Fix false negative with PIE and verify DDC

Change 3528899 by Martin.Wilson

	Smart name refactor part 1 - Changed FindUID api to return UID rather than pointer to UID, fix code in Orion that was caching a pointer to internal TMap allocated memory.

	#jira UEAP-264

Change 3530148 by Aaron.McLeran

	Making check for Supporting multiple audio devices only happen in editor builds.

Change 3530519 by Jonathan.Poncelet

	Deprecated original angular physics functions in preference of a consistent API, using degrees vs. radians

	Functions are now suffixed "InDegrees" or "InRadians", to make it obvious which are used.
	The deprecated functions now call whichever degrees or radians counterpart is needed.
	FBodyInstance now works entirely in radians, to avoid unnecessary conversions.
	Automated tests have been added to verify behaviour.

	#jira UE-39757 Torque and angular velocity are inconsistent

Change 3530943 by Benn.Gallagher

	Fixed clothing shader model automation test.

	#jira UE-47052

Change 3530993 by Thomas.Sarkanen

	Merging using Dev-Rendering_To_Dev-AnimPhys from CL 3512333. Converting integrates to edits.

	Oiriginal CL desc:

	Texture source data is not released anymore in WillNeverCacheCookedPlatformDataAgain().
	This prevents an issue where texture referenced through CompositeTexture have no source data available.
	This doesn't affect peak memory so much as texture loaded with AllowAsyncLoading already release their temporary load data.

	#jira UE-47083 - Cook Odin fails with LogTexture: Error: Unable to get texture source mips because its bulk data was released.

Change 3536312 by Chad.Garyet

	adding verifyddc into the automatedtestbuild xml

Change 3537375 by James.Golding

	Merge Ocean GC crash fix (OCN-7666) from CL 3512485
	#jira UE-47211


DONE!

[CL 3537460 by Thomas Sarkanen in Main branch]
2017-07-14 06:36:47 -04:00

1090 lines
33 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "Misc/CommandLine.h"
#include "HAL/ThreadSafeCounter.h"
#include "Misc/ScopeLock.h"
#include "Stats/StatsMisc.h"
#include "Stats/Stats.h"
#include "Async/AsyncWork.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "Modules/ModuleManager.h"
#include "DerivedDataCacheInterface.h"
#include "DerivedDataBackendInterface.h"
#include "DerivedDataPluginInterface.h"
#include "DDCCleanup.h"
#include "ProfilingDebugging/CookStats.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);
#if ENABLE_COOK_STATS
#include "DerivedDataCacheUsageStats.h"
namespace DerivedDataCacheCookStats
{
FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
{
TMap<FString, FDerivedDataCacheUsageStats> DDCStats;
GetDerivedDataCacheRef().GatherUsageStats(DDCStats);
{
const FString StatName(TEXT("DDC.Usage"));
for (const auto& UsageStatPair : DDCStats)
{
UsageStatPair.Value.LogStats(AddStat, StatName, UsageStatPair.Key);
}
}
// Now lets add some summary data to that applies some crazy knowledge of how we set up our DDC. The goal
// is to print out the global hit rate, and the hit rate of the local and shared DDC.
// This is done by adding up the total get/miss calls the root node receives.
// Then we find the FileSystem nodes that correspond to the local and shared cache using some hacky logic to detect a "network drive".
// If the DDC graph ever contains more than one local or remote filesystem, this will only find one of them.
{
TArray<FString, TInlineAllocator<20>> Keys;
DDCStats.GenerateKeyArray(Keys);
FString* RootKey = Keys.FindByPredicate([](const FString& Key) {return Key.StartsWith(TEXT(" 0:")); });
// look for a Filesystem DDC that doesn't have a UNC path. Ugly, yeah, but we only cook on PC at the moment.
FString* LocalDDCKey = Keys.FindByPredicate([](const FString& Key) {return Key.Contains(TEXT(": FileSystem.")) && !Key.Contains(TEXT("//")); });
// look for a UNC path
FString* SharedDDCKey = Keys.FindByPredicate([](const FString& Key) {return Key.Contains(TEXT(": FileSystem.//")); });
if (RootKey)
{
const FDerivedDataCacheUsageStats& RootStats = DDCStats[*RootKey];
int64 TotalGetHits =
RootStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Hit, FCookStats::CallStats::EStatType::Counter, true) +
RootStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Hit, FCookStats::CallStats::EStatType::Counter, false);
int64 TotalGetMisses =
RootStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Miss, FCookStats::CallStats::EStatType::Counter, true) +
RootStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Miss, FCookStats::CallStats::EStatType::Counter, false);
int64 TotalGets = TotalGetHits + TotalGetMisses;
int64 LocalHits = 0;
if (LocalDDCKey)
{
const FDerivedDataCacheUsageStats& LocalDDCStats = DDCStats[*LocalDDCKey];
LocalHits =
LocalDDCStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Hit, FCookStats::CallStats::EStatType::Counter, true) +
LocalDDCStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Hit, FCookStats::CallStats::EStatType::Counter, false);
}
int64 SharedHits = 0;
if (SharedDDCKey)
{
// The shared DDC is only queried if the local one misses (or there isn't one). So it's hit rate is technically
const FDerivedDataCacheUsageStats& SharedDDCStats = DDCStats[*SharedDDCKey];
SharedHits =
SharedDDCStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Hit, FCookStats::CallStats::EStatType::Counter, true) +
SharedDDCStats.GetStats.GetAccumulatedValue(FCookStats::CallStats::EHitOrMiss::Hit, FCookStats::CallStats::EStatType::Counter, false);
}
AddStat(TEXT("DDC.Summary"), FCookStatsManager::CreateKeyValueArray(
TEXT("TotalGetHits"), TotalGetHits,
TEXT("TotalGets"), TotalGets,
TEXT("TotalHitPct"), (double)TotalGetHits / TotalGets,
TEXT("LocalHitPct"), (double)LocalHits / TotalGets,
TEXT("SharedHitPct"), (double)SharedHits / TotalGets,
TEXT("OtherHitPct"), double(TotalGetHits - LocalHits - SharedHits) / TotalGets,
TEXT("MissPct"), (double)TotalGetMisses / TotalGets
));
}
}
});
}
#endif
/** Whether we want to verify the DDC (pass in -VerifyDDC on the command line)*/
bool GVerifyDDC = false;
/**
* 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)
, bDataWasBuilt(false)
, 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()
{
const int32 NumBeforeDDC = Data.Num();
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)
{
if(GVerifyDDC && DataDeriver && DataDeriver->IsDeterministic())
{
TArray<uint8> CmpData;
DataDeriver->Build(CmpData);
const int32 NumInDDC = Data.Num() - NumBeforeDDC;
const int32 NumGenerated = CmpData.Num();
bool bMatchesInSize = NumGenerated == NumInDDC;
bool bDifferentMemory = true;
if (bMatchesInSize)
{
bDifferentMemory = 0 != FMemory::Memcmp(CmpData.GetData(), &Data[NumBeforeDDC], NumGenerated);
}
if(!bMatchesInSize || bDifferentMemory)
{
FString ErrMsg = FString::Printf(TEXT("There is a mismatch between the DDC data and the generated data for plugin (%s) for asset (%s). BytesInDDC:%d, BytesGenerated:%d, bDifferentMemory:%d"), DataDeriver->GetPluginName(), *DataDeriver->GetDebugContextString(), NumInDDC, NumGenerated, bDifferentMemory);
ensureMsgf(false, *ErrMsg);
UE_LOG(LogDerivedDataCache, Error, TEXT("%s"), *ErrMsg );
}
}
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);
bDataWasBuilt = true;
}
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;
/** true if we had to build the data */
bool bDataWasBuilt;
/** 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
GVerifyDDC = FParse::Param(FCommandLine::Get(), TEXT("VerifyDDC"));
}
/** 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, bool* bDataWasBuilt = nullptr) 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;
if (bDataWasBuilt)
{
*bDataWasBuilt = PendingTask.GetTask().bDataWasBuilt;
}
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, bool* bDataWasBuilt = nullptr) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DDC_GetAsynchronousResults);
FAsyncTask<FBuildAsyncWorker>* AsyncTask = NULL;
{
FScopeLock ScopeLock(&SynchronizationObject);
PendingTasks.RemoveAndCopyValue(Handle,AsyncTask);
}
check(AsyncTask);
if (bDataWasBuilt)
{
*bDataWasBuilt = AsyncTask->GetTask().bDataWasBuilt;
}
if (!AsyncTask->GetTask().bSuccess)
{
delete AsyncTask;
return false;
}
OutData = MoveTemp(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
}
virtual void GatherUsageStats(TMap<FString, FDerivedDataCacheUsageStats>& UsageStatsMap) override
{
FDerivedDataBackend::Get().GatherUsageStats(UsageStatsMap);
}
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];
}
}
UE_LOG(LogDerivedDataCache, Fatal, TEXT("Illegal async handle passed to FRollupItem::FindItem()."));
return Items[0];// shouldn't get here anyway, won't make it worse
}
/**
* Finds an item by cache key, 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];
}
}
UE_LOG(LogDerivedDataCache, Fatal, TEXT("Illegal cache key passed to FRollupItem::FindItem()."));
return Items[0];// shouldn't get here anyway, won't make it worse
}
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, bool* bDataWasBuilt)
{
check(Contains(Handle));
FRollupItem& Item = FindItem(Handle);
Item.FinishedFromThePerspectiveOfTheCaller = true;
OutData.Empty();
if (CurrentPhase == EPhase::AsyncRollupGetSucceed)
{
if (bDataWasBuilt)
{
*bDataWasBuilt = false;
}
OutData = Item.Payload;
CheckForDone();
return !!OutData.Num();
}
if (CurrentPhase == EPhase::AsyncRollupGetFailed_GettingItemsAndWaitingForPuts)
{
if (InternalSingleton().FDerivedDataCache::GetAsynchronousResults(Handle, OutData, bDataWasBuilt))
{
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, bool* bDataWasBuilt = nullptr) override
{
FScopeLock ScopeLock(&SynchronizationObject);
for (TSet<FDerivedDataRollup*>::TIterator Iter(PendingRollups); Iter; ++Iter)
{
if ((*Iter)->Contains(Handle))
{
bool Result = (*Iter)->GetAsynchronousResults(Handle, OutData, bDataWasBuilt);
if ((*Iter)->IsDone())
{
delete *Iter;
Iter.RemoveCurrent();
}
return Result;
}
}
return Super::GetAsynchronousResults(Handle, OutData, bDataWasBuilt);
}
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);
const uint32 RollupHandle = NextHandle();
((FDerivedDataRollup*)Rollup)->Add(CacheKey, RollupHandle);
return RollupHandle;
}
return Super::GetAsynchronous(CacheKey, nullptr);
}
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
{
/** Cached reference to DDC singleton, helpful to control singleton's lifetime. */
FDerivedDataCache* DDC;
public:
virtual FDerivedDataCacheInterface& GetDDC() override
{
return InternalSingleton();
}
virtual void StartupModule() override
{
// make sure DDC gets created early, previously it might have happened in ShutdownModule() (for PrintLeaks()) when it was already too late
DDC = static_cast< FDerivedDataCache* >( &GetDDC() );
}
virtual void ShutdownModule() override
{
FDDCCleanup::Shutdown();
if (DDC)
{
DDC->PrintLeaks();
}
}
FDerivedDataCacheModule()
: DDC(nullptr)
{
}
};
IMPLEMENT_MODULE( FDerivedDataCacheModule, DerivedDataCache);