diff --git a/src/GameCult.Caching.MessagePack/CultCacheMessagePack.cs b/src/GameCult.Caching.MessagePack/CultCacheMessagePack.cs index 02ee32d5..e464e5e6 100644 --- a/src/GameCult.Caching.MessagePack/CultCacheMessagePack.cs +++ b/src/GameCult.Caching.MessagePack/CultCacheMessagePack.cs @@ -4,54 +4,27 @@ using System.Threading.Tasks; namespace GameCult.Caching.MessagePack { - /// - /// Options for opening a CultCache over the canonical single-file MessagePack backing store. - /// public sealed class CultCacheOpenOptions { - /// - /// Gets or sets the document registry to use. When omitted, the shared registry is used. - /// public CultDocumentRegistry? Registry { get; set; } - /// - /// Gets or sets whether the cache should pull the existing snapshot during open. - /// public bool PullOnOpen { get; set; } = true; - /// - /// Gets or sets whether the cache should flush attached stores when disposed. - /// public bool FlushOnDispose { get; set; } - /// - /// Gets or sets whether the backing store should flush when disposed. - /// public bool StoreFlushOnDispose { get; set; } - /// - /// Gets or sets whether the cache should use a paged directory store instead of one whole-file snapshot. - /// public bool UseDirectoryStore { get; set; } } - /// - /// Friendly MessagePack entrypoints for opening a CultCache with the canonical backing store. - /// public static class CultCacheMessagePack { - /// - /// Creates a cache with the canonical single-file MessagePack backing store attached. - /// public static CultCache Create(string filePath, CultCacheOpenOptions? options = null) { options ??= new CultCacheOpenOptions(); return CreateCore(filePath, options, initializeGlobals: true); } - /// - /// Creates a cache with the canonical single-file MessagePack backing store attached and optionally pulls the on-disk snapshot. - /// public static async Task OpenAsync(string filePath, CultCacheOpenOptions? options = null) { options ??= new CultCacheOpenOptions(); diff --git a/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs b/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs index fe9519b9..b5a621fb 100644 --- a/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs +++ b/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs @@ -7,20 +7,11 @@ using MessagePack.Resolvers; namespace GameCult.Caching.MessagePack; -/// -/// MessagePack resolver for CultCache-specific value types. -/// public sealed class CultDocumentResolver : IFormatterResolver { - /// - /// Gets the shared resolver instance. - /// public static readonly CultDocumentResolver Instance = new(); private CultDocumentResolver() { } - /// - /// Gets a formatter for the requested type, when this resolver owns it. - /// public IMessagePackFormatter? GetFormatter() { var type = typeof(T); @@ -34,9 +25,6 @@ public sealed class CultDocumentResolver : IFormatterResolver } } -/// -/// MessagePack serialization helpers for CultCache documents and backing stores. -/// public static class CultDocumentMessagePackSerialization { private const int PersistedRecordFieldCount = 4; @@ -44,9 +32,6 @@ public static class CultDocumentMessagePackSerialization private const int SchemaCatalogMemberFieldCount = 8; private const int StoreSnapshotFieldCount = 3; - /// - /// Gets the shared MessagePack serializer options for CultCache payloads. - /// public static readonly MessagePackSerializerOptions Options = MessagePackSerializerOptions.Standard .WithResolver(CompositeResolver.Create( @@ -54,33 +39,21 @@ public static class CultDocumentMessagePackSerialization StandardResolver.Instance)) .WithSecurity(MessagePackSecurity.UntrustedData); - /// - /// Serializes a typed value with the CultCache MessagePack options. - /// public static byte[] Serialize(T value) { return MessagePackSerializer.Serialize(value, Options); } - /// - /// Deserializes a typed value with the CultCache MessagePack options. - /// public static T Deserialize(byte[] payload) { return MessagePackSerializer.Deserialize(payload, Options); } - /// - /// Serializes a value whose document type is known at runtime. - /// public static byte[] SerializeUntyped(object value, Type type) { return SerializeUntyped(value, type, CultDocumentRegistry.Shared); } - /// - /// Serializes a value through an explicitly owned document registry. - /// public static byte[] SerializeUntyped(object value, Type type, CultDocumentRegistry registry) { if (registry == null) throw new ArgumentNullException(nameof(registry)); @@ -96,17 +69,11 @@ public static class CultDocumentMessagePackSerialization return MessagePackSerializer.Serialize(type, value, Options); } - /// - /// Deserializes a value whose document type is known at runtime. - /// public static object DeserializeUntyped(Type type, byte[] payload) { return DeserializeUntyped(type, payload, CultDocumentRegistry.Shared); } - /// - /// Deserializes a value through an explicitly owned document registry. - /// public static object DeserializeUntyped(Type type, byte[] payload, CultDocumentRegistry registry) { if (registry == null) throw new ArgumentNullException(nameof(registry)); @@ -120,9 +87,6 @@ public static class CultDocumentMessagePackSerialization ?? throw new InvalidOperationException($"MessagePack returned null for Cult document type {type.FullName}."); } - /// - /// Serializes one persisted store record. - /// public static byte[] SerializePersistedRecord(CultPersistedRecord record) { var buffer = new global::System.Buffers.ArrayBufferWriter(); @@ -132,18 +96,12 @@ public static class CultDocumentMessagePackSerialization return buffer.WrittenSpan.ToArray(); } - /// - /// Deserializes one persisted store record. - /// public static CultPersistedRecord DeserializePersistedRecord(byte[] payload) { var reader = new MessagePackReader(payload); return ReadPersistedRecord(ref reader); } - /// - /// Serializes a complete persisted store snapshot. - /// public static byte[] SerializeSnapshot(CultPersistedStoreSnapshot snapshot) { var buffer = new global::System.Buffers.ArrayBufferWriter(); @@ -166,9 +124,6 @@ public static class CultDocumentMessagePackSerialization return buffer.WrittenSpan.ToArray(); } - /// - /// Deserializes a complete persisted store snapshot. - /// public static CultPersistedStoreSnapshot DeserializeSnapshot(byte[] payload) { var reader = new MessagePackReader(payload); @@ -396,45 +351,27 @@ public static class CultDocumentMessagePackSerialization } } -/// -/// Single-file CultCache backing store that persists snapshots as MessagePack. -/// public class SingleFileMessagePackBackingStore : SingleFileBackingStore { - /// - /// Creates a MessagePack single-file backing store. - /// public SingleFileMessagePackBackingStore(string filePath) : base(filePath) { } - /// - /// Serializes a store snapshot. - /// protected override byte[] SerializeSnapshot(CultPersistedStoreSnapshot snapshot) { return CultDocumentMessagePackSerialization.SerializeSnapshot(snapshot); } - /// - /// Deserializes a store snapshot. - /// protected override CultPersistedStoreSnapshot DeserializeSnapshot(byte[] data) { return CultDocumentMessagePackSerialization.DeserializeSnapshot(data); } - /// - /// Serializes one document payload. - /// protected override byte[] SerializePayload(object document) { return CultDocumentMessagePackSerialization.SerializeUntyped(document, document.GetType(), Registry); } - /// - /// Deserializes one document payload. - /// protected override object DeserializePayload(Type documentType, byte[] payload) { return CultDocumentMessagePackSerialization.DeserializeUntyped(documentType, payload, Registry); diff --git a/src/GameCult.Caching.MessagePack/CultRecordRefFormatter.cs b/src/GameCult.Caching.MessagePack/CultRecordRefFormatter.cs index 3348c1c3..a5f984fc 100644 --- a/src/GameCult.Caching.MessagePack/CultRecordRefFormatter.cs +++ b/src/GameCult.Caching.MessagePack/CultRecordRefFormatter.cs @@ -3,22 +3,13 @@ using MessagePack.Formatters; namespace GameCult.Caching; -/// -/// Serializes typed CultCache record references as their persisted key strings. -/// public sealed class CultRecordRefFormatter : IMessagePackFormatter> { - /// - /// Writes a typed record reference to MessagePack. - /// public void Serialize(ref MessagePackWriter writer, CultRecordRef value, MessagePackSerializerOptions options) { options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.Key.Value, options); } - /// - /// Reads a typed record reference from MessagePack. - /// public CultRecordRef Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var key = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options) ?? string.Empty; diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 0e6ab7bb..fd8d3c26 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -11,9 +11,6 @@ using MessagePack; namespace GameCult.Caching.MessagePack; -/// -/// MessagePack backing store that keeps a small schema manifest hot and stores each record as its own cold file. -/// public sealed class DirectoryMessagePackBackingStore : CacheBackingStore { private const string IndexedFormatVersion = "cultcache.store.v4.directory-content-addressed-pages"; @@ -26,9 +23,6 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore private Dictionary _durableIndex = new(StringComparer.Ordinal); private CultSchemaCatalogEntry[] _durableCatalog = Array.Empty(); - /// - /// Creates a paged MessagePack backing store. - /// public DirectoryMessagePackBackingStore(string manifestPath, string? recordDirectoryPath = null) { if (string.IsNullOrWhiteSpace(manifestPath)) @@ -40,12 +34,8 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore _recordDirectory = new DirectoryInfo(recordDirectoryPath ?? DefaultRecordDirectoryPath(manifestPath)); } - /// - /// Gets the default record directory path for a manifest path. - /// public static string DefaultRecordDirectoryPath(string manifestPath) => manifestPath + ".records"; - /// public override void PullAll() { lock (_mutationGate) @@ -120,14 +110,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore Trace("publish"); } - /// public override bool ContainsDurableRecord(CultRecordKey key) { lock (_mutationGate) return _durableIndex.ContainsKey(key.Value) || base.ContainsDurableRecord(key); } - /// public override void Push(CultStoredDocument entry) { lock (_mutationGate) @@ -139,7 +127,6 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } - /// public override void Delete(CultStoredDocument entry) { lock (_mutationGate) @@ -151,7 +138,6 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } - /// public override void CommitBatch( IReadOnlyCollection upserts, IReadOnlyCollection deletes) @@ -196,7 +182,6 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } - /// public override void PushAll() { lock (_mutationGate) diff --git a/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj b/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj index 71fcc969..24e7a48e 100644 --- a/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj +++ b/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj @@ -1,6 +1,7 @@ File-based MessagePack backing store for CultCache + $(NoWarn);CS1591 diff --git a/src/GameCult.Caching/CultCache.cs b/src/GameCult.Caching/CultCache.cs index 84b5e869..8cf59d40 100644 --- a/src/GameCult.Caching/CultCache.cs +++ b/src/GameCult.Caching/CultCache.cs @@ -13,205 +13,82 @@ using R3; namespace GameCult.Caching { - /// - /// Persisted schema metadata embedded in a CultCache backing store. - /// public sealed class CultSchemaCatalogEntry { - /// - /// Gets or sets the content-derived schema identifier. - /// public string SchemaId { get; set; } = string.Empty; - /// - /// Gets or sets the stable schema name. - /// public string SchemaName { get; set; } = string.Empty; - /// - /// Gets or sets the schema version string. - /// public string SchemaVersion { get; set; } = string.Empty; - /// - /// Gets or sets the canonical schema content hash. - /// public string ContentHash { get; set; } = string.Empty; - /// - /// Gets or sets the canonical schema description. - /// public string CanonicalSchemaJson { get; set; } = string.Empty; - /// - /// Gets or sets schema identifiers compatible with this entry. - /// public string[] CompatibleSchemaIds { get; set; } = Array.Empty(); - /// - /// Gets or sets the persisted member descriptors used for compatibility checks. - /// public CultSchemaMemberCatalogEntry[] Members { get; set; } = Array.Empty(); } - /// - /// Persisted catalog entry describing one schema member. - /// public sealed class CultSchemaMemberCatalogEntry { - /// - /// Gets or sets the persisted slot number. - /// public int Slot { get; set; } - /// - /// Gets or sets the member name. - /// public string MemberName { get; set; } = string.Empty; - /// - /// Gets or sets the persisted type name. - /// public string TypeName { get; set; } = string.Empty; - /// - /// Gets or sets whether the member is a reference. - /// public bool IsReference { get; set; } - /// - /// Gets or sets whether the member stores many values. - /// public bool IsMany { get; set; } - /// - /// Gets or sets the referenced schema name, when the member is a reference. - /// public string? TargetSchemaName { get; set; } - /// - /// Gets or sets whether the member provides the name lookup. - /// public bool IsName { get; set; } - /// - /// Gets or sets the index alias, when the member participates in lookups. - /// public string? IndexAlias { get; set; } } - /// - /// Persisted serialized document record. - /// public sealed class CultPersistedRecord { - /// - /// Gets or sets the persisted record key. - /// public string Key { get; set; } = string.Empty; - /// - /// Gets or sets the schema identifier used to deserialize the payload. - /// public string SchemaId { get; set; } = string.Empty; - /// - /// Gets or sets the storage timestamp. - /// public string StoredAt { get; set; } = string.Empty; - /// - /// Gets or sets the serialized document payload. - /// public byte[] Payload { get; set; } = Array.Empty(); } - /// - /// Complete snapshot persisted by a single-file CultCache backing store. - /// public sealed class CultPersistedStoreSnapshot { - /// - /// Gets or sets the backing store format version. - /// public string FormatVersion { get; set; } = "cultcache.store.v1"; - /// - /// Gets or sets the embedded schema catalog. - /// public CultSchemaCatalogEntry[] SchemaCatalog { get; set; } = Array.Empty(); - /// - /// Gets or sets the persisted records. - /// public CultPersistedRecord[] Records { get; set; } = Array.Empty(); } - /// - /// Classifies how a persisted schema mapped onto the local document registry. - /// public enum CultSchemaMigrationKind { - /// - /// The persisted schema matched the local schema exactly. - /// Exact, - /// - /// The persisted schema required a compatible soft-migration path. - /// CompatibleDrift } - /// - /// Describes one schema-resolution warning emitted during load. - /// public sealed class CultSchemaMigrationWarning { - /// - /// Gets or sets the stable warning code. - /// public string Code { get; set; } = string.Empty; - /// - /// Gets or sets the human-readable warning text. - /// public string Message { get; set; } = string.Empty; } - /// - /// Reports how one persisted schema entry resolved against the local schema catalog. - /// public sealed class CultSchemaMigrationReport { - /// - /// Gets or sets the persisted schema identifier. - /// public string PersistedSchemaId { get; set; } = string.Empty; - /// - /// Gets or sets the local schema identifier selected for reading. - /// public string LocalSchemaId { get; set; } = string.Empty; - /// - /// Gets or sets the persisted schema name. - /// public string PersistedSchemaName { get; set; } = string.Empty; - /// - /// Gets or sets the local schema name. - /// public string LocalSchemaName { get; set; } = string.Empty; - /// - /// Gets or sets how the persisted schema matched locally. - /// public CultSchemaMigrationKind Kind { get; set; } - /// - /// Gets or sets the persisted member slots ignored by the local reader. - /// public int[] IgnoredExtraSlots { get; set; } = Array.Empty(); - /// - /// Gets or sets the local member slots defaulted because the persisted record did not contain them. - /// public int[] DefaultedMissingSlots { get; set; } = Array.Empty(); - /// - /// Gets or sets emitted migration warnings. - /// public CultSchemaMigrationWarning[] Warnings { get; set; } = Array.Empty(); } @@ -228,9 +105,6 @@ namespace GameCult.Caching public CultSchemaMigrationReport Report { get; } } - /// - /// Runtime descriptor for a CultCache document type. - /// public sealed class CultDocumentDescriptor { internal CultDocumentDescriptor( @@ -263,53 +137,20 @@ namespace GameCult.Caching Members = members; } - /// - /// Gets the CLR document type. - /// public Type DocumentType { get; } - /// - /// Gets the stable schema name. - /// public string SchemaName { get; } - /// - /// Gets the schema version string. - /// public string SchemaVersion { get; } - /// - /// Gets the content-derived schema identifier. - /// public string SchemaId { get; } - /// - /// Gets the canonical schema content hash. - /// public string ContentHash { get; } - /// - /// Gets the canonical schema description. - /// public string CanonicalSchemaJson { get; } - /// - /// Gets whether this document type stores one global record. - /// public bool IsGlobal { get; } - /// - /// Gets the document name member, if any. - /// public string? NameMember { get; } internal Func? NameAccessor { get; } - /// - /// Gets the generated payload serializer, if one is available. - /// public Func? GeneratedPayloadSerializer { get; } - /// - /// Gets the generated payload deserializer, if one is available. - /// public Func? GeneratedPayloadDeserializer { get; } internal IReadOnlyDictionary> IndexAccessors { get; } internal IReadOnlyList Members { get; } - /// - /// Converts this descriptor into a persisted schema catalog entry. - /// public CultSchemaCatalogEntry ToCatalogEntry() { return new CultSchemaCatalogEntry @@ -358,14 +199,8 @@ namespace GameCult.Caching public string? IndexAlias { get; set; } } - /// - /// Stores a resolved document together with its descriptor and key. - /// public sealed class CultStoredDocument { - /// - /// Creates a stored document wrapper. - /// public CultStoredDocument( CultRecordKey key, string storedAt, @@ -378,27 +213,12 @@ namespace GameCult.Caching Document = document; } - /// - /// Gets the record key. - /// public CultRecordKey Key { get; } - /// - /// Gets the storage timestamp. - /// public string StoredAt { get; } - /// - /// Gets the document descriptor. - /// public CultDocumentDescriptor Descriptor { get; } - /// - /// Gets the document instance. - /// public object Document { get; } } - /// - /// Discovers, indexes, and resolves CultCache document descriptors. - /// public sealed class CultDocumentRegistry { private static readonly Lazy SharedRegistry = @@ -407,14 +227,8 @@ namespace GameCult.Caching private volatile RegistryIndexes _indexes = new(); private readonly object _registrationGate = new(); - /// - /// Gets the shared process-wide document registry. - /// public static CultDocumentRegistry Shared => SharedRegistry.Value; - /// - /// Creates a registry and discovers currently loaded document metadata. - /// public CultDocumentRegistry() { Refresh(); @@ -426,9 +240,6 @@ namespace GameCult.Caching Refresh(); } - /// - /// Creates a registry containing exactly the requested document types without scanning loaded assemblies. - /// public static CultDocumentRegistry ForTypes(IEnumerable documentTypes) { if (documentTypes == null) throw new ArgumentNullException(nameof(documentTypes)); @@ -442,15 +253,9 @@ namespace GameCult.Caching return registry; } - /// - /// Gets all known document descriptors. - /// public IEnumerable AllDescriptors => _indexes.ByType.Values.OrderBy(d => d.SchemaName, StringComparer.Ordinal); - /// - /// Rebuilds the registry from generated metadata and reflected document attributes. - /// public void Refresh() { lock (_registrationGate) @@ -473,9 +278,6 @@ namespace GameCult.Caching } } - /// - /// Gets the descriptor for a document type, building it when needed. - /// public CultDocumentDescriptor GetRequired(Type type) { if (_indexes.ByType.TryGetValue(type, out var descriptor)) @@ -500,17 +302,11 @@ namespace GameCult.Caching return RegisterDescriptor(descriptor); } - /// - /// Gets the descriptor for a document type. - /// public CultDocumentDescriptor GetRequired() where T : class { return GetRequired(typeof(T)); } - /// - /// Gets a descriptor by its schema identifier. - /// public CultDocumentDescriptor GetRequiredBySchemaId(string schemaId) { if (_indexes.BySchemaId.TryGetValue(schemaId, out var descriptor)) @@ -521,17 +317,11 @@ namespace GameCult.Caching throw new InvalidOperationException($"Unknown CultCache schema id '{schemaId}'."); } - /// - /// Resolves a persisted schema identifier against the local registry and embedded catalog. - /// public CultDocumentDescriptor ResolvePersistedSchema(string schemaId, IReadOnlyCollection catalog) { return ResolvePersistedSchemaDetailed(schemaId, catalog).Descriptor; } - /// - /// Resolves a persisted schema identifier and returns the selected descriptor with migration diagnostics. - /// public CultSchemaMigrationReport ResolvePersistedSchemaReport(string schemaId, IReadOnlyCollection catalog) { return ResolvePersistedSchemaDetailed(schemaId, catalog).Report; @@ -1263,9 +1053,6 @@ namespace GameCult.Caching } } - /// - /// In-memory document cache with pluggable persisted backing stores. - /// public sealed class CultCache : IDisposable { private readonly CultDocumentRegistry _registry; @@ -1282,18 +1069,11 @@ namespace GameCult.Caching private readonly object _stateGate = new(); private bool _hasUnflushedMutations; - /// - /// Creates a cache using the supplied document registry or the shared registry. - /// public CultCache(CultDocumentRegistry? registry = null) : this(registry, initializeGlobals: true) { } - /// - /// Creates a cache using the supplied document registry, optionally deferring global defaults - /// until a durable backing store has been hydrated. - /// public CultCache(CultDocumentRegistry? registry, bool initializeGlobals) { _registry = registry ?? CultDocumentRegistry.Shared; @@ -1303,38 +1083,20 @@ namespace GameCult.Caching } } - /// - /// Creates default instances for global document types that do not already have a record. - /// Durable open paths call this after hydration so persisted globals remain authoritative. - /// + // Durable open paths call this after hydration so persisted globals remain authoritative. public void MaterializeMissingGlobals() { InitializeGlobals(); } - /// - /// Gets whether the cache currently holds unflushed mutations in any attached backing store or only in memory. - /// public bool IsDirty => _hasUnflushedMutations || _backingStores.Any(store => store.IsDirty); - /// - /// Gets or sets whether disposing the cache should flush attached dirty backing stores first. - /// public bool FlushAttachedStoresOnDispose { get; set; } - /// - /// Raised when a backing store adds, updates, or removes a document. - /// public event Action? OnUpdate; - /// - /// Gets attached backing stores in registration order. - /// public IReadOnlyList BackingStores => _backingStores; - /// - /// Gets all document instances currently held by the cache. - /// public IEnumerable AllEntries { get @@ -1344,9 +1106,6 @@ namespace GameCult.Caching } } - /// - /// Gets all stored document records currently held by the cache. - /// public IEnumerable AllStoredDocuments { get @@ -1359,14 +1118,8 @@ namespace GameCult.Caching } } - /// - /// Gets the document registry used by this cache. - /// public CultDocumentRegistry Registry => _registry; - /// - /// Watches all local cache changes assignable to the requested document type. - /// public Observable> Watch() where T : class { return _changes @@ -1374,17 +1127,11 @@ namespace GameCult.Caching .Select(change => (CultCacheDocumentChange)change); } - /// - /// Watches one local cache record. - /// public Observable> WatchRecord(CultRecordKey key) where T : class { return Watch().Where(change => change.Key.Equals(key)); } - /// - /// Attaches a backing store to this cache. - /// public void AddBackingStore(CacheBackingStore store) { if (store == null) throw new ArgumentNullException(nameof(store)); @@ -1400,9 +1147,6 @@ namespace GameCult.Caching _backingStores.Add(store); } - /// - /// Pulls all documents from every attached backing store. - /// public async Task PullAllBackingStoresAsync() { if (_ambientTransaction.Value != null) @@ -1423,9 +1167,6 @@ namespace GameCult.Caching } } - /// - /// Flushes all attached backing stores. - /// public void FlushAllBackingStores() { if (_ambientTransaction.Value != null) @@ -1453,11 +1194,8 @@ namespace GameCult.Caching return Task.CompletedTask; } - /// - /// Runs a buffered cache mutation. Staged records are visible only to the executing - /// async flow. The durable backing store is committed before the live cache and its - /// observers advance; an exception discards the entire staged batch. - /// + // Staged records are visible only to the executing async flow. The durable backing store + // commits before the live cache and its observers advance; an exception discards the whole batch. public async Task ExecuteTransactionAsync(Func stageAsync) { if (stageAsync == null) throw new ArgumentNullException(nameof(stageAsync)); @@ -1488,7 +1226,6 @@ namespace GameCult.Caching PublishChange(change.Stored, change.Previous, change.Removed); } - /// Runs a value-producing buffered cache mutation. public async Task ExecuteTransactionAsync(Func> stageAsync) { if (stageAsync == null) throw new ArgumentNullException(nameof(stageAsync)); @@ -1500,9 +1237,6 @@ namespace GameCult.Caching return result; } - /// - /// Adds or replaces a typed document and returns its record handle. - /// public async Task> AddAsync(T document, CultRecordHandle? handle = null) { if (document == null) throw new ArgumentNullException(nameof(document)); @@ -1526,17 +1260,11 @@ namespace GameCult.Caching } } - /// - /// Adds or replaces a typed document and returns its record handle. - /// public Task> UpsertAsync(T document, CultRecordHandle? handle = null) { return AddAsync(document, handle); } - /// - /// Adds or replaces a document whose concrete type is known at runtime. - /// public async Task UpsertAsync(Type documentType, object document, CultRecordKey? key = null) { if (documentType == null) throw new ArgumentNullException(nameof(documentType)); @@ -1568,9 +1296,6 @@ namespace GameCult.Caching } } - /// - /// Gets the record handle for a document instance, if it is tracked. - /// public CultRecordHandle? TryGetHandle(T document) { if (document == null) throw new ArgumentNullException(nameof(document)); @@ -1579,9 +1304,6 @@ namespace GameCult.Caching : null; } - /// - /// Gets a document by record key. - /// public object? Get(CultRecordKey key) { lock (_stateGate) @@ -1595,17 +1317,11 @@ namespace GameCult.Caching } } - /// - /// Gets a typed document by record key. - /// public T? Get(CultRecordKey key) where T : class { return Get(key) as T; } - /// - /// Gets all cached documents assignable to the requested type. - /// public IEnumerable GetAll() where T : class { lock (_stateGate) @@ -1618,9 +1334,6 @@ namespace GameCult.Caching } } - /// - /// Gets the global document for the requested type, if one exists. - /// public T? GetGlobal() where T : class { return _globalKeys.TryGetValue(typeof(T), out var key) @@ -1628,9 +1341,6 @@ namespace GameCult.Caching : null; } - /// - /// Gets a typed document by its CultName value. - /// public T? GetByName(string name) where T : class { var type = typeof(T); @@ -1643,9 +1353,6 @@ namespace GameCult.Caching return null; } - /// - /// Gets a typed document by an indexed value. - /// public T? GetByIndex(string alias, string value) where T : class { if (_indexMaps.TryGetValue((typeof(T), alias), out var map) && @@ -1657,9 +1364,6 @@ namespace GameCult.Caching return null; } - /// - /// Removes a document by typed handle. - /// public void Remove(CultRecordHandle handle) { if (_ambientTransaction.Value is { } transaction) @@ -1680,9 +1384,6 @@ namespace GameCult.Caching } } - /// - /// Removes a document by cache-owned record key. - /// public bool Remove(CultRecordKey key) { if (Get(key) == null) @@ -1709,18 +1410,12 @@ namespace GameCult.Caching return true; } - /// - /// Removes a document by typed handle. - /// public Task DeleteAsync(CultRecordHandle handle) { Remove(handle); return Task.CompletedTask; } - /// - /// Disposes attached disposable backing stores. - /// public void Dispose() { if (FlushAttachedStoresOnDispose && IsDirty) @@ -2124,44 +1819,26 @@ namespace GameCult.Caching } } - /// - /// Base class for CultCache persistence adapters. - /// public abstract class CacheBackingStore : IDisposable { private CultDocumentRegistry? _registry; private bool _isDirty; private CultSchemaMigrationReport[] _lastSchemaMigrationReports = Array.Empty(); - /// - /// Gets the attached document registry. - /// protected CultDocumentRegistry Registry => _registry ?? throw new InvalidOperationException("Backing store is not attached to a CultDocumentRegistry."); - /// - /// Gets the backing store entries by record key. - /// protected ConcurrentDictionary Entries { get; } = new(StringComparer.Ordinal); - /// - /// Gets whether the backing store holds staged mutations not yet durably flushed. - /// public bool IsDirty { get => _isDirty; protected set => _isDirty = value; } - /// - /// Gets or sets whether disposing the backing store should flush staged mutations first. - /// public bool FlushOnDispose { get; set; } - /// - /// Gets schema migration reports emitted during the most recent pull. - /// public IReadOnlyList LastSchemaMigrationReports => _lastSchemaMigrationReports; // Set by the cache at attach; a store calls them for records it loads or drops. @@ -2173,29 +1850,15 @@ namespace GameCult.Caching _registry = registry ?? throw new ArgumentNullException(nameof(registry)); } - /// - /// Pulls all persisted records into the backing store. - /// public abstract void PullAll(); - /// - /// Returns whether the backing store knows a record is durable, including records left cold by selective hydration. - /// public virtual bool ContainsDurableRecord(CultRecordKey key) { return Entries.ContainsKey(key.Value); } - /// - /// Pushes one stored document into the backing store. - /// public abstract void Push(CultStoredDocument entry); - /// - /// Deletes one stored document from the backing store. - /// public abstract void Delete(CultStoredDocument entry); - /// - /// Atomically stages and durably commits a buffered record batch for this store. - /// Implementations must restore their prior staged view when finality fails. - /// + // Stages and durably commits a batch as one step. Implementations must restore their prior + // staged view when finality fails. public virtual void CommitBatch( IReadOnlyCollection upserts, IReadOnlyCollection deletes) @@ -2219,13 +1882,7 @@ namespace GameCult.Caching throw; } } - /// - /// Persists all current backing store entries. - /// public abstract void PushAll(); - /// - /// Flushes staged mutations first when FlushOnDispose is set. - /// public virtual void Dispose() { if (FlushOnDispose && IsDirty) @@ -2234,9 +1891,6 @@ namespace GameCult.Caching } } - /// - /// Converts a stored document into a persisted record. - /// protected CultPersistedRecord ToPersistedRecord(CultStoredDocument entry, Func serializePayload) { return new CultPersistedRecord @@ -2248,9 +1902,6 @@ namespace GameCult.Caching }; } - /// - /// Converts a persisted record into a stored document. - /// protected CultStoredDocument ToStoredDocument( CultPersistedRecord record, IReadOnlyCollection catalog, @@ -2265,60 +1916,30 @@ namespace GameCult.Caching document); } - /// - /// Records schema migration reports captured during a pull. - /// protected void SetLastSchemaMigrationReports(IEnumerable reports) { _lastSchemaMigrationReports = reports?.ToArray() ?? Array.Empty(); } - /// - /// Marks the backing store as durably flushed. - /// protected void MarkFlushSucceeded() { IsDirty = false; } } - /// - /// Base class for backing stores that persist a complete snapshot to one file. - /// public abstract class SingleFileBackingStore : CacheBackingStore { - /// - /// Creates a single-file backing store. - /// protected SingleFileBackingStore(string filePath) { FileInfo = new FileInfo(filePath); } - /// - /// Gets the file used by this backing store. - /// protected FileInfo FileInfo { get; } - /// - /// Serializes a full store snapshot. - /// protected abstract byte[] SerializeSnapshot(CultPersistedStoreSnapshot snapshot); - /// - /// Deserializes a full store snapshot. - /// protected abstract CultPersistedStoreSnapshot DeserializeSnapshot(byte[] data); - /// - /// Serializes one document payload. - /// protected abstract byte[] SerializePayload(object document); - /// - /// Deserializes one document payload. - /// protected abstract object DeserializePayload(Type documentType, byte[] payload); - /// - /// Loads every persisted record from disk. - /// public override void PullAll() { FileInfo.Refresh(); @@ -2375,27 +1996,18 @@ namespace GameCult.Caching IsDirty = false; } - /// - /// Stages one stored document in memory. - /// public override void Push(CultStoredDocument entry) { Entries[entry.Key.Value] = entry; IsDirty = true; } - /// - /// Removes one staged document. - /// public override void Delete(CultStoredDocument entry) { Entries.TryRemove(entry.Key.Value, out _); IsDirty = true; } - /// - /// Writes the staged snapshot to disk. - /// public override void PushAll() { var snapshot = new CultPersistedStoreSnapshot diff --git a/src/GameCult.Caching/CultDocumentContracts.cs b/src/GameCult.Caching/CultDocumentContracts.cs index 46dca479..e601f0bc 100644 --- a/src/GameCult.Caching/CultDocumentContracts.cs +++ b/src/GameCult.Caching/CultDocumentContracts.cs @@ -2,15 +2,9 @@ using System; namespace GameCult.Caching { - /// - /// Marks a type as a CultCache document with a stable schema name and version. - /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class CultDocumentAttribute : Attribute { - /// - /// Creates a document marker for the supplied schema identity. - /// public CultDocumentAttribute(string schemaName, string schemaVersion) { SchemaName = string.IsNullOrWhiteSpace(schemaName) @@ -21,172 +15,91 @@ namespace GameCult.Caching : schemaVersion; } - /// - /// Gets the stable schema name used by CultCache and CultNet. - /// public string SchemaName { get; } - /// - /// Gets the semantic schema version string for this document shape. - /// public string SchemaVersion { get; } } - /// - /// Marks a document type as having one global record in a cache. - /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class CultGlobalAttribute : Attribute { } - /// - /// Marks the member that supplies the human-readable document name. - /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] public sealed class CultNameAttribute : Attribute { } - /// - /// Marks a member as an indexed lookup value. - /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public sealed class CultIndexAttribute : Attribute { - /// - /// Creates an index marker with an optional alias. - /// public CultIndexAttribute(string? alias = null) { Alias = alias; } - /// - /// Gets the external index alias, or null to use the member name. - /// public string? Alias { get; } } - /// - /// Marks a member as a reference to another CultCache document. - /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true)] public sealed class CultReferenceAttribute : Attribute { - /// - /// Creates a document reference marker. - /// public CultReferenceAttribute(Type? targetType = null, bool many = false) { TargetType = targetType; Many = many; } - /// - /// Gets the explicit referenced document type, when one is required. - /// public Type? TargetType { get; } - /// - /// Gets whether this member stores many references rather than one reference. - /// public bool Many { get; } } - /// - /// Identifies a record inside a CultCache backing store. - /// public readonly struct CultRecordKey : IEquatable { - /// - /// Creates a record key from its persisted string value. - /// public CultRecordKey(string value) { Value = value ?? string.Empty; } - /// - /// Gets the persisted key value. - /// public string Value { get; } - /// - /// Returns the persisted key value. - /// public override string ToString() => Value; - /// - /// Returns whether this key and another key contain the same persisted value. - /// public bool Equals(CultRecordKey other) => string.Equals(Value, other.Value, StringComparison.Ordinal); - /// - /// Returns whether this key and another object contain the same persisted value. - /// public override bool Equals(object? obj) => obj is CultRecordKey other && Equals(other); - /// - /// Returns a hash code for the persisted key value. - /// public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value); } - /// - /// Carries the persisted record key for a typed document. - /// public readonly struct CultRecordHandle { - /// - /// Creates a typed document handle. - /// public CultRecordHandle(CultRecordKey key) { Key = key; } - /// - /// Gets the persisted record key. - /// public CultRecordKey Key { get; } - /// - /// Returns the persisted key value. - /// public override string ToString() => Key.ToString(); } - /// - /// Stores a typed reference to a CultCache document. - /// public readonly struct CultRecordRef { - /// - /// Creates a typed document reference. - /// public CultRecordRef(CultRecordKey key) { Key = key; } - /// - /// Gets the referenced record key. - /// public CultRecordKey Key { get; } - /// - /// Resolves the referenced record against the provided cache. - /// public T? Resolve(CultCache cache) { if (cache == null) throw new ArgumentNullException(nameof(cache)); return cache.Get(Key) is T typed ? typed : default; } - /// - /// Returns the referenced key value. - /// public override string ToString() => Key.ToString(); } } diff --git a/src/GameCult.Caching/CultGeneratedDocumentMetadata.cs b/src/GameCult.Caching/CultGeneratedDocumentMetadata.cs index 7314c87f..c0bb601d 100644 --- a/src/GameCult.Caching/CultGeneratedDocumentMetadata.cs +++ b/src/GameCult.Caching/CultGeneratedDocumentMetadata.cs @@ -6,45 +6,24 @@ using System.Text; namespace GameCult.Caching { - /// - /// Registers a generated CultCache document metadata provider with an assembly. - /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class CultGeneratedDocumentMetadataProviderAttribute : Attribute { - /// - /// Creates an assembly metadata provider registration. - /// public CultGeneratedDocumentMetadataProviderAttribute(Type providerType) { ProviderType = providerType ?? throw new ArgumentNullException(nameof(providerType)); } - /// - /// Gets the provider type that can create generated document definitions. - /// public Type ProviderType { get; } } - /// - /// Supplies generated CultCache document metadata for an assembly. - /// public interface ICultGeneratedDocumentMetadataProvider { - /// - /// Gets the generated document definitions exposed by the provider. - /// IEnumerable GetDocumentDefinitions(); } - /// - /// Describes one generated CultCache document contract. - /// public sealed class CultGeneratedDocumentDefinition { - /// - /// Creates a generated document contract definition. - /// public CultGeneratedDocumentDefinition( Type documentType, string schemaName, @@ -73,65 +52,29 @@ namespace GameCult.Caching Members = members ?? Array.Empty(); } - /// - /// Gets the CLR document type. - /// public Type DocumentType { get; } - /// - /// Gets the stable CultCache schema name. - /// public string SchemaName { get; } - /// - /// Gets the schema version string. - /// public string SchemaVersion { get; } - /// - /// Gets whether this document type stores one global record. - /// public bool IsGlobal { get; } - /// - /// Gets the member used as the document name, if any. - /// public string? NameMember { get; } - /// - /// Gets the generated name accessor, if any. - /// public Func? NameAccessor { get; } - /// - /// Gets the generated payload serializer, if one was emitted. - /// public Func? SerializePayload { get; } - /// - /// Gets the generated payload deserializer, if one was emitted. - /// public Func? DeserializePayload { get; } - /// - /// Gets the generated index accessors. - /// public IReadOnlyList IndexAccessors { get; } - /// - /// Gets the generated persisted member definitions. - /// public IReadOnlyList Members { get; } } - /// - /// Describes a generated accessor for one CultCache index. - /// public sealed class CultGeneratedDocumentIndexAccessor { - /// - /// Creates a generated index accessor. - /// public CultGeneratedDocumentIndexAccessor(string alias, Func accessor) { Alias = string.IsNullOrWhiteSpace(alias) @@ -140,25 +83,13 @@ namespace GameCult.Caching Accessor = accessor ?? throw new ArgumentNullException(nameof(accessor)); } - /// - /// Gets the index alias. - /// public string Alias { get; } - /// - /// Gets the function that extracts the index value. - /// public Func Accessor { get; } } - /// - /// Describes one generated persisted document member. - /// public sealed class CultGeneratedDocumentMemberDefinition { - /// - /// Creates a generated persisted member definition. - /// public CultGeneratedDocumentMemberDefinition( string memberName, int slot, @@ -183,44 +114,20 @@ namespace GameCult.Caching IndexAlias = indexAlias; } - /// - /// Gets the CLR member name. - /// public string MemberName { get; } - /// - /// Gets the persisted slot number. - /// public int Slot { get; } - /// - /// Gets the persisted type name. - /// public string TypeName { get; } - /// - /// Gets whether this member stores a document reference. - /// public bool IsReference { get; } - /// - /// Gets whether this member stores many references. - /// public bool IsMany { get; } - /// - /// Gets the target schema name for reference members. - /// public string? TargetSchemaName { get; } - /// - /// Gets whether this member is the document name. - /// public bool IsName { get; } - /// - /// Gets the index alias for indexed members. - /// public string? IndexAlias { get; } } diff --git a/src/GameCult.Caching/CultManagedDocument.cs b/src/GameCult.Caching/CultManagedDocument.cs index ce86871b..fcfeaee8 100644 --- a/src/GameCult.Caching/CultManagedDocument.cs +++ b/src/GameCult.Caching/CultManagedDocument.cs @@ -7,35 +7,17 @@ using R3; namespace GameCult.Caching { - /// - /// Describes how a CultCache-managed document changed. - /// public enum CultCacheDocumentChangeKind { - /// - /// A document was added. - /// Added, - /// - /// A document was updated. - /// Updated, - /// - /// A document was removed. - /// Removed } - /// - /// One reactive CultCache document change. - /// public sealed class CultCacheDocumentChange where T : class { - /// - /// Creates a document change. - /// public CultCacheDocumentChange( CultCacheDocumentChangeKind kind, CultRecordKey key, @@ -48,24 +30,12 @@ namespace GameCult.Caching PreviousDocument = previousDocument; } - /// - /// Gets the change kind. - /// public CultCacheDocumentChangeKind Kind { get; } - /// - /// Gets the document record key. - /// public CultRecordKey Key { get; } - /// - /// Gets the current POCO presentation, when present. - /// public T? Document { get; } - /// - /// Gets the previous POCO presentation, when present. - /// public T? PreviousDocument { get; } } } diff --git a/src/GameCult.Caching/GameCult.Caching.csproj b/src/GameCult.Caching/GameCult.Caching.csproj index 194872bc..9188de14 100644 --- a/src/GameCult.Caching/GameCult.Caching.csproj +++ b/src/GameCult.Caching/GameCult.Caching.csproj @@ -1,6 +1,7 @@ CultCache: Concurrent polymorphic cache with modular backing stores for .NET + $(NoWarn);CS1591