Route Verse client inputs through managed sessions

This commit is contained in:
Meta Vens 2026-06-28 14:53:58 +01:00
commit 8a14dd74ee
3 changed files with 68 additions and 50 deletions

View file

@ -11235,17 +11235,14 @@ static void RequireAetheriaRuntimeVerseClientContract(string root)
"return _aetheriaState ??= CreateAetheriaState();",
"AetheriaRuntimeLoadoutTemplatesDocument",
"ProjectStarbridgeSummaryAsync",
"new AetheriaRuntimeManagedClientInputs(",
"catalogDocument.Reactive()",
"loadoutTemplatesDocument.Reactive()",
"starbridgeScenarioDocument.Reactive()",
"starbridgeSessionDocument.Reactive()",
"latestFrameDocument.Reactive()",
"managedInputs.RequireFrame()",
"managedInputs.Catalog",
"managedInputs.LoadoutTemplates.Templates",
"managedInputs.StarbridgeScenario",
"managedInputs.StarbridgeSession",
"AetheriaRuntimeManagedClientInputs? managedInputs = null;",
"managedInputs = new AetheriaRuntimeManagedClientInputs(state);",
"AetheriaRuntimeManagedClientInputs RequireManagedInputs()",
"RequireManagedInputs().RequireFrame()",
"inputs.Catalog",
"inputs.LoadoutTemplates.Templates",
"inputs.StarbridgeScenario",
"inputs.StarbridgeSession",
"BootstrapCatalogDocument(",
"CatalogBootstrapSource(",
"BootstrapRuntimeCatalogSnapshot()",
@ -11272,11 +11269,17 @@ static void RequireAetheriaRuntimeVerseClientContract(string root)
var requiredManagedInputSymbols = new[]
{
"internal sealed class AetheriaRuntimeManagedClientInputs : IDisposable",
"CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>",
"CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>",
"CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument>",
"CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument>",
"CultMeshReactiveDocument<AetheriaRuntimeStarbridgeSessionDocument>",
"private readonly AetheriaRuntimeDaemonFrameSession _daemonFrame;",
"private readonly AetheriaRuntimeCatalogSession _catalog;",
"private readonly AetheriaRuntimeLoadoutTemplatesSession _loadoutTemplates;",
"private readonly AetheriaRuntimeStarbridgeScenarioSession _starbridgeScenario;",
"private readonly AetheriaRuntimeStarbridgeRunSession _starbridgeSession;",
"public AetheriaRuntimeManagedClientInputs(AetheriaClientState state)",
"_daemonFrame = state.ObserveDaemonFrame();",
"_catalog = state.ObserveCatalog();",
"_loadoutTemplates = state.ObserveLoadoutTemplates();",
"_starbridgeScenario = state.Starbridge.ObserveScenario();",
"_starbridgeSession = state.Starbridge.ObserveSession();",
"public AetheriaRuntimeCatalogSnapshot Catalog => _catalog.Current",
"public AetheriaRuntimeLoadoutTemplatesDocument LoadoutTemplates => _loadoutTemplates.Current",
"public AetheriaRuntimeDaemonFrameDocument RequireFrame()",
@ -11294,6 +11297,18 @@ static void RequireAetheriaRuntimeVerseClientContract(string root)
string.Join(", ", missingManagedInputSymbols));
}
if (managedClientInputs.Contains("CultMeshReactiveDocument<", StringComparison.Ordinal) ||
managedClientInputs.Contains(".Reactive()", StringComparison.Ordinal) ||
client.Contains("catalogDocument.Reactive()", StringComparison.Ordinal) ||
client.Contains("loadoutTemplatesDocument.Reactive()", StringComparison.Ordinal) ||
client.Contains("starbridgeScenarioDocument.Reactive()", StringComparison.Ordinal) ||
client.Contains("starbridgeSessionDocument.Reactive()", StringComparison.Ordinal) ||
client.Contains("latestFrameDocument.Reactive()", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Aetheria managed client inputs must sample named managed state sessions instead of owning raw reactive CultMesh documents.");
}
if (client.Contains("AetheriaRuntimeReactiveProjectionInputs", StringComparison.Ordinal) ||
client.Contains("private sealed class AetheriaRuntimeManagedClientInputs", StringComparison.Ordinal) ||
client.Contains("_projectionInputs", StringComparison.Ordinal) ||
@ -11436,7 +11451,7 @@ static void RequireAetheriaRuntimeVerseClientContract(string root)
var starbridgeSummaryBlock = indexedDocumentStart > starbridgeSummaryStart
? client.Substring(starbridgeSummaryStart, indexedDocumentStart - starbridgeSummaryStart)
: client.Substring(starbridgeSummaryStart);
if (!starbridgeSummaryBlock.Contains("managedInputs.Catalog", StringComparison.Ordinal) ||
if (!starbridgeSummaryBlock.Contains("inputs.Catalog", StringComparison.Ordinal) ||
starbridgeSummaryBlock.Contains("catalogDocument.LatestAsync()", StringComparison.Ordinal) ||
starbridgeSummaryBlock.Contains("BootstrapRuntimeCatalogSnapshot()", StringComparison.Ordinal) ||
starbridgeSummaryBlock.Contains("AetheriaRuntimeCatalogStore.OpenReadOnly", StringComparison.Ordinal))
@ -17442,7 +17457,7 @@ static void RequireInventoryLoadoutRestoreRequestAuthority(string root)
!rtsProjection.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryProjectLoadoutTemplatePrice(", StringComparison.Ordinal) ||
!rtsProjection.Contains("credits >= price", StringComparison.Ordinal) ||
!clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeLoadoutTemplatesDocument> LoadoutTemplates", StringComparison.Ordinal) ||
!verseClient.Contains("managedInputs.LoadoutTemplates.Templates", StringComparison.Ordinal) ||
!verseClient.Contains("inputs.LoadoutTemplates.Templates", StringComparison.Ordinal) ||
!verseClient.Contains("ProjectStationRefit(", StringComparison.Ordinal))
{
throw new InvalidOperationException(

View file

@ -1,5 +1,4 @@
using System;
using GameCult.Mesh;
#nullable enable
@ -7,24 +6,22 @@ namespace GameCult.Aetheria.State.Verse
{
internal sealed class AetheriaRuntimeManagedClientInputs : IDisposable
{
private readonly CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _daemonFrame;
private readonly CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> _catalog;
private readonly CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument> _loadoutTemplates;
private readonly CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument> _starbridgeScenario;
private readonly CultMeshReactiveDocument<AetheriaRuntimeStarbridgeSessionDocument> _starbridgeSession;
private readonly AetheriaRuntimeDaemonFrameSession _daemonFrame;
private readonly AetheriaRuntimeCatalogSession _catalog;
private readonly AetheriaRuntimeLoadoutTemplatesSession _loadoutTemplates;
private readonly AetheriaRuntimeStarbridgeScenarioSession _starbridgeScenario;
private readonly AetheriaRuntimeStarbridgeRunSession _starbridgeSession;
public AetheriaRuntimeManagedClientInputs(
CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> daemonFrame,
CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> catalog,
CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument> loadoutTemplates,
CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument> starbridgeScenario,
CultMeshReactiveDocument<AetheriaRuntimeStarbridgeSessionDocument> starbridgeSession)
public AetheriaRuntimeManagedClientInputs(AetheriaClientState state)
{
_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));
_starbridgeSession = starbridgeSession ?? throw new ArgumentNullException(nameof(starbridgeSession));
if (state == null)
throw new ArgumentNullException(nameof(state));
_daemonFrame = state.ObserveDaemonFrame();
_catalog = state.ObserveCatalog();
_loadoutTemplates = state.ObserveLoadoutTemplates();
_starbridgeScenario = state.Starbridge.ObserveScenario();
_starbridgeSession = state.Starbridge.ObserveSession();
}
public AetheriaRuntimeCatalogSnapshot Catalog => _catalog.Current

View file

@ -546,15 +546,9 @@ namespace GameCult.Aetheria.State.Verse
AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest);
var latestFrameDocument = Document<AetheriaRuntimeDaemonFrameDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest);
var managedInputs = new AetheriaRuntimeManagedClientInputs(
latestFrameDocument.Reactive(),
catalogDocument.Reactive(),
loadoutTemplatesDocument.Reactive(),
starbridgeScenarioDocument.Reactive(),
starbridgeSessionDocument.Reactive());
_managedClientInputs = managedInputs;
AetheriaRuntimeManagedClientInputs? managedInputs = null;
return new AetheriaClientState(
var state = new AetheriaClientState(
Document<AetheriaRuntimeDaemonProviderAdvertisementDocument>(
AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement),
Document<AetheriaRuntimeDaemonHealthDocument>(
@ -656,6 +650,10 @@ namespace GameCult.Aetheria.State.Verse
seatId => Document<AetheriaRuntimeStarbridgePlayerSeatDocument>(
AetheriaRuntimeVerseRecordKeys.StarbridgePlayerSeat(seatId)));
managedInputs = new AetheriaRuntimeManagedClientInputs(state);
_managedClientInputs = managedInputs;
return state;
CultMeshDocumentHandle<TDocument> ProjectedDocument<TDocument>(
string documentId,
Func<AetheriaRuntimeDaemonFrameDocument, Task<TDocument>> project,
@ -678,7 +676,7 @@ namespace GameCult.Aetheria.State.Verse
return CultMesh.Document(
documentId,
verse,
async _ => await project(managedInputs.RequireFrame()).ConfigureAwait(false),
async _ => await project(RequireManagedInputs().RequireFrame()).ConfigureAwait(false),
_ => frameChanges
.SelectAwait(async (frame, cancellationToken) =>
await project(frame).ConfigureAwait(false)),
@ -686,6 +684,12 @@ namespace GameCult.Aetheria.State.Verse
routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed projected state"));
}
AetheriaRuntimeManagedClientInputs RequireManagedInputs()
{
return managedInputs
?? throw new InvalidOperationException("Aetheria Verse client managed inputs were sampled before client state was initialized.");
}
CultMeshDocumentHandle<TDocument> BootstrapCatalogDocument<TDocument>(
string documentId,
Func<Task<TDocument>> bootstrap,
@ -712,20 +716,22 @@ namespace GameCult.Aetheria.State.Verse
Task<AetheriaRuntimeStationRefitDocument> ProjectStationRefitAsync(
AetheriaRuntimeDaemonFrameDocument frame)
{
var inputs = RequireManagedInputs();
return Task.FromResult(AetheriaRuntimeRtsProjection.ProjectStationRefit(
frame,
managedInputs.LoadoutTemplates.Templates,
managedInputs.Catalog));
inputs.LoadoutTemplates.Templates,
inputs.Catalog));
}
Task<AetheriaRuntimeStarbridgeSessionSummaryDocument> ProjectStarbridgeSummaryAsync(
AetheriaRuntimeDaemonFrameDocument frame)
{
var inputs = RequireManagedInputs();
return Task.FromResult(AetheriaRuntimeStarbridgeProjection.ProjectSessionSummary(
frame,
managedInputs.StarbridgeScenario,
managedInputs.StarbridgeSession,
managedInputs.Catalog));
inputs.StarbridgeScenario,
inputs.StarbridgeSession,
inputs.Catalog));
}
static string IndexedDocumentId(string prefix, int index)