aboutsummaryrefslogtreecommitdiff
path: root/zenutil/cache/cachepolicy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zenutil/cache/cachepolicy.cpp')
-rw-r--r--zenutil/cache/cachepolicy.cpp326
1 files changed, 172 insertions, 154 deletions
diff --git a/zenutil/cache/cachepolicy.cpp b/zenutil/cache/cachepolicy.cpp
index 3bf7a0c67..7aa36d4d3 100644
--- a/zenutil/cache/cachepolicy.cpp
+++ b/zenutil/cache/cachepolicy.cpp
@@ -10,149 +10,156 @@
#include <algorithm>
#include <unordered_map>
+namespace zen::Private {
+class CacheRecordPolicyShared;
+}
+
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;
+namespace DerivedData::Private {
- using TextToPolicyMap = std::unordered_map<std::string_view, CachePolicy>;
- 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}};
+ constexpr char CachePolicyDelimiter = ',';
- using PolicyTextPair = std::pair<CachePolicy, std::string_view>;
- 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},
+ struct CachePolicyToTextData
+ {
+ CachePolicy Policy;
+ std::string_view Text;
+ };
- // None must come at the end of the array, to write out only if no others exist
- {CachePolicy::None, None},
+ const CachePolicyToTextData CachePolicyToText[]{
+ // Flags with multiple bits are ordered by bit count to minimize token count in the text format.
+ {CachePolicy::Default, "Default"sv},
+ {CachePolicy::Remote, "Remote"sv},
+ {CachePolicy::Local, "Local"sv},
+ {CachePolicy::Store, "Store"sv},
+ {CachePolicy::Query, "Query"sv},
+ // Flags with only one bit can be in any order. Match the order in CachePolicy.
+ {CachePolicy::QueryLocal, "QueryLocal"sv},
+ {CachePolicy::QueryRemote, "QueryRemote"sv},
+ {CachePolicy::StoreLocal, "StoreLocal"sv},
+ {CachePolicy::StoreRemote, "StoreRemote"sv},
+ {CachePolicy::SkipMeta, "SkipMeta"sv},
+ {CachePolicy::SkipData, "SkipData"sv},
+ {CachePolicy::PartialRecord, "PartialRecord"sv},
+ {CachePolicy::KeepAlive, "KeepAlive"sv},
+ // None must be last because it matches every policy.
+ {CachePolicy::None, "None"sv},
};
- 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)
+ constexpr CachePolicy CachePolicyKnownFlags =
+ CachePolicy::Default | CachePolicy::SkipMeta | CachePolicy::SkipData | CachePolicy::PartialRecord | CachePolicy::KeepAlive;
+
+ StringBuilderBase& CachePolicyToString(StringBuilderBase& Builder, CachePolicy Policy)
{
- if (EnumHasAllFlags(Policy, Pair.first))
+ // Mask out unknown flags. None will be written if no flags are known.
+ Policy &= CachePolicyKnownFlags;
+ for (const CachePolicyToTextData& Pair : CachePolicyToText)
{
- EnumRemoveFlags(Policy, Pair.first);
- Builder << Pair.second << detail::CachePolicyImpl::DelimiterChar;
- if (Policy == CachePolicy::None)
+ if (EnumHasAllFlags(Policy, Pair.Policy))
{
- break;
+ EnumRemoveFlags(Policy, Pair.Policy);
+ Builder << Pair.Text << CachePolicyDelimiter;
+ if (Policy == CachePolicy::None)
+ {
+ break;
+ }
}
}
+ Builder.RemoveSuffix(1);
+ return Builder;
}
- Builder.RemoveSuffix(1); // Text will have been added by CachePolicy::None if not by anything else
- return Builder;
-}
+
+ CachePolicy ParseCachePolicy(const std::string_view Text)
+ {
+ ZEN_ASSERT(!Text.empty()); // ParseCachePolicy requires a non-empty string
+ CachePolicy Policy = CachePolicy::None;
+ ForEachStrTok(Text, CachePolicyDelimiter, [&Policy, Index = int32_t(0)](const std::string_view& Token) mutable {
+ const int32_t EndIndex = Index;
+ for (; Index < sizeof(CachePolicyToText) / sizeof(CachePolicyToText[0]); ++Index)
+ {
+ if (CachePolicyToText[Index].Text == Token)
+ {
+ Policy |= CachePolicyToText[Index].Policy;
+ ++Index;
+ return true;
+ }
+ }
+ for (Index = 0; Index < EndIndex; ++Index)
+ {
+ if (CachePolicyToText[Index].Text == Token)
+ {
+ Policy |= CachePolicyToText[Index].Policy;
+ ++Index;
+ return true;
+ }
+ }
+ return true;
+ });
+ return Policy;
+ }
+
+} // namespace DerivedData::Private
+
StringBuilderBase&
operator<<(StringBuilderBase& Builder, CachePolicy Policy)
{
- return AppendToBuilderImpl(Builder, Policy);
+ return DerivedData::Private::CachePolicyToString(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;
+ return DerivedData::Private::ParseCachePolicy(Text);
}
-namespace Private {
+CachePolicy
+ConvertToUpstream(CachePolicy Policy)
+{
+ // Set Local flags equal to downstream's Remote flags.
+ // Delete Skip flags if StoreLocal is true, otherwise use the downstream value.
+ // Use the downstream value for all other flags.
+ return (EnumHasAllFlags(Policy, CachePolicy::QueryRemote) ? CachePolicy::QueryLocal : CachePolicy::None) |
+ (EnumHasAllFlags(Policy, CachePolicy::StoreRemote) ? CachePolicy::StoreLocal : CachePolicy::None) |
+ (!EnumHasAllFlags(Policy, CachePolicy::StoreLocal) ? (Policy & (CachePolicy::SkipData | CachePolicy::SkipMeta))
+ : CachePolicy::None) |
+ (Policy & ~(CachePolicy::Local | CachePolicy::SkipData | CachePolicy::SkipMeta));
+}
- class CacheRecordPolicyShared final : public ICacheRecordPolicyShared
+class Private::CacheRecordPolicyShared final : public Private::ICacheRecordPolicyShared
+{
+public:
+ inline void AddValuePolicy(const CacheValuePolicy& Value) final
{
- public:
- inline std::span<const CacheValuePolicy> 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; });
- }
+ ZEN_ASSERT(Value.Id); // Failed to add value policy because the ID is null.
+ const auto Insert =
+ std::lower_bound(Values.begin(), Values.end(), Value, [](const CacheValuePolicy& Existing, const CacheValuePolicy& New) {
+ return Existing.Id < New.Id;
+ });
+ ZEN_ASSERT(
+ !(Insert < Values.end() &&
+ Insert->Id == Value.Id)); // Failed to add value policy with ID %s because it has an existing value policy with that ID. ")
+ Values.insert(Insert, Value);
+ }
- private:
- std::vector<CacheValuePolicy> Values;
- };
+ inline std::span<const CacheValuePolicy> GetValuePolicies() const final { return Values; }
-} // namespace Private
+private:
+ std::vector<CacheValuePolicy> Values;
+};
CachePolicy
CacheRecordPolicy::GetValuePolicy(const Oid& Id) const
{
if (Shared)
{
- if (std::span<const CacheValuePolicy> Values = Shared->GetValuePolicies(); !Values.empty())
+ const std::span<const CacheValuePolicy> Values = Shared->GetValuePolicies();
+ const 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)
{
- 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 Iter->Policy;
}
}
return DefaultValuePolicy;
@@ -162,46 +169,58 @@ void
CacheRecordPolicy::Save(CbWriter& Writer) const
{
Writer.BeginObject();
+ // The RecordPolicy is calculated from the ValuePolicies and does not need to be saved separately.
+ Writer.AddString("BasePolicy"sv, WriteToString<128>(GetBasePolicy()));
+ if (!IsUniform())
{
- // The RecordPolicy is calculated from the ValuePolicies and does not need to be saved separately.
- Writer << "DefaultValuePolicy"sv << WriteToString<128>(GetDefaultValuePolicy());
- if (!IsUniform())
+ Writer.BeginArray("ValuePolicies"sv);
+ for (const CacheValuePolicy& Value : GetValuePolicies())
{
- // 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.BeginObject();
+ Writer.AddObjectId("Id"sv, Value.Id);
+ Writer.AddString("Policy"sv, WriteToString<128>(Value.Policy));
+ Writer.EndObject();
}
+ Writer.EndArray();
}
Writer.EndObject();
}
-CacheRecordPolicy
-CacheRecordPolicy::Load(CbObjectView Object, CachePolicy DefaultPolicy)
+OptionalCacheRecordPolicy
+CacheRecordPolicy::Load(const CbObjectView Object)
{
- std::string_view PolicyText = Object["DefaultValuePolicy"sv].AsString();
- CachePolicy DefaultValuePolicy = !PolicyText.empty() ? ParseCachePolicy(PolicyText) : DefaultPolicy;
+ std::string_view BasePolicyText = Object["BasePolicy"sv].AsString();
+#if BACKWARDS_COMPATABILITY_JAN2022
+ if (BasePolicyText.empty())
+ {
+ BasePolicyText = Object["DefaultValuePolicy"sv].AsString();
+ }
+#endif
+ if (BasePolicyText.empty())
+ {
+ return {};
+ }
- CacheRecordPolicyBuilder Builder(DefaultValuePolicy);
- for (CbFieldView ValueObjectField : Object["ValuePolicies"sv])
+ CacheRecordPolicyBuilder Builder(ParseCachePolicy(BasePolicyText));
+ for (CbFieldView ValueField : 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);
+ const CbObjectView Value = ValueField.AsObjectView();
+ const Oid Id = Value["Id"sv].AsObjectId();
+ const std::string_view PolicyText = Value["Policy"sv].AsString();
+ if (!Id || PolicyText.empty())
+ {
+ return {};
+ }
+ CachePolicy Policy = ParseCachePolicy(PolicyText);
+#if BACKWARDS_COMPATABILITY_JAN2022
+ Policy = Policy & ~CacheValuePolicy::PolicyMask;
+#else
+ if (EnumHasAnyFlags(Policy, ~CacheValuePolicy::PolicyMask))
+ {
+ return {};
+ }
+#endif
+ Builder.AddValuePolicy(Id, Policy);
}
return Builder.Build();
@@ -210,30 +229,28 @@ CacheRecordPolicy::Load(CbObjectView Object, CachePolicy DefaultPolicy)
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()));
+ CacheRecordPolicyBuilder Builder(zen::ConvertToUpstream(GetBasePolicy()));
for (const CacheValuePolicy& ValuePolicy : GetValuePolicies())
{
- Builder.AddValuePolicy(ValuePolicy.Id, DownstreamToUpstream(ValuePolicy.Policy));
+ Builder.AddValuePolicy(ValuePolicy.Id, zen::ConvertToUpstream(ValuePolicy.Policy));
}
return Builder.Build();
}
void
-CacheRecordPolicyBuilder::AddValuePolicy(const CacheValuePolicy& Policy)
+CacheRecordPolicyBuilder::AddValuePolicy(const CacheValuePolicy& Value)
{
+ ZEN_ASSERT(!EnumHasAnyFlags(Value.Policy,
+ ~Value.PolicyMask)); // Value policy contains flags that only make sense on the record policy. Policy: %s
+ if (Value.Policy == (BasePolicy & ~Value.PolicyMask))
+ {
+ return;
+ }
if (!Shared)
{
Shared = new Private::CacheRecordPolicyShared;
}
- Shared->AddValuePolicy(Policy);
+ Shared->AddValuePolicy(Value);
}
CacheRecordPolicy
@@ -242,13 +259,14 @@ CacheRecordPolicyBuilder::Build()
CacheRecordPolicy Policy(BasePolicy);
if (Shared)
{
- Shared->Build();
- const auto PolicyOr = [](CachePolicy A, CachePolicy B) { return A | (B & ~CachePolicy::SkipData); };
- const std::span<const CacheValuePolicy> Values = Shared->GetValuePolicies();
- Policy.RecordPolicy = BasePolicy;
+ const auto Add = [](const CachePolicy A, const CachePolicy B) {
+ return ((A | B) & ~CachePolicy::SkipData) | ((A & B) & CachePolicy::SkipData);
+ };
+ const std::span<const CacheValuePolicy> Values = Shared->GetValuePolicies();
+ Policy.RecordPolicy = BasePolicy;
for (const CacheValuePolicy& ValuePolicy : Values)
{
- Policy.RecordPolicy = PolicyOr(Policy.RecordPolicy, ValuePolicy.Policy);
+ Policy.RecordPolicy = Add(Policy.RecordPolicy, ValuePolicy.Policy);
}
Policy.Shared = std::move(Shared);
}