diff options
| author | mattpetersepic <[email protected]> | 2022-02-01 08:06:36 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-02-01 08:06:36 -0700 |
| commit | 154743f2d2ff2b7163bcf8d7b76eea3e3579aaba (patch) | |
| tree | aef417b5c9a0d5502c7afdb01c4cc598071e956d /zenutil | |
| parent | Tweaked remote_build.py TTY output (diff) | |
| download | zen-154743f2d2ff2b7163bcf8d7b76eea3e3579aaba.tar.xz zen-154743f2d2ff2b7163bcf8d7b76eea3e3579aaba.zip | |
Cache policy support (#47)
Add HandleRpc methods for the remaining ICacheStore requests from unreal: PutCacheValues/GetCacheValues. We now have batched versions for PutCacheRecords,GetCacheRecords,PutCacheValues,GetCacheValues,GetCacheChunks. Add support for CachePolicy flags to all of these batched methods.
* Add Batched PutCacheValues/GetCacheValues. Rename old GetCacheValues to GetCacheChunks.
* HandleRpcGetCacheRecords: Receive a CacheRecordPolicy with each key, and skipdata on attachments we already have.
* Changes to CachePolicy copied from Release-5.0 depot. Change serialization to use the key BasePolicy instead of DefaultValuePolicy.
* GetChunks: Read CacheRecords from remote if necessary to find ContentId. Implement QueryLocal, StoreLocal, and SkipData.
Diffstat (limited to 'zenutil')
| -rw-r--r-- | zenutil/cache/cachepolicy.cpp | 326 | ||||
| -rw-r--r-- | zenutil/include/zenutil/cache/cachekey.h | 6 | ||||
| -rw-r--r-- | zenutil/include/zenutil/cache/cachepolicy.h | 143 |
3 files changed, 277 insertions, 198 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); } diff --git a/zenutil/include/zenutil/cache/cachekey.h b/zenutil/include/zenutil/cache/cachekey.h index a0a83a883..aa649b4dc 100644 --- a/zenutil/include/zenutil/cache/cachekey.h +++ b/zenutil/include/zenutil/cache/cachekey.h @@ -50,6 +50,12 @@ struct CacheChunkRequest CachePolicy Policy = CachePolicy::Default; }; +struct CacheKeyRequest +{ + CacheKey Key; + CacheRecordPolicy Policy; +}; + inline bool operator<(const CacheChunkRequest& A, const CacheChunkRequest& B) { diff --git a/zenutil/include/zenutil/cache/cachepolicy.h b/zenutil/include/zenutil/cache/cachepolicy.h index b3602edbd..3eb0fda66 100644 --- a/zenutil/include/zenutil/cache/cachepolicy.h +++ b/zenutil/include/zenutil/cache/cachepolicy.h @@ -12,14 +12,26 @@ #include <span> #include <unordered_map> +#define BACKWARDS_COMPATABILITY_JAN2022 1 +#if BACKWARDS_COMPATABILITY_JAN2022 +# define BACKWARDS_COMPATABILITY_JAN2022_CODE(...) __VA_ARGS__ +#else +# define BACKWARDS_COMPATABILITY_JAN2022_CODE(...) +#endif + +namespace zen::Private { +class ICacheRecordPolicyShared; +} namespace zen { class CbObjectView; class CbWriter; +class OptionalCacheRecordPolicy; + enum class CachePolicy : uint32_t { - /** A value without any flags set. */ + /** A value with no flags. Disables access to the cache unless combined with other flags. */ None = 0, /** Allow a cache request to query local caches. */ @@ -29,17 +41,26 @@ enum class CachePolicy : uint32_t /** Allow a cache request to query any caches. */ Query = QueryLocal | QueryRemote, - /** Allow cache records and values to be stored in local caches. */ + /** Allow cache requests to query and store records and values in local caches. */ StoreLocal = 1 << 2, /** Allow cache records and values to be stored in remote caches. */ StoreRemote = 1 << 3, /** Allow cache records and values to be stored in any caches. */ Store = StoreLocal | StoreRemote, - /** Skip fetching the metadata for record requests. */ - SkipMeta = 1 << 4, + /** Allow cache requests to query and store records and values in local caches. */ + Local = QueryLocal | StoreLocal, + /** Allow cache requests to query and store records and values in remote caches. */ + Remote = QueryRemote | StoreRemote, + + /** Allow cache requests to query and store records and values in any caches. */ + Default = Query | Store, + /** Skip fetching the data for values. */ - SkipData = 1 << 5, + SkipData = 1 << 4, + + /** Skip fetching the metadata for record requests. */ + SkipMeta = 1 << 5, /** * Partial output will be provided with the error status when a required value is missing. @@ -48,7 +69,7 @@ enum class CachePolicy : uint32_t * without rebuilding the whole record. The cache automatically adds this flag when there are * other cache stores that it may be able to recover missing values from. * - * Missing values will be returned in the records or chunks, but with only the hash and size. + * Missing values will be returned in the records, but with only the hash and size. * * Applying this flag for a put of a record allows a partial record to be stored. */ @@ -61,50 +82,48 @@ enum class CachePolicy : uint32_t * to be used when subsequent accesses will not tolerate a cache miss. */ KeepAlive = 1 << 7, - - /** Allow cache requests to query and store records and values in local caches. */ - Local = QueryLocal | StoreLocal, - /** Allow cache requests to query and store records and values in remote caches. */ - Remote = QueryRemote | StoreRemote, - - /** Allow cache requests to query and store records and values in any caches. */ - Default = Query | Store, - - /** Do not allow cache requests to query or store records and values in any caches. */ - Disable = None, }; gsl_DEFINE_ENUM_BITMASK_OPERATORS(CachePolicy); -/** Serialize Policy to text and append to Builder. Appended text will not be empty. */ +/** Append a non-empty text version of the policy to the builder. */ StringBuilderBase& operator<<(StringBuilderBase& Builder, CachePolicy Policy); -/** Parse text written by operator<< back into an ECachePolicy. Text must not be empty. */ +/** Parse non-empty text written by operator<< into a policy. */ CachePolicy ParseCachePolicy(std::string_view Text); +/** Return input converted into the equivalent policy that the upstream should use when forwarding a put or get to an upstream server. */ +CachePolicy ConvertToUpstream(CachePolicy Policy); + +inline CachePolicy +Union(CachePolicy A, CachePolicy B) +{ + constexpr CachePolicy InvertedFlags = CachePolicy::SkipData | CachePolicy::SkipMeta; + return (A & ~(InvertedFlags)) | (B & ~(InvertedFlags)) | (A & B & InvertedFlags); +} /** A value ID and the cache policy to use for that value. */ struct CacheValuePolicy { Oid Id; CachePolicy Policy = CachePolicy::Default; + + /** Flags that are valid on a value policy. */ + static constexpr CachePolicy PolicyMask = CachePolicy::Default | CachePolicy::SkipData; }; -namespace Private { - /** Interface for the private implementation of the cache record policy. */ - class ICacheRecordPolicyShared : public RefCounted - { - public: - virtual ~ICacheRecordPolicyShared() = default; - virtual std::span<const CacheValuePolicy> GetValuePolicies() const = 0; - virtual void AddValuePolicy(const CacheValuePolicy& Policy) = 0; - virtual void Build() = 0; - }; -} // namespace Private +/** Interface for the private implementation of the cache record policy. */ +class Private::ICacheRecordPolicyShared : public RefCounted +{ +public: + virtual ~ICacheRecordPolicyShared() = default; + virtual void AddValuePolicy(const CacheValuePolicy& Policy) = 0; + virtual std::span<const CacheValuePolicy> GetValuePolicies() const = 0; +}; /** * Flags to control the behavior of cache record requests, with optional overrides by value. * * Examples: - * - A base policy of Disable, with value policy overrides of Default, will fetch those values if - * they exist in the record, and skip data for any other values. + * - A base policy of None with value policy overrides of Default will fetch those values if they + * exist in the record, and skip data for any other values. * - A base policy of Default, with value policy overrides of (Query | SkipData), will skip those * values, but still check if they exist, and will load any other values. */ @@ -115,34 +134,35 @@ public: CacheRecordPolicy() = default; /** Construct a cache record policy with a uniform policy for the record and every value. */ - inline CacheRecordPolicy(CachePolicy Policy) : RecordPolicy(Policy), DefaultValuePolicy(Policy) {} + inline CacheRecordPolicy(CachePolicy BasePolicy) + : RecordPolicy(BasePolicy) + , DefaultValuePolicy(BasePolicy & CacheValuePolicy::PolicyMask) + { + } /** Returns true if the record and every value use the same cache policy. */ - inline bool IsUniform() const { return !Shared && RecordPolicy == DefaultValuePolicy; } + inline bool IsUniform() const { return !Shared; } /** Returns the cache policy to use for the record. */ inline CachePolicy GetRecordPolicy() const { return RecordPolicy; } + /** Returns the base cache policy that this was constructed from. */ + inline CachePolicy GetBasePolicy() const { return DefaultValuePolicy | (RecordPolicy & ~CacheValuePolicy::PolicyMask); } + /** Returns the cache policy to use for the value. */ CachePolicy GetValuePolicy(const Oid& Id) const; - /** Returns the cache policy to use for values with no override. */ - inline CachePolicy GetDefaultValuePolicy() const { return DefaultValuePolicy; } - /** Returns the array of cache policy overrides for values, sorted by ID. */ inline std::span<const CacheValuePolicy> GetValuePolicies() const { return Shared ? Shared->GetValuePolicies() : std::span<const CacheValuePolicy>(); } - /** Save the values from *this into the given writer. */ + /** Saves the cache record policy to a compact binary object. */ void Save(CbWriter& Writer) const; - /** - * Returns a policy loaded from values on Object. - * Invalid data will result in a uniform CacheRecordPolicy with defaultValuePolicy == DefaultPolicy. - */ - static CacheRecordPolicy Load(CbObjectView Object, CachePolicy DefaultPolicy = CachePolicy::Default); + /** Loads a cache record policy from an object. */ + static OptionalCacheRecordPolicy Load(CbObjectView Object); /** Return *this converted into the equivalent policy that the upstream should use when forwarding a put or get to an upstream server. */ @@ -150,6 +170,7 @@ public: private: friend class CacheRecordPolicyBuilder; + friend class OptionalCacheRecordPolicy; CachePolicy RecordPolicy = CachePolicy::Default; CachePolicy DefaultValuePolicy = CachePolicy::Default; @@ -167,7 +188,7 @@ public: inline explicit CacheRecordPolicyBuilder(CachePolicy Policy) : BasePolicy(Policy) {} /** Adds a cache policy override for a value. */ - void AddValuePolicy(const CacheValuePolicy& Policy); + void AddValuePolicy(const CacheValuePolicy& Value); inline void AddValuePolicy(const Oid& Id, CachePolicy Policy) { AddValuePolicy({Id, Policy}); } /** Build a cache record policy, which makes this builder subsequently unusable. */ @@ -178,4 +199,38 @@ private: RefPtr<Private::ICacheRecordPolicyShared> Shared; }; +/** + * A cache record policy that can be null. + * + * @see CacheRecordPolicy + */ +class OptionalCacheRecordPolicy : private CacheRecordPolicy +{ +public: + inline OptionalCacheRecordPolicy() : CacheRecordPolicy(~CachePolicy::None) {} + + inline OptionalCacheRecordPolicy(CacheRecordPolicy&& InOutput) : CacheRecordPolicy(std::move(InOutput)) {} + inline OptionalCacheRecordPolicy(const CacheRecordPolicy& InOutput) : CacheRecordPolicy(InOutput) {} + inline OptionalCacheRecordPolicy& operator=(CacheRecordPolicy&& InOutput) + { + CacheRecordPolicy::operator=(std::move(InOutput)); + return *this; + } + inline OptionalCacheRecordPolicy& operator=(const CacheRecordPolicy& InOutput) + { + CacheRecordPolicy::operator=(InOutput); + return *this; + } + + /** Returns the cache record policy. The caller must check for null before using this accessor. */ + inline const CacheRecordPolicy& Get() const& { return *this; } + inline CacheRecordPolicy Get() && { return std::move(*this); } + + inline bool IsNull() const { return RecordPolicy == ~CachePolicy::None; } + inline bool IsValid() const { return !IsNull(); } + inline explicit operator bool() const { return !IsNull(); } + + inline void Reset() { *this = OptionalCacheRecordPolicy(); } +}; + } // namespace zen |