Use reactive frame inputs for Verse projections

This commit is contained in:
Meta Vens 2026-06-28 10:39:09 +01:00
commit 1f066b793b
3 changed files with 32 additions and 16 deletions

View file

@ -10741,8 +10741,10 @@ static void RequireAetheriaRuntimeVerseClientContract(string root)
"loadoutTemplatesDocument.Reactive()",
"starbridgeScenarioDocument.Reactive()",
"starbridgeSessionDocument.Reactive()",
"latestFrameDocument.Reactive()",
"BootstrapRuntimeCatalogSnapshot(),",
"BootstrapLoadoutTemplatesDocument())",
"projectionInputs.RequireFrame()",
"projectionInputs.Catalog",
"projectionInputs.LoadoutTemplates.Templates",
"projectionInputs.StarbridgeScenario",
@ -10787,6 +10789,13 @@ static void RequireAetheriaRuntimeVerseClientContract(string root)
"Aetheria runtime Verse client Starbridge projection still polls sibling documents instead of sampling managed reactive projection inputs.");
}
if (client.Contains("RequireFrameAsync", StringComparison.Ordinal) ||
client.Contains("Aetheria().Daemon.LatestFrame.LatestAsync()", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Aetheria runtime Verse client projected documents still bootstrap through one-shot latest-frame reads instead of the managed reactive projection inputs.");
}
var requiredClientStateSymbols = new[]
{
"public Func<string, string> CreateEveSurfaceStateRefResolver()",
@ -14766,7 +14775,9 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root)
var requiredManagedClientConnectionSymbols = new[]
{
"public AetheriaClientState State => _state;",
"public AetheriaClientState Aetheria() => State;"
"public AetheriaClientState Aetheria() => State;",
"using var observedState = State.ReactiveObservedDaemon();",
"observedState.TryCurrent(out var current)"
};
var missingManagedClientConnectionSymbols = requiredManagedClientConnectionSymbols
.Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal))
@ -14840,6 +14851,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root)
var forbiddenDaemonClientBypassSymbols = new[]
{
".ReadAsync(State)",
"return _verse.GetObservedDaemonStateAsync();",
"return _verse.GetLatestAuthoritativeRunFrameAsync();",
"return _verse.GetHealthAsync();",

View file

@ -161,10 +161,10 @@ namespace GameCult.Aetheria.State.Verse
ThrowIfDisposed();
if (submit == null) throw new ArgumentNullException(nameof(submit));
var observed = AetheriaRuntimeObservedDaemonState
.ReadAsync(State)
.GetAwaiter()
.GetResult();
using var observedState = State.ReactiveObservedDaemon();
var observed = observedState.TryCurrent(out var current)
? current
: null;
var operationClient = new AetheriaRuntimeDaemonOperationClient(
StatePath,
_clientId,

View file

@ -544,7 +544,10 @@ namespace GameCult.Aetheria.State.Verse
AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest);
var starbridgeSessionDocument = Document<AetheriaRuntimeStarbridgeSessionDocument>(
AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest);
var latestFrameDocument = Document<AetheriaRuntimeDaemonFrameDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest);
var projectionInputs = new AetheriaRuntimeReactiveProjectionInputs(
latestFrameDocument.Reactive(),
catalogDocument.Reactive(),
loadoutTemplatesDocument.Reactive(),
starbridgeScenarioDocument.Reactive(),
@ -562,8 +565,7 @@ namespace GameCult.Aetheria.State.Verse
AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary),
Document<AetheriaRuntimeVerseAuthorityPolicyDocument>(
AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy),
Document<AetheriaRuntimeDaemonFrameDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest),
latestFrameDocument,
Document<AetheriaRuntimeDaemonSoaViewDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest),
Document<EveSurfaceState>(
@ -678,7 +680,7 @@ namespace GameCult.Aetheria.State.Verse
return CultMesh.Document(
documentId,
verse,
async _ => await project(await RequireFrameAsync().ConfigureAwait(false)).ConfigureAwait(false),
async _ => await project(projectionInputs.RequireFrame()).ConfigureAwait(false),
_ => frameChanges
.SelectAwait(async (frame, cancellationToken) =>
await project(frame).ConfigureAwait(false)),
@ -754,14 +756,6 @@ namespace GameCult.Aetheria.State.Verse
}
}
private async Task<AetheriaRuntimeDaemonFrameDocument> RequireFrameAsync()
{
var frame = await Aetheria().Daemon.LatestFrame.LatestAsync().ConfigureAwait(false);
if (frame == null)
throw new InvalidOperationException("Aetheria Verse client has no daemon frame yet.");
return frame;
}
private CultMeshMutableStatePointer<T> MutableDocumentPointer<T>(CultRecordKey key) where T : class
{
return CultMesh.MutableStatePointer(
@ -786,6 +780,7 @@ namespace GameCult.Aetheria.State.Verse
private sealed class AetheriaRuntimeReactiveProjectionInputs : IDisposable
{
private readonly CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _daemonFrame;
private readonly CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> _catalog;
private readonly CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument> _loadoutTemplates;
private readonly CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument> _starbridgeScenario;
@ -794,6 +789,7 @@ namespace GameCult.Aetheria.State.Verse
private readonly AetheriaRuntimeLoadoutTemplatesDocument _fallbackLoadoutTemplates;
public AetheriaRuntimeReactiveProjectionInputs(
CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> daemonFrame,
CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> catalog,
CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument> loadoutTemplates,
CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument> starbridgeScenario,
@ -801,6 +797,7 @@ namespace GameCult.Aetheria.State.Verse
AetheriaRuntimeCatalogSnapshot fallbackCatalog,
AetheriaRuntimeLoadoutTemplatesDocument fallbackLoadoutTemplates)
{
_daemonFrame = daemonFrame ?? throw new ArgumentNullException(nameof(daemonFrame));
_catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
_loadoutTemplates = loadoutTemplates ?? throw new ArgumentNullException(nameof(loadoutTemplates));
_starbridgeScenario = starbridgeScenario ?? throw new ArgumentNullException(nameof(starbridgeScenario));
@ -814,8 +811,15 @@ namespace GameCult.Aetheria.State.Verse
public AetheriaRuntimeStarbridgeScenarioDocument? StarbridgeScenario => _starbridgeScenario.Current;
public AetheriaRuntimeStarbridgeSessionDocument? StarbridgeSession => _starbridgeSession.Current;
public AetheriaRuntimeDaemonFrameDocument RequireFrame()
{
return _daemonFrame.Current
?? throw new InvalidOperationException("Aetheria Verse client has no daemon frame yet.");
}
public void Dispose()
{
_daemonFrame.Dispose();
_catalog.Dispose();
_loadoutTemplates.Dispose();
_starbridgeScenario.Dispose();