// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include namespace zen { using namespace std::literals; namespace detail::CachePolicyImpl { constexpr char DelimiterChar = ','; constexpr std::string_view None = "None"sv; constexpr std::string_view QueryLocal = "QueryLocal"sv; constexpr std::string_view QueryRemote = "QueryRemote"sv; constexpr std::string_view Query = "Query"sv; constexpr std::string_view StoreLocal = "StoreLocal"sv; constexpr std::string_view StoreRemote = "StoreRemote"sv; constexpr std::string_view Store = "Store"sv; constexpr std::string_view SkipMeta = "SkipMeta"sv; constexpr std::string_view SkipData = "SkipData"sv; constexpr std::string_view PartialRecord = "PartialRecord"sv; constexpr std::string_view KeepAlive = "KeepAlive"sv; constexpr std::string_view Local = "Local"sv; constexpr std::string_view Remote = "Remote"sv; constexpr std::string_view Default = "Default"sv; constexpr std::string_view Disable = "Disable"sv; using TextToPolicyMap = std::unordered_map; const TextToPolicyMap TextToPolicy = {{None, CachePolicy::None}, {QueryLocal, CachePolicy::QueryLocal}, {QueryRemote, CachePolicy::QueryRemote}, {Query, CachePolicy::Query}, {StoreLocal, CachePolicy::StoreLocal}, {StoreRemote, CachePolicy::StoreRemote}, {Store, CachePolicy::Store}, {SkipMeta, CachePolicy::SkipMeta}, {SkipData, CachePolicy::SkipData}, {PartialRecord, CachePolicy::PartialRecord}, {KeepAlive, CachePolicy::KeepAlive}, {Local, CachePolicy::Local}, {Remote, CachePolicy::Remote}, {Default, CachePolicy::Default}, {Disable, CachePolicy::Disable}}; using PolicyTextPair = std::pair; const PolicyTextPair FlagsToString[]{ // Order of these Flags is important: we want the aliases before the atomic values, // and the bigger aliases first, to reduce the number of tokens we add {CachePolicy::Default, Default}, {CachePolicy::Remote, Remote}, {CachePolicy::Local, Local}, {CachePolicy::Store, Store}, {CachePolicy::Query, Query}, // Order of Atomics doesn't matter, so arbitrarily we list them in enum order {CachePolicy::QueryLocal, QueryLocal}, {CachePolicy::QueryRemote, QueryRemote}, {CachePolicy::StoreLocal, StoreLocal}, {CachePolicy::StoreRemote, StoreRemote}, {CachePolicy::SkipMeta, SkipMeta}, {CachePolicy::SkipData, SkipData}, {CachePolicy::PartialRecord, PartialRecord}, {CachePolicy::KeepAlive, KeepAlive}, // None must come at the end of the array, to write out only if no others exist {CachePolicy::None, None}, }; constexpr CachePolicy KnownFlags = CachePolicy::Default | CachePolicy::SkipMeta | CachePolicy::SkipData | CachePolicy::KeepAlive | CachePolicy::PartialRecord; } // namespace detail::CachePolicyImpl StringBuilderBase& AppendToBuilderImpl(StringBuilderBase& Builder, CachePolicy Policy) { // Remove any bits we don't recognize; write None if there are not any bits we recognize Policy = Policy & detail::CachePolicyImpl::KnownFlags; for (const detail::CachePolicyImpl::PolicyTextPair& Pair : detail::CachePolicyImpl::FlagsToString) { if (EnumHasAllFlags(Policy, Pair.first)) { EnumRemoveFlags(Policy, Pair.first); Builder << Pair.second << detail::CachePolicyImpl::DelimiterChar; if (Policy == CachePolicy::None) { break; } } } Builder.RemoveSuffix(1); // Text will have been added by CachePolicy::None if not by anything else return Builder; } StringBuilderBase& operator<<(StringBuilderBase& Builder, CachePolicy Policy) { return AppendToBuilderImpl(Builder, Policy); } CachePolicy ParseCachePolicy(std::string_view Text) { ZEN_ASSERT(!Text.empty()); // Empty string is not valid input to ParseCachePolicy CachePolicy Result = CachePolicy::None; ForEachStrTok(Text, detail::CachePolicyImpl::DelimiterChar, [&Result](const std::string_view& Token) { auto it = detail::CachePolicyImpl::TextToPolicy.find(Token); if (it != detail::CachePolicyImpl::TextToPolicy.end()) { Result |= it->second; } return true; }); return Result; } namespace Private { class CacheRecordPolicyShared final : public ICacheRecordPolicyShared { public: inline std::span GetValuePolicies() const final { return Values; } inline void AddValuePolicy(const CacheValuePolicy& Policy) final { Values.push_back(Policy); } inline void Build() final { std::sort(Values.begin(), Values.end(), [](const CacheValuePolicy& A, const CacheValuePolicy& B) { return A.Id < B.Id; }); } private: std::vector Values; }; } // namespace Private CachePolicy CacheRecordPolicy::GetValuePolicy(const Oid& Id) const { if (Shared) { if (std::span Values = Shared->GetValuePolicies(); !Values.empty()) { auto Iter = std::lower_bound(Values.begin(), Values.end(), Id, [](const CacheValuePolicy& A, const Oid& B) { return A.Id < B; }); if (Iter != Values.end() && Iter->Id == Id) { return Iter->Policy; } } } return DefaultValuePolicy; } void CacheRecordPolicy::Save(CbWriter& Writer) const { Writer.BeginObject(); { // The RecordPolicy is calculated from the ValuePolicies and does not need to be saved separately. Writer << "DefaultValuePolicy"sv << WriteToString<128>(GetDefaultValuePolicy()); if (!IsUniform()) { // FCacheRecordPolicyBuilder guarantees IsUniform -> non-empty GetValuePolicies. Small size penalty here if not. Writer.BeginArray("ValuePolicies"sv); { for (const CacheValuePolicy& ValuePolicy : GetValuePolicies()) { // FCacheRecordPolicyBuilder is responsible for ensuring that each ValuePolicy != DefaultValuePolicy // If it lets any duplicates through we will incur a small serialization size penalty here Writer.BeginObject(); Writer << "Id"sv << ValuePolicy.Id; Writer << "Policy"sv << WriteToString<128>(ValuePolicy.Policy); Writer.EndObject(); } } Writer.EndArray(); } } Writer.EndObject(); } CacheRecordPolicy CacheRecordPolicy::Load(CbObjectView Object, CachePolicy DefaultPolicy) { std::string_view PolicyText = Object["DefaultValuePolicy"sv].AsString(); CachePolicy DefaultValuePolicy = !PolicyText.empty() ? ParseCachePolicy(PolicyText) : DefaultPolicy; CacheRecordPolicyBuilder Builder(DefaultValuePolicy); for (CbFieldView ValueObjectField : Object["ValuePolicies"sv]) { CbObjectView ValueObject = ValueObjectField.AsObjectView(); const Oid ValueId = ValueObject["Id"sv].AsObjectId(); PolicyText = ValueObject["Policy"sv].AsString(); CachePolicy ValuePolicy = !PolicyText.empty() ? ParseCachePolicy(PolicyText) : DefaultValuePolicy; // FCacheRecordPolicyBuilder should guarantee that FValueId(ValueId).IsValid and ValuePolicy != DefaultValuePolicy // If it lets any through we will have unused data in the record we create. Builder.AddValuePolicy(ValueId, ValuePolicy); } return Builder.Build(); } CacheRecordPolicy CacheRecordPolicy::ConvertToUpstream() const { auto DownstreamToUpstream = [](CachePolicy P) { // Remote|Local -> Set Remote // Delete Skip Flags // Maintain Remaining Flags return (EnumHasAllFlags(P, CachePolicy::QueryRemote) ? CachePolicy::QueryLocal : CachePolicy::None) | (EnumHasAllFlags(P, CachePolicy::StoreRemote) ? CachePolicy::StoreLocal : CachePolicy::None) | (P & ~(CachePolicy::SkipData | CachePolicy::SkipMeta)); }; CacheRecordPolicyBuilder Builder(DownstreamToUpstream(GetDefaultValuePolicy())); for (const CacheValuePolicy& ValuePolicy : GetValuePolicies()) { Builder.AddValuePolicy(ValuePolicy.Id, DownstreamToUpstream(ValuePolicy.Policy)); } return Builder.Build(); } void CacheRecordPolicyBuilder::AddValuePolicy(const CacheValuePolicy& Policy) { if (!Shared) { Shared = new Private::CacheRecordPolicyShared; } Shared->AddValuePolicy(Policy); } CacheRecordPolicy CacheRecordPolicyBuilder::Build() { CacheRecordPolicy Policy(BasePolicy); if (Shared) { Shared->Build(); const auto PolicyOr = [](CachePolicy A, CachePolicy B) { return A | (B & ~CachePolicy::SkipData); }; const std::span Values = Shared->GetValuePolicies(); Policy.RecordPolicy = BasePolicy; for (const CacheValuePolicy& ValuePolicy : Values) { Policy.RecordPolicy = PolicyOr(Policy.RecordPolicy, ValuePolicy.Policy); } Policy.Shared = std::move(Shared); } return Policy; } } // namespace zen