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

313 lines
9.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DerivedDataCachePolicy.h"
#include "Algo/Accumulate.h"
#include "Algo/BinarySearch.h"
#include "Containers/StringConv.h"
#include "Containers/StringView.h"
#include "Misc/StringBuilder.h"
#include "Serialization/CompactBinary.h"
#include "Serialization/CompactBinaryWriter.h"
#include "String/ParseTokens.h"
#include "Templates/Function.h"
namespace UE::DerivedData::Private { class FCacheRecordPolicyShared; }
namespace UE::DerivedData::Private
{
static constexpr ANSICHAR CachePolicyDelimiter = ',';
struct FCachePolicyToText
{
ECachePolicy Policy;
FAnsiStringView Text;
};
static const FCachePolicyToText CachePolicyToText[]
{
// Flags with multiple bits are ordered by bit count to minimize token count in the text format.
{ ECachePolicy::Default, ANSITEXTVIEW("Default") },
{ ECachePolicy::Remote, ANSITEXTVIEW("Remote") },
{ ECachePolicy::Local, ANSITEXTVIEW("Local") },
{ ECachePolicy::Store, ANSITEXTVIEW("Store") },
{ ECachePolicy::Query, ANSITEXTVIEW("Query") },
// Flags with only one bit can be in any order. Match the order in ECachePolicy.
{ ECachePolicy::QueryLocal, ANSITEXTVIEW("QueryLocal") },
{ ECachePolicy::QueryRemote, ANSITEXTVIEW("QueryRemote") },
{ ECachePolicy::StoreLocal, ANSITEXTVIEW("StoreLocal") },
{ ECachePolicy::StoreRemote, ANSITEXTVIEW("StoreRemote") },
{ ECachePolicy::SkipMeta, ANSITEXTVIEW("SkipMeta") },
{ ECachePolicy::SkipData, ANSITEXTVIEW("SkipData") },
{ ECachePolicy::PartialRecord, ANSITEXTVIEW("PartialRecord") },
{ ECachePolicy::KeepAlive, ANSITEXTVIEW("KeepAlive") },
// None must be last because it matches every policy.
{ ECachePolicy::None, ANSITEXTVIEW("None") },
};
static constexpr ECachePolicy CachePolicyKnownFlags
= ECachePolicy::Default
| ECachePolicy::SkipMeta
| ECachePolicy::SkipData
| ECachePolicy::PartialRecord
| ECachePolicy::KeepAlive;
template <typename CharType>
static TStringBuilderBase<CharType>& CachePolicyToString(TStringBuilderBase<CharType>& Builder, ECachePolicy Policy)
{
// Mask out unknown flags. None will be written if no flags are known.
Policy &= CachePolicyKnownFlags;
for (const FCachePolicyToText& Pair : CachePolicyToText)
{
if (EnumHasAllFlags(Policy, Pair.Policy))
{
EnumRemoveFlags(Policy, Pair.Policy);
Builder << Pair.Text << CachePolicyDelimiter;
if (Policy == ECachePolicy::None)
{
break;
}
}
}
Builder.RemoveSuffix(1);
return Builder;
}
template <typename CharType>
static bool CachePolicyFromString(ECachePolicy& OutPolicy, const TStringView<CharType> String)
{
if (String.IsEmpty())
{
return false;
}
String::ParseTokens(StringCast<UTF8CHAR, 128>(String.GetData(), String.Len()), UTF8CHAR(CachePolicyDelimiter),
[&OutPolicy, Index = int32(0)](FUtf8StringView Token) mutable
{
const int32 EndIndex = Index;
for (; Index < UE_ARRAY_COUNT(CachePolicyToText); ++Index)
{
if (CachePolicyToText[Index].Text == Token)
{
OutPolicy |= CachePolicyToText[Index].Policy;
++Index;
return;
}
}
for (Index = 0; Index < EndIndex; ++Index)
{
if (CachePolicyToText[Index].Text == Token)
{
OutPolicy |= CachePolicyToText[Index].Policy;
++Index;
return;
}
}
});
return true;
}
template <typename CharType>
static ECachePolicy ParseCachePolicy(const TStringView<CharType> Text)
{
checkf(!Text.IsEmpty(), TEXT("ParseCachePolicy requires a non-empty string."));
ECachePolicy Policy = ECachePolicy::None;
CachePolicyFromString(Policy, Text);
return Policy;
}
} // UE::DerivedData::Private
namespace UE::DerivedData
{
ECachePolicy CombineCachePolicy(const ECachePolicy A, const ECachePolicy B)
{
constexpr ECachePolicy NegativeFlags = ECachePolicy::SkipData | ECachePolicy::SkipMeta;
return ((A | B) & ~NegativeFlags) | ((A & B) & NegativeFlags);
}
FAnsiStringBuilderBase& operator<<(FAnsiStringBuilderBase& Builder, ECachePolicy Policy) { return Private::CachePolicyToString(Builder, Policy); }
FWideStringBuilderBase& operator<<(FWideStringBuilderBase& Builder, ECachePolicy Policy) { return Private::CachePolicyToString(Builder, Policy); }
FUtf8StringBuilderBase& operator<<(FUtf8StringBuilderBase& Builder, ECachePolicy Policy) { return Private::CachePolicyToString(Builder, Policy); }
bool TryLexFromString(ECachePolicy& OutPolicy, const FUtf8StringView String) { return Private::CachePolicyFromString(OutPolicy, String); }
bool TryLexFromString(ECachePolicy& OutPolicy, const FWideStringView String) { return Private::CachePolicyFromString(OutPolicy, String); }
ECachePolicy ParseCachePolicy(FAnsiStringView Text) { return Private::ParseCachePolicy(Text); }
ECachePolicy ParseCachePolicy(FWideStringView Text) { return Private::ParseCachePolicy(Text); }
ECachePolicy ParseCachePolicy(FUtf8StringView Text) { return Private::ParseCachePolicy(Text); }
FCbWriter& operator<<(FCbWriter& Writer, const ECachePolicy Policy)
{
Writer.AddString(WriteToUtf8String<64>(Policy));
return Writer;
}
bool LoadFromCompactBinary(FCbFieldView Field, ECachePolicy& OutPolicy, const ECachePolicy Default)
{
if (TryLexFromString(OutPolicy, Field.AsString()))
{
return true;
}
OutPolicy = Default;
return false;
}
FCbWriter& operator<<(FCbWriter& Writer, const FCacheValuePolicy& Policy)
{
Writer.BeginObject();
Writer << ANSITEXTVIEW("Id") << Policy.Id;
Writer << ANSITEXTVIEW("Policy") << Policy.Policy;
Writer.EndObject();
return Writer;
}
bool LoadFromCompactBinary(FCbFieldView Field, FCacheValuePolicy& OutPolicy)
{
bool bOk = Field.IsObject();
bOk &= LoadFromCompactBinary(Field[ANSITEXTVIEW("Id")], OutPolicy.Id);
bOk &= LoadFromCompactBinary(Field[ANSITEXTVIEW("Policy")], OutPolicy.Policy);
return bOk;
}
class Private::FCacheRecordPolicyShared final : public Private::ICacheRecordPolicyShared
{
public:
inline void AddRef() const final
{
ReferenceCount.fetch_add(1, std::memory_order_relaxed);
}
inline void Release() const final
{
if (ReferenceCount.fetch_sub(1, std::memory_order_acq_rel) == 1)
{
delete this;
}
}
inline void AddValuePolicy(const FCacheValuePolicy& Value) final
{
checkf(Value.Id.IsValid(), TEXT("Failed to add value policy because the ID is null."));
const int32 Index = Algo::LowerBoundBy(Values, Value.Id, &FCacheValuePolicy::Id);
checkf(!(Values.IsValidIndex(Index) && Values[Index].Id == Value.Id),
TEXT("Failed to add value policy with ID %s because it has an existing value policy with that ID. "
"New: %s. Existing: %s."),
*WriteToString<32>(Value.Id), *WriteToString<128>(Value.Policy), *WriteToString<128>(Values[Index].Policy));
Values.Insert(Value, Index);
}
inline TConstArrayView<FCacheValuePolicy> GetValuePolicies() const final
{
return Values;
}
private:
TArray<FCacheValuePolicy, TInlineAllocator<14>> Values;
mutable std::atomic<uint32> ReferenceCount{0};
};
ECachePolicy FCacheRecordPolicy::GetValuePolicy(const FValueId& Id) const
{
if (Shared)
{
const TConstArrayView<FCacheValuePolicy> Values = Shared->GetValuePolicies();
if (const int32 Index = Algo::BinarySearchBy(Values, Id, &FCacheValuePolicy::Id); Index != INDEX_NONE)
{
return Values[Index].Policy;
}
}
return DefaultValuePolicy;
}
FCacheRecordPolicy FCacheRecordPolicy::Transform(const TFunctionRef<ECachePolicy (ECachePolicy)> Op) const
{
if (IsUniform())
{
return Op(RecordPolicy);
}
FCacheRecordPolicyBuilder Builder(Op(GetBasePolicy()));
for (const FCacheValuePolicy& Value : GetValuePolicies())
{
Builder.AddValuePolicy({Value.Id, Op(Value.Policy) & FCacheValuePolicy::PolicyMask});
}
return Builder.Build();
}
void FCacheRecordPolicyBuilder::AddValuePolicy(const FCacheValuePolicy& Value)
{
checkf(!EnumHasAnyFlags(Value.Policy, ~Value.PolicyMask),
TEXT("Value policy contains flags that only make sense on the record policy. Policy: %s"),
*WriteToString<128>(Value.Policy));
if (Value.Policy == (BasePolicy & Value.PolicyMask))
{
return;
}
if (!Shared)
{
Shared = new Private::FCacheRecordPolicyShared;
}
Shared->AddValuePolicy(Value);
}
FCacheRecordPolicy FCacheRecordPolicyBuilder::Build()
{
FCacheRecordPolicy Policy(BasePolicy);
if (Shared)
{
const TConstArrayView<FCacheValuePolicy> Values = Shared->GetValuePolicies();
Policy.RecordPolicy = Algo::TransformAccumulate(Values, &FCacheValuePolicy::Policy, BasePolicy, CombineCachePolicy);
Policy.Shared = MoveTemp(Shared);
}
return Policy;
}
FCbWriter& operator<<(FCbWriter& Writer, const FCacheRecordPolicy& Policy)
{
Writer.BeginObject();
Writer << ANSITEXTVIEW("BasePolicy") << Policy.GetBasePolicy();
if (!Policy.IsUniform())
{
Writer.BeginArray(ANSITEXTVIEW("ValuePolicies"));
for (const FCacheValuePolicy& Value : Policy.GetValuePolicies())
{
Writer << Value;
}
Writer.EndArray();
}
Writer.EndObject();
return Writer;
}
bool LoadFromCompactBinary(FCbFieldView Field, FCacheRecordPolicy& OutPolicy)
{
ECachePolicy BasePolicy;
if (!LoadFromCompactBinary(Field[ANSITEXTVIEW("BasePolicy")], BasePolicy))
{
OutPolicy = {};
return false;
}
FCacheRecordPolicyBuilder Builder(BasePolicy);
for (FCbFieldView Value : Field[ANSITEXTVIEW("ValuePolicies")])
{
FCacheValuePolicy ValuePolicy;
if (!LoadFromCompactBinary(Value, ValuePolicy) ||
ValuePolicy.Id.IsNull() ||
EnumHasAnyFlags(ValuePolicy.Policy, ~FCacheValuePolicy::PolicyMask))
{
OutPolicy = {};
return false;
}
Builder.AddValuePolicy(ValuePolicy);
}
OutPolicy = Builder.Build();
return true;
}
} // UE::DerivedData