Hydrate durable globals before defaults

This commit is contained in:
Meta Vens 2026-07-20 02:23:42 +01:00
commit d69fa61412
4 changed files with 125 additions and 19 deletions

View file

@ -70,15 +70,39 @@ namespace GameCult.Caching.MessagePack
/// Creates a cache with the canonical single-file MessagePack backing store attached.
/// </summary>
public static CultCache Create(string filePath, CultCacheOpenOptions? options = null)
{
options ??= new CultCacheOpenOptions();
return CreateCore(filePath, options, initializeGlobals: true);
}
/// <summary>
/// Creates a cache with the canonical single-file MessagePack backing store attached and optionally pulls the on-disk snapshot.
/// </summary>
public static async Task<CultCache> OpenAsync(string filePath, CultCacheOpenOptions? options = null)
{
options ??= new CultCacheOpenOptions();
var cache = CreateCore(filePath, options, initializeGlobals: false);
if (options.PullOnOpen)
{
await cache.PullAllBackingStoresAsync().ConfigureAwait(false);
}
cache.MaterializeMissingGlobals();
return cache;
}
private static CultCache CreateCore(
string filePath,
CultCacheOpenOptions options,
bool initializeGlobals)
{
if (string.IsNullOrWhiteSpace(filePath))
{
throw new ArgumentException("File path must be non-empty.", nameof(filePath));
}
options ??= new CultCacheOpenOptions();
var cache = new CultCache(options.Registry)
var cache = new CultCache(options.Registry, initializeGlobals)
{
FlushAttachedStoresOnDispose = options.FlushOnDispose
};
@ -106,20 +130,5 @@ namespace GameCult.Caching.MessagePack
return cache;
}
/// <summary>
/// Creates a cache with the canonical single-file MessagePack backing store attached and optionally pulls the on-disk snapshot.
/// </summary>
public static async Task<CultCache> OpenAsync(string filePath, CultCacheOpenOptions? options = null)
{
options ??= new CultCacheOpenOptions();
var cache = Create(filePath, options);
if (options.PullOnOpen)
{
await cache.PullAllBackingStoresAsync().ConfigureAwait(false);
}
return cache;
}
}
}

View file

@ -154,6 +154,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
IsDirty = !_dirtyKeys.IsEmpty || !_deletedKeys.IsEmpty;
}
/// <inheritdoc />
public override bool ContainsDurableRecord(CultRecordKey key)
{
return _durableIndex.ContainsKey(key.Value) || base.ContainsDurableRecord(key);
}
/// <inheritdoc />
public override void Push(CultStoredDocument entry)
{

View file

@ -1284,8 +1284,29 @@ namespace GameCult.Caching
/// Creates a cache using the supplied document registry or the shared registry.
/// </summary>
public CultCache(CultDocumentRegistry? registry = null)
: this(registry, initializeGlobals: true)
{
}
/// <summary>
/// Creates a cache using the supplied document registry, optionally deferring global defaults
/// until a durable backing store has been hydrated.
/// </summary>
public CultCache(CultDocumentRegistry? registry, bool initializeGlobals)
{
_registry = registry ?? CultDocumentRegistry.Shared;
if (initializeGlobals)
{
MaterializeMissingGlobals();
}
}
/// <summary>
/// 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.
/// </summary>
public void MaterializeMissingGlobals()
{
InitializeGlobals();
}
@ -1800,6 +1821,13 @@ namespace GameCult.Caching
{
foreach (var descriptor in _registry.AllDescriptors.Where(candidate => candidate.IsGlobal))
{
var key = new CultRecordKey($"global:{descriptor.SchemaId}");
if (_entries.ContainsKey(key.Value) ||
_backingStores.Any(store => store.ContainsDurableRecord(key)))
{
continue;
}
if (descriptor.DocumentType.GetConstructor(Type.EmptyTypes) == null)
{
continue;
@ -1813,7 +1841,7 @@ namespace GameCult.Caching
AddStoredDocumentInternal(
new CultStoredDocument(
new CultRecordKey($"global:{descriptor.SchemaId}"),
key,
DateTimeOffset.UtcNow.ToString("O"),
descriptor,
instance),
@ -2037,6 +2065,13 @@ namespace GameCult.Caching
PullAll();
}
/// <summary>
/// Returns whether the backing store knows a record is durable, including records left cold by selective hydration.
/// </summary>
public virtual bool ContainsDurableRecord(CultRecordKey key)
{
return Entries.ContainsKey(key.Value);
}
/// <summary>
/// Pushes one stored document into the backing store.
/// </summary>
public abstract void Push(CultStoredDocument entry);