refactor(streaming): own presentation retirement in pipeline
Move full and near-tier presentation retirement out of GameWindow and into a concrete pipeline-owned stage mapper. Harden the retry ledger against reentrancy and committed visibility observer failures, enforce a single state/resource ownership graph, and split concrete versus legacy controller construction so callbacks cannot be silently ignored. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
dc39772bf9
commit
ea7ffbc186
10 changed files with 860 additions and 140 deletions
|
|
@ -150,7 +150,6 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
// Phase A.1: streaming fields replacing the one-shot _entities list.
|
||||
private AcDream.App.Streaming.LandblockStreamer? _streamer;
|
||||
private AcDream.App.Streaming.GpuWorldState _worldState = new();
|
||||
private AcDream.App.Streaming.LandblockRetirementCoordinator? _landblockRetirements;
|
||||
private AcDream.App.Streaming.LandblockRenderPublisher? _landblockRenderPublisher;
|
||||
private AcDream.App.Streaming.LandblockPhysicsPublisher? _landblockPhysicsPublisher;
|
||||
private AcDream.App.Streaming.LandblockStaticPresentationPublisher?
|
||||
|
|
@ -2208,11 +2207,6 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
wbSpawnAdapter,
|
||||
onLandblockUnloaded: _classificationCache.InvalidateLandblock,
|
||||
entityScriptActivator: entityScriptActivator);
|
||||
_landblockRetirements =
|
||||
new AcDream.App.Streaming.LandblockRetirementCoordinator(
|
||||
_worldState,
|
||||
AdvanceLandblockPresentationRetirement);
|
||||
|
||||
_liveEntities = new AcDream.App.World.LiveEntityRuntime(
|
||||
_worldState,
|
||||
new AcDream.App.World.CompositeLiveEntityResourceLifecycle(
|
||||
|
|
@ -2529,17 +2523,24 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
});
|
||||
_streamer.Start();
|
||||
|
||||
var landblockRetirementOwner =
|
||||
new AcDream.App.Streaming.LandblockPresentationRetirementOwner(
|
||||
_landblockRenderPublisher!,
|
||||
_landblockPhysicsPublisher!,
|
||||
_landblockStaticPresentationPublisher!,
|
||||
_lightingSink!,
|
||||
_translucencyFades);
|
||||
var landblockPresentationPipeline =
|
||||
new AcDream.App.Streaming.LandblockPresentationPipeline(
|
||||
_landblockRenderPublisher!,
|
||||
_landblockPhysicsPublisher!,
|
||||
_landblockStaticPresentationPublisher!,
|
||||
_worldState,
|
||||
landblockRetirementOwner,
|
||||
onLandblockLoaded: loadedLandblockId =>
|
||||
_liveEntityHydration!.OnLandblockLoaded(loadedLandblockId),
|
||||
ensureEnvCellMeshes:
|
||||
_landblockRenderPublisher!.PrepareAfterRenderPins,
|
||||
retirementCoordinator: _landblockRetirements);
|
||||
_landblockRenderPublisher!.PrepareAfterRenderPins);
|
||||
_streamingController = new AcDream.App.Streaming.StreamingController(
|
||||
enqueueLoad: (id, kind, generation) => _streamer.EnqueueLoad(
|
||||
new AcDream.App.Streaming.LandblockBuildRequest(
|
||||
|
|
@ -2551,18 +2552,13 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
_liveCenterY))),
|
||||
enqueueUnload: (id, generation) => _streamer.EnqueueUnload(id, generation),
|
||||
drainCompletions: _streamer.DrainCompletions,
|
||||
applyTerrain: ApplyLoadedTerrain,
|
||||
state: _worldState,
|
||||
nearRadius: _nearRadius,
|
||||
farRadius: _farRadius,
|
||||
clearPendingLoads: _streamer.ClearPendingLoads,
|
||||
// #138: restore retained server objects when a landblock reloads
|
||||
// Retained-object recovery runs after each
|
||||
// (dungeon-exit expand or Far→Near promote). ACE won't re-send the
|
||||
// objects it thinks we still know, so we re-project them ourselves.
|
||||
onLandblockLoaded: loadedLandblockId =>
|
||||
_liveEntityHydration!.OnLandblockLoaded(loadedLandblockId),
|
||||
ensureEnvCellMeshes: _landblockRenderPublisher!.PrepareAfterRenderPins,
|
||||
retirementCoordinator: _landblockRetirements,
|
||||
// objects it still considers known.
|
||||
presentationPipeline: landblockPresentationPipeline);
|
||||
// A.5 T22.5: apply max-completions from resolved quality.
|
||||
_streamingController.MaxCompletionsPerFrame = _resolvedQuality.MaxCompletionsPerFrame;
|
||||
|
|
@ -3805,65 +3801,6 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
$"live: AdminEnvirons sound cue = {name} " +
|
||||
$"(0x{environChangeType:X2}) — audio binding pending");
|
||||
}
|
||||
private void AdvanceLandblockPresentationRetirement(
|
||||
AcDream.App.Streaming.LandblockRetirementTicket ticket)
|
||||
{
|
||||
bool staticOnly =
|
||||
ticket.Kind == AcDream.App.Streaming.LandblockRetirementKind.NearLayer;
|
||||
|
||||
ticket.RunForEachEntity(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.EntityLighting,
|
||||
entity => !staticOnly || entity.ServerGuid == 0,
|
||||
entity =>
|
||||
{
|
||||
if (entity.ServerGuid == 0)
|
||||
_landblockStaticPresentationPublisher!.RemoveLighting(entity);
|
||||
else
|
||||
_lightingSink?.UnregisterOwner(entity.Id);
|
||||
});
|
||||
ticket.RunForEachEntity(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.EntityTranslucency,
|
||||
entity => !staticOnly || entity.ServerGuid == 0,
|
||||
entity =>
|
||||
{
|
||||
if (entity.ServerGuid == 0)
|
||||
_landblockStaticPresentationPublisher!.RemoveTranslucency(entity);
|
||||
else
|
||||
_translucencyFades.ClearEntity(entity.Id);
|
||||
});
|
||||
ticket.RunForEachEntity(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.PluginProjection,
|
||||
static entity => entity.ServerGuid == 0,
|
||||
entity => _landblockStaticPresentationPublisher!
|
||||
.RemovePluginProjection(entity));
|
||||
|
||||
if (ticket.Kind == AcDream.App.Streaming.LandblockRetirementKind.Full)
|
||||
{
|
||||
ticket.RunOnce(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.Terrain,
|
||||
() => _landblockRenderPublisher?.RemoveTerrain(ticket.LandblockId));
|
||||
}
|
||||
|
||||
ticket.RunOnce(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.Physics,
|
||||
() =>
|
||||
{
|
||||
if (ticket.Kind == AcDream.App.Streaming.LandblockRetirementKind.Full)
|
||||
_landblockPhysicsPublisher!.RemoveLandblock(ticket.LandblockId);
|
||||
else
|
||||
_landblockPhysicsPublisher!.DemoteToTerrain(ticket.LandblockId);
|
||||
});
|
||||
ticket.RunOnce(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.CellVisibility,
|
||||
() => _landblockRenderPublisher?.RemoveCellVisibility(ticket.LandblockId));
|
||||
ticket.RunOnce(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.BuildingRegistry,
|
||||
() => _landblockRenderPublisher?.RemoveBuildingRegistry(ticket.LandblockId));
|
||||
ticket.RunOnce(
|
||||
AcDream.App.Streaming.LandblockRetirementStage.EnvironmentCells,
|
||||
() => _landblockRenderPublisher?.RemoveEnvironmentCells(ticket.LandblockId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase A.1 / A.5 T12: render-thread callback from StreamingController.Tick
|
||||
/// whenever a new landblock's terrain + entities are ready for GPU upload.
|
||||
|
|
@ -5961,7 +5898,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
LiveEntities: _liveEntities?.Count ?? 0,
|
||||
MaterializedLiveEntities: _liveEntities?.MaterializedCount ?? 0,
|
||||
PendingLiveTeardowns: _liveEntities?.PendingTeardownCount ?? 0,
|
||||
PendingLandblockRetirements: _landblockRetirements?.PendingCount ?? 0,
|
||||
PendingLandblockRetirements:
|
||||
_streamingController?.PendingRetirementCount ?? 0,
|
||||
ParticleEmitters: _particleSystem?.ActiveEmitterCount ?? 0,
|
||||
Particles: _particleSystem?.ActiveParticleCount ?? 0,
|
||||
ParticleBindings: _particleSink?.ActiveBindingCount ?? 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue