refactor(runtime): cut graphical host over to canonical root

Make every App composition phase borrow one GameRuntime, retire the duplicate view/event adapters, and dispose the root only after its graphical borrowers release. This preserves synchronous UI commands while giving shutdown one exact ownership ledger.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 19:06:09 +02:00
parent ecb9f79444
commit ce41efb9e5
29 changed files with 613 additions and 778 deletions

View file

@ -6,6 +6,7 @@ using AcDream.App.Rendering.Wb;
using AcDream.App.Settings;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
@ -163,7 +164,7 @@ public sealed class GameWindow :
private readonly AcDream.App.Input.MovementTruthDiagnosticController
_movementTruthDiagnostics;
private readonly LocalPlayerOutboundController _localPlayerOutbound;
private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock = new();
private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock;
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
// Step 7 projectile presentation. The controller owns no identity map;
// each runtime component is stored on the canonical LiveEntityRecord.
@ -325,8 +326,11 @@ public sealed class GameWindow :
// J4/J5.1: Runtime owns communication, selection, combat, and target-mode
// state. App, UI, plugins, and live-session routing borrow exact children.
private readonly RuntimeCommunicationState _runtimeCommunication = new();
private readonly RuntimeActionState _runtimeActions;
private readonly GameRuntime _runtime;
private readonly IDisposable _runtimeHostLease;
private RuntimeCommunicationState _runtimeCommunication =>
_runtime.CommunicationOwner;
private RuntimeActionState _runtimeActions => _runtime.ActionOwner;
public AcDream.Core.Selection.SelectionState Selection =>
_runtimeActions.Selection;
public AcDream.Core.Chat.ChatLog Chat => _runtimeCommunication.Chat;
@ -338,9 +342,12 @@ public sealed class GameWindow :
_runtimeCommunication.Squelch;
public AcDream.Core.Combat.CombatState Combat =>
_runtimeActions.Combat;
private readonly RuntimeEntityObjectLifetime _runtimeEntityObjects = new();
private readonly RuntimeInventoryState _runtimeInventory;
private readonly RuntimeCharacterState _runtimeCharacter;
private RuntimeEntityObjectLifetime _runtimeEntityObjects =>
_runtime.EntityObjects;
private RuntimeInventoryState _runtimeInventory =>
_runtime.InventoryOwner;
private RuntimeCharacterState _runtimeCharacter =>
_runtime.CharacterOwner;
public AcDream.Core.Items.ItemManaState ItemMana =>
_runtimeInventory.ItemMana;
// Retail resolves learned IDs through portal.dat's CSpellTable. MagicCatalog
@ -431,7 +438,8 @@ public sealed class GameWindow :
private AcDream.App.Rendering.SceneLightingUboBinding? _sceneLightingUbo;
private AcDream.App.Rendering.Sky.SkyRenderer? _skyRenderer;
// Phase B.2: player movement mode.
private readonly RuntimeLocalPlayerMovementState _playerControllerSlot = new();
private RuntimeLocalPlayerMovementState _playerControllerSlot =>
_runtime.MovementOwner;
private PlayerMovementController? _playerController
=> _playerControllerSlot.Controller;
private readonly AcDream.App.Input.ChaseCameraInputState _chaseCameraInput = new();
@ -442,7 +450,8 @@ public sealed class GameWindow :
private readonly AcDream.App.Input.LocalPlayerModeState _localPlayerMode = new();
private bool _playerMode
=> _localPlayerMode.IsPlayerMode;
private readonly AcDream.App.Input.LocalPlayerIdentityState _localPlayerIdentity = new();
private readonly AcDream.App.Input.LocalPlayerIdentityState
_localPlayerIdentity;
private uint _playerServerGuid
{
get => _localPlayerIdentity.ServerGuid;
@ -510,7 +519,6 @@ public sealed class GameWindow :
// Runtime owns the canonical session generation and transport lifetime.
// The window retains only the composition handles needed for startup and
// shutdown; graphical feature callbacks borrow state through App adapters.
private LiveSessionController? _liveSessionController;
private LiveSessionHost? _liveSessionHost;
private AcDream.Core.Net.WorldSession? LiveSession =>
_liveSessionHost?.CurrentSession;
@ -565,22 +573,24 @@ public sealed class GameWindow :
AcDream.App.Plugins.BufferedUiRegistry? uiRegistry = null)
{
_options = options ?? throw new System.ArgumentNullException(nameof(options));
_worldEnvironment = new AcDream.App.World.WorldEnvironmentController(
new AcDream.Runtime.World.RuntimeWorldEnvironmentState(
log: Console.WriteLine,
timeSyncDiagnostic:
options.DumpSky ? Console.WriteLine : null),
options.ForcedDayGroupIndex,
Console.WriteLine);
_runtimeInventory = new RuntimeInventoryState(_runtimeEntityObjects);
_runtimeCharacter = new RuntimeCharacterState();
_runtimeActions = new RuntimeActionState(
_runtimeInventory.Transactions,
_runtimeCharacter.Spellbook,
_runtime = new GameRuntime(new GameRuntimeDependencies(
_combatAttackOperations,
_combatTargetOperations,
_combatModeOperations,
_spellCastOperations);
_spellCastOperations,
Log: Console.WriteLine,
TimeSyncDiagnostic:
options.DumpSky ? Console.WriteLine : null));
_runtimeHostLease = _runtime.AcquireHostLease(
"graphical GameWindow");
_localPlayerIdentity = new AcDream.App.Input.LocalPlayerIdentityState(
_runtime.PlayerIdentity);
_updateFrameClock = new AcDream.App.Update.UpdateFrameClock(
_runtime.Clock);
_worldEnvironment = new AcDream.App.World.WorldEnvironmentController(
_runtime.EnvironmentOwner,
options.ForcedDayGroupIndex,
Console.WriteLine);
var alphaScratchBudgets =
AcDream.App.Rendering.Residency.AlphaScratchBudgetProfile.Create(
_options.ResidencyBudgets.AlphaScratchBytes);
@ -1039,7 +1049,6 @@ public sealed class GameWindow :
|| _streamingOriginRecenter is not null
|| _worldReveal is not null
|| _spawnClaimHydration is not null
|| _liveSessionController is not null
|| _liveEntityHydration is not null
|| _liveEntityNetworkUpdates is not null
|| _liveEntityLiveness is not null
@ -1063,7 +1072,6 @@ public sealed class GameWindow :
_streamingOriginRecenter = result.StreamingOriginRecenter;
_worldReveal = result.WorldReveal;
_spawnClaimHydration = result.SpawnClaimHydration;
_liveSessionController = result.LiveSession;
_liveEntityHydration = result.Hydration;
_liveEntityNetworkUpdates = result.NetworkUpdates;
_liveEntityLiveness = result.Liveness;
@ -1163,7 +1171,7 @@ public sealed class GameWindow :
_options.ResidencyBudgets,
_physicsDataCache,
_animationDiagnostics.DumpMotionEnabled,
_runtimeCharacter,
_runtime,
_hookRouter,
_effectPoses,
_entityEffectAdvance,
@ -1221,9 +1229,7 @@ public sealed class GameWindow :
hostInputCamera.CameraController,
contentEffectsAudio.Audio?.Engine),
_hostQuiescence,
_runtimeCommunication,
Combat,
LocalPlayer,
_runtime,
optionalDevTools,
_runtimeDiagnosticCommands,
_combatFeedback,
@ -1270,19 +1276,15 @@ public sealed class GameWindow :
_retainedInputCapture,
hostInputCamera.InputDispatcher,
_runtimeSettings,
_runtimeActions,
_runtime,
_combatAttackOperations,
_combatTargetOperations,
_spellCastOperations,
_runtimeInventory,
contentEffectsAudio.MagicCatalog,
_runtimeCharacter,
_runtimeCommunication,
_stackSplitQuantity,
_uiRegistry,
_liveCombatModeCommands,
_localPlayerIdentity,
_playerControllerSlot,
_localPlayerMode,
viewPlane => new SelectionCameraSource(
hostInputCamera.CameraController,
@ -1323,8 +1325,7 @@ public sealed class GameWindow :
_physicsDataCache,
_worldGameState,
_worldEvents,
Selection,
_runtimeEntityObjects,
_runtime,
_liveEntityRuntimeSlot,
_liveEntityMotionBindings,
_entityEffectAdvance,
@ -1339,7 +1340,6 @@ public sealed class GameWindow :
_cellVisibility,
_liveWorldOrigin,
_localPlayerIdentity,
_playerControllerSlot,
_pointerPosition,
_playerApproachCompletions,
_renderResourceLifetime,
@ -1369,6 +1369,7 @@ public sealed class GameWindow :
new SessionPlayerCompositionPhase(
new SessionPlayerDependencies(
_options,
_runtime,
_window!,
_datLock,
_runtimeSettings,
@ -1383,8 +1384,6 @@ public sealed class GameWindow :
_physicsDataCache,
_worldGameState,
_worldEvents,
_runtimeActions,
_runtimeEntityObjects,
_classificationCache,
_liveEntityRuntimeSlot,
_animatedEntities,
@ -1394,7 +1393,6 @@ public sealed class GameWindow :
_inboundEntityEvents,
_liveEntityMotionBindings,
_localPlayerIdentity,
_playerControllerSlot,
_playerHostSlot,
_localPlayerMode,
_chaseCameraInput,
@ -1405,7 +1403,6 @@ public sealed class GameWindow :
_localPlayerShadow,
_viewportAspect,
_playerApproachCompletions,
_runtimeInventory,
_pointerPosition,
_movementInput,
_inputCapture,
@ -1416,8 +1413,6 @@ public sealed class GameWindow :
_movementTruthDiagnostics,
_combatAttackOperations,
_combatFeedback,
_runtimeCommunication,
_runtimeCharacter,
_portalTunnelFallback,
Console.WriteLine),
this).Compose(
@ -1437,6 +1432,7 @@ public sealed class GameWindow :
sessionPlayer) => new FrameRootCompositionPhase(
new FrameRootDependencies(
_options,
_runtime,
platformResult.Graphics,
_window!,
platformResult.Input,
@ -1449,7 +1445,6 @@ public sealed class GameWindow :
_localPlayerMode,
_localPlayerIdentity,
_chaseCameraInput,
_playerControllerSlot,
_liveWorldOrigin,
_particleVisibility,
_effectPoses,
@ -1464,10 +1459,8 @@ public sealed class GameWindow :
_debugVmRenderFacts,
_inputCapture,
_cameraInput,
Selection,
_animatedEntities,
_updateFrameClock,
Combat,
_frameGraphs,
Console.WriteLine),
this).Compose(
@ -1571,7 +1564,7 @@ public sealed class GameWindow :
_retailUiLease,
_uiHost,
_devToolsComposition,
_liveSessionController,
_runtime,
_runtimeSettings,
_movementInput,
_cameraInput,
@ -1590,12 +1583,8 @@ public sealed class GameWindow :
_streamer,
_equippedChildRenderer,
_liveEntities,
_runtimeEntityObjects,
_runtimeInventory,
_runtimeCharacter,
_runtimeCommunication,
_runtimeActions,
_playerControllerSlot,
_runtime,
_runtimeHostLease,
_renderSceneShadow,
_livePresentationBindings,
_entityEffectAdvance,