aboutsummaryrefslogtreecommitdiff
path: root/zenutil
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2022-01-25 15:16:04 +0100
committerStefan Boberg <[email protected]>2022-01-25 15:16:04 +0100
commit080b73be664064d13eb88e53cd627ef859aa7da8 (patch)
tree1614a7637a33551e46dcb53ebb0e92d02a26b4f7 /zenutil
parentImplemented support for storing compressed buffers as values in structured ca... (diff)
parentCachepolicy (#36) (diff)
downloadzen-080b73be664064d13eb88e53cd627ef859aa7da8.tar.xz
zen-080b73be664064d13eb88e53cd627ef859aa7da8.zip
Merge branch 'main' of https://github.com/EpicGames/zen
Diffstat (limited to 'zenutil')
-rw-r--r--zenutil/cache/cachepolicy.cpp284
-rw-r--r--zenutil/include/zenutil/cache/cachepolicy.h137
2 files changed, 278 insertions, 143 deletions
diff --git a/zenutil/cache/cachepolicy.cpp b/zenutil/cache/cachepolicy.cpp
index e1c31d885..142ba682a 100644
--- a/zenutil/cache/cachepolicy.cpp
+++ b/zenutil/cache/cachepolicy.cpp
@@ -6,62 +6,113 @@
#include <zencore/compactbinarybuilder.h>
#include <zencore/string.h>
+#include <algorithm>
+#include <unordered_map>
+
namespace zen {
using namespace std::literals;
-namespace detail { namespace cacheopt {
- constexpr std::string_view Local = "local"sv;
- constexpr std::string_view Remote = "remote"sv;
- constexpr std::string_view Data = "data"sv;
- constexpr std::string_view Meta = "meta"sv;
- constexpr std::string_view Value = "value"sv;
- constexpr std::string_view Attachments = "attachments"sv;
-}} // namespace detail::cacheopt
-
-CachePolicy
-ParseQueryCachePolicy(std::string_view QueryPolicy, CachePolicy Default)
+namespace detail::CachePolicyImpl {
+ constexpr char DelimiterChar = ',';
+ constexpr std::string_view Delimiter = ","sv;
+ 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<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}};
+
+ 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},
+
+ // 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)
{
- if (QueryPolicy.empty())
+ // 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)
{
- return Default;
- }
-
- CachePolicy Result = CachePolicy::None;
-
- ForEachStrTok(QueryPolicy, ',', [&Result](const std::string_view& Token) {
- if (Token == detail::cacheopt::Local)
+ if (EnumHasAllFlags(Policy, Pair.first))
{
- Result |= CachePolicy::QueryLocal;
+ EnumRemoveFlags(Policy, Pair.first);
+ Builder << Pair.second << detail::CachePolicyImpl::DelimiterChar;
+ if (Policy == CachePolicy::None)
+ {
+ break;
+ }
}
- if (Token == detail::cacheopt::Remote)
- {
- Result |= CachePolicy::QueryRemote;
- }
- return true;
- });
-
- return Result;
+ }
+ 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
-ParseStoreCachePolicy(std::string_view StorePolicy, CachePolicy Default)
+ParseCachePolicy(std::string_view Text)
{
- if (StorePolicy.empty())
- {
- return Default;
- }
+ ZEN_ASSERT(!Text.empty()); // Empty string is not valid input to ParseCachePolicy
CachePolicy Result = CachePolicy::None;
-
- ForEachStrTok(StorePolicy, ',', [&Result](const std::string_view& Token) {
- if (Token == detail::cacheopt::Local)
- {
- Result |= CachePolicy::StoreLocal;
- }
- if (Token == detail::cacheopt::Remote)
+ 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 |= CachePolicy::StoreRemote;
+ Result |= it->second;
}
return true;
});
@@ -69,101 +120,120 @@ ParseStoreCachePolicy(std::string_view StorePolicy, CachePolicy Default)
return Result;
}
-CachePolicy
-ParseSkipCachePolicy(std::string_view SkipPolicy, CachePolicy Default)
-{
- if (SkipPolicy.empty())
+namespace Private {
+
+ class CacheRecordPolicyShared final : public ICacheRecordPolicyShared
{
- return Default;
- }
+ public:
+ inline std::span<const CacheValuePolicy> GetValuePolicies() const final { return Values; }
- CachePolicy Result = CachePolicy::None;
+ inline void AddValuePolicy(const CacheValuePolicy& Policy) final { Values.push_back(Policy); }
- ForEachStrTok(SkipPolicy, ',', [&Result](const std::string_view& Token) {
- if (Token == detail::cacheopt::Meta)
- {
- Result |= CachePolicy::SkipMeta;
- }
- if (Token == detail::cacheopt::Value)
+ inline void Build() final
{
- Result |= CachePolicy::SkipValue;
+ std::sort(Values.begin(), Values.end(), [](const CacheValuePolicy& A, const CacheValuePolicy& B) { return A.Id < B.Id; });
}
- if (Token == detail::cacheopt::Attachments)
- {
- Result |= CachePolicy::SkipAttachments;
- }
- if (Token == detail::cacheopt::Data)
- {
- Result |= CachePolicy::SkipData;
- }
- return true;
- });
- return Result;
-}
+ private:
+ std::vector<CacheValuePolicy> Values;
+ };
-CacheRecordPolicy::CacheRecordPolicy(const CachePolicy RecordPolicy, const CachePolicy PayloadPolicy)
-: m_RecordPolicy(RecordPolicy)
-, m_DefaultPayloadPolicy(PayloadPolicy)
-{
-}
+} // namespace Private
CachePolicy
-CacheRecordPolicy::GetPayloadPolicy(const Oid& PayloadId) const
+CacheRecordPolicy::GetValuePolicy(const Oid& Id) const
{
- if (const auto It = m_PayloadPolicies.find(PayloadId); It != m_PayloadPolicies.end())
+ if (Shared)
{
- return It->second;
+ if (std::span<const CacheValuePolicy> 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 m_DefaultPayloadPolicy;
+ return DefaultValuePolicy;
}
-bool
-CacheRecordPolicy::Load(CbObjectView RecordPolicyObject, CacheRecordPolicy& OutRecordPolicy)
+void
+CacheRecordPolicy::Save(CbWriter& Writer) const
{
- using namespace std::literals;
-
- const uint32_t RecordPolicy = RecordPolicyObject["RecordPolicy"sv].AsUInt32(static_cast<uint32_t>(CachePolicy::Default));
- const uint32_t DefaultPayloadPolicy =
- RecordPolicyObject["DefaultPayloadPolicy"sv].AsUInt32(static_cast<uint32_t>(CachePolicy::Default));
-
- OutRecordPolicy.m_RecordPolicy = static_cast<CachePolicy>(RecordPolicy);
- OutRecordPolicy.m_DefaultPayloadPolicy = static_cast<CachePolicy>(DefaultPayloadPolicy);
-
- for (CbFieldView PayloadPolicyView : RecordPolicyObject["PayloadPolicies"sv])
+ Writer.BeginObject();
{
- CbObjectView PayloadPolicyObject = PayloadPolicyView.AsObjectView();
- const Oid PayloadId = PayloadPolicyObject["Id"sv].AsObjectId();
- const uint32_t PayloadPolicy = PayloadPolicyObject["Policy"sv].AsUInt32();
-
- if (PayloadId != Oid::Zero && PayloadPolicy != 0)
+ // The RecordPolicy is calculated from the ValuePolicies and does not need to be saved separately.
+ Writer << "DefaultValuePolicy"sv << WriteToString<128>(GetDefaultValuePolicy());
+ if (!IsUniform())
{
- OutRecordPolicy.m_PayloadPolicies.emplace(PayloadId, static_cast<CachePolicy>(PayloadPolicy));
+ // 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 true;
+ return Builder.Build();
}
void
-CacheRecordPolicy::Save(const CacheRecordPolicy& Policy, CbWriter& Writer)
+CacheRecordPolicyBuilder::AddValuePolicy(const CacheValuePolicy& Policy)
{
- Writer << "RecordPolicy"sv << static_cast<uint32_t>(Policy.GetRecordPolicy());
- Writer << "DefaultPayloadPolicy"sv << static_cast<uint32_t>(Policy.GetDefaultPayloadPolicy());
+ if (!Shared)
+ {
+ Shared = new Private::CacheRecordPolicyShared;
+ }
+ Shared->AddValuePolicy(Policy);
+}
- if (!Policy.m_PayloadPolicies.empty())
+CacheRecordPolicy
+CacheRecordPolicyBuilder::Build()
+{
+ CacheRecordPolicy Policy(BasePolicy);
+ if (Shared)
{
- Writer.BeginArray("PayloadPolicies"sv);
- for (const auto& Kv : Policy.m_PayloadPolicies)
+ 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;
+ for (const CacheValuePolicy& ValuePolicy : Values)
{
- Writer.BeginObject();
- Writer.AddObjectId("Id"sv, Kv.first);
- Writer << "Policy"sv << static_cast<uint32_t>(Kv.second);
- Writer.EndObject();
+ Policy.RecordPolicy = PolicyOr(Policy.RecordPolicy, ValuePolicy.Policy);
}
- Writer.EndArray();
+ Policy.Shared = std::move(Shared);
}
+ return Policy;
}
} // namespace zen
diff --git a/zenutil/include/zenutil/cache/cachepolicy.h b/zenutil/include/zenutil/cache/cachepolicy.h
index 5675ccf4d..f967f707b 100644
--- a/zenutil/include/zenutil/cache/cachepolicy.h
+++ b/zenutil/include/zenutil/cache/cachepolicy.h
@@ -2,10 +2,14 @@
#pragma once
+#include <zencore/compactbinary.h>
+#include <zencore/enumflags.h>
+#include <zencore/refcount.h>
#include <zencore/string.h>
#include <zencore/uid.h>
#include <gsl/gsl-lite.hpp>
+#include <span>
#include <unordered_map>
namespace zen {
@@ -34,16 +38,21 @@ enum class CachePolicy : uint32_t
/** Skip fetching the metadata for record requests. */
SkipMeta = 1 << 4,
- /** Skip fetching the value for record, chunk, or value requests. */
- SkipValue = 1 << 5,
- /** Skip fetching the attachments for record requests. */
- SkipAttachments = 1 << 6,
+ /** Skip fetching the data for values. */
+ SkipData = 1 << 5,
+
/**
- * Skip fetching the data for any requests.
+ * Partial output will be provided with the error status when a required value is missing.
+ *
+ * This is meant for cases when the missing values can be individually recovered, or rebuilt,
+ * 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.
*
- * Put requests with skip flags may assume that record existence implies payload existence.
+ * Applying this flag for a put of a record allows a partial record to be stored.
*/
- SkipData = SkipMeta | SkipValue | SkipAttachments,
+ PartialRecord = 1 << 6,
/**
* Keep records in the cache for at least the duration of the session.
@@ -53,18 +62,6 @@ enum class CachePolicy : uint32_t
*/
KeepAlive = 1 << 7,
- /**
- * Partial output will be provided with the error status when a required payload is missing.
- *
- * This is meant for cases when the missing payloads can be individually recovered or rebuilt
- * 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 payloads from.
- *
- * Requests for records would return records where the missing payloads have a hash and size,
- * but no data. Requests for chunks or values would return the hash and size, but no data.
- */
- PartialOnError = 1 << 8,
-
/** 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. */
@@ -78,35 +75,103 @@ enum class CachePolicy : uint32_t
};
gsl_DEFINE_ENUM_BITMASK_OPERATORS(CachePolicy);
+/** Serialize Policy to text and append to Builder. Appended text will not be empty. */
+StringBuilderBase& operator<<(StringBuilderBase& Builder, CachePolicy Policy);
+/** Parse text written by operator<< back into an ECachePolicy. Text must not be empty. */
+CachePolicy ParseCachePolicy(std::string_view Text);
-CachePolicy ParseQueryCachePolicy(std::string_view QueryPolicy, CachePolicy Default = CachePolicy::Query);
-
-CachePolicy ParseStoreCachePolicy(std::string_view StorePolicy, CachePolicy Default = CachePolicy::Store);
-
-CachePolicy ParseSkipCachePolicy(std::string_view SkipPolicy, CachePolicy Default = CachePolicy::None);
+/** A value ID and the cache policy to use for that value. */
+struct CacheValuePolicy
+{
+ Oid Id;
+ CachePolicy Policy = CachePolicy::Default;
+};
+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
+
+/**
+ * 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 Default, with value policy overrides of (Query | SkipData), will skip those
+ * values, but still check if they exist, and will load any other values.
+ */
class CacheRecordPolicy
{
public:
+ /** Construct a cache record policy that uses the default policy. */
CacheRecordPolicy() = default;
- CacheRecordPolicy(const CachePolicy RecordPolicy, const CachePolicy DefaultPayloadPolicy = CachePolicy::Default);
- CachePolicy GetRecordPolicy() const { return m_RecordPolicy; }
- CachePolicy GetPayloadPolicy(const Oid& PayloadId) const;
- CachePolicy GetDefaultPayloadPolicy() const { return m_DefaultPayloadPolicy; }
+ /** Construct a cache record policy with a uniform policy for the record and every value. */
+ inline CacheRecordPolicy(CachePolicy Policy) : RecordPolicy(Policy), DefaultValuePolicy(Policy) {}
+
+ /** Returns true if the record and every value use the same cache policy. */
+ inline bool IsUniform() const { return !Shared && RecordPolicy == DefaultValuePolicy; }
- bool HasRecordPolicy(const CachePolicy Policy) const { return (m_RecordPolicy & Policy) == Policy; }
- bool HasPayloadPolicy(const Oid& PayloadId, const CachePolicy Policy) const { return (GetPayloadPolicy(PayloadId) & Policy) == Policy; }
+ /** Returns the cache policy to use for the record. */
+ inline CachePolicy GetRecordPolicy() const { return RecordPolicy; }
- static bool Load(CbObjectView RecordPolicyObject, CacheRecordPolicy& OutRecordPolicy);
- static void Save(const CacheRecordPolicy& Policy, CbWriter& Writer);
+ /** 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. */
+ 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);
private:
- using PayloadPolicyMap = std::unordered_map<Oid, CachePolicy, Oid::Hasher>;
+ friend class CacheRecordPolicyBuilder;
+
+ CachePolicy RecordPolicy = CachePolicy::Default;
+ CachePolicy DefaultValuePolicy = CachePolicy::Default;
+ RefPtr<const Private::ICacheRecordPolicyShared> Shared;
+};
- CachePolicy m_RecordPolicy = CachePolicy::Default;
- CachePolicy m_DefaultPayloadPolicy = CachePolicy::Default;
- PayloadPolicyMap m_PayloadPolicies;
+/** A cache record policy builder is used to construct a cache record policy. */
+class CacheRecordPolicyBuilder
+{
+public:
+ /** Construct a policy builder that uses the default policy as its base policy. */
+ CacheRecordPolicyBuilder() = default;
+
+ /** Construct a policy builder that uses the provided policy for the record and values with no override. */
+ inline explicit CacheRecordPolicyBuilder(CachePolicy Policy) : BasePolicy(Policy) {}
+
+ /** Adds a cache policy override for a value. */
+ void AddValuePolicy(const CacheValuePolicy& Policy);
+ inline void AddValuePolicy(const Oid& Id, CachePolicy Policy) { AddValuePolicy({Id, Policy}); }
+
+ /** Build a cache record policy, which makes this builder subsequently unusable. */
+ CacheRecordPolicy Build();
+
+private:
+ CachePolicy BasePolicy = CachePolicy::Default;
+ RefPtr<Private::ICacheRecordPolicyShared> Shared;
};
} // namespace zen