refactor(lifetime): own render resource prefixes
Give terrain, sky, retained UI, portal preparation, and the update/render frame pair explicit single owners. Make shader, texture, text, bindless, and GL construction prefixes checked and retryable so partial failure cannot lose or replay resource ownership. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
fec0d94148
commit
c87b15303d
41 changed files with 3768 additions and 355 deletions
|
|
@ -92,8 +92,11 @@ public sealed class GameWindow : IDisposable
|
|||
private AcDream.App.Diagnostics.FrameScreenshotController? _frameScreenshots;
|
||||
private AcDream.App.Diagnostics.WorldLifecycleAutomationController? _worldLifecycleAutomation;
|
||||
private AcDream.App.Rendering.GpuFrameFlightController? _gpuFrameFlights;
|
||||
private AcDream.App.Rendering.RenderFrameOrchestrator?
|
||||
_renderFrameOrchestrator;
|
||||
private readonly AcDream.App.Rendering.GameFrameGraphSlot _frameGraphs = new();
|
||||
private readonly AcDream.App.Rendering.GameRenderResourceLifetime
|
||||
_renderResourceLifetime = new();
|
||||
private readonly AcDream.App.Rendering.GlConstructionCleanupLedger
|
||||
_glConstructionCleanup = new();
|
||||
private readonly AcDream.App.World.WorldEnvironmentController _worldEnvironment =
|
||||
new(Console.WriteLine);
|
||||
private ResourceShutdownTransaction? _shutdown;
|
||||
|
|
@ -148,7 +151,6 @@ public sealed class GameWindow : IDisposable
|
|||
private readonly AcDream.App.Input.MovementTruthDiagnosticController
|
||||
_movementTruthDiagnostics;
|
||||
private readonly AcDream.App.Input.LocalPlayerOutboundController _localPlayerOutbound;
|
||||
private AcDream.App.Update.UpdateFrameOrchestrator _updateFrameOrchestrator = null!;
|
||||
private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock = new();
|
||||
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
|
||||
// Step 7 projectile presentation. The controller owns no identity map;
|
||||
|
|
@ -202,7 +204,8 @@ public sealed class GameWindow : IDisposable
|
|||
// R1 (render redesign): portal-view draw owners are retained transitively
|
||||
// by the frame orchestrator rather than duplicated as GameWindow roots.
|
||||
private AcDream.App.Rendering.PortalDepthMaskRenderer? _portalDepthMask;
|
||||
private AcDream.App.Rendering.PortalTunnelPresentation? _portalTunnel;
|
||||
private readonly AcDream.App.Rendering.TransferableResourceSlot<
|
||||
AcDream.App.Rendering.PortalTunnelPresentation> _portalTunnelFallback = new();
|
||||
|
||||
// Phase U.3: the shared per-frame clip data (binding=2 mesh SSBO + terrain
|
||||
// UBO). In U.3 a single ClipFrame.NoClip() instance is created lazily (??=) and
|
||||
|
|
@ -342,6 +345,7 @@ public sealed class GameWindow : IDisposable
|
|||
// Phase D.2b — retained host + composition runtime. Null unless ACDREAM_RETAIL_UI=1.
|
||||
private AcDream.App.UI.UiHost? _uiHost;
|
||||
private AcDream.App.UI.RetailUiRuntime? _retailUiRuntime;
|
||||
private readonly AcDream.App.UI.RetailUiRuntimeLease _retailUiLease = new();
|
||||
private readonly AcDream.App.Combat.CombatAttackOperationsSlot
|
||||
_combatAttackOperations = new();
|
||||
private readonly AcDream.App.Combat.CombatFeedbackSlot
|
||||
|
|
@ -638,7 +642,15 @@ public sealed class GameWindow : IDisposable
|
|||
_displayFramePacing,
|
||||
_hostQuiescence);
|
||||
_windowCallbacks.Attach();
|
||||
_window.Run();
|
||||
try
|
||||
{
|
||||
_window.Run();
|
||||
}
|
||||
catch (Exception failure)
|
||||
{
|
||||
_glConstructionCleanup.RetainFrom(failure);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoad()
|
||||
|
|
@ -1100,7 +1112,11 @@ public sealed class GameWindow : IDisposable
|
|||
// Build the terrain atlas once from the Region dat. Phase N.5b: the
|
||||
// atlas exposes bindless handles for the modern terrain path, so
|
||||
// BindlessSupport is threaded through.
|
||||
var terrainAtlas = AcDream.App.Rendering.TerrainAtlas.Build(_gl, _dats, _bindlessSupport);
|
||||
var terrainAtlas = _renderResourceLifetime.AcquireTerrainAtlas(
|
||||
() => AcDream.App.Rendering.TerrainAtlas.Build(
|
||||
_gl,
|
||||
_dats,
|
||||
_bindlessSupport));
|
||||
// A.5 T22.5: apply anisotropic level from quality preset. Build()
|
||||
// hard-codes 16x; override here to match the resolved quality so Low
|
||||
// (4x) and Medium (8x) actually take effect.
|
||||
|
|
@ -1230,11 +1246,12 @@ public sealed class GameWindow : IDisposable
|
|||
if (_options.RetailUi)
|
||||
{
|
||||
_vitalsVm ??= new AcDream.UI.Abstractions.Panels.Vitals.VitalsVM(Combat, LocalPlayer);
|
||||
_uiHost = new AcDream.App.UI.UiHost(
|
||||
_gl,
|
||||
shadersDir,
|
||||
_debugFont,
|
||||
_hostQuiescence);
|
||||
_uiHost = _retailUiLease.AcquireHost(
|
||||
() => new AcDream.App.UI.UiHost(
|
||||
_gl,
|
||||
shadersDir,
|
||||
_debugFont,
|
||||
_hostQuiescence));
|
||||
_retainedInputCapture.Root = _uiHost.Root;
|
||||
_uiHost.Root.UiLocked = _runtimeSettings.Gameplay.LockUI;
|
||||
var cursorFeedbackController = new AcDream.App.UI.CursorFeedbackController(
|
||||
|
|
@ -1360,8 +1377,7 @@ public sealed class GameWindow : IDisposable
|
|||
UiProbeLog);
|
||||
}
|
||||
|
||||
_retailUiRuntime = AcDream.App.UI.RetailUiRuntime.Mount(
|
||||
new AcDream.App.UI.RetailUiRuntimeBindings(
|
||||
var retailUiBindings = new AcDream.App.UI.RetailUiRuntimeBindings(
|
||||
Host: _uiHost,
|
||||
Assets: new AcDream.App.UI.RetailUiAssets(
|
||||
_dats!, _datLock, ResolveChrome, ResolveDatFont,
|
||||
|
|
@ -1511,7 +1527,10 @@ public sealed class GameWindow : IDisposable
|
|||
action => _inputDispatcher?.TryInvokeAutomationAction(action) == true,
|
||||
(action, held) =>
|
||||
_inputDispatcher?.TrySetAutomationActionHeld(action, held) == true,
|
||||
_worldLifecycleAutomation)));
|
||||
_worldLifecycleAutomation));
|
||||
_retailUiRuntime = _retailUiLease.Mount(
|
||||
() => AcDream.App.UI.RetailUiRuntime.CreateUninitialized(
|
||||
retailUiBindings));
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1966,26 +1985,29 @@ public sealed class GameWindow : IDisposable
|
|||
// T1: invisible portal depth writes (seal/punch) — retail
|
||||
// DrawPortalPolyInternal (Ghidra 0x0059bc90).
|
||||
_portalDepthMask = new AcDream.App.Rendering.PortalDepthMaskRenderer(_gl);
|
||||
_portalTunnel = AcDream.App.Rendering.PortalTunnelPresentation.CreateRequired(
|
||||
_gl,
|
||||
_dats!,
|
||||
_animLoader!,
|
||||
_hookRouter,
|
||||
_wbDrawDispatcher,
|
||||
_sceneLightingUbo!,
|
||||
_wbMeshAdapter!);
|
||||
_portalTunnelFallback.AcquirePrepared(
|
||||
() => AcDream.App.Rendering.PortalTunnelPresentation.CreateRequired(
|
||||
_gl,
|
||||
_dats!,
|
||||
_animLoader!,
|
||||
_hookRouter,
|
||||
_wbDrawDispatcher,
|
||||
_sceneLightingUbo!,
|
||||
_wbMeshAdapter!),
|
||||
static portalTunnel => portalTunnel.PrepareResources());
|
||||
// Publish the presentation before touching the mesh-reference
|
||||
// backend. Partial acquisition remains reachable by staged
|
||||
// shutdown instead of becoming constructor-local leaked state.
|
||||
_portalTunnel.PrepareResources();
|
||||
}
|
||||
|
||||
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
|
||||
// with depth writes off + far plane 1e6 so celestial meshes
|
||||
// never clip. Shares the TextureCache with the static pipeline.
|
||||
var skyShader = new Shader(_gl,
|
||||
Path.Combine(shadersDir, "sky.vert"),
|
||||
Path.Combine(shadersDir, "sky.frag"));
|
||||
var skyShader = _renderResourceLifetime.AcquireSkyShader(
|
||||
() => new Shader(
|
||||
_gl,
|
||||
Path.Combine(shadersDir, "sky.vert"),
|
||||
Path.Combine(shadersDir, "sky.frag")));
|
||||
_skyRenderer = new AcDream.App.Rendering.Sky.SkyRenderer(
|
||||
_gl, _dats, skyShader, _textureCache!, _samplerCache);
|
||||
|
||||
|
|
@ -2422,13 +2444,8 @@ public sealed class GameWindow : IDisposable
|
|||
_localPlayerMode,
|
||||
_playerModeController));
|
||||
_playerModeController.BindAutoEntry(_playerModeAutoEntry);
|
||||
var localTeleportPresentation =
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportPresentation(
|
||||
_portalTunnel
|
||||
?? throw new InvalidOperationException(
|
||||
"Portal presentation was not composed before local teleport."));
|
||||
_localPlayerTeleport =
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportController(
|
||||
_localPlayerTeleport = _portalTunnelFallback.Transfer(
|
||||
portalTunnel => new AcDream.App.Streaming.LocalPlayerTeleportController(
|
||||
new AcDream.App.Streaming.LiveLocalPlayerTeleportAuthority(
|
||||
_liveEntities,
|
||||
_localPlayerIdentity),
|
||||
|
|
@ -2451,11 +2468,8 @@ public sealed class GameWindow : IDisposable
|
|||
liveSpatialReconciler),
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportSession(
|
||||
_liveSessionController),
|
||||
localTeleportPresentation);
|
||||
// Ownership transferred to LocalPlayerTeleportController. This field
|
||||
// remains only as the staged-startup fallback used by shutdown when
|
||||
// composition fails before the transfer.
|
||||
_portalTunnel = null;
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportPresentation(
|
||||
portalTunnel)));
|
||||
_localPlayerTeleportSink.Bind(_localPlayerTeleport);
|
||||
var teleportRenderState =
|
||||
new AcDream.App.Rendering.LocalPlayerTeleportRenderStateSource(
|
||||
|
|
@ -2652,7 +2666,7 @@ public sealed class GameWindow : IDisposable
|
|||
renderFrameResources,
|
||||
_devToolsFramePresenter,
|
||||
renderWeatherFrame);
|
||||
_renderFrameOrchestrator =
|
||||
var renderFrameOrchestrator =
|
||||
new AcDream.App.Rendering.RenderFrameOrchestrator(
|
||||
_gpuFrameFlights!,
|
||||
framePreparation,
|
||||
|
|
@ -2681,7 +2695,7 @@ public sealed class GameWindow : IDisposable
|
|||
Combat,
|
||||
_selection,
|
||||
_worldSelectionQuery!));
|
||||
_updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(
|
||||
var updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(
|
||||
new AcDream.App.Update.LiveEntityTeardownFramePhase(
|
||||
_liveEntities),
|
||||
new AcDream.App.Update.ConsoleUpdateFrameFailureSink(),
|
||||
|
|
@ -2697,6 +2711,7 @@ public sealed class GameWindow : IDisposable
|
|||
new AcDream.App.Update.PlayerModeAutoEntryFramePhase(
|
||||
_playerModeAutoEntry),
|
||||
cameraFrame);
|
||||
_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);
|
||||
_liveSessionHost = CreateLiveSessionHost();
|
||||
|
||||
AcDream.UI.Abstractions.Panels.Debug.DebugVM? debugVm = _debugVm;
|
||||
|
|
@ -3055,8 +3070,7 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
using var _updStage = _frameProfiler.BeginStage(
|
||||
AcDream.App.Diagnostics.FrameStage.Update);
|
||||
_updateFrameOrchestrator.Tick(
|
||||
new AcDream.App.Update.UpdateFrameInput(dt));
|
||||
_frameGraphs.Tick(new AcDream.App.Update.UpdateFrameInput(dt));
|
||||
}
|
||||
|
||||
// Performance overlay state — updated every ~0.5s and written to the
|
||||
|
|
@ -3064,11 +3078,12 @@ public sealed class GameWindow : IDisposable
|
|||
private void OnRender(double deltaSeconds)
|
||||
{
|
||||
Vector2D<int> size = _window!.Size;
|
||||
_renderFrameOrchestrator!.Render(
|
||||
_frameGraphs.Render(
|
||||
new AcDream.App.Rendering.RenderFrameInput(
|
||||
deltaSeconds,
|
||||
size.X,
|
||||
size.Y));
|
||||
size.Y),
|
||||
out _);
|
||||
}
|
||||
|
||||
private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot
|
||||
|
|
@ -3336,7 +3351,7 @@ public sealed class GameWindow : IDisposable
|
|||
new("dispatcher", () => _inputDispatcher?.Deactivate()),
|
||||
new("mouse source", () => _mouseSource?.Deactivate()),
|
||||
new("keyboard source", () => _kbSource?.Deactivate()),
|
||||
new("retained UI input", () => _uiHost?.QuiesceInput()),
|
||||
new("retained UI input", _retailUiLease.QuiesceInput),
|
||||
new("developer tools input", () => _devToolsInputContext?.Deactivate()),
|
||||
]),
|
||||
// Live-session reset retires the complete streaming window. Every
|
||||
|
|
@ -3390,7 +3405,7 @@ public sealed class GameWindow : IDisposable
|
|||
"Gameplay action callback removal remains pending.");
|
||||
_gameplayInputActions = null;
|
||||
}),
|
||||
new("retained UI input", () => _uiHost?.DeactivateInput()),
|
||||
new("retained UI input", _retailUiLease.DeactivateInput),
|
||||
new("developer tools input", () => _devToolsInputContext?.Dispose()),
|
||||
new("camera pointer", () =>
|
||||
{
|
||||
|
|
@ -3444,7 +3459,7 @@ public sealed class GameWindow : IDisposable
|
|||
new("runtime settings targets", _runtimeSettings.UnbindRuntimeTargets),
|
||||
new("world frame composition", () =>
|
||||
{
|
||||
_renderFrameOrchestrator = null;
|
||||
_frameGraphs.Withdraw();
|
||||
}),
|
||||
]),
|
||||
new ResourceShutdownStage("session dependents",
|
||||
|
|
@ -3461,7 +3476,10 @@ public sealed class GameWindow : IDisposable
|
|||
}),
|
||||
new("retail UI", () =>
|
||||
{
|
||||
_retailUiRuntime?.Dispose();
|
||||
_retailUiLease.Dispose();
|
||||
if (!_retailUiLease.IsDisposalComplete)
|
||||
throw new InvalidOperationException(
|
||||
"The retained UI ownership lease did not complete disposal.");
|
||||
_retailUiRuntime = null;
|
||||
_retainedInputCapture.Root = null;
|
||||
_uiHost = null;
|
||||
|
|
@ -3544,8 +3562,7 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
_localPlayerTeleport?.Dispose();
|
||||
_localPlayerTeleport = null;
|
||||
_portalTunnel?.Dispose();
|
||||
_portalTunnel = null;
|
||||
_portalTunnelFallback.ReleaseFallback();
|
||||
}),
|
||||
new("paperdoll viewport", () =>
|
||||
{
|
||||
|
|
@ -3589,6 +3606,15 @@ public sealed class GameWindow : IDisposable
|
|||
new("frame pacing", _displayFramePacing.Dispose),
|
||||
new("frame profiler", _frameProfiler.Dispose),
|
||||
]),
|
||||
new ResourceShutdownStage("dedicated render resources",
|
||||
[
|
||||
new("sky shader", _renderResourceLifetime.ReleaseSkyShader),
|
||||
new("terrain atlas", _renderResourceLifetime.ReleaseTerrainAtlas),
|
||||
]),
|
||||
new ResourceShutdownStage("failed render construction cleanup",
|
||||
[
|
||||
new("GL construction ledger", _glConstructionCleanup.Dispose),
|
||||
]),
|
||||
new ResourceShutdownStage("frame flight owner",
|
||||
[
|
||||
new("frame flights", () =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue