Serve committed Eve state before restart tick
This commit is contained in:
parent
947ce78786
commit
02c004897c
4 changed files with 104 additions and 25 deletions
|
|
@ -116,36 +116,63 @@ var nextApiPublicationUtc = DateTimeOffset.UtcNow;
|
|||
var ingressState = new AetheriaDaemonIngressState();
|
||||
var hotState = new AetheriaHotEntityPublicationState();
|
||||
var reactiveSurfaceState = new AetheriaReactiveSurfacePublicationState();
|
||||
var firstTick = await TickAsync(node, options, unityBundles, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
TraceStartup("first-tick");
|
||||
var publishRestoredFrame = !options.Once &&
|
||||
latestFrame?.Run != null &&
|
||||
HasPlayableRun(latestFrame.Run) &&
|
||||
HasPersistedClientBootstrap(node, latestFrame);
|
||||
AetheriaRuntimeDaemonTickResult? initialPublication = null;
|
||||
AetheriaPreparedPublication? initialPrepared = null;
|
||||
Task publicationTask;
|
||||
if (publishRestoredFrame)
|
||||
{
|
||||
await RefreshControlPlaneInputsAsync(node, ingressState).ConfigureAwait(false);
|
||||
TraceStartup("restored-client-state");
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstTick = await TickAsync(
|
||||
node, options, unityBundles, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
TraceStartup("first-tick");
|
||||
latestFrame = firstTick.Frame;
|
||||
initialPrepared = PreparePublication(
|
||||
node.StatePath,
|
||||
options,
|
||||
firstTick,
|
||||
ingressState,
|
||||
physicsPersistence);
|
||||
initialPublication = initialPrepared.Publication;
|
||||
}
|
||||
ThrowIfClientHostFaulted(cultMeshClientHost);
|
||||
latestFrame = firstTick.Frame;
|
||||
var firstPublication = PreparePublication(
|
||||
node.StatePath,
|
||||
options,
|
||||
firstTick,
|
||||
ingressState,
|
||||
physicsPersistence);
|
||||
await PublishClientGameplayDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
unityBundles,
|
||||
firstPublication.Publication,
|
||||
firstPublication.Catalog,
|
||||
reactiveSurfaceState,
|
||||
publishTopology: true).ConfigureAwait(false);
|
||||
if (!publishRestoredFrame)
|
||||
{
|
||||
await PublishClientGameplayDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
unityBundles,
|
||||
initialPublication!,
|
||||
ingressState.Catalog ?? throw new InvalidOperationException("Aetheria runtime catalog was not initialized."),
|
||||
reactiveSurfaceState,
|
||||
publishTopology: true).ConfigureAwait(false);
|
||||
}
|
||||
TraceStartup("client-bootstrap");
|
||||
await PublishHotEntityStateAsync(
|
||||
node, soaPublisher, hotState, firstTick.Frame, ingressState.Catalog, cultMeshClientHost).ConfigureAwait(false);
|
||||
node, soaPublisher, hotState, latestFrame!, ingressState.Catalog, cultMeshClientHost).ConfigureAwait(false);
|
||||
TraceStartup("hot-entity-state");
|
||||
Task publicationTask = Task.Run(() => PersistPreparedDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
physicsPersistence,
|
||||
firstPublication,
|
||||
initialTopology: true));
|
||||
if (publishRestoredFrame)
|
||||
{
|
||||
publicationTask = Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
publicationTask = Task.Run(() => PersistPreparedDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
physicsPersistence,
|
||||
initialPrepared!,
|
||||
initialTopology: true));
|
||||
}
|
||||
nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval);
|
||||
Console.WriteLine($"Aetheria Verse daemon published frame {firstTick.Frame.FrameId}.");
|
||||
Console.WriteLine($"Aetheria Verse daemon published frame {latestFrame!.FrameId}.");
|
||||
Console.WriteLine($"Aetheria daemon playable-world-ready in {startup.Elapsed.TotalMilliseconds:0.###}ms.");
|
||||
|
||||
if (options.Once)
|
||||
|
|
@ -466,6 +493,22 @@ static AetheriaPreparedPublication PreparePublication(
|
|||
ingressState.Catalog ?? throw new InvalidOperationException("Aetheria runtime catalog was not initialized."));
|
||||
}
|
||||
|
||||
static bool HasPersistedClientBootstrap(
|
||||
AetheriaStateNode node,
|
||||
AetheriaRuntimeDaemonFrameDocument frame)
|
||||
{
|
||||
var input = node.Cache.Get<EveInputCapabilityDocument>(AetheriaRuntimeVerseRecordKeys.PilotInputCapability);
|
||||
var game = node.Cache.Get<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface);
|
||||
var reactive = node.Cache.Get<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface);
|
||||
var map = node.Cache.Get<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MapMenuSurface);
|
||||
return node.Cache.Get<EveProviderAdvertisementDocument>(AetheriaRuntimeVerseRecordKeys.EveProviderAdvertisement) != null &&
|
||||
node.Cache.Get<EveAssetCatalogDocument>(AetheriaRuntimeVerseRecordKeys.EveAssetCatalog) != null &&
|
||||
input?.Version == frame.FrameId &&
|
||||
game?.Version == frame.FrameId &&
|
||||
reactive?.Version == frame.FrameId &&
|
||||
map?.Version == frame.FrameId;
|
||||
}
|
||||
|
||||
static async Task PersistPreparedDocumentsAsync(
|
||||
AetheriaStateNode node,
|
||||
AetheriaDaemonHostOptions options,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public static class AetheriaDocumentRegistry
|
|||
typeof(AetheriaRuntimeDaemonProviderAdvertisementDocument),
|
||||
typeof(AetheriaRuntimeDaemonHealthDocument),
|
||||
typeof(AetheriaRuntimeDaemonCommandBoundaryDocument),
|
||||
typeof(AetheriaRuntimeAssetManifestDocument),
|
||||
typeof(AetheriaRuntimeDaemonFrameDocument),
|
||||
typeof(AetheriaRuntimeGameViewportDocument),
|
||||
typeof(AetheriaRuntimeObjectsViewportDocument),
|
||||
|
|
|
|||
|
|
@ -127,6 +127,14 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
|
|||
MainMenuStateKey.ToString(),
|
||||
EveCommandAcceptanceStatusKey.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.EveProviderAdvertisement.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.PilotInputCapability.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.MapMenuSurface.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest.ToString(),
|
||||
AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest.ToString()
|
||||
|
|
|
|||
|
|
@ -115,6 +115,33 @@ Historical journal pages remain durable and cold. Retention/garbage collection
|
|||
of obsolete generations is separate from restart authority and must not be
|
||||
smuggled into filtered hydration.
|
||||
|
||||
### Restored-frame client readiness
|
||||
|
||||
- **Owner:** the latest hard-flushed daemon frame owns restart world truth. Its
|
||||
frame-versioned Eve input, pilot topology, reactive fragment, and map surface
|
||||
are durable projections of that same boundary.
|
||||
- **Inputs:** exact persisted Eve bootstrap record keys plus the latest frame;
|
||||
the daemon boot filter hydrates those records without loading unrelated menu
|
||||
surfaces or history.
|
||||
- **Outputs:** on an ordinary restored daemon run, CultMesh can advertise the
|
||||
committed Eve surfaces and publish a fresh hot SoA body before frame N+1 is
|
||||
simulated. New stores and explicit `--once` runs retain the initialization
|
||||
tick path.
|
||||
- **Derived state:** provider advertisement and asset catalog are stable
|
||||
bootstrap projections. Input and Eve surfaces qualify for the fast path only
|
||||
when every record version exactly equals the durable frame id; missing or
|
||||
stale records force a normal rebuild.
|
||||
- **Forbidden writers:** startup must not advance simulation merely to make an
|
||||
already committed frame presentable. It also must not pair a current frame
|
||||
with stale client projections or let existence alone prove consistency.
|
||||
- **Cut line:** restored readiness no longer calls the simulation tick,
|
||||
`BuildPublications`, or the surface lowerers. Ordinary ticking resumes after
|
||||
readiness and owns frame N+1.
|
||||
- **Verification layer:** the restored artifact reached client bootstrap in
|
||||
under one millisecond and playable readiness in 1.41–1.55 seconds, down from
|
||||
2.19 seconds after Ymir compaction alone. The explicit `--once` witness still
|
||||
executed and committed one new frame.
|
||||
|
||||
## Materialized Import And Publication Ownership
|
||||
|
||||
### Catalog import
|
||||
|
|
|
|||
Loading…
Reference in a new issue