diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index a8de0557f..58d7561b7 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -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 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();", diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs index 9f0fab470..61fba67f3 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs @@ -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, diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index d71a86e2a..e52f3797c 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -544,7 +544,10 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest); var starbridgeSessionDocument = Document( AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest); + var latestFrameDocument = Document( + 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( AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy), - Document( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest), + latestFrameDocument, Document( AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest), Document( @@ -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 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 MutableDocumentPointer(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 _daemonFrame; private readonly CultMeshReactiveDocument _catalog; private readonly CultMeshReactiveDocument _loadoutTemplates; private readonly CultMeshReactiveDocument _starbridgeScenario; @@ -794,6 +789,7 @@ namespace GameCult.Aetheria.State.Verse private readonly AetheriaRuntimeLoadoutTemplatesDocument _fallbackLoadoutTemplates; public AetheriaRuntimeReactiveProjectionInputs( + CultMeshReactiveDocument daemonFrame, CultMeshReactiveDocument catalog, CultMeshReactiveDocument loadoutTemplates, CultMeshReactiveDocument 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();