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:
parent
ecb9f79444
commit
ce41efb9e5
29 changed files with 613 additions and 778 deletions
|
|
@ -12,6 +12,7 @@ using AcDream.Core.Physics;
|
|||
using AcDream.Core.Rendering;
|
||||
using AcDream.Core.Spells;
|
||||
using AcDream.Core.Vfx;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using DatReaderWriter;
|
||||
using Silk.NET.Input;
|
||||
|
|
@ -49,7 +50,7 @@ internal sealed record ContentEffectsAudioDependencies(
|
|||
ResidencyBudgetOptions ResidencyBudgets,
|
||||
PhysicsDataCache PhysicsDataCache,
|
||||
bool DumpMotionEnabled,
|
||||
RuntimeCharacterState Character,
|
||||
GameRuntime Runtime,
|
||||
AnimationHookRouter HookRouter,
|
||||
EntityEffectPoseRegistry EffectPoses,
|
||||
DeferredEntityEffectAdvanceSource EntityEffectAdvance,
|
||||
|
|
@ -57,7 +58,10 @@ internal sealed record ContentEffectsAudioDependencies(
|
|||
TranslucencyFadeManager TranslucencyFades,
|
||||
bool NoAudio,
|
||||
Action<string> Log,
|
||||
Action<string> Error);
|
||||
Action<string> Error)
|
||||
{
|
||||
public RuntimeCharacterState Character => Runtime.CharacterOwner;
|
||||
}
|
||||
|
||||
internal interface IGameWindowContentEffectsAudioPublication
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using AcDream.App.Runtime;
|
|||
using AcDream.App.Settings;
|
||||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Lighting;
|
||||
|
|
@ -21,6 +22,7 @@ namespace AcDream.App.Composition;
|
|||
|
||||
internal sealed record FrameRootDependencies(
|
||||
RuntimeOptions Options,
|
||||
GameRuntime Runtime,
|
||||
GL Gl,
|
||||
IWindow Window,
|
||||
IInputContext Input,
|
||||
|
|
@ -33,7 +35,6 @@ internal sealed record FrameRootDependencies(
|
|||
LocalPlayerModeState PlayerMode,
|
||||
LocalPlayerIdentityState PlayerIdentity,
|
||||
ChaseCameraInputState ChaseCameraInput,
|
||||
RuntimeLocalPlayerMovementState PlayerController,
|
||||
LiveWorldOriginState WorldOrigin,
|
||||
ParticleVisibilityController ParticleVisibility,
|
||||
EntityEffectPoseRegistry EffectPoses,
|
||||
|
|
@ -48,12 +49,18 @@ internal sealed record FrameRootDependencies(
|
|||
DebugVmRenderFactsPublisher DebugVmRenderFacts,
|
||||
IInputCaptureSource InputCapture,
|
||||
DispatcherCameraInputSource CameraInput,
|
||||
SelectionState Selection,
|
||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> Animations,
|
||||
UpdateFrameClock UpdateClock,
|
||||
CombatState Combat,
|
||||
GameFrameGraphSlot FrameGraphs,
|
||||
Action<string> Log);
|
||||
Action<string> Log)
|
||||
{
|
||||
public RuntimeLocalPlayerMovementState PlayerController =>
|
||||
Runtime.MovementOwner;
|
||||
|
||||
public SelectionState Selection => Runtime.ActionOwner.Selection;
|
||||
|
||||
public CombatState Combat => Runtime.ActionOwner.Combat;
|
||||
}
|
||||
|
||||
internal sealed record FrameRootResult(
|
||||
UpdateFrameOrchestrator Update,
|
||||
|
|
|
|||
|
|
@ -44,19 +44,15 @@ internal sealed record InteractionRetainedUiDependencies(
|
|||
RetainedUiInputCaptureSlot RetainedInputCapture,
|
||||
InputDispatcher? InputDispatcher,
|
||||
RuntimeSettingsController Settings,
|
||||
RuntimeActionState Actions,
|
||||
GameRuntime Runtime,
|
||||
IRuntimeCombatAttackOperations CombatAttackOperations,
|
||||
RuntimeCombatTargetOperationsSlot CombatTargetOperations,
|
||||
RuntimeSpellCastOperationsSlot SpellCastOperations,
|
||||
RuntimeInventoryState Inventory,
|
||||
MagicCatalog MagicCatalog,
|
||||
RuntimeCharacterState Character,
|
||||
RuntimeCommunicationState Communication,
|
||||
StackSplitQuantityState StackSplitQuantity,
|
||||
BufferedUiRegistry? UiRegistry,
|
||||
LiveCombatModeCommandSlot CombatModeCommands,
|
||||
ILocalPlayerIdentitySource PlayerIdentity,
|
||||
IRuntimeLocalPlayerControllerSource PlayerController,
|
||||
ILocalPlayerModeSource PlayerMode,
|
||||
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
|
||||
SelectionCameraFactory,
|
||||
|
|
@ -64,7 +60,20 @@ internal sealed record InteractionRetainedUiDependencies(
|
|||
VitalsVM? ExistingVitals,
|
||||
Action<string>? Toast,
|
||||
Func<double> ClientTime,
|
||||
Action<string> Log);
|
||||
Action<string> Log)
|
||||
{
|
||||
public RuntimeActionState Actions => Runtime.ActionOwner;
|
||||
|
||||
public RuntimeInventoryState Inventory => Runtime.InventoryOwner;
|
||||
|
||||
public RuntimeCharacterState Character => Runtime.CharacterOwner;
|
||||
|
||||
public RuntimeCommunicationState Communication =>
|
||||
Runtime.CommunicationOwner;
|
||||
|
||||
public IRuntimeLocalPlayerControllerSource PlayerController =>
|
||||
Runtime.MovementOwner;
|
||||
}
|
||||
|
||||
internal sealed record RetainedUiComposition(
|
||||
UiHost Host,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ using AcDream.Core.Rendering;
|
|||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.Vfx;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter;
|
||||
|
|
@ -45,8 +46,7 @@ internal sealed record LivePresentationDependencies(
|
|||
PhysicsDataCache PhysicsDataCache,
|
||||
WorldGameState WorldGameState,
|
||||
WorldEvents WorldEvents,
|
||||
SelectionState Selection,
|
||||
RuntimeEntityObjectLifetime EntityObjects,
|
||||
GameRuntime Runtime,
|
||||
LiveEntityRuntimeSlot RuntimeSlot,
|
||||
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
||||
DeferredEntityEffectAdvanceSource EffectAdvance,
|
||||
|
|
@ -61,7 +61,6 @@ internal sealed record LivePresentationDependencies(
|
|||
CellVisibility CellVisibility,
|
||||
LiveWorldOriginState WorldOrigin,
|
||||
LocalPlayerIdentityState PlayerIdentity,
|
||||
RuntimeLocalPlayerMovementState PlayerController,
|
||||
PointerPositionState PointerPosition,
|
||||
PlayerApproachCompletionState PlayerApproachCompletions,
|
||||
GameRenderResourceLifetime RenderResourceLifetime,
|
||||
|
|
@ -73,7 +72,18 @@ internal sealed record LivePresentationDependencies(
|
|||
DeferredRenderFrameDiagnosticsSource? DevFrameDiagnostics,
|
||||
DeferredRenderFrameDiagnosticsSource UiFrameDiagnostics,
|
||||
Action<string> Log,
|
||||
Action<string>? Toast);
|
||||
Action<string>? Toast)
|
||||
{
|
||||
public SelectionState Selection => Runtime.ActionOwner.Selection;
|
||||
|
||||
public RuntimeEntityObjectLifetime EntityObjects =>
|
||||
Runtime.EntityObjects;
|
||||
|
||||
public RuntimeWorldTransitState WorldTransit => Runtime.TransitOwner;
|
||||
|
||||
public RuntimeLocalPlayerMovementState PlayerController =>
|
||||
Runtime.MovementOwner;
|
||||
}
|
||||
|
||||
internal sealed record LivePresentationResult(
|
||||
DeferredLiveEntityRuntimeComponentLifecycle ComponentLifecycle,
|
||||
|
|
@ -336,7 +346,7 @@ internal sealed class LivePresentationCompositionPhase
|
|||
staticAnimationScheduler.Unregister,
|
||||
(entity, info) => staticAnimationScheduler.Rebind(entity, info));
|
||||
|
||||
var worldTransit = new RuntimeWorldTransitState(d.Log);
|
||||
RuntimeWorldTransitState worldTransit = d.WorldTransit;
|
||||
var worldAvailability =
|
||||
new WorldGenerationAvailabilityState(worldTransit);
|
||||
var worldState = new GpuWorldState(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ using AcDream.Core.Player;
|
|||
using AcDream.Core.Social;
|
||||
using AcDream.Core.Spells;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
|
|
@ -35,6 +36,7 @@ namespace AcDream.App.Composition;
|
|||
|
||||
internal sealed record SessionPlayerDependencies(
|
||||
RuntimeOptions Options,
|
||||
GameRuntime Runtime,
|
||||
IWindow Window,
|
||||
object DatLock,
|
||||
RuntimeSettingsController Settings,
|
||||
|
|
@ -49,8 +51,6 @@ internal sealed record SessionPlayerDependencies(
|
|||
PhysicsDataCache PhysicsDataCache,
|
||||
WorldGameState WorldGameState,
|
||||
WorldEvents WorldEvents,
|
||||
RuntimeActionState Actions,
|
||||
RuntimeEntityObjectLifetime EntityObjects,
|
||||
EntityClassificationCache ClassificationCache,
|
||||
LiveEntityRuntimeSlot RuntimeSlot,
|
||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
|
||||
|
|
@ -60,7 +60,6 @@ internal sealed record SessionPlayerDependencies(
|
|||
RetailInboundEventDispatcher InboundEntityEvents,
|
||||
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
||||
LocalPlayerIdentityState PlayerIdentity,
|
||||
RuntimeLocalPlayerMovementState PlayerController,
|
||||
LocalPlayerPhysicsHostSlot PlayerHost,
|
||||
LocalPlayerModeState PlayerMode,
|
||||
ChaseCameraInputState ChaseCameraInput,
|
||||
|
|
@ -71,7 +70,6 @@ internal sealed record SessionPlayerDependencies(
|
|||
LocalPlayerShadowState PlayerShadow,
|
||||
ViewportAspectState ViewportAspect,
|
||||
PlayerApproachCompletionState PlayerApproachCompletions,
|
||||
RuntimeInventoryState Inventory,
|
||||
PointerPositionState PointerPosition,
|
||||
DispatcherMovementInputSource MovementInput,
|
||||
IInputCaptureSource InputCapture,
|
||||
|
|
@ -82,10 +80,24 @@ internal sealed record SessionPlayerDependencies(
|
|||
MovementTruthDiagnosticController MovementDiagnostics,
|
||||
CombatAttackOperationsSlot CombatAttackOperations,
|
||||
CombatFeedbackSlot CombatFeedback,
|
||||
RuntimeCommunicationState Communication,
|
||||
RuntimeCharacterState Character,
|
||||
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
|
||||
Action<string> Log);
|
||||
Action<string> Log)
|
||||
{
|
||||
public RuntimeActionState Actions => Runtime.ActionOwner;
|
||||
|
||||
public RuntimeEntityObjectLifetime EntityObjects =>
|
||||
Runtime.EntityObjects;
|
||||
|
||||
public RuntimeLocalPlayerMovementState PlayerController =>
|
||||
Runtime.MovementOwner;
|
||||
|
||||
public RuntimeInventoryState Inventory => Runtime.InventoryOwner;
|
||||
|
||||
public RuntimeCommunicationState Communication =>
|
||||
Runtime.CommunicationOwner;
|
||||
|
||||
public RuntimeCharacterState Character => Runtime.CharacterOwner;
|
||||
}
|
||||
|
||||
internal sealed record SessionPlayerResult(
|
||||
LandblockStreamer Streamer,
|
||||
|
|
@ -430,11 +442,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
sealedDungeonCells,
|
||||
d.Log);
|
||||
|
||||
var liveSessionLease = scope.Acquire(
|
||||
"live-session controller",
|
||||
static () => new LiveSessionController(),
|
||||
static value => value.Dispose());
|
||||
LiveSessionController liveSession = liveSessionLease.Resource;
|
||||
LiveSessionController liveSession = d.Runtime.Session;
|
||||
var liveSessionCommands = new LiveSessionCommandSurface();
|
||||
var liveSessionSource = new LiveSessionAppSource(
|
||||
liveSession,
|
||||
|
|
@ -869,19 +877,9 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
"live combat-mode commands",
|
||||
d.CombatModeCommands.BindOwned(combatCommand));
|
||||
var gameRuntime = new CurrentGameRuntimeAdapter(
|
||||
liveSession,
|
||||
d.Runtime,
|
||||
sessionHost,
|
||||
liveSessionCommands,
|
||||
d.PlayerIdentity,
|
||||
d.EntityObjects,
|
||||
d.Inventory,
|
||||
d.Character,
|
||||
d.Communication,
|
||||
d.Actions,
|
||||
d.PlayerController,
|
||||
d.WorldEnvironment.Runtime,
|
||||
live.WorldTransit,
|
||||
d.UpdateClock,
|
||||
live.SelectionInteractions);
|
||||
bindings.Adopt("current game runtime adapter", gameRuntime);
|
||||
bindings.Adopt(
|
||||
|
|
@ -987,7 +985,6 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
_publication.PublishSessionPlayer(result);
|
||||
|
||||
streamerLease.Transfer();
|
||||
liveSessionLease.Transfer();
|
||||
teleportLease.Transfer();
|
||||
gameplayActionsLease?.Transfer();
|
||||
componentLifecycleLease.Transfer();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using AcDream.App.Settings;
|
|||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Player;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.UI.Abstractions.Input;
|
||||
using AcDream.UI.Abstractions.Panels.Chat;
|
||||
|
|
@ -35,16 +36,22 @@ internal sealed record SettingsDevToolsDependencies(
|
|||
RuntimeSettingsController Settings,
|
||||
IRuntimeSettingsStartupTarget StartupTarget,
|
||||
HostQuiescenceGate HostQuiescence,
|
||||
RuntimeCommunicationState Communication,
|
||||
CombatState Combat,
|
||||
LocalPlayerState LocalPlayer,
|
||||
GameRuntime Runtime,
|
||||
SettingsDevToolsOptionalDependencies? DevTools,
|
||||
RuntimeDiagnosticCommandSlot DiagnosticCommands,
|
||||
CombatFeedbackSlot CombatFeedback,
|
||||
KeyBindings KeyBindings,
|
||||
FrameProfiler FrameProfiler,
|
||||
FramebufferResizeController FramebufferResize,
|
||||
Action<string> Log);
|
||||
Action<string> Log)
|
||||
{
|
||||
public RuntimeCommunicationState Communication =>
|
||||
Runtime.CommunicationOwner;
|
||||
|
||||
public CombatState Combat => Runtime.ActionOwner.Combat;
|
||||
|
||||
public LocalPlayerState LocalPlayer => Runtime.CharacterOwner.LocalPlayer;
|
||||
}
|
||||
|
||||
internal interface IGameWindowSettingsDevToolsPublication
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using AcDream.Core.Physics;
|
||||
using AcDream.App.Physics;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.App.Input;
|
||||
|
||||
|
|
@ -13,7 +14,23 @@ internal interface ILocalPlayerIdentitySource
|
|||
/// </summary>
|
||||
internal sealed class LocalPlayerIdentityState : ILocalPlayerIdentitySource
|
||||
{
|
||||
public uint ServerGuid { get; set; }
|
||||
private readonly RuntimeLocalPlayerIdentityState _runtime;
|
||||
|
||||
public LocalPlayerIdentityState()
|
||||
: this(new RuntimeLocalPlayerIdentityState())
|
||||
{
|
||||
}
|
||||
|
||||
public LocalPlayerIdentityState(RuntimeLocalPlayerIdentityState runtime)
|
||||
{
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
}
|
||||
|
||||
public uint ServerGuid
|
||||
{
|
||||
get => _runtime.ServerGuid;
|
||||
set => _runtime.ServerGuid = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal interface ILocalPlayerPhysicsHostSource
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using AcDream.App.World;
|
|||
using AcDream.Content;
|
||||
using AcDream.Core.Audio;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
|
|
@ -62,7 +63,7 @@ internal sealed record IngressShutdownRoots(
|
|||
// Keeps failed physical UI bindings alive through native-window release.
|
||||
UiHost? RetainedUiHost,
|
||||
DevToolsCompositionOwner? DevTools,
|
||||
LiveSessionController? LiveSession,
|
||||
GameRuntime Runtime,
|
||||
RuntimeSettingsController Settings,
|
||||
DispatcherMovementInputSource MovementInput,
|
||||
DispatcherCameraInputSource CameraInput,
|
||||
|
|
@ -83,12 +84,8 @@ internal sealed record LiveShutdownRoots(
|
|||
LandblockStreamer? Streamer,
|
||||
EquippedChildRenderController? EquippedChildren,
|
||||
LiveEntityRuntime? LiveEntities,
|
||||
RuntimeEntityObjectLifetime EntityObjects,
|
||||
RuntimeInventoryState Inventory,
|
||||
RuntimeCharacterState Character,
|
||||
RuntimeCommunicationState Communication,
|
||||
RuntimeActionState Actions,
|
||||
RuntimeLocalPlayerMovementState Movement,
|
||||
GameRuntime Runtime,
|
||||
IDisposable RuntimeHostLease,
|
||||
RenderSceneShadowRuntime? RenderSceneShadow,
|
||||
LivePresentationRuntimeBindings? PresentationBindings,
|
||||
DeferredEntityEffectAdvanceSource EffectAdvance,
|
||||
|
|
@ -352,7 +349,7 @@ internal static class GameWindowShutdownManifest
|
|||
Hard("keyboard source", () => ingress.KeyboardSource?.Deactivate()),
|
||||
Hard("retained UI input", ingress.RetailUi.QuiesceInput),
|
||||
Hard("developer tools input", () => ingress.DevTools?.DeactivateInput()),
|
||||
Hard("live session", () => DisposeLiveSession(ingress.LiveSession)),
|
||||
Hard("game runtime session", ingress.Runtime.StopSession),
|
||||
]),
|
||||
new ResourceShutdownStage("physical ingress cleanup",
|
||||
[
|
||||
|
|
@ -391,24 +388,6 @@ internal static class GameWindowShutdownManifest
|
|||
"shadow render scene",
|
||||
() => live.RenderSceneShadow?.Dispose()),
|
||||
]),
|
||||
new ResourceShutdownStage("runtime entity/object stream",
|
||||
[
|
||||
Hard(
|
||||
"runtime local movement state",
|
||||
live.Movement.Dispose),
|
||||
Hard(
|
||||
"runtime action state",
|
||||
live.Actions.Dispose),
|
||||
Hard(
|
||||
"runtime character state",
|
||||
live.Character.Dispose),
|
||||
Hard(
|
||||
"runtime inventory state",
|
||||
live.Inventory.Dispose),
|
||||
Hard(
|
||||
"runtime entity/object lifetime",
|
||||
live.EntityObjects.Dispose),
|
||||
]),
|
||||
new ResourceShutdownStage("effect dispatch edges",
|
||||
[
|
||||
Hard("live-presentation bindings", () => live.PresentationBindings?.Dispose()),
|
||||
|
|
@ -456,9 +435,10 @@ internal static class GameWindowShutdownManifest
|
|||
Hard("sky", () => render.Sky?.Dispose()),
|
||||
Hard("particles", () => render.Particles?.Dispose()),
|
||||
]),
|
||||
new ResourceShutdownStage("runtime communication state",
|
||||
new ResourceShutdownStage("game runtime root",
|
||||
[
|
||||
Hard("communication owner", live.Communication.Dispose),
|
||||
Hard("graphical runtime host lease", live.RuntimeHostLease.Dispose),
|
||||
Hard("game runtime", () => DisposeGameRuntime(live.Runtime)),
|
||||
]),
|
||||
new ResourceShutdownStage("shared texture owners",
|
||||
[
|
||||
|
|
@ -515,15 +495,14 @@ internal static class GameWindowShutdownManifest
|
|||
private static ResourceShutdownOperation Soft(string name, Action action) =>
|
||||
new(name, action, ResourceShutdownOperationPolicy.ReportAndContinue);
|
||||
|
||||
private static void DisposeLiveSession(LiveSessionController? controller)
|
||||
private static void DisposeGameRuntime(GameRuntime runtime)
|
||||
{
|
||||
if (controller is null)
|
||||
return;
|
||||
controller.Dispose();
|
||||
if (!controller.IsDisposalComplete)
|
||||
runtime.Dispose();
|
||||
GameRuntimeOwnershipSnapshot ownership = runtime.CaptureOwnership();
|
||||
if (!ownership.IsConverged)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Live-session disposal was deferred by a reentrant lifecycle callback.");
|
||||
"The canonical game runtime did not converge after disposal.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Interaction;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
using AcDream.UI.Abstractions;
|
||||
|
|
@ -12,9 +8,9 @@ using AcDream.UI.Abstractions;
|
|||
namespace AcDream.App.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// App composition root for Slice J's borrowed runtime boundary. Focused
|
||||
/// adapters project the existing canonical owners; this root owns only those
|
||||
/// adapters and never owns or copies gameplay state.
|
||||
/// Synchronous graphical command adapter over one canonical
|
||||
/// <see cref="GameRuntime"/>. It owns only its host lease and observer
|
||||
/// subscriptions; every view and mutable owner is borrowed directly.
|
||||
/// </summary>
|
||||
internal sealed class CurrentGameRuntimeAdapter
|
||||
: IGameRuntimeView,
|
||||
|
|
@ -22,72 +18,80 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
IRuntimeEventSource,
|
||||
IDisposable
|
||||
{
|
||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
||||
private readonly CurrentGameRuntimeEventAdapter _events;
|
||||
private readonly GameRuntime _runtime;
|
||||
private readonly CurrentGameRuntimeCommandAdapter _commands;
|
||||
private readonly IDisposable _hostLease;
|
||||
private readonly object _subscriptionGate = new();
|
||||
private readonly HashSet<AdapterSubscription> _subscriptions = [];
|
||||
private bool _disposed;
|
||||
|
||||
public CurrentGameRuntimeAdapter(
|
||||
LiveSessionController session,
|
||||
GameRuntime runtime,
|
||||
LiveSessionHost sessionHost,
|
||||
ICommandBus commands,
|
||||
LocalPlayerIdentityState playerIdentity,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
RuntimeInventoryState inventory,
|
||||
RuntimeCharacterState character,
|
||||
RuntimeCommunicationState communication,
|
||||
RuntimeActionState actions,
|
||||
RuntimeLocalPlayerMovementState movement,
|
||||
RuntimeWorldEnvironmentState environment,
|
||||
RuntimeWorldTransitState worldTransit,
|
||||
IGameRuntimeClock clock,
|
||||
SelectionInteractionController selection)
|
||||
{
|
||||
_view = new CurrentGameRuntimeViewAdapter(
|
||||
session,
|
||||
playerIdentity,
|
||||
entityObjects,
|
||||
inventory,
|
||||
character,
|
||||
communication,
|
||||
actions,
|
||||
movement,
|
||||
environment,
|
||||
worldTransit,
|
||||
clock);
|
||||
entityObjects.BindEventContext(
|
||||
() => _view.Generation,
|
||||
() => clock.FrameNumber);
|
||||
_events = new CurrentGameRuntimeEventAdapter(
|
||||
_view,
|
||||
entityObjects,
|
||||
communication);
|
||||
_commands = new CurrentGameRuntimeCommandAdapter(
|
||||
session,
|
||||
sessionHost,
|
||||
commands,
|
||||
_view,
|
||||
inventory,
|
||||
character,
|
||||
actions,
|
||||
movement,
|
||||
selection,
|
||||
_events);
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
ArgumentNullException.ThrowIfNull(sessionHost);
|
||||
ArgumentNullException.ThrowIfNull(commands);
|
||||
ArgumentNullException.ThrowIfNull(selection);
|
||||
|
||||
_hostLease = runtime.AcquireHostLease(
|
||||
"graphical game-runtime command adapter");
|
||||
try
|
||||
{
|
||||
_commands = new CurrentGameRuntimeCommandAdapter(
|
||||
runtime.Session,
|
||||
sessionHost,
|
||||
commands,
|
||||
this,
|
||||
runtime.InventoryOwner,
|
||||
runtime.CharacterOwner,
|
||||
runtime.ActionOwner,
|
||||
runtime.MovementOwner,
|
||||
selection,
|
||||
runtime.EventSink);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_hostLease.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public RuntimeGenerationToken Generation => _view.Generation;
|
||||
public RuntimeLifecycleSnapshot Lifecycle => _view.Lifecycle;
|
||||
public IGameRuntimeClock Clock => _view.Clock;
|
||||
public IRuntimeEntityView Entities => _view.Entities;
|
||||
public IRuntimeInventoryView Inventory => _view.Inventory;
|
||||
public IRuntimeInventoryStateView InventoryState => _view.InventoryState;
|
||||
public IRuntimeCharacterView Character => _view.Character;
|
||||
public IRuntimeSocialView Social => _view.Social;
|
||||
public IRuntimeChatView Chat => _view.Chat;
|
||||
public IRuntimeActionView Actions => _view.Actions;
|
||||
public IRuntimeMovementView Movement => _view.Movement;
|
||||
public IRuntimeWorldEnvironmentView Environment => _view.Environment;
|
||||
public IRuntimePortalView Portal => _view.Portal;
|
||||
private bool IsActive =>
|
||||
!_disposed
|
||||
&& !_runtime.Session.IsDisposalComplete;
|
||||
|
||||
public RuntimeGenerationToken Generation => _runtime.Generation;
|
||||
|
||||
public RuntimeLifecycleSnapshot Lifecycle
|
||||
{
|
||||
get
|
||||
{
|
||||
RuntimeLifecycleSnapshot current = _runtime.Lifecycle;
|
||||
return IsActive
|
||||
? current
|
||||
: current with
|
||||
{
|
||||
State = RuntimeLifecycleState.Disposed,
|
||||
HasTransport = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public IGameRuntimeClock Clock => _runtime.Clock;
|
||||
public IRuntimeEntityView Entities => _runtime.Entities;
|
||||
public IRuntimeInventoryView Inventory => _runtime.Inventory;
|
||||
public IRuntimeInventoryStateView InventoryState =>
|
||||
_runtime.InventoryState;
|
||||
public IRuntimeCharacterView Character => _runtime.Character;
|
||||
public IRuntimeSocialView Social => _runtime.Social;
|
||||
public IRuntimeChatView Chat => _runtime.Chat;
|
||||
public IRuntimeActionView Actions => _runtime.Actions;
|
||||
public IRuntimeMovementView Movement => _runtime.Movement;
|
||||
public IRuntimeWorldEnvironmentView Environment => _runtime.Environment;
|
||||
public IRuntimePortalView Portal => _runtime.Portal;
|
||||
|
||||
public IRuntimeSessionCommands Session => _commands;
|
||||
public IRuntimeSelectionCommands Selection => _commands;
|
||||
|
|
@ -109,18 +113,64 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
public IRuntimeSocialCommands SocialCommands => _commands;
|
||||
IRuntimeSocialCommands IGameRuntimeCommands.Social => _commands;
|
||||
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
||||
_view.CaptureCheckpoint();
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint()
|
||||
{
|
||||
RuntimeStateCheckpoint checkpoint = _runtime.CaptureCheckpoint();
|
||||
return checkpoint with { Lifecycle = Lifecycle.State };
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IRuntimeEventObserver observer) =>
|
||||
_events.Subscribe(observer);
|
||||
public IDisposable Subscribe(IRuntimeEventObserver observer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(observer);
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
IDisposable runtimeSubscription = _runtime.Subscribe(observer);
|
||||
var subscription = new AdapterSubscription(
|
||||
this,
|
||||
runtimeSubscription);
|
||||
_subscriptions.Add(subscription);
|
||||
return subscription;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
_view.Deactivate();
|
||||
_events.Dispose();
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
while (_subscriptions.Count != 0)
|
||||
_subscriptions.First().DisposeCore();
|
||||
}
|
||||
_hostLease.Dispose();
|
||||
}
|
||||
|
||||
private void Remove(AdapterSubscription subscription)
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
_subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
private sealed class AdapterSubscription(
|
||||
CurrentGameRuntimeAdapter owner,
|
||||
IDisposable runtimeSubscription) : IDisposable
|
||||
{
|
||||
private CurrentGameRuntimeAdapter? _owner = owner;
|
||||
private IDisposable? _runtimeSubscription = runtimeSubscription;
|
||||
|
||||
public void Dispose() => DisposeCore();
|
||||
|
||||
public void DisposeCore()
|
||||
{
|
||||
IDisposable? runtime = Interlocked.Exchange(
|
||||
ref _runtimeSubscription,
|
||||
null);
|
||||
if (runtime is null)
|
||||
return;
|
||||
runtime.Dispose();
|
||||
Interlocked.Exchange(ref _owner, null)?.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,25 +30,25 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
private readonly LiveSessionController _session;
|
||||
private readonly LiveSessionHost _sessionHost;
|
||||
private readonly ICommandBus _commands;
|
||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
||||
private readonly IGameRuntimeView _view;
|
||||
private readonly RuntimeInventoryState _inventory;
|
||||
private readonly RuntimeCharacterState _character;
|
||||
private readonly RuntimeActionState _actions;
|
||||
private readonly RuntimeLocalPlayerMovementState _movement;
|
||||
private readonly SelectionInteractionController _selection;
|
||||
private readonly ICurrentGameRuntimeEventSink _events;
|
||||
private readonly IGameRuntimeEventSink _events;
|
||||
|
||||
public CurrentGameRuntimeCommandAdapter(
|
||||
LiveSessionController session,
|
||||
LiveSessionHost sessionHost,
|
||||
ICommandBus commands,
|
||||
CurrentGameRuntimeViewAdapter view,
|
||||
IGameRuntimeView view,
|
||||
RuntimeInventoryState inventory,
|
||||
RuntimeCharacterState character,
|
||||
RuntimeActionState actions,
|
||||
RuntimeLocalPlayerMovementState movement,
|
||||
SelectionInteractionController selection,
|
||||
ICurrentGameRuntimeEventSink events)
|
||||
IGameRuntimeEventSink events)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_sessionHost = sessionHost ?? throw new ArgumentNullException(nameof(sessionHost));
|
||||
|
|
@ -80,7 +80,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
ToCommandStatus(result.Status),
|
||||
result.CharacterId,
|
||||
result.CharacterName);
|
||||
_events.EmitLifecycle(previous);
|
||||
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
ToCommandStatus(result.Status),
|
||||
result.CharacterId,
|
||||
result.CharacterName);
|
||||
_events.EmitLifecycle(previous);
|
||||
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
operation: 2,
|
||||
acknowledgement.Status,
|
||||
text: acknowledgement.Error?.GetType().Name ?? string.Empty);
|
||||
_events.EmitLifecycle(previous);
|
||||
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||
return acknowledgement;
|
||||
}
|
||||
|
||||
|
|
@ -674,7 +674,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
|||
RuntimeGenerationToken expectedGeneration,
|
||||
bool requireWorld)
|
||||
{
|
||||
if (!_view.IsActive)
|
||||
if (_view.Lifecycle.State == RuntimeLifecycleState.Disposed)
|
||||
return RuntimeCommandStatus.Inactive;
|
||||
if (expectedGeneration != _view.Generation)
|
||||
return RuntimeCommandStatus.StaleGeneration;
|
||||
|
|
|
|||
|
|
@ -1,275 +0,0 @@
|
|||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.App.Runtime;
|
||||
|
||||
internal interface ICurrentGameRuntimeEventSink
|
||||
{
|
||||
void EmitCommand(
|
||||
RuntimeCommandDomain domain,
|
||||
int operation,
|
||||
RuntimeCommandStatus status,
|
||||
uint primaryObjectId = 0u,
|
||||
string? text = null);
|
||||
|
||||
void EmitLifecycle(RuntimeLifecycleState previous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lazily projects existing owner notifications into one ordered diagnostic
|
||||
/// stream. With no subscribers it attaches no callbacks and adds no per-event
|
||||
/// production work.
|
||||
/// </summary>
|
||||
internal sealed class CurrentGameRuntimeEventAdapter
|
||||
: IRuntimeEventSource,
|
||||
ICurrentGameRuntimeEventSink,
|
||||
IRuntimeEntityObjectObserver,
|
||||
IRuntimeCommunicationObserver,
|
||||
IDisposable
|
||||
{
|
||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
||||
private readonly RuntimeEntityObjectLifetime _entityObjects;
|
||||
private readonly RuntimeCommunicationState _communication;
|
||||
private readonly object _observerGate = new();
|
||||
private IRuntimeEventObserver[] _observers = [];
|
||||
private IDisposable? _entityObjectSubscription;
|
||||
private IDisposable? _communicationSubscription;
|
||||
private bool _ownerEventsAttached;
|
||||
private bool _disposed;
|
||||
|
||||
internal long DispatchFailureCount { get; private set; }
|
||||
internal Exception? LastDispatchFailure { get; private set; }
|
||||
|
||||
public CurrentGameRuntimeEventAdapter(
|
||||
CurrentGameRuntimeViewAdapter view,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
RuntimeCommunicationState communication)
|
||||
{
|
||||
_view = view ?? throw new ArgumentNullException(nameof(view));
|
||||
_entityObjects = entityObjects
|
||||
?? throw new ArgumentNullException(nameof(entityObjects));
|
||||
_communication = communication
|
||||
?? throw new ArgumentNullException(nameof(communication));
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IRuntimeEventObserver observer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(observer);
|
||||
lock (_observerGate)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
IRuntimeEventObserver[] current = _observers;
|
||||
if (Array.IndexOf(current, observer) >= 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The runtime observer is already subscribed.");
|
||||
}
|
||||
|
||||
var replacement = new IRuntimeEventObserver[current.Length + 1];
|
||||
Array.Copy(current, replacement, current.Length);
|
||||
replacement[^1] = observer;
|
||||
Volatile.Write(ref _observers, replacement);
|
||||
if (!_ownerEventsAttached)
|
||||
AttachOwnerEvents();
|
||||
}
|
||||
|
||||
return new ObserverSubscription(this, observer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_observerGate)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
Volatile.Write(ref _observers, []);
|
||||
DetachOwnerEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public void EmitCommand(
|
||||
RuntimeCommandDomain domain,
|
||||
int operation,
|
||||
RuntimeCommandStatus status,
|
||||
uint primaryObjectId = 0u,
|
||||
string? text = null)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
var delta = new RuntimeCommandDelta(
|
||||
NextStamp(),
|
||||
domain,
|
||||
operation,
|
||||
status,
|
||||
primaryObjectId,
|
||||
text);
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnCommand(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EmitLifecycle(RuntimeLifecycleState previous)
|
||||
{
|
||||
RuntimeLifecycleState current = _view.Lifecycle.State;
|
||||
if (current == previous
|
||||
|| !TryObservers(out IRuntimeEventObserver[] observers))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = new RuntimeLifecycleDelta(
|
||||
NextStamp(),
|
||||
previous,
|
||||
current);
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnLifecycle(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Unsubscribe(IRuntimeEventObserver observer)
|
||||
{
|
||||
lock (_observerGate)
|
||||
{
|
||||
IRuntimeEventObserver[] current = _observers;
|
||||
int index = Array.IndexOf(current, observer);
|
||||
if (index < 0)
|
||||
return;
|
||||
if (current.Length == 1)
|
||||
{
|
||||
Volatile.Write(ref _observers, []);
|
||||
DetachOwnerEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
var replacement = new IRuntimeEventObserver[current.Length - 1];
|
||||
if (index != 0)
|
||||
Array.Copy(current, 0, replacement, 0, index);
|
||||
if (index != current.Length - 1)
|
||||
{
|
||||
Array.Copy(
|
||||
current,
|
||||
index + 1,
|
||||
replacement,
|
||||
index,
|
||||
current.Length - index - 1);
|
||||
}
|
||||
Volatile.Write(ref _observers, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachOwnerEvents()
|
||||
{
|
||||
_entityObjectSubscription = _entityObjects.Events.Subscribe(this);
|
||||
_communicationSubscription = _communication.Events.Subscribe(this);
|
||||
_ownerEventsAttached = true;
|
||||
}
|
||||
|
||||
private void DetachOwnerEvents()
|
||||
{
|
||||
if (!_ownerEventsAttached)
|
||||
return;
|
||||
_communicationSubscription?.Dispose();
|
||||
_communicationSubscription = null;
|
||||
_entityObjectSubscription?.Dispose();
|
||||
_entityObjectSubscription = null;
|
||||
_ownerEventsAttached = false;
|
||||
}
|
||||
|
||||
public void OnEntity(in RuntimeEntityDelta delta)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnEntity(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInventory(in RuntimeInventoryDelta delta)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnInventory(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnChat(in RuntimeCommunicationEvent committed)
|
||||
{
|
||||
if (!TryObservers(out IRuntimeEventObserver[] observers))
|
||||
return;
|
||||
var delta = new RuntimeChatDelta(
|
||||
NextStamp(),
|
||||
committed.Entry);
|
||||
foreach (IRuntimeEventObserver observer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
observer.OnChat(in delta);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RecordDispatchFailure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeEventStamp NextStamp() =>
|
||||
_entityObjects.Events.NextStamp();
|
||||
|
||||
private bool TryObservers(out IRuntimeEventObserver[] observers)
|
||||
{
|
||||
observers = Volatile.Read(ref _observers);
|
||||
return observers.Length != 0;
|
||||
}
|
||||
|
||||
private void RecordDispatchFailure(Exception error)
|
||||
{
|
||||
DispatchFailureCount++;
|
||||
LastDispatchFailure = error;
|
||||
}
|
||||
|
||||
private sealed class ObserverSubscription(
|
||||
CurrentGameRuntimeEventAdapter owner,
|
||||
IRuntimeEventObserver observer)
|
||||
: IDisposable
|
||||
{
|
||||
private CurrentGameRuntimeEventAdapter? _owner = owner;
|
||||
|
||||
public void Dispose() =>
|
||||
Interlocked.Exchange(ref _owner, null)?.Unsubscribe(observer);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.App.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Read-only borrowed projections over the current App owners. Enumeration is
|
||||
/// synchronous and allocation-free; no mutable collection is copied or owned.
|
||||
/// </summary>
|
||||
internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
|
||||
{
|
||||
private readonly LiveSessionController _session;
|
||||
private readonly LocalPlayerIdentityState _playerIdentity;
|
||||
private readonly IGameRuntimeClock _clock;
|
||||
private readonly IRuntimeEntityView _entityView;
|
||||
private readonly IRuntimeInventoryView _inventoryView;
|
||||
private readonly IRuntimeInventoryStateView _inventoryStateView;
|
||||
private readonly IRuntimeCharacterView _characterView;
|
||||
private readonly IRuntimeSocialView _socialView;
|
||||
private readonly IRuntimeChatView _chatView;
|
||||
private readonly IRuntimeActionView _actionView;
|
||||
private readonly IRuntimeMovementView _movementView;
|
||||
private readonly IRuntimeWorldEnvironmentView _environmentView;
|
||||
private readonly IRuntimePortalView _portalView;
|
||||
private bool _active = true;
|
||||
|
||||
public CurrentGameRuntimeViewAdapter(
|
||||
LiveSessionController session,
|
||||
LocalPlayerIdentityState playerIdentity,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
RuntimeInventoryState inventory,
|
||||
RuntimeCharacterState character,
|
||||
RuntimeCommunicationState communication,
|
||||
RuntimeActionState actions,
|
||||
RuntimeLocalPlayerMovementState movement,
|
||||
RuntimeWorldEnvironmentState environment,
|
||||
RuntimeWorldTransitState worldTransit,
|
||||
IGameRuntimeClock clock)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_playerIdentity = playerIdentity
|
||||
?? throw new ArgumentNullException(nameof(playerIdentity));
|
||||
ArgumentNullException.ThrowIfNull(entityObjects);
|
||||
ArgumentNullException.ThrowIfNull(communication);
|
||||
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||
|
||||
_entityView = entityObjects.EntityView;
|
||||
_inventoryView = entityObjects.InventoryView;
|
||||
_inventoryStateView = (
|
||||
inventory ?? throw new ArgumentNullException(nameof(inventory))).View;
|
||||
_characterView = (
|
||||
character ?? throw new ArgumentNullException(nameof(character))).View;
|
||||
_chatView = communication.View;
|
||||
_socialView = communication.SocialView;
|
||||
_actionView = (
|
||||
actions ?? throw new ArgumentNullException(nameof(actions))).View;
|
||||
_movementView = (
|
||||
movement ?? throw new ArgumentNullException(nameof(movement))).View;
|
||||
_environmentView = environment
|
||||
?? throw new ArgumentNullException(nameof(environment));
|
||||
_portalView = worldTransit
|
||||
?? throw new ArgumentNullException(nameof(worldTransit));
|
||||
}
|
||||
|
||||
internal bool IsActive => _active && !_session.IsDisposalComplete;
|
||||
|
||||
public RuntimeGenerationToken Generation => _session.Generation;
|
||||
|
||||
public RuntimeLifecycleSnapshot Lifecycle
|
||||
{
|
||||
get
|
||||
{
|
||||
RuntimeLifecycleState state;
|
||||
if (!IsActive)
|
||||
state = RuntimeLifecycleState.Disposed;
|
||||
else if (_session.IsInWorld)
|
||||
state = RuntimeLifecycleState.InWorld;
|
||||
else if (_session.CurrentSession is not null)
|
||||
state = RuntimeLifecycleState.Starting;
|
||||
else if (_session.SessionGeneration == 0UL)
|
||||
state = RuntimeLifecycleState.Constructed;
|
||||
else
|
||||
state = RuntimeLifecycleState.Stopped;
|
||||
|
||||
return new RuntimeLifecycleSnapshot(
|
||||
Generation,
|
||||
state,
|
||||
_playerIdentity.ServerGuid,
|
||||
_session.CurrentSession is not null);
|
||||
}
|
||||
}
|
||||
|
||||
public IGameRuntimeClock Clock => _clock;
|
||||
public IRuntimeEntityView Entities => _entityView;
|
||||
public IRuntimeInventoryView Inventory => _inventoryView;
|
||||
public IRuntimeInventoryStateView InventoryState => _inventoryStateView;
|
||||
public IRuntimeCharacterView Character => _characterView;
|
||||
public IRuntimeSocialView Social => _socialView;
|
||||
public IRuntimeChatView Chat => _chatView;
|
||||
public IRuntimeActionView Actions => _actionView;
|
||||
public IRuntimeMovementView Movement => _movementView;
|
||||
public IRuntimeWorldEnvironmentView Environment => _environmentView;
|
||||
public IRuntimePortalView Portal => _portalView;
|
||||
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
||||
new(
|
||||
Generation,
|
||||
Lifecycle.State,
|
||||
_clock.FrameNumber,
|
||||
_entityView.Count,
|
||||
_entityView.MaterializedCount,
|
||||
_inventoryView.ObjectCount,
|
||||
_inventoryView.ContainerCount,
|
||||
_inventoryStateView.Snapshot,
|
||||
_characterView.Snapshot,
|
||||
_socialView.Snapshot,
|
||||
_chatView.Revision,
|
||||
_chatView.Count,
|
||||
_actionView.Snapshot,
|
||||
_movementView.Snapshot,
|
||||
_environmentView.Snapshot,
|
||||
_environmentView.Ownership,
|
||||
_portalView.Snapshot,
|
||||
_portalView.Ownership);
|
||||
|
||||
internal void Deactivate() => _active = false;
|
||||
}
|
||||
|
|
@ -31,7 +31,17 @@ internal sealed class UpdateFrameClock
|
|||
: IPhysicsScriptTimeSource,
|
||||
IGameRuntimeClock
|
||||
{
|
||||
private readonly GameRuntimeClock _runtime = new();
|
||||
private readonly GameRuntimeClock _runtime;
|
||||
|
||||
public UpdateFrameClock()
|
||||
: this(new GameRuntimeClock())
|
||||
{
|
||||
}
|
||||
|
||||
public UpdateFrameClock(GameRuntimeClock runtime)
|
||||
{
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
}
|
||||
|
||||
public ulong FrameNumber => _runtime.FrameNumber;
|
||||
public double SimulationTimeSeconds => _runtime.SimulationTimeSeconds;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue