You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
358 lines
10 KiB
C++
358 lines
10 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
/**
|
|
* A simple thread safe, memory based backend. This is used for Async puts and the boot cache.
|
|
**/
|
|
class FMemoryDerivedDataBackend : public FDerivedDataBackendInterface
|
|
{
|
|
public:
|
|
FMemoryDerivedDataBackend(int64 InMaxCacheSize = -1)
|
|
: MaxCacheSize(InMaxCacheSize)
|
|
, bDisabled( false )
|
|
, CurrentCacheSize( SerializationSpecificDataSize )
|
|
, bMaxSizeExceeded(false)
|
|
{
|
|
}
|
|
~FMemoryDerivedDataBackend()
|
|
{
|
|
Disable();
|
|
}
|
|
|
|
/** return true if this cache is writable **/
|
|
virtual bool IsWritable()
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
return !bDisabled;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
if (bDisabled)
|
|
{
|
|
return false;
|
|
}
|
|
// to avoid constant error reporting in async put due to restricted cache size,
|
|
// we report true if the max size has been exceeded
|
|
if (bMaxSizeExceeded)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return CacheItems.Contains(FString(CacheKey));
|
|
}
|
|
/**
|
|
* 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<uint8>& OutData)
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
if (!bDisabled)
|
|
{
|
|
FCacheValue* Item = CacheItems.FindRef(FString(CacheKey));
|
|
if (Item)
|
|
{
|
|
OutData = Item->Data;
|
|
Item->Age = 0;
|
|
check(OutData.Num());
|
|
return true;
|
|
}
|
|
}
|
|
OutData.Empty();
|
|
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<uint8>& InData, bool bPutEvenIfExists) OVERRIDE
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
if (bDisabled || bMaxSizeExceeded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString Key(CacheKey);
|
|
FCacheValue* Item = CacheItems.FindRef(FString(CacheKey));
|
|
if (Item)
|
|
{
|
|
//check(Item->Data == InData); // any second attempt to push data should be identical data
|
|
}
|
|
else
|
|
{
|
|
FCacheValue* Val = new FCacheValue(InData);
|
|
int32 CacheValueSize = CalcCacheValueSize(Key, *Val);
|
|
|
|
// check if we haven't exceeded the MaxCacheSize
|
|
if (MaxCacheSize > 0 && (CurrentCacheSize + CacheValueSize) > MaxCacheSize)
|
|
{
|
|
delete Val;
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("Failed to cache data. Maximum cache size reached. CurrentSize %d kb / MaxSize: %d kb"), CurrentCacheSize / 1024, MaxCacheSize / 1024);
|
|
bMaxSizeExceeded = true;
|
|
}
|
|
else
|
|
{
|
|
CacheItems.Add(Key, Val);
|
|
CalcCacheValueSize(Key, *Val);
|
|
|
|
CurrentCacheSize += CacheValueSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void RemoveCachedData(const TCHAR* CacheKey, bool bTransient) OVERRIDE
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
if (bDisabled || bTransient)
|
|
{
|
|
return;
|
|
}
|
|
FString Key(CacheKey);
|
|
FCacheValue* Item = NULL;
|
|
if (CacheItems.RemoveAndCopyValue(Key, Item))
|
|
{
|
|
CurrentCacheSize -= CalcCacheValueSize(Key, *Item);
|
|
bMaxSizeExceeded = false;
|
|
|
|
check(Item);
|
|
delete Item;
|
|
}
|
|
else
|
|
{
|
|
check(!Item);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the cache to disk
|
|
* @param Filename Filename to save
|
|
* @return true if file was saved sucessfully
|
|
*/
|
|
bool SaveCache(const TCHAR* Filename)
|
|
{
|
|
double StartTime = FPlatformTime::Seconds();
|
|
TAutoPtr<FArchive> SaverArchive(IFileManager::Get().CreateFileWriter(Filename, FILEWRITE_EvenIfReadOnly));
|
|
if (!SaverArchive.IsValid())
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("Could not save memory cache %s."), Filename);
|
|
return false;
|
|
}
|
|
|
|
FArchive& Saver = *SaverArchive;
|
|
uint32 Magic = MemCache_Magic64;
|
|
Saver << Magic;
|
|
const int64 DataStartOffset = Saver.Tell();
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
check(!bDisabled);
|
|
for (TMap<FString, FCacheValue*>::TIterator It(CacheItems); It; ++It )
|
|
{
|
|
Saver << It.Key();
|
|
Saver << It.Value()->Age;
|
|
Saver << It.Value()->Data;
|
|
}
|
|
}
|
|
const int64 DataSize = Saver.Tell(); // Everything except the footer
|
|
int64 Size = DataSize;
|
|
uint32 Crc = MemCache_Magic64; // Crc takes more time than I want to spend FCrc::MemCrc_DEPRECATED(&Buffer[0], Size);
|
|
Saver << Size;
|
|
Saver << Crc;
|
|
|
|
check(SerializationSpecificDataSize + DataSize <= MaxCacheSize || MaxCacheSize <= 0);
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("Saved boot cache %4.2fs %lldMB %s."), float(FPlatformTime::Seconds() - StartTime), DataSize / (1024 * 1024), Filename);
|
|
return true;
|
|
}
|
|
/**
|
|
* Load the cache from disk
|
|
* @param Filename Filename to load
|
|
* @return true if file was loaded sucessfully
|
|
*/
|
|
bool LoadCache(const TCHAR* Filename)
|
|
{
|
|
double StartTime = FPlatformTime::Seconds();
|
|
const int64 FileSize = IFileManager::Get().FileSize(Filename);
|
|
if (FileSize < 0)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("Could not find memory cache %s."), Filename);
|
|
return false;
|
|
}
|
|
// We test 3 * uint32 which is the old format (< SerializationSpecificDataSize). We'll test
|
|
// agains SerializationSpecificDataSize later when we read the magic number from the cache.
|
|
if (FileSize < sizeof(uint32) * 3)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("Memory cache was corrputed (short) %s."), Filename);
|
|
return false;
|
|
}
|
|
if (FileSize > MaxCacheSize*2 && MaxCacheSize > 0)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("Refusing to load DDC cache %s. Size exceeds doubled MaxCacheSize."), Filename);
|
|
return false;
|
|
}
|
|
|
|
TAutoPtr<FArchive> LoderArchive(IFileManager::Get().CreateFileReader(Filename));
|
|
if (!LoderArchive.IsValid())
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("Could not read memory cache %s."), Filename);
|
|
return false;
|
|
}
|
|
|
|
FArchive& Loader = *LoderArchive;
|
|
uint32 Magic = 0;
|
|
Loader << Magic;
|
|
if (Magic != MemCache_Magic && Magic != MemCache_Magic64)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("Memory cache was corrputed (magic) %s."), Filename);
|
|
return false;
|
|
}
|
|
// Check the file size again, this time against the correct minimum size.
|
|
if (Magic == MemCache_Magic64 && FileSize < SerializationSpecificDataSize)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("Memory cache was corrputed (short) %s."), Filename);
|
|
return false;
|
|
}
|
|
// Calculate expected DataSize based on the magic number (footer size difference)
|
|
const int64 DataSize = FileSize - (Magic == MemCache_Magic64 ? (SerializationSpecificDataSize - sizeof(uint32)) : (sizeof(uint32) * 2));
|
|
Loader.Seek(DataSize);
|
|
int64 Size = 0;
|
|
uint32 Crc = 0;
|
|
if (Magic == MemCache_Magic64)
|
|
{
|
|
Loader << Size;
|
|
}
|
|
else
|
|
{
|
|
uint32 Size32 = 0;
|
|
Loader << Size32;
|
|
Size = (int64)Size32;
|
|
}
|
|
Loader << Crc;
|
|
if (Size != DataSize)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("Memory cache was corrputed (size) %s."), Filename);
|
|
return false;
|
|
}
|
|
if ((Crc != MemCache_Magic && Crc != MemCache_Magic64) || Crc != Magic)
|
|
{
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("Memory cache was corrputed (crc) %s."), Filename);
|
|
return false;
|
|
}
|
|
// Seek to data start offset (skip magic number)
|
|
Loader.Seek(sizeof(uint32));
|
|
{
|
|
TArray<uint8> Working;
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
check(!bDisabled);
|
|
while (Loader.Tell() < DataSize)
|
|
{
|
|
FString Key;
|
|
int32 Age;
|
|
Loader << Key;
|
|
Loader << Age;
|
|
Age++;
|
|
Loader << Working;
|
|
if (Age < MaxAge)
|
|
{
|
|
CacheItems.Add(Key, new FCacheValue(Working, Age));
|
|
}
|
|
Working.Reset();
|
|
}
|
|
// these are just a double check on ending correctly
|
|
if (Magic == MemCache_Magic64)
|
|
{
|
|
Loader << Size;
|
|
}
|
|
else
|
|
{
|
|
uint32 Size32 = 0;
|
|
Loader << Size32;
|
|
Size = (int64)Size32;
|
|
}
|
|
Loader << Crc;
|
|
}
|
|
|
|
CurrentCacheSize = FileSize;
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("Loaded boot cache %4.2fs %lldMB %s."), float(FPlatformTime::Seconds() - StartTime), DataSize / (1024 * 1024), Filename);
|
|
return true;
|
|
}
|
|
/**
|
|
* Disable cache and ignore all subsequent requests
|
|
*/
|
|
void Disable()
|
|
{
|
|
FScopeLock ScopeLock(&SynchronizationObject);
|
|
bDisabled = true;
|
|
for (TMap<FString,FCacheValue*>::TIterator It(CacheItems); It; ++It )
|
|
{
|
|
delete It.Value();
|
|
}
|
|
CacheItems.Empty();
|
|
|
|
CurrentCacheSize = SerializationSpecificDataSize;
|
|
}
|
|
|
|
private:
|
|
|
|
struct FCacheValue
|
|
{
|
|
int32 Age;
|
|
TArray<uint8> Data;
|
|
FCacheValue(const TArray<uint8>& InData, int32 InAge = 0)
|
|
: Age(InAge)
|
|
, Data(InData)
|
|
{
|
|
}
|
|
};
|
|
|
|
FORCEINLINE int32 CalcCacheValueSize(const FString& Key, const FCacheValue& Val)
|
|
{
|
|
return (Key.Len() + 1) * sizeof(TCHAR) + sizeof(Val.Age) + Val.Data.Num();
|
|
}
|
|
|
|
/** Set of files that are being written to disk asynchronously. */
|
|
TMap<FString, FCacheValue*> CacheItems;
|
|
/** Maximum size the cached items can grow up to ( in bytes ) */
|
|
int64 MaxCacheSize;
|
|
/** When set to true, this cache is disabled...ignore all requests. */
|
|
bool bDisabled;
|
|
/** Object used for synchronization via a scoped lock */
|
|
FCriticalSection SynchronizationObject;
|
|
/** Current estimated cache size in bytes */
|
|
int64 CurrentCacheSize;
|
|
/** Indicates that the cache max size has been exceeded. This is used to avoid
|
|
warning spam after the size has reached the limit. */
|
|
bool bMaxSizeExceeded;
|
|
enum
|
|
{
|
|
/** Magic number to use in header */
|
|
MemCache_Magic = 0x0cac0ddc,
|
|
/** Magic number to use in header (new, > 2GB size compatible) */
|
|
MemCache_Magic64 = 0x0cac1ddc,
|
|
/** Oldest cache items to keep */
|
|
MaxAge = 3,
|
|
/** Size of data that is stored in the cachefile apart from the cache entries (64 bit size). */
|
|
SerializationSpecificDataSize = sizeof(uint32) // Magic
|
|
+ sizeof(int64) // Size
|
|
+ sizeof(uint32), // CRC
|
|
};
|
|
};
|
|
|