Files
UnrealEngine/Engine/Source/Developer/DerivedDataCache/Private/MemoryCacheStore.cpp
2025-05-18 13:04:45 +08:00

762 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MemoryCacheStore.h"
#include "Algo/Accumulate.h"
#include "Algo/AllOf.h"
#include "Algo/BinarySearch.h"
#include "Algo/NoneOf.h"
#include "DerivedDataBackendInterface.h"
#include "DerivedDataCachePrivate.h"
#include "DerivedDataCacheRecord.h"
#include "DerivedDataValue.h"
#include "Misc/ScopeExit.h"
#include "Misc/ScopeRWLock.h"
#include "Serialization/CompactBinary.h"
namespace UE::DerivedData
{
/**
* A simple thread safe, memory based backend. This is used for Async puts and the boot cache.
*/
class FMemoryCacheStore final : public IMemoryCacheStore
{
public:
explicit FMemoryCacheStore(
IMemoryCacheStore*& OutCache,
const TCHAR* InName,
const TCHAR* InConfig,
ICacheStoreOwner* InOwner);
~FMemoryCacheStore() final;
// ICacheStore Interface
void Put(
TConstArrayView<FCachePutRequest> Requests,
IRequestOwner& Owner,
FOnCachePutComplete&& OnComplete) final;
void Get(
TConstArrayView<FCacheGetRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetComplete&& OnComplete) final;
void PutValue(
TConstArrayView<FCachePutValueRequest> Requests,
IRequestOwner& Owner,
FOnCachePutValueComplete&& OnComplete) final;
void GetValue(
TConstArrayView<FCacheGetValueRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetValueComplete&& OnComplete) final;
void GetChunks(
TConstArrayView<FCacheGetChunkRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetChunkComplete&& OnComplete) final;
// ILegacyCacheStore Interface
void LegacyStats(FDerivedDataCacheStatsNode& OutNode) final;
bool LegacyDebugOptions(FBackendDebugOptions& Options) final;
// IMemoryCacheStore Interface
void Delete(const FCacheKey& Key, const FSharedString& Name) final;
void DeleteValue(const FCacheKey& Key, const FSharedString& Name) final;
void Disable() final;
private:
struct FCacheRecordComponents
{
FCbObject Meta;
TArray<FValueWithId> Values;
};
/** Name of this cache (used for debugging) */
FString Name;
ICacheStoreOwner* StoreOwner;
ICacheStoreStats* StoreStats = nullptr;
/** Set of records in this cache. */
TMap<FCacheKey, FCacheRecordComponents> CacheRecords;
/** Set of values in this cache. */
TMap<FCacheKey, FValue> CacheValues;
/** Maximum size the cached items can grow up to (in bytes) */
uint64 MaxCacheSize;
/** When set to true, this cache is disabled...ignore all requests. */
std::atomic<bool> bDisabled;
/** Object used for synchronization via a scoped lock */
mutable FRWLock SynchronizationObject;
/** Current estimated cache size in bytes */
uint64 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;
/**
* When a memory cache can be disabled, it won't return true for CachedDataProbablyExists calls.
* This is to avoid having the Boot DDC tells it has some resources that will suddenly disappear after the boot.
* Get() requests will still get fulfilled and other cache level will be properly back-filled
* offering the speed benefit of the boot cache while maintaining coherency at all cache levels.
*
* The problem is that most asset types (audio/staticmesh/texture) will always verify if their different LODS/Chunks can be found in the cache using CachedDataProbablyExists.
* If any of the LOD/MIP can't be found, a build of the asset is triggered, otherwise they skip asset compilation altogether.
* However, we should not skip the compilation based on the CachedDataProbablyExists result of the boot cache because it is a lie and will disappear at some point.
* When the boot cache disappears and the streamer tries to fetch a LOD that it has been told was cached, it will fail and will then have no choice but to rebuild the asset synchronously.
* This obviously causes heavy game-thread stutters.
* However, if the bootcache returns false during CachedDataProbablyExists. The async compilation will be triggered and data will be put in the both the boot.ddc and the local cache.
* This way, no more heavy game-thread stutters during streaming...
* This can be reproed when you clear the local cache but do not clear the boot.ddc file, but even if it's a corner case, I stumbled upon it enough times that I though it was worth to fix so the caches are coherent.
*/
bool bCanBeDisabled = false;
bool bShuttingDown = false;
protected:
FBackendDebugOptions DebugOptions;
};
FMemoryCacheStore::FMemoryCacheStore(
IMemoryCacheStore*& OutCache,
const TCHAR* InName,
const TCHAR* InConfig,
ICacheStoreOwner* InOwner)
: Name(InName)
, StoreOwner(InOwner)
, MaxCacheSize(0)
, bDisabled(false)
, CurrentCacheSize(0)
, bMaxSizeExceeded(false)
, bCanBeDisabled(FParse::Param(InConfig, TEXT("Boot")))
{
OutCache = this;
// Make sure MaxCacheSize does not exceed 2 GB
int64 SignedMaxCacheSize = -1; // in MB
FParse::Value(InConfig, TEXT("MaxCacheSize="), SignedMaxCacheSize);
const int64 MaxSupportedCacheSize = 2048; // 2 GB
MaxCacheSize = FMath::Min(SignedMaxCacheSize, MaxSupportedCacheSize);
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Max Cache Size: %" INT64_FMT " MB"), *Name, SignedMaxCacheSize);
MaxCacheSize = SignedMaxCacheSize < 0 ? 0 : SignedMaxCacheSize * 1024 * 1024;
if (StoreOwner)
{
ECacheStoreFlags Flags = ECacheStoreFlags::Local | ECacheStoreFlags::Query;
if (!FParse::Param(InConfig, TEXT("ReadOnly")))
{
Flags |= ECacheStoreFlags::Store;
}
if (FParse::Param(InConfig, TEXT("StopGetStore")))
{
Flags |= ECacheStoreFlags::StopGetStore;
}
StoreOwner->Add(this, Flags);
if (!FParse::Param(InConfig, TEXT("NoStats")))
{
StoreStats = StoreOwner->CreateStats(this, Flags, TEXTVIEW("Memory"), Name);
}
}
}
FMemoryCacheStore::~FMemoryCacheStore()
{
bShuttingDown = true;
Disable();
if (StoreStats)
{
StoreOwner->DestroyStats(StoreStats);
}
}
void FMemoryCacheStore::Disable()
{
check(bCanBeDisabled || bShuttingDown);
FWriteScopeLock ScopeLock(SynchronizationObject);
bDisabled = true;
CacheRecords.Empty();
CacheValues.Empty();
CurrentCacheSize = 0;
}
void FMemoryCacheStore::LegacyStats(FDerivedDataCacheStatsNode& OutNode)
{
checkNoEntry();
}
bool FMemoryCacheStore::LegacyDebugOptions(FBackendDebugOptions& InOptions)
{
DebugOptions = InOptions;
return true;
}
void FMemoryCacheStore::Put(
const TConstArrayView<FCachePutRequest> Requests,
IRequestOwner& Owner,
FOnCachePutComplete&& OnComplete)
{
if (bDisabled)
{
return CompleteWithStatus(Requests, OnComplete, EStatus::Error);
}
for (const FCachePutRequest& Request : Requests)
{
EStatus Status = EStatus::Error;
ON_SCOPE_EXIT
{
OnComplete(Request.MakeResponse(Status));
};
const FCacheRecord& Record = Request.Record;
const FCacheKey& Key = Record.GetKey();
const TConstArrayView<FValueWithId> Values = Record.GetValues();
FRequestStats RequestStats;
RequestStats.Name = Request.Name;
RequestStats.Bucket = Key.Bucket;
RequestStats.Type = ERequestType::Record;
RequestStats.Op = ERequestOp::Put;
ON_SCOPE_EXIT
{
RequestStats.Latency = RequestStats.EndTime - RequestStats.StartTime;
RequestStats.Status = Status;
if (StoreStats)
{
StoreStats->AddRequest(RequestStats);
}
};
FRequestTimer RequestTimer(RequestStats);
if (Algo::NoneOf(Values, &FValue::HasData))
{
continue;
}
if (bCanBeDisabled && DebugOptions.ShouldSimulatePutMiss(Key))
{
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for put of %s from '%s'"),
*Name, *WriteToString<96>(Key), *Request.Name);
continue;
}
const bool bReplaceExisting = !EnumHasAnyFlags(Request.Policy.GetRecordPolicy(), ECachePolicy::QueryLocal);
FWriteScopeLock ScopeLock(SynchronizationObject);
FCacheRecordComponents& Components = CacheRecords.FindOrAdd(Key);
const bool bHasExisting = !Components.Values.IsEmpty();
Status = EStatus::Ok;
if (bHasExisting && !bReplaceExisting && Algo::AllOf(Components.Values, &FValue::HasData))
{
continue;
}
int64 RequiredSize = 0;
if (bHasExisting && !bReplaceExisting)
{
if (!Components.Meta && Record.GetMeta())
{
RequiredSize += Record.GetMeta().GetSize();
}
for (const FValueWithId& Value : Components.Values)
{
if (!Value.HasData())
{
if (const FValueWithId& NewValue = Record.GetValue(Value.GetId());
NewValue &&
NewValue.HasData() &&
NewValue.GetRawHash() == Value.GetRawHash())
{
RequiredSize += NewValue.GetData().GetCompressedSize();
}
else if (!EnumHasAnyFlags(Request.Policy.GetRecordPolicy(), ECachePolicy::PartialRecord) &&
EnumHasAnyFlags(Request.Policy.GetValuePolicy(Value.GetId()), ECachePolicy::StoreLocal))
{
Status = EStatus::Error;
}
}
}
}
else
{
RequiredSize = Record.GetMeta().GetSize() - Components.Meta.GetSize();
RequiredSize -= Algo::TransformAccumulate(Components.Values, [](const FValue& Value) { return Value.GetData().GetCompressedSize(); }, uint64(0));
for (const FValueWithId& NewValue : Record.GetValues())
{
if (NewValue.HasData())
{
RequiredSize += NewValue.GetData().GetCompressedSize();
}
else if (const int32 ValueIndex = Algo::BinarySearchBy(Components.Values, NewValue.GetId(), &FValueWithId::GetId);
ValueIndex != INDEX_NONE &&
Components.Values[ValueIndex].HasData() &&
Components.Values[ValueIndex].GetRawHash() == NewValue.GetRawHash())
{
RequiredSize += Components.Values[ValueIndex].GetData().GetCompressedSize();
}
else if (!EnumHasAnyFlags(Request.Policy.GetRecordPolicy(), ECachePolicy::PartialRecord) &&
EnumHasAnyFlags(Request.Policy.GetValuePolicy(NewValue.GetId()), ECachePolicy::StoreLocal))
{
Status = EStatus::Error;
}
}
}
if (Status == EStatus::Error)
{
continue;
}
if (MaxCacheSize > 0 && (CurrentCacheSize + RequiredSize) > MaxCacheSize)
{
UE_CLOG(!bMaxSizeExceeded, LogDerivedDataCache, Display,
TEXT("Failed to cache data. Maximum cache size reached. "
"CurrentSize %" UINT64_FMT " KiB / MaxSize: %" UINT64_FMT " KiB"),
CurrentCacheSize / 1024, MaxCacheSize / 1024);
bMaxSizeExceeded = true;
Status = EStatus::Ok;
continue;
}
Status = EStatus::Ok;
CurrentCacheSize += RequiredSize;
if (!bHasExisting)
{
Components.Meta = Record.GetMeta();
Components.Values = Record.GetValues();
RequestStats.LogicalWriteSize += Components.Meta.GetSize();
RequestStats.PhysicalWriteSize += Components.Meta.GetSize();
for (const FValue& Value : Components.Values)
{
RequestStats.AddLogicalWrite(Value);
RequestStats.PhysicalWriteSize += Value.GetData().GetCompressedSize();
}
}
else if (!bReplaceExisting)
{
if (!Components.Meta)
{
Components.Meta = Record.GetMeta();
RequestStats.LogicalWriteSize += Components.Meta.GetSize();
RequestStats.PhysicalWriteSize += Components.Meta.GetSize();
}
for (FValueWithId& Value : Components.Values)
{
if (!Value.HasData())
{
if (const FValueWithId& NewValue = Record.GetValue(Value.GetId()); NewValue && NewValue.GetRawHash() == Value.GetRawHash())
{
Value = NewValue;
RequestStats.AddLogicalWrite(Value);
RequestStats.PhysicalWriteSize += Value.GetData().GetCompressedSize();
}
}
}
}
else
{
FCacheRecordComponents ExistingComponents = MoveTemp(Components);
Components.Meta = Record.GetMeta();
Components.Values = Record.GetValues();
RequestStats.LogicalWriteSize += Components.Meta.GetSize();
RequestStats.PhysicalWriteSize += Components.Meta.GetSize();
for (FValueWithId& Value : Components.Values)
{
RequestStats.AddLogicalWrite(Value);
RequestStats.PhysicalWriteSize += Value.GetData().GetCompressedSize();
if (!Value.HasData())
{
if (const int32 ExistingValueIndex = Algo::BinarySearchBy(ExistingComponents.Values, Value.GetId(), &FValueWithId::GetId);
ExistingValueIndex != INDEX_NONE &&
ExistingComponents.Values[ExistingValueIndex].HasData() &&
ExistingComponents.Values[ExistingValueIndex].GetRawHash() == Value.GetRawHash())
{
Value = ExistingComponents.Values[ExistingValueIndex];
}
}
}
}
}
}
void FMemoryCacheStore::Get(
const TConstArrayView<FCacheGetRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetComplete&& OnComplete)
{
if (bDisabled)
{
return CompleteWithStatus(Requests, OnComplete, EStatus::Error);
}
for (const FCacheGetRequest& Request : Requests)
{
const FCacheKey& Key = Request.Key;
const FCacheRecordPolicy& Policy = Request.Policy;
FCacheRecordComponents Components;
EStatus Status = EStatus::Error;
FRequestStats RequestStats;
RequestStats.Name = Request.Name;
RequestStats.Bucket = Key.Bucket;
RequestStats.Type = ERequestType::Record;
RequestStats.Op = ERequestOp::Get;
ON_SCOPE_EXIT
{
RequestStats.Latency = RequestStats.EndTime - RequestStats.StartTime;
RequestStats.Status = Status;
if (StoreStats)
{
StoreStats->AddRequest(RequestStats);
}
};
FRequestTimer RequestTimer(RequestStats);
if (bCanBeDisabled && DebugOptions.ShouldSimulateGetMiss(Key))
{
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for get of %s from '%s'"),
*Name, *WriteToString<96>(Key), *Request.Name);
}
else if (FReadScopeLock ScopeLock(SynchronizationObject); const FCacheRecordComponents* FoundComponents = CacheRecords.Find(Key))
{
if (!FoundComponents->Values.IsEmpty())
{
Status = EStatus::Ok;
Components = *FoundComponents;
}
}
FCacheRecordBuilder Builder(Request.Key);
if (Status == EStatus::Ok)
{
const ECachePolicy RecordPolicy = Policy.GetRecordPolicy();
if (!EnumHasAnyFlags(RecordPolicy, ECachePolicy::SkipMeta) && Components.Meta)
{
Builder.SetMeta(CopyTemp(Components.Meta));
RequestStats.LogicalReadSize += Components.Meta.GetSize();
RequestStats.PhysicalReadSize += Components.Meta.GetSize();
}
for (const FValueWithId& Value : Components.Values)
{
const ECachePolicy ValuePolicy = Policy.GetValuePolicy(Value.GetId());
if (!EnumHasAnyFlags(ValuePolicy, ECachePolicy::QueryLocal))
{
Builder.AddValue(Value.RemoveData());
continue;
}
if (!Value.HasData())
{
Status = EStatus::Error;
if (!EnumHasAnyFlags(RecordPolicy, ECachePolicy::PartialRecord))
{
Builder = FCacheRecordBuilder(Request.Key);
break;
}
}
if (EnumHasAnyFlags(ValuePolicy, ECachePolicy::SkipData))
{
Builder.AddValue(Value.RemoveData());
}
else
{
Builder.AddValue(Value);
RequestStats.AddLogicalRead(Value);
RequestStats.PhysicalReadSize += Value.GetData().GetCompressedSize();
}
}
}
OnComplete({Request.Name, Builder.Build(), Request.UserData, Status});
}
}
void FMemoryCacheStore::Delete(const FCacheKey& Key, const FSharedString& DebugName)
{
FWriteScopeLock ScopeLock(SynchronizationObject);
FCacheRecordComponents Components;
if (CacheRecords.RemoveAndCopyValue(Key, Components))
{
CurrentCacheSize -= Components.Meta.GetSize() +
Algo::TransformAccumulate(Components.Values, [](const FValue& Value) { return Value.GetData().GetCompressedSize(); }, uint64(0));
bMaxSizeExceeded = false;
}
}
void FMemoryCacheStore::PutValue(
const TConstArrayView<FCachePutValueRequest> Requests,
IRequestOwner& Owner,
FOnCachePutValueComplete&& OnComplete)
{
if (bDisabled)
{
return CompleteWithStatus(Requests, OnComplete, EStatus::Error);
}
for (const FCachePutValueRequest& Request : Requests)
{
EStatus Status = EStatus::Error;
ON_SCOPE_EXIT
{
OnComplete(Request.MakeResponse(Status));
};
const FCacheKey& Key = Request.Key;
const FValue& Value = Request.Value;
FRequestStats RequestStats;
RequestStats.Name = Request.Name;
RequestStats.Bucket = Key.Bucket;
RequestStats.Type = ERequestType::Value;
RequestStats.Op = ERequestOp::Put;
ON_SCOPE_EXIT
{
RequestStats.Latency = RequestStats.EndTime - RequestStats.StartTime;
RequestStats.Status = Status;
if (StoreStats)
{
StoreStats->AddRequest(RequestStats);
}
};
FRequestTimer RequestTimer(RequestStats);
if (!Value.HasData())
{
continue;
}
if (bCanBeDisabled && DebugOptions.ShouldSimulatePutMiss(Key))
{
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for put of %s from '%s'"),
*Name, *WriteToString<96>(Key), *Request.Name);
continue;
}
const int64 ValueSize = Value.GetData().GetCompressedSize();
const bool bReplaceExisting = !EnumHasAnyFlags(Request.Policy, ECachePolicy::QueryLocal);
FWriteScopeLock ScopeLock(SynchronizationObject);
FValue* const ExistingValue = CacheValues.Find(Key);
if (ExistingValue && !bReplaceExisting)
{
Status = EStatus::Ok;
continue;
}
const int64 ExistingValueSize = ExistingValue ? ExistingValue->GetData().GetCompressedSize() : 0;
const int64 RequiredSize = ValueSize - ExistingValueSize;
if (MaxCacheSize > 0 && (CurrentCacheSize + RequiredSize) > MaxCacheSize)
{
UE_CLOG(!bMaxSizeExceeded, LogDerivedDataCache, Display,
TEXT("Failed to cache data. Maximum cache size reached. "
"CurrentSize %" UINT64_FMT " KiB / MaxSize: %" UINT64_FMT " KiB"),
CurrentCacheSize / 1024, MaxCacheSize / 1024);
bMaxSizeExceeded = true;
continue;
}
CurrentCacheSize += RequiredSize;
if (ExistingValue)
{
*ExistingValue = Value;
}
else
{
CacheValues.Add(Key, Value);
}
RequestStats.AddLogicalWrite(Value);
RequestStats.PhysicalWriteSize += Value.GetData().GetCompressedSize();
Status = EStatus::Ok;
}
}
void FMemoryCacheStore::GetValue(
const TConstArrayView<FCacheGetValueRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetValueComplete&& OnComplete)
{
if (bDisabled)
{
return CompleteWithStatus(Requests, OnComplete, EStatus::Error);
}
for (const FCacheGetValueRequest& Request : Requests)
{
const FCacheKey& Key = Request.Key;
const bool bExistsOnly = EnumHasAllFlags(Request.Policy, ECachePolicy::SkipData);
FValue Value;
EStatus Status = EStatus::Error;
FRequestStats RequestStats;
RequestStats.Name = Request.Name;
RequestStats.Bucket = Key.Bucket;
RequestStats.Type = ERequestType::Value;
RequestStats.Op = ERequestOp::Get;
ON_SCOPE_EXIT
{
RequestStats.Latency = RequestStats.EndTime - RequestStats.StartTime;
RequestStats.Status = Status;
if (StoreStats)
{
StoreStats->AddRequest(RequestStats);
}
};
FRequestTimer RequestTimer(RequestStats);
if (bCanBeDisabled && DebugOptions.ShouldSimulateGetMiss(Key))
{
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for get of %s from '%s'"),
*Name, *WriteToString<96>(Key), *Request.Name);
}
else if (bExistsOnly && bCanBeDisabled)
{
}
else if (FReadScopeLock ScopeLock(SynchronizationObject); const FValue* CacheValue = CacheValues.Find(Key))
{
Status = EStatus::Ok;
Value = !bExistsOnly ? *CacheValue : CacheValue->RemoveData();
}
RequestStats.AddLogicalRead(Value);
RequestStats.PhysicalReadSize += Value.GetData().GetCompressedSize();
OnComplete({Request.Name, Request.Key, MoveTemp(Value), Request.UserData, Status});
}
}
void FMemoryCacheStore::DeleteValue(const FCacheKey& Key, const FSharedString& DebugName)
{
FWriteScopeLock ScopeLock(SynchronizationObject);
FValue Value;
if (CacheValues.RemoveAndCopyValue(Key, Value))
{
CurrentCacheSize -= Value.GetData().GetCompressedSize();
bMaxSizeExceeded = false;
}
}
void FMemoryCacheStore::GetChunks(
const TConstArrayView<FCacheGetChunkRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetChunkComplete&& OnComplete)
{
if (bDisabled)
{
return CompleteWithStatus(Requests, OnComplete, EStatus::Error);
}
bool bHasValue = false;
FValue Value;
FValueId ValueId;
FCacheKey ValueKey;
FCompressedBufferReader Reader;
for (const FCacheGetChunkRequest& Request : Requests)
{
FRequestStats RequestStats;
RequestStats.Name = Request.Name;
RequestStats.Bucket = Request.Key.Bucket;
RequestStats.Type = Request.Id.IsNull() ? ERequestType::Value : ERequestType::Record;
RequestStats.Op = ERequestOp::GetChunk;
ON_SCOPE_EXIT
{
RequestStats.Latency = RequestStats.EndTime - RequestStats.StartTime;
if (StoreStats)
{
StoreStats->AddRequest(RequestStats);
}
};
FRequestTimer RequestTimer(RequestStats);
bool bProcessHit = false;
const bool bExistsOnly = EnumHasAnyFlags(Request.Policy, ECachePolicy::SkipData);
if (bCanBeDisabled && DebugOptions.ShouldSimulateGetMiss(Request.Key))
{
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for get of %s from '%s'"),
*Name, *WriteToString<96>(Request.Key, '/', Request.Id), *Request.Name);
}
else if (bHasValue && (ValueKey == Request.Key) && (ValueId == Request.Id) && (bExistsOnly || Reader.HasSource()))
{
// Value matches the request.
bProcessHit = true;
}
else
{
FReadScopeLock ScopeLock(SynchronizationObject);
if (Request.Id.IsValid())
{
if (const FCacheRecordComponents* Components = CacheRecords.Find(Request.Key))
{
if (const int32 ValueIndex = Algo::BinarySearchBy(Components->Values, Request.Id, &FValueWithId::GetId); ValueIndex != INDEX_NONE)
{
const FValueWithId& ValueWithId = Components->Values[ValueIndex];
bHasValue = ValueWithId.IsValid();
Reader.ResetSource();
Value.Reset();
Value = ValueWithId;
ValueId = Request.Id;
ValueKey = Request.Key;
Reader.SetSource(Value.GetData());
bProcessHit = true;
}
}
}
else
{
if (const FValue* ExistingValue = CacheValues.Find(Request.Key))
{
bHasValue = true;
Reader.ResetSource();
Value.Reset();
Value = *ExistingValue;
ValueId.Reset();
ValueKey = Request.Key;
Reader.SetSource(Value.GetData());
bProcessHit = true;
}
}
}
if (bProcessHit && Request.RawOffset <= Value.GetRawSize())
{
const uint64 RawSize = FMath::Min(Value.GetRawSize() - Request.RawOffset, Request.RawSize);
FSharedBuffer Buffer;
if (Value.HasData() && !bExistsOnly)
{
Buffer = Reader.Decompress(Request.RawOffset, RawSize);
RequestStats.LogicalReadSize += Buffer.GetSize();
}
const EStatus Status = bExistsOnly || Buffer ? EStatus::Ok : EStatus::Error;
RequestStats.Status = Status;
OnComplete({Request.Name, Request.Key, Request.Id, Request.RawOffset,
RawSize, Value.GetRawHash(), MoveTemp(Buffer), Request.UserData, Status});
}
else
{
RequestStats.Status = EStatus::Error;
OnComplete(Request.MakeResponse(EStatus::Error));
}
}
}
void CreateMemoryCacheStore(IMemoryCacheStore*& OutCache, const TCHAR* Name, const TCHAR* Config, ICacheStoreOwner* Owner)
{
new FMemoryCacheStore(OutCache, Name, Config, Owner);
}
} // UE::DerivedData