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

255 lines
6.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DerivedDataCacheKey.h"
#include "Containers/Map.h"
#include "Containers/Set.h"
#include "Containers/StringConv.h"
#include "DerivedDataCachePrivate.h"
#include "Hash/xxhash.h"
#include "IO/IoHash.h"
#include "Math/UnrealMathUtility.h"
#include "Misc/ScopeRWLock.h"
#include "Misc/StringBuilder.h"
#include "Serialization/CompactBinary.h"
#include "Serialization/CompactBinarySerialization.h"
#include "Serialization/CompactBinaryWriter.h"
#include "String/Find.h"
namespace UE::DerivedData::Private
{
class FCacheBucketOwner : public FCacheBucket
{
public:
inline explicit FCacheBucketOwner(FUtf8StringView Bucket);
inline FCacheBucketOwner(FCacheBucketOwner&& Bucket);
inline ~FCacheBucketOwner();
FCacheBucketOwner(const FCacheBucketOwner&) = delete;
FCacheBucketOwner& operator=(const FCacheBucketOwner&) = delete;
using FCacheBucket::operator==;
inline bool operator==(const FCacheBucketOwner& Other) const { return FCacheBucket::operator==(Other); }
inline bool operator==(FUtf8StringView Bucket) const { return ToString() == Bucket; }
};
inline FCacheBucketOwner::FCacheBucketOwner(FUtf8StringView Bucket)
{
checkf(FCacheBucket::IsValidName(Bucket),
TEXT("A cache bucket name must be alphanumeric, non-empty, and contain at most %d code units. Name: '%s'"),
FCacheBucket::MaxNameLen, *WriteToString<256>(Bucket));
static_assert(sizeof(ANSICHAR) == sizeof(UTF8CHAR));
const int32 BucketLen = Bucket.Len();
const int32 PrefixSize = FMath::DivideAndRoundUp<int32>(1, sizeof(ANSICHAR));
UTF8CHAR* Buffer = new UTF8CHAR[PrefixSize + BucketLen + 1];
Buffer += PrefixSize;
Name = reinterpret_cast<ANSICHAR*>(Buffer);
reinterpret_cast<uint8*>(Buffer)[LengthOffset] = static_cast<uint8>(BucketLen);
Bucket.CopyString(Buffer, BucketLen);
Buffer += BucketLen;
*Buffer = UTF8CHAR('\0');
}
inline FCacheBucketOwner::FCacheBucketOwner(FCacheBucketOwner&& Bucket)
: FCacheBucket(Bucket)
{
Bucket.Reset();
}
inline FCacheBucketOwner::~FCacheBucketOwner()
{
if (Name)
{
const int32 PrefixSize = FMath::DivideAndRoundUp<int32>(1, sizeof(ANSICHAR));
delete[] (Name - PrefixSize);
}
}
struct FCacheBucketOwnerKeyFuncs : DefaultKeyFuncs<FCacheBucketOwner>
{
static uint32 GetKeyHash(const FUtf8StringView Key)
{
const int32 Len = Key.Len();
check(Len <= FCacheBucket::MaxNameLen);
UTF8CHAR LowerKey[FCacheBucket::MaxNameLen];
UTF8CHAR* LowerKeyIt = LowerKey;
for (const UTF8CHAR& Char : Key)
{
*LowerKeyIt++ = TChar<UTF8CHAR>::ToLower(Char);
}
return uint32(FXxHash64::HashBuffer(LowerKey, Len).Hash);
}
static uint32 GetKeyHash(const FCacheBucketOwner& Key)
{
return GetKeyHash(Key.ToString());
}
};
class FCacheBuckets
{
public:
template <typename CharType>
inline FCacheBucket FindOrAdd(TStringView<CharType> Name);
inline void GetDisplayName(FCacheBucket Bucket, FStringBuilderBase& OutDisplayName);
inline void SetDisplayName(FCacheBucket Bucket, FStringView DisplayName);
private:
FRWLock Lock;
TSet<FCacheBucketOwner, FCacheBucketOwnerKeyFuncs> Buckets;
TMap<FCacheBucket, FString> DisplayNames;
};
template <typename CharType>
inline FCacheBucket FCacheBuckets::FindOrAdd(const TStringView<CharType> Name)
{
const auto NameCast = StringCast<UTF8CHAR, FCacheBucket::MaxNameLen + 1>(Name.GetData(), Name.Len());
const FUtf8StringView NameView = NameCast;
uint32 Hash = 0;
if (NameView.Len() <= FCacheBucket::MaxNameLen)
{
Hash = FCacheBucketOwnerKeyFuncs::GetKeyHash(NameView);
FReadScopeLock ReadLock(Lock);
if (const FCacheBucketOwner* Bucket = Buckets.FindByHash(Hash, NameView))
{
return *Bucket;
}
}
FCacheBucketOwner LocalBucket(NameView);
FWriteScopeLock WriteLock(Lock);
return Buckets.FindOrAddByHash(Hash, MoveTemp(LocalBucket));
}
void FCacheBuckets::GetDisplayName(FCacheBucket Bucket, FStringBuilderBase& OutDisplayName)
{
if (FReadScopeLock ReadLock(Lock); const FString* DisplayName = DisplayNames.Find(Bucket))
{
OutDisplayName << *DisplayName;
return;
}
FAnsiStringView BucketName = Bucket.ToString();
if (BucketName.StartsWith(ANSITEXTVIEW("Legacy")))
{
BucketName.RightChopInline(TEXTVIEW("Legacy").Len());
}
OutDisplayName << BucketName;
}
void FCacheBuckets::SetDisplayName(FCacheBucket Bucket, FStringView DisplayName)
{
if (FReadScopeLock ReadLock(Lock); const FString* ExistingDisplayName = DisplayNames.Find(Bucket))
{
if (*ExistingDisplayName == DisplayName)
{
return;
}
}
FWriteScopeLock WriteLock(Lock);
DisplayNames.Emplace(Bucket, DisplayName);
}
static FCacheBuckets& GetCacheBuckets()
{
static FCacheBuckets Buckets;
return Buckets;
}
} // UE::DerivedData::Private
namespace UE::DerivedData
{
FCacheBucket::FCacheBucket(FUtf8StringView InName)
: FCacheBucket(Private::GetCacheBuckets().FindOrAdd(InName))
{
}
FCacheBucket::FCacheBucket(FWideStringView InName)
: FCacheBucket(Private::GetCacheBuckets().FindOrAdd(InName))
{
}
FCacheBucket::FCacheBucket(FUtf8StringView Name, FStringView DisplayName)
: FCacheBucket(Name)
{
if (!DisplayName.IsEmpty())
{
Private::GetCacheBuckets().SetDisplayName(*this, DisplayName);
}
}
FCacheBucket::FCacheBucket(FWideStringView Name, FStringView DisplayName)
: FCacheBucket(Name)
{
if (!DisplayName.IsEmpty())
{
Private::GetCacheBuckets().SetDisplayName(*this, DisplayName);
}
}
void FCacheBucket::ToDisplayName(FStringBuilderBase& OutDisplayName) const
{
Private::GetCacheBuckets().GetDisplayName(*this, OutDisplayName);
}
FCbWriter& operator<<(FCbWriter& Writer, const FCacheBucket Bucket)
{
Writer.AddString(Bucket.ToString());
return Writer;
}
bool LoadFromCompactBinary(FCbFieldView Field, FCacheBucket& OutBucket)
{
if (const FUtf8StringView Bucket = Field.AsString(); !Field.HasError() && FCacheBucket::IsValidName(Bucket))
{
OutBucket = FCacheBucket(Bucket);
return true;
}
OutBucket.Reset();
return false;
}
FCbWriter& operator<<(FCbWriter& Writer, const FCacheKey& Key)
{
Writer.BeginObject();
Writer << ANSITEXTVIEW("Bucket") << Key.Bucket;
Writer << ANSITEXTVIEW("Hash") << Key.Hash;
Writer.EndObject();
return Writer;
}
void SerializeForLog(FCbWriter& Writer, const FCacheKey& Key)
{
Writer.AddString(WriteToString<96>(Key));
}
bool LoadFromCompactBinary(const FCbFieldView Field, FCacheKey& OutKey)
{
bool bOk = Field.IsObject();
bOk &= LoadFromCompactBinary(Field[ANSITEXTVIEW("Bucket")], OutKey.Bucket);
bOk &= LoadFromCompactBinary(Field[ANSITEXTVIEW("Hash")], OutKey.Hash);
return bOk;
}
FCacheKey ConvertLegacyCacheKey(const FStringView Key)
{
FTCHARToUTF8 Utf8Key(Key);
TUtf8StringBuilder<64> Utf8Bucket;
Utf8Bucket << ANSITEXTVIEW("Legacy");
if (const int32 BucketEnd = String::FindFirstChar(Utf8Key, '_'); BucketEnd != INDEX_NONE)
{
Utf8Bucket << FUtf8StringView(Utf8Key).Left(BucketEnd);
}
const FCacheBucket Bucket(Utf8Bucket);
return {Bucket, FIoHash::HashBuffer(MakeMemoryView(Utf8Key))};
}
} // UE::DerivedData