Collapse legacy backing stores to pull-only

This commit is contained in:
Meta Vens 2026-06-13 07:01:35 +01:00
commit 8f9852da4d
2 changed files with 5 additions and 155 deletions

View file

@ -3,13 +3,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Buffers.Text;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using MessagePack;
using UniRx;
@ -18,11 +15,6 @@ public class CultCache
private readonly object addLock = new object();
//public Action<string> Logger = Console.WriteLine;
//private string _filePath;
public event Action<DatabaseEntry, DatabaseEntry> OnUpdate;
private List<CacheBackingStore> _backingStores = new List<CacheBackingStore>();
private readonly Dictionary<Type, CacheBackingStore> _typeStores = new Dictionary<Type, CacheBackingStore>();
@ -33,21 +25,6 @@ public class CultCache
private readonly Dictionary<Type, DatabaseEntry> _globals = new Dictionary<Type, DatabaseEntry>();
private readonly Dictionary<Type, HashSet<DatabaseEntry>> _types = new Dictionary<Type, HashSet<DatabaseEntry>>();
// private readonly Dictionary<Type, (DirectoryInfo directory, List<Guid> entries)> _externalTypes = new Dictionary<Type, (DirectoryInfo directory, List<Guid> entries)>();
// private class ExternalEntry
// {
// public string FilePath;
// public DatabaseEntry Entry;
//
// public ExternalEntry(string filePath, DatabaseEntry entry)
// {
// FilePath = filePath;
// Entry = entry;
// }
// }
// private readonly Dictionary<Guid, ExternalEntry> _externalEntries = new Dictionary<Guid, ExternalEntry>();
public IEnumerable<DatabaseEntry> AllEntries => _entries.Values;
public bool ReadOnly { get; }
@ -75,23 +52,11 @@ public class CultCache
}
else
{
foreach(var existingStore in _backingStores) store.SubscribeTo(existingStore);
_backingStores.Add(store);
}
store.EntryAdded.Subscribe(entry =>
{
Add(entry, store);
OnUpdate?.Invoke(null, entry);
});
store.EntryUpdated.Subscribe(entry =>
{
Add(entry, store);
OnUpdate?.Invoke(entry, entry);
});
store.EntryDeleted.Subscribe(entry =>
{
Remove(entry, store);
OnUpdate?.Invoke(entry, null);
});
}
@ -121,25 +86,6 @@ public class CultCache
_globals[type] = entry;
}
if(_typeStores.ContainsKey(type))
{
var typeStore = _typeStores[type];
if(typeStore != source)
{
ThrowIfReadOnlyWrite();
typeStore.Push(entry);
}
}
else
{
var masterStore = _backingStores.FirstOrDefault();
if(masterStore != null && masterStore!=source)
{
ThrowIfReadOnlyWrite();
masterStore.Push(entry);
}
}
_entries[entry.ID] = entry;
_types[type].Add(entry);
foreach (var parentType in type.GetParentTypes())
@ -200,40 +146,12 @@ public class CultCache
{
_entries.Remove(entry.ID);
var type = entry.GetType();
if(_typeStores.ContainsKey(type))
{
var typeStore = _typeStores[type];
if(typeStore != source)
{
ThrowIfReadOnlyWrite();
typeStore.Delete(entry);
}
}
else foreach(var store in _backingStores)
{
if(store != source)
{
ThrowIfReadOnlyWrite();
store.Delete(entry);
}
}
foreach (var parentType in type.GetParentTypes())
{
if(_types.ContainsKey(parentType))
_types[parentType].Remove(entry);
}
}
private void ThrowIfReadOnlyWrite()
{
if (ReadOnly)
throw new InvalidOperationException("Legacy CultCache is read-only. Durable state belongs to the Verse state spine.");
}
}
public interface RealtimeBackingStore
{
public void ObserveChanges();
}
public abstract class CacheBackingStore
@ -241,30 +159,16 @@ public abstract class CacheBackingStore
protected CacheBackingStore()
{
EntryAdded = new Subject<DatabaseEntry>();
EntryDeleted = new Subject<DatabaseEntry>();
EntryUpdated = new Subject<DatabaseEntry>();
}
public abstract void PullAll();
public abstract void Push(DatabaseEntry entry);
public abstract void Delete(DatabaseEntry entry);
public abstract void PushAll(bool soft = false);
public Subject<DatabaseEntry> EntryAdded { get; }
public Subject<DatabaseEntry> EntryDeleted { get; }
public Subject<DatabaseEntry> EntryUpdated { get; }
protected Dictionary<Guid, DatabaseEntry> Entries = new Dictionary<Guid, DatabaseEntry>();
public void SubscribeTo(CacheBackingStore targetStore)
{
targetStore.EntryAdded.Subscribe(Push);
targetStore.EntryDeleted.Subscribe(Delete);
targetStore.EntryUpdated.Subscribe(Push);
}
}
public abstract class MultiFileBackingStore : CacheBackingStore, RealtimeBackingStore
public abstract class MultiFileBackingStore : CacheBackingStore
{
public DirectoryInfo DirectoryInfo { get; }
protected Dictionary<Type, DirectoryInfo> _entryTypeDirectories = new Dictionary<Type, DirectoryInfo>();
@ -297,47 +201,6 @@ public abstract class MultiFileBackingStore : CacheBackingStore, RealtimeBacking
}
}
public override void Push(DatabaseEntry entry)
{
throw new NotSupportedException("Legacy multi-file backing stores are read-only migration inputs.");
}
public override void Delete(DatabaseEntry entry)
{
throw new NotSupportedException("Legacy multi-file backing stores are read-only migration inputs.");
}
public override void PushAll(bool soft = false)
{
throw new NotSupportedException("Legacy multi-file backing stores are read-only migration inputs.");
}
public void ObserveChanges()
{
foreach (var directory in _entryTypeDirectories.Values)
{
if (!directory.Exists) continue;
var watcher = new FileSystemWatcher(directory.FullName);
watcher.Changed += (sender, args) =>
{
var entry = Deserialize(File.ReadAllBytes(args.FullPath));
Entries[entry.ID] = entry;
EntryUpdated.OnNext(entry);
};
watcher.Created += (sender, args) =>
{
var entry = Deserialize(File.ReadAllBytes(args.FullPath));
Entries[entry.ID] = entry;
EntryAdded.OnNext(entry);
};
watcher.Deleted += (sender, args) =>
{
var entry = Deserialize(File.ReadAllBytes(args.FullPath));
Entries[entry.ID] = entry;
EntryDeleted.OnNext(entry);
};
}
}
}
public class MultiFileMessagePackBackingStore : MultiFileBackingStore
@ -383,20 +246,6 @@ public abstract class SingleFileBackingStore : CacheBackingStore
}
}
public override void Push(DatabaseEntry entry)
{
throw new NotSupportedException("Legacy single-file backing stores are read-only migration inputs.");
}
public override void Delete(DatabaseEntry entry)
{
throw new NotSupportedException("Legacy single-file backing stores are read-only migration inputs.");
}
public override void PushAll(bool soft = false)
{
throw new NotSupportedException("Legacy single-file backing stores are read-only migration inputs.");
}
}
public class SingleFileMessagePackBackingStore : SingleFileBackingStore

View file

@ -34,9 +34,9 @@ local CultCache, and legacy UI paths should be migration-only or deleted.
deleted.
- `LegacyCatalogBoundary` opens the legacy `CultCache` in read-only mode. Old
MessagePack backing stores may hydrate in-memory domain objects for the
current Unity runtime, but this path cannot push or delete legacy files.
`SingleFileMessagePackBackingStore` and `MultiFileMessagePackBackingStore`
now throw on `Push`, `Delete`, and `PushAll`.
current Unity runtime, but this path cannot push or delete legacy files. The
legacy backing-store abstraction is pull-only; its write and realtime
watcher APIs have been deleted.
- `DatabaseLink<T>.Value` can resolve legacy links only after
`LegacyCatalogBoundary` binds the read-only catalog cache. `CultCache`
construction no longer grabs global `DatabaseLinkBase` authority.
@ -216,6 +216,7 @@ First Aetheria surfaces to publish:
- Done: delete the old `SavedGame`/`SavedZone` runtime save DTO and loader.
- Done: stop legacy `CultCache` pull/read paths from writing entries back to
their source backing store.
- Done: delete legacy `CultCache` backing-store write/realtime APIs.
- Remaining: delete or quarantine old cache abstractions that no longer
protect an invariant once catalog migration has a typed reader.