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.Rendering;
|
||||||
using AcDream.Core.Spells;
|
using AcDream.Core.Spells;
|
||||||
using AcDream.Core.Vfx;
|
using AcDream.Core.Vfx;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
using DatReaderWriter;
|
using DatReaderWriter;
|
||||||
using Silk.NET.Input;
|
using Silk.NET.Input;
|
||||||
|
|
@ -49,7 +50,7 @@ internal sealed record ContentEffectsAudioDependencies(
|
||||||
ResidencyBudgetOptions ResidencyBudgets,
|
ResidencyBudgetOptions ResidencyBudgets,
|
||||||
PhysicsDataCache PhysicsDataCache,
|
PhysicsDataCache PhysicsDataCache,
|
||||||
bool DumpMotionEnabled,
|
bool DumpMotionEnabled,
|
||||||
RuntimeCharacterState Character,
|
GameRuntime Runtime,
|
||||||
AnimationHookRouter HookRouter,
|
AnimationHookRouter HookRouter,
|
||||||
EntityEffectPoseRegistry EffectPoses,
|
EntityEffectPoseRegistry EffectPoses,
|
||||||
DeferredEntityEffectAdvanceSource EntityEffectAdvance,
|
DeferredEntityEffectAdvanceSource EntityEffectAdvance,
|
||||||
|
|
@ -57,7 +58,10 @@ internal sealed record ContentEffectsAudioDependencies(
|
||||||
TranslucencyFadeManager TranslucencyFades,
|
TranslucencyFadeManager TranslucencyFades,
|
||||||
bool NoAudio,
|
bool NoAudio,
|
||||||
Action<string> Log,
|
Action<string> Log,
|
||||||
Action<string> Error);
|
Action<string> Error)
|
||||||
|
{
|
||||||
|
public RuntimeCharacterState Character => Runtime.CharacterOwner;
|
||||||
|
}
|
||||||
|
|
||||||
internal interface IGameWindowContentEffectsAudioPublication
|
internal interface IGameWindowContentEffectsAudioPublication
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using AcDream.App.Runtime;
|
||||||
using AcDream.App.Settings;
|
using AcDream.App.Settings;
|
||||||
using AcDream.App.Update;
|
using AcDream.App.Update;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
using AcDream.Core.Combat;
|
using AcDream.Core.Combat;
|
||||||
using AcDream.Core.Lighting;
|
using AcDream.Core.Lighting;
|
||||||
|
|
@ -21,6 +22,7 @@ namespace AcDream.App.Composition;
|
||||||
|
|
||||||
internal sealed record FrameRootDependencies(
|
internal sealed record FrameRootDependencies(
|
||||||
RuntimeOptions Options,
|
RuntimeOptions Options,
|
||||||
|
GameRuntime Runtime,
|
||||||
GL Gl,
|
GL Gl,
|
||||||
IWindow Window,
|
IWindow Window,
|
||||||
IInputContext Input,
|
IInputContext Input,
|
||||||
|
|
@ -33,7 +35,6 @@ internal sealed record FrameRootDependencies(
|
||||||
LocalPlayerModeState PlayerMode,
|
LocalPlayerModeState PlayerMode,
|
||||||
LocalPlayerIdentityState PlayerIdentity,
|
LocalPlayerIdentityState PlayerIdentity,
|
||||||
ChaseCameraInputState ChaseCameraInput,
|
ChaseCameraInputState ChaseCameraInput,
|
||||||
RuntimeLocalPlayerMovementState PlayerController,
|
|
||||||
LiveWorldOriginState WorldOrigin,
|
LiveWorldOriginState WorldOrigin,
|
||||||
ParticleVisibilityController ParticleVisibility,
|
ParticleVisibilityController ParticleVisibility,
|
||||||
EntityEffectPoseRegistry EffectPoses,
|
EntityEffectPoseRegistry EffectPoses,
|
||||||
|
|
@ -48,12 +49,18 @@ internal sealed record FrameRootDependencies(
|
||||||
DebugVmRenderFactsPublisher DebugVmRenderFacts,
|
DebugVmRenderFactsPublisher DebugVmRenderFacts,
|
||||||
IInputCaptureSource InputCapture,
|
IInputCaptureSource InputCapture,
|
||||||
DispatcherCameraInputSource CameraInput,
|
DispatcherCameraInputSource CameraInput,
|
||||||
SelectionState Selection,
|
|
||||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> Animations,
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> Animations,
|
||||||
UpdateFrameClock UpdateClock,
|
UpdateFrameClock UpdateClock,
|
||||||
CombatState Combat,
|
|
||||||
GameFrameGraphSlot FrameGraphs,
|
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(
|
internal sealed record FrameRootResult(
|
||||||
UpdateFrameOrchestrator Update,
|
UpdateFrameOrchestrator Update,
|
||||||
|
|
|
||||||
|
|
@ -44,19 +44,15 @@ internal sealed record InteractionRetainedUiDependencies(
|
||||||
RetainedUiInputCaptureSlot RetainedInputCapture,
|
RetainedUiInputCaptureSlot RetainedInputCapture,
|
||||||
InputDispatcher? InputDispatcher,
|
InputDispatcher? InputDispatcher,
|
||||||
RuntimeSettingsController Settings,
|
RuntimeSettingsController Settings,
|
||||||
RuntimeActionState Actions,
|
GameRuntime Runtime,
|
||||||
IRuntimeCombatAttackOperations CombatAttackOperations,
|
IRuntimeCombatAttackOperations CombatAttackOperations,
|
||||||
RuntimeCombatTargetOperationsSlot CombatTargetOperations,
|
RuntimeCombatTargetOperationsSlot CombatTargetOperations,
|
||||||
RuntimeSpellCastOperationsSlot SpellCastOperations,
|
RuntimeSpellCastOperationsSlot SpellCastOperations,
|
||||||
RuntimeInventoryState Inventory,
|
|
||||||
MagicCatalog MagicCatalog,
|
MagicCatalog MagicCatalog,
|
||||||
RuntimeCharacterState Character,
|
|
||||||
RuntimeCommunicationState Communication,
|
|
||||||
StackSplitQuantityState StackSplitQuantity,
|
StackSplitQuantityState StackSplitQuantity,
|
||||||
BufferedUiRegistry? UiRegistry,
|
BufferedUiRegistry? UiRegistry,
|
||||||
LiveCombatModeCommandSlot CombatModeCommands,
|
LiveCombatModeCommandSlot CombatModeCommands,
|
||||||
ILocalPlayerIdentitySource PlayerIdentity,
|
ILocalPlayerIdentitySource PlayerIdentity,
|
||||||
IRuntimeLocalPlayerControllerSource PlayerController,
|
|
||||||
ILocalPlayerModeSource PlayerMode,
|
ILocalPlayerModeSource PlayerMode,
|
||||||
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
|
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
|
||||||
SelectionCameraFactory,
|
SelectionCameraFactory,
|
||||||
|
|
@ -64,7 +60,20 @@ internal sealed record InteractionRetainedUiDependencies(
|
||||||
VitalsVM? ExistingVitals,
|
VitalsVM? ExistingVitals,
|
||||||
Action<string>? Toast,
|
Action<string>? Toast,
|
||||||
Func<double> ClientTime,
|
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(
|
internal sealed record RetainedUiComposition(
|
||||||
UiHost Host,
|
UiHost Host,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ using AcDream.Core.Rendering;
|
||||||
using AcDream.Core.Selection;
|
using AcDream.Core.Selection;
|
||||||
using AcDream.Core.Vfx;
|
using AcDream.Core.Vfx;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Entities;
|
using AcDream.Runtime.Entities;
|
||||||
using AcDream.Runtime.World;
|
using AcDream.Runtime.World;
|
||||||
using DatReaderWriter;
|
using DatReaderWriter;
|
||||||
|
|
@ -45,8 +46,7 @@ internal sealed record LivePresentationDependencies(
|
||||||
PhysicsDataCache PhysicsDataCache,
|
PhysicsDataCache PhysicsDataCache,
|
||||||
WorldGameState WorldGameState,
|
WorldGameState WorldGameState,
|
||||||
WorldEvents WorldEvents,
|
WorldEvents WorldEvents,
|
||||||
SelectionState Selection,
|
GameRuntime Runtime,
|
||||||
RuntimeEntityObjectLifetime EntityObjects,
|
|
||||||
LiveEntityRuntimeSlot RuntimeSlot,
|
LiveEntityRuntimeSlot RuntimeSlot,
|
||||||
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
||||||
DeferredEntityEffectAdvanceSource EffectAdvance,
|
DeferredEntityEffectAdvanceSource EffectAdvance,
|
||||||
|
|
@ -61,7 +61,6 @@ internal sealed record LivePresentationDependencies(
|
||||||
CellVisibility CellVisibility,
|
CellVisibility CellVisibility,
|
||||||
LiveWorldOriginState WorldOrigin,
|
LiveWorldOriginState WorldOrigin,
|
||||||
LocalPlayerIdentityState PlayerIdentity,
|
LocalPlayerIdentityState PlayerIdentity,
|
||||||
RuntimeLocalPlayerMovementState PlayerController,
|
|
||||||
PointerPositionState PointerPosition,
|
PointerPositionState PointerPosition,
|
||||||
PlayerApproachCompletionState PlayerApproachCompletions,
|
PlayerApproachCompletionState PlayerApproachCompletions,
|
||||||
GameRenderResourceLifetime RenderResourceLifetime,
|
GameRenderResourceLifetime RenderResourceLifetime,
|
||||||
|
|
@ -73,7 +72,18 @@ internal sealed record LivePresentationDependencies(
|
||||||
DeferredRenderFrameDiagnosticsSource? DevFrameDiagnostics,
|
DeferredRenderFrameDiagnosticsSource? DevFrameDiagnostics,
|
||||||
DeferredRenderFrameDiagnosticsSource UiFrameDiagnostics,
|
DeferredRenderFrameDiagnosticsSource UiFrameDiagnostics,
|
||||||
Action<string> Log,
|
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(
|
internal sealed record LivePresentationResult(
|
||||||
DeferredLiveEntityRuntimeComponentLifecycle ComponentLifecycle,
|
DeferredLiveEntityRuntimeComponentLifecycle ComponentLifecycle,
|
||||||
|
|
@ -336,7 +346,7 @@ internal sealed class LivePresentationCompositionPhase
|
||||||
staticAnimationScheduler.Unregister,
|
staticAnimationScheduler.Unregister,
|
||||||
(entity, info) => staticAnimationScheduler.Rebind(entity, info));
|
(entity, info) => staticAnimationScheduler.Rebind(entity, info));
|
||||||
|
|
||||||
var worldTransit = new RuntimeWorldTransitState(d.Log);
|
RuntimeWorldTransitState worldTransit = d.WorldTransit;
|
||||||
var worldAvailability =
|
var worldAvailability =
|
||||||
new WorldGenerationAvailabilityState(worldTransit);
|
new WorldGenerationAvailabilityState(worldTransit);
|
||||||
var worldState = new GpuWorldState(
|
var worldState = new GpuWorldState(
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ using AcDream.Core.Player;
|
||||||
using AcDream.Core.Social;
|
using AcDream.Core.Social;
|
||||||
using AcDream.Core.Spells;
|
using AcDream.Core.Spells;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Entities;
|
using AcDream.Runtime.Entities;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
|
|
@ -35,6 +36,7 @@ namespace AcDream.App.Composition;
|
||||||
|
|
||||||
internal sealed record SessionPlayerDependencies(
|
internal sealed record SessionPlayerDependencies(
|
||||||
RuntimeOptions Options,
|
RuntimeOptions Options,
|
||||||
|
GameRuntime Runtime,
|
||||||
IWindow Window,
|
IWindow Window,
|
||||||
object DatLock,
|
object DatLock,
|
||||||
RuntimeSettingsController Settings,
|
RuntimeSettingsController Settings,
|
||||||
|
|
@ -49,8 +51,6 @@ internal sealed record SessionPlayerDependencies(
|
||||||
PhysicsDataCache PhysicsDataCache,
|
PhysicsDataCache PhysicsDataCache,
|
||||||
WorldGameState WorldGameState,
|
WorldGameState WorldGameState,
|
||||||
WorldEvents WorldEvents,
|
WorldEvents WorldEvents,
|
||||||
RuntimeActionState Actions,
|
|
||||||
RuntimeEntityObjectLifetime EntityObjects,
|
|
||||||
EntityClassificationCache ClassificationCache,
|
EntityClassificationCache ClassificationCache,
|
||||||
LiveEntityRuntimeSlot RuntimeSlot,
|
LiveEntityRuntimeSlot RuntimeSlot,
|
||||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
|
||||||
|
|
@ -60,7 +60,6 @@ internal sealed record SessionPlayerDependencies(
|
||||||
RetailInboundEventDispatcher InboundEntityEvents,
|
RetailInboundEventDispatcher InboundEntityEvents,
|
||||||
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
||||||
LocalPlayerIdentityState PlayerIdentity,
|
LocalPlayerIdentityState PlayerIdentity,
|
||||||
RuntimeLocalPlayerMovementState PlayerController,
|
|
||||||
LocalPlayerPhysicsHostSlot PlayerHost,
|
LocalPlayerPhysicsHostSlot PlayerHost,
|
||||||
LocalPlayerModeState PlayerMode,
|
LocalPlayerModeState PlayerMode,
|
||||||
ChaseCameraInputState ChaseCameraInput,
|
ChaseCameraInputState ChaseCameraInput,
|
||||||
|
|
@ -71,7 +70,6 @@ internal sealed record SessionPlayerDependencies(
|
||||||
LocalPlayerShadowState PlayerShadow,
|
LocalPlayerShadowState PlayerShadow,
|
||||||
ViewportAspectState ViewportAspect,
|
ViewportAspectState ViewportAspect,
|
||||||
PlayerApproachCompletionState PlayerApproachCompletions,
|
PlayerApproachCompletionState PlayerApproachCompletions,
|
||||||
RuntimeInventoryState Inventory,
|
|
||||||
PointerPositionState PointerPosition,
|
PointerPositionState PointerPosition,
|
||||||
DispatcherMovementInputSource MovementInput,
|
DispatcherMovementInputSource MovementInput,
|
||||||
IInputCaptureSource InputCapture,
|
IInputCaptureSource InputCapture,
|
||||||
|
|
@ -82,10 +80,24 @@ internal sealed record SessionPlayerDependencies(
|
||||||
MovementTruthDiagnosticController MovementDiagnostics,
|
MovementTruthDiagnosticController MovementDiagnostics,
|
||||||
CombatAttackOperationsSlot CombatAttackOperations,
|
CombatAttackOperationsSlot CombatAttackOperations,
|
||||||
CombatFeedbackSlot CombatFeedback,
|
CombatFeedbackSlot CombatFeedback,
|
||||||
RuntimeCommunicationState Communication,
|
|
||||||
RuntimeCharacterState Character,
|
|
||||||
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
|
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(
|
internal sealed record SessionPlayerResult(
|
||||||
LandblockStreamer Streamer,
|
LandblockStreamer Streamer,
|
||||||
|
|
@ -430,11 +442,7 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
sealedDungeonCells,
|
sealedDungeonCells,
|
||||||
d.Log);
|
d.Log);
|
||||||
|
|
||||||
var liveSessionLease = scope.Acquire(
|
LiveSessionController liveSession = d.Runtime.Session;
|
||||||
"live-session controller",
|
|
||||||
static () => new LiveSessionController(),
|
|
||||||
static value => value.Dispose());
|
|
||||||
LiveSessionController liveSession = liveSessionLease.Resource;
|
|
||||||
var liveSessionCommands = new LiveSessionCommandSurface();
|
var liveSessionCommands = new LiveSessionCommandSurface();
|
||||||
var liveSessionSource = new LiveSessionAppSource(
|
var liveSessionSource = new LiveSessionAppSource(
|
||||||
liveSession,
|
liveSession,
|
||||||
|
|
@ -869,19 +877,9 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
"live combat-mode commands",
|
"live combat-mode commands",
|
||||||
d.CombatModeCommands.BindOwned(combatCommand));
|
d.CombatModeCommands.BindOwned(combatCommand));
|
||||||
var gameRuntime = new CurrentGameRuntimeAdapter(
|
var gameRuntime = new CurrentGameRuntimeAdapter(
|
||||||
liveSession,
|
d.Runtime,
|
||||||
sessionHost,
|
sessionHost,
|
||||||
liveSessionCommands,
|
liveSessionCommands,
|
||||||
d.PlayerIdentity,
|
|
||||||
d.EntityObjects,
|
|
||||||
d.Inventory,
|
|
||||||
d.Character,
|
|
||||||
d.Communication,
|
|
||||||
d.Actions,
|
|
||||||
d.PlayerController,
|
|
||||||
d.WorldEnvironment.Runtime,
|
|
||||||
live.WorldTransit,
|
|
||||||
d.UpdateClock,
|
|
||||||
live.SelectionInteractions);
|
live.SelectionInteractions);
|
||||||
bindings.Adopt("current game runtime adapter", gameRuntime);
|
bindings.Adopt("current game runtime adapter", gameRuntime);
|
||||||
bindings.Adopt(
|
bindings.Adopt(
|
||||||
|
|
@ -987,7 +985,6 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
_publication.PublishSessionPlayer(result);
|
_publication.PublishSessionPlayer(result);
|
||||||
|
|
||||||
streamerLease.Transfer();
|
streamerLease.Transfer();
|
||||||
liveSessionLease.Transfer();
|
|
||||||
teleportLease.Transfer();
|
teleportLease.Transfer();
|
||||||
gameplayActionsLease?.Transfer();
|
gameplayActionsLease?.Transfer();
|
||||||
componentLifecycleLease.Transfer();
|
componentLifecycleLease.Transfer();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using AcDream.App.Settings;
|
||||||
using AcDream.Core.Chat;
|
using AcDream.Core.Chat;
|
||||||
using AcDream.Core.Combat;
|
using AcDream.Core.Combat;
|
||||||
using AcDream.Core.Player;
|
using AcDream.Core.Player;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
using AcDream.UI.Abstractions.Input;
|
using AcDream.UI.Abstractions.Input;
|
||||||
using AcDream.UI.Abstractions.Panels.Chat;
|
using AcDream.UI.Abstractions.Panels.Chat;
|
||||||
|
|
@ -35,16 +36,22 @@ internal sealed record SettingsDevToolsDependencies(
|
||||||
RuntimeSettingsController Settings,
|
RuntimeSettingsController Settings,
|
||||||
IRuntimeSettingsStartupTarget StartupTarget,
|
IRuntimeSettingsStartupTarget StartupTarget,
|
||||||
HostQuiescenceGate HostQuiescence,
|
HostQuiescenceGate HostQuiescence,
|
||||||
RuntimeCommunicationState Communication,
|
GameRuntime Runtime,
|
||||||
CombatState Combat,
|
|
||||||
LocalPlayerState LocalPlayer,
|
|
||||||
SettingsDevToolsOptionalDependencies? DevTools,
|
SettingsDevToolsOptionalDependencies? DevTools,
|
||||||
RuntimeDiagnosticCommandSlot DiagnosticCommands,
|
RuntimeDiagnosticCommandSlot DiagnosticCommands,
|
||||||
CombatFeedbackSlot CombatFeedback,
|
CombatFeedbackSlot CombatFeedback,
|
||||||
KeyBindings KeyBindings,
|
KeyBindings KeyBindings,
|
||||||
FrameProfiler FrameProfiler,
|
FrameProfiler FrameProfiler,
|
||||||
FramebufferResizeController FramebufferResize,
|
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
|
internal interface IGameWindowSettingsDevToolsPublication
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using AcDream.Core.Physics;
|
using AcDream.Core.Physics;
|
||||||
using AcDream.App.Physics;
|
using AcDream.App.Physics;
|
||||||
|
using AcDream.Runtime.Gameplay;
|
||||||
|
|
||||||
namespace AcDream.App.Input;
|
namespace AcDream.App.Input;
|
||||||
|
|
||||||
|
|
@ -13,7 +14,23 @@ internal interface ILocalPlayerIdentitySource
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class LocalPlayerIdentityState : ILocalPlayerIdentitySource
|
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
|
internal interface ILocalPlayerPhysicsHostSource
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.App.Settings;
|
using AcDream.App.Settings;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
using AcDream.Content;
|
using AcDream.Content;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Entities;
|
using AcDream.Runtime.Entities;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
|
|
@ -163,7 +164,7 @@ public sealed class GameWindow :
|
||||||
private readonly AcDream.App.Input.MovementTruthDiagnosticController
|
private readonly AcDream.App.Input.MovementTruthDiagnosticController
|
||||||
_movementTruthDiagnostics;
|
_movementTruthDiagnostics;
|
||||||
private readonly LocalPlayerOutboundController _localPlayerOutbound;
|
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;
|
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
|
||||||
// Step 7 projectile presentation. The controller owns no identity map;
|
// Step 7 projectile presentation. The controller owns no identity map;
|
||||||
// each runtime component is stored on the canonical LiveEntityRecord.
|
// 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
|
// J4/J5.1: Runtime owns communication, selection, combat, and target-mode
|
||||||
// state. App, UI, plugins, and live-session routing borrow exact children.
|
// state. App, UI, plugins, and live-session routing borrow exact children.
|
||||||
private readonly RuntimeCommunicationState _runtimeCommunication = new();
|
private readonly GameRuntime _runtime;
|
||||||
private readonly RuntimeActionState _runtimeActions;
|
private readonly IDisposable _runtimeHostLease;
|
||||||
|
private RuntimeCommunicationState _runtimeCommunication =>
|
||||||
|
_runtime.CommunicationOwner;
|
||||||
|
private RuntimeActionState _runtimeActions => _runtime.ActionOwner;
|
||||||
public AcDream.Core.Selection.SelectionState Selection =>
|
public AcDream.Core.Selection.SelectionState Selection =>
|
||||||
_runtimeActions.Selection;
|
_runtimeActions.Selection;
|
||||||
public AcDream.Core.Chat.ChatLog Chat => _runtimeCommunication.Chat;
|
public AcDream.Core.Chat.ChatLog Chat => _runtimeCommunication.Chat;
|
||||||
|
|
@ -338,9 +342,12 @@ public sealed class GameWindow :
|
||||||
_runtimeCommunication.Squelch;
|
_runtimeCommunication.Squelch;
|
||||||
public AcDream.Core.Combat.CombatState Combat =>
|
public AcDream.Core.Combat.CombatState Combat =>
|
||||||
_runtimeActions.Combat;
|
_runtimeActions.Combat;
|
||||||
private readonly RuntimeEntityObjectLifetime _runtimeEntityObjects = new();
|
private RuntimeEntityObjectLifetime _runtimeEntityObjects =>
|
||||||
private readonly RuntimeInventoryState _runtimeInventory;
|
_runtime.EntityObjects;
|
||||||
private readonly RuntimeCharacterState _runtimeCharacter;
|
private RuntimeInventoryState _runtimeInventory =>
|
||||||
|
_runtime.InventoryOwner;
|
||||||
|
private RuntimeCharacterState _runtimeCharacter =>
|
||||||
|
_runtime.CharacterOwner;
|
||||||
public AcDream.Core.Items.ItemManaState ItemMana =>
|
public AcDream.Core.Items.ItemManaState ItemMana =>
|
||||||
_runtimeInventory.ItemMana;
|
_runtimeInventory.ItemMana;
|
||||||
// Retail resolves learned IDs through portal.dat's CSpellTable. MagicCatalog
|
// 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.SceneLightingUboBinding? _sceneLightingUbo;
|
||||||
private AcDream.App.Rendering.Sky.SkyRenderer? _skyRenderer;
|
private AcDream.App.Rendering.Sky.SkyRenderer? _skyRenderer;
|
||||||
// Phase B.2: player movement mode.
|
// Phase B.2: player movement mode.
|
||||||
private readonly RuntimeLocalPlayerMovementState _playerControllerSlot = new();
|
private RuntimeLocalPlayerMovementState _playerControllerSlot =>
|
||||||
|
_runtime.MovementOwner;
|
||||||
private PlayerMovementController? _playerController
|
private PlayerMovementController? _playerController
|
||||||
=> _playerControllerSlot.Controller;
|
=> _playerControllerSlot.Controller;
|
||||||
private readonly AcDream.App.Input.ChaseCameraInputState _chaseCameraInput = new();
|
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 readonly AcDream.App.Input.LocalPlayerModeState _localPlayerMode = new();
|
||||||
private bool _playerMode
|
private bool _playerMode
|
||||||
=> _localPlayerMode.IsPlayerMode;
|
=> _localPlayerMode.IsPlayerMode;
|
||||||
private readonly AcDream.App.Input.LocalPlayerIdentityState _localPlayerIdentity = new();
|
private readonly AcDream.App.Input.LocalPlayerIdentityState
|
||||||
|
_localPlayerIdentity;
|
||||||
private uint _playerServerGuid
|
private uint _playerServerGuid
|
||||||
{
|
{
|
||||||
get => _localPlayerIdentity.ServerGuid;
|
get => _localPlayerIdentity.ServerGuid;
|
||||||
|
|
@ -510,7 +519,6 @@ public sealed class GameWindow :
|
||||||
// Runtime owns the canonical session generation and transport lifetime.
|
// Runtime owns the canonical session generation and transport lifetime.
|
||||||
// The window retains only the composition handles needed for startup and
|
// The window retains only the composition handles needed for startup and
|
||||||
// shutdown; graphical feature callbacks borrow state through App adapters.
|
// shutdown; graphical feature callbacks borrow state through App adapters.
|
||||||
private LiveSessionController? _liveSessionController;
|
|
||||||
private LiveSessionHost? _liveSessionHost;
|
private LiveSessionHost? _liveSessionHost;
|
||||||
private AcDream.Core.Net.WorldSession? LiveSession =>
|
private AcDream.Core.Net.WorldSession? LiveSession =>
|
||||||
_liveSessionHost?.CurrentSession;
|
_liveSessionHost?.CurrentSession;
|
||||||
|
|
@ -565,22 +573,24 @@ public sealed class GameWindow :
|
||||||
AcDream.App.Plugins.BufferedUiRegistry? uiRegistry = null)
|
AcDream.App.Plugins.BufferedUiRegistry? uiRegistry = null)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new System.ArgumentNullException(nameof(options));
|
_options = options ?? throw new System.ArgumentNullException(nameof(options));
|
||||||
_worldEnvironment = new AcDream.App.World.WorldEnvironmentController(
|
_runtime = new GameRuntime(new GameRuntimeDependencies(
|
||||||
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,
|
|
||||||
_combatAttackOperations,
|
_combatAttackOperations,
|
||||||
_combatTargetOperations,
|
_combatTargetOperations,
|
||||||
_combatModeOperations,
|
_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 =
|
var alphaScratchBudgets =
|
||||||
AcDream.App.Rendering.Residency.AlphaScratchBudgetProfile.Create(
|
AcDream.App.Rendering.Residency.AlphaScratchBudgetProfile.Create(
|
||||||
_options.ResidencyBudgets.AlphaScratchBytes);
|
_options.ResidencyBudgets.AlphaScratchBytes);
|
||||||
|
|
@ -1039,7 +1049,6 @@ public sealed class GameWindow :
|
||||||
|| _streamingOriginRecenter is not null
|
|| _streamingOriginRecenter is not null
|
||||||
|| _worldReveal is not null
|
|| _worldReveal is not null
|
||||||
|| _spawnClaimHydration is not null
|
|| _spawnClaimHydration is not null
|
||||||
|| _liveSessionController is not null
|
|
||||||
|| _liveEntityHydration is not null
|
|| _liveEntityHydration is not null
|
||||||
|| _liveEntityNetworkUpdates is not null
|
|| _liveEntityNetworkUpdates is not null
|
||||||
|| _liveEntityLiveness is not null
|
|| _liveEntityLiveness is not null
|
||||||
|
|
@ -1063,7 +1072,6 @@ public sealed class GameWindow :
|
||||||
_streamingOriginRecenter = result.StreamingOriginRecenter;
|
_streamingOriginRecenter = result.StreamingOriginRecenter;
|
||||||
_worldReveal = result.WorldReveal;
|
_worldReveal = result.WorldReveal;
|
||||||
_spawnClaimHydration = result.SpawnClaimHydration;
|
_spawnClaimHydration = result.SpawnClaimHydration;
|
||||||
_liveSessionController = result.LiveSession;
|
|
||||||
_liveEntityHydration = result.Hydration;
|
_liveEntityHydration = result.Hydration;
|
||||||
_liveEntityNetworkUpdates = result.NetworkUpdates;
|
_liveEntityNetworkUpdates = result.NetworkUpdates;
|
||||||
_liveEntityLiveness = result.Liveness;
|
_liveEntityLiveness = result.Liveness;
|
||||||
|
|
@ -1163,7 +1171,7 @@ public sealed class GameWindow :
|
||||||
_options.ResidencyBudgets,
|
_options.ResidencyBudgets,
|
||||||
_physicsDataCache,
|
_physicsDataCache,
|
||||||
_animationDiagnostics.DumpMotionEnabled,
|
_animationDiagnostics.DumpMotionEnabled,
|
||||||
_runtimeCharacter,
|
_runtime,
|
||||||
_hookRouter,
|
_hookRouter,
|
||||||
_effectPoses,
|
_effectPoses,
|
||||||
_entityEffectAdvance,
|
_entityEffectAdvance,
|
||||||
|
|
@ -1221,9 +1229,7 @@ public sealed class GameWindow :
|
||||||
hostInputCamera.CameraController,
|
hostInputCamera.CameraController,
|
||||||
contentEffectsAudio.Audio?.Engine),
|
contentEffectsAudio.Audio?.Engine),
|
||||||
_hostQuiescence,
|
_hostQuiescence,
|
||||||
_runtimeCommunication,
|
_runtime,
|
||||||
Combat,
|
|
||||||
LocalPlayer,
|
|
||||||
optionalDevTools,
|
optionalDevTools,
|
||||||
_runtimeDiagnosticCommands,
|
_runtimeDiagnosticCommands,
|
||||||
_combatFeedback,
|
_combatFeedback,
|
||||||
|
|
@ -1270,19 +1276,15 @@ public sealed class GameWindow :
|
||||||
_retainedInputCapture,
|
_retainedInputCapture,
|
||||||
hostInputCamera.InputDispatcher,
|
hostInputCamera.InputDispatcher,
|
||||||
_runtimeSettings,
|
_runtimeSettings,
|
||||||
_runtimeActions,
|
_runtime,
|
||||||
_combatAttackOperations,
|
_combatAttackOperations,
|
||||||
_combatTargetOperations,
|
_combatTargetOperations,
|
||||||
_spellCastOperations,
|
_spellCastOperations,
|
||||||
_runtimeInventory,
|
|
||||||
contentEffectsAudio.MagicCatalog,
|
contentEffectsAudio.MagicCatalog,
|
||||||
_runtimeCharacter,
|
|
||||||
_runtimeCommunication,
|
|
||||||
_stackSplitQuantity,
|
_stackSplitQuantity,
|
||||||
_uiRegistry,
|
_uiRegistry,
|
||||||
_liveCombatModeCommands,
|
_liveCombatModeCommands,
|
||||||
_localPlayerIdentity,
|
_localPlayerIdentity,
|
||||||
_playerControllerSlot,
|
|
||||||
_localPlayerMode,
|
_localPlayerMode,
|
||||||
viewPlane => new SelectionCameraSource(
|
viewPlane => new SelectionCameraSource(
|
||||||
hostInputCamera.CameraController,
|
hostInputCamera.CameraController,
|
||||||
|
|
@ -1323,8 +1325,7 @@ public sealed class GameWindow :
|
||||||
_physicsDataCache,
|
_physicsDataCache,
|
||||||
_worldGameState,
|
_worldGameState,
|
||||||
_worldEvents,
|
_worldEvents,
|
||||||
Selection,
|
_runtime,
|
||||||
_runtimeEntityObjects,
|
|
||||||
_liveEntityRuntimeSlot,
|
_liveEntityRuntimeSlot,
|
||||||
_liveEntityMotionBindings,
|
_liveEntityMotionBindings,
|
||||||
_entityEffectAdvance,
|
_entityEffectAdvance,
|
||||||
|
|
@ -1339,7 +1340,6 @@ public sealed class GameWindow :
|
||||||
_cellVisibility,
|
_cellVisibility,
|
||||||
_liveWorldOrigin,
|
_liveWorldOrigin,
|
||||||
_localPlayerIdentity,
|
_localPlayerIdentity,
|
||||||
_playerControllerSlot,
|
|
||||||
_pointerPosition,
|
_pointerPosition,
|
||||||
_playerApproachCompletions,
|
_playerApproachCompletions,
|
||||||
_renderResourceLifetime,
|
_renderResourceLifetime,
|
||||||
|
|
@ -1369,6 +1369,7 @@ public sealed class GameWindow :
|
||||||
new SessionPlayerCompositionPhase(
|
new SessionPlayerCompositionPhase(
|
||||||
new SessionPlayerDependencies(
|
new SessionPlayerDependencies(
|
||||||
_options,
|
_options,
|
||||||
|
_runtime,
|
||||||
_window!,
|
_window!,
|
||||||
_datLock,
|
_datLock,
|
||||||
_runtimeSettings,
|
_runtimeSettings,
|
||||||
|
|
@ -1383,8 +1384,6 @@ public sealed class GameWindow :
|
||||||
_physicsDataCache,
|
_physicsDataCache,
|
||||||
_worldGameState,
|
_worldGameState,
|
||||||
_worldEvents,
|
_worldEvents,
|
||||||
_runtimeActions,
|
|
||||||
_runtimeEntityObjects,
|
|
||||||
_classificationCache,
|
_classificationCache,
|
||||||
_liveEntityRuntimeSlot,
|
_liveEntityRuntimeSlot,
|
||||||
_animatedEntities,
|
_animatedEntities,
|
||||||
|
|
@ -1394,7 +1393,6 @@ public sealed class GameWindow :
|
||||||
_inboundEntityEvents,
|
_inboundEntityEvents,
|
||||||
_liveEntityMotionBindings,
|
_liveEntityMotionBindings,
|
||||||
_localPlayerIdentity,
|
_localPlayerIdentity,
|
||||||
_playerControllerSlot,
|
|
||||||
_playerHostSlot,
|
_playerHostSlot,
|
||||||
_localPlayerMode,
|
_localPlayerMode,
|
||||||
_chaseCameraInput,
|
_chaseCameraInput,
|
||||||
|
|
@ -1405,7 +1403,6 @@ public sealed class GameWindow :
|
||||||
_localPlayerShadow,
|
_localPlayerShadow,
|
||||||
_viewportAspect,
|
_viewportAspect,
|
||||||
_playerApproachCompletions,
|
_playerApproachCompletions,
|
||||||
_runtimeInventory,
|
|
||||||
_pointerPosition,
|
_pointerPosition,
|
||||||
_movementInput,
|
_movementInput,
|
||||||
_inputCapture,
|
_inputCapture,
|
||||||
|
|
@ -1416,8 +1413,6 @@ public sealed class GameWindow :
|
||||||
_movementTruthDiagnostics,
|
_movementTruthDiagnostics,
|
||||||
_combatAttackOperations,
|
_combatAttackOperations,
|
||||||
_combatFeedback,
|
_combatFeedback,
|
||||||
_runtimeCommunication,
|
|
||||||
_runtimeCharacter,
|
|
||||||
_portalTunnelFallback,
|
_portalTunnelFallback,
|
||||||
Console.WriteLine),
|
Console.WriteLine),
|
||||||
this).Compose(
|
this).Compose(
|
||||||
|
|
@ -1437,6 +1432,7 @@ public sealed class GameWindow :
|
||||||
sessionPlayer) => new FrameRootCompositionPhase(
|
sessionPlayer) => new FrameRootCompositionPhase(
|
||||||
new FrameRootDependencies(
|
new FrameRootDependencies(
|
||||||
_options,
|
_options,
|
||||||
|
_runtime,
|
||||||
platformResult.Graphics,
|
platformResult.Graphics,
|
||||||
_window!,
|
_window!,
|
||||||
platformResult.Input,
|
platformResult.Input,
|
||||||
|
|
@ -1449,7 +1445,6 @@ public sealed class GameWindow :
|
||||||
_localPlayerMode,
|
_localPlayerMode,
|
||||||
_localPlayerIdentity,
|
_localPlayerIdentity,
|
||||||
_chaseCameraInput,
|
_chaseCameraInput,
|
||||||
_playerControllerSlot,
|
|
||||||
_liveWorldOrigin,
|
_liveWorldOrigin,
|
||||||
_particleVisibility,
|
_particleVisibility,
|
||||||
_effectPoses,
|
_effectPoses,
|
||||||
|
|
@ -1464,10 +1459,8 @@ public sealed class GameWindow :
|
||||||
_debugVmRenderFacts,
|
_debugVmRenderFacts,
|
||||||
_inputCapture,
|
_inputCapture,
|
||||||
_cameraInput,
|
_cameraInput,
|
||||||
Selection,
|
|
||||||
_animatedEntities,
|
_animatedEntities,
|
||||||
_updateFrameClock,
|
_updateFrameClock,
|
||||||
Combat,
|
|
||||||
_frameGraphs,
|
_frameGraphs,
|
||||||
Console.WriteLine),
|
Console.WriteLine),
|
||||||
this).Compose(
|
this).Compose(
|
||||||
|
|
@ -1571,7 +1564,7 @@ public sealed class GameWindow :
|
||||||
_retailUiLease,
|
_retailUiLease,
|
||||||
_uiHost,
|
_uiHost,
|
||||||
_devToolsComposition,
|
_devToolsComposition,
|
||||||
_liveSessionController,
|
_runtime,
|
||||||
_runtimeSettings,
|
_runtimeSettings,
|
||||||
_movementInput,
|
_movementInput,
|
||||||
_cameraInput,
|
_cameraInput,
|
||||||
|
|
@ -1590,12 +1583,8 @@ public sealed class GameWindow :
|
||||||
_streamer,
|
_streamer,
|
||||||
_equippedChildRenderer,
|
_equippedChildRenderer,
|
||||||
_liveEntities,
|
_liveEntities,
|
||||||
_runtimeEntityObjects,
|
_runtime,
|
||||||
_runtimeInventory,
|
_runtimeHostLease,
|
||||||
_runtimeCharacter,
|
|
||||||
_runtimeCommunication,
|
|
||||||
_runtimeActions,
|
|
||||||
_playerControllerSlot,
|
|
||||||
_renderSceneShadow,
|
_renderSceneShadow,
|
||||||
_livePresentationBindings,
|
_livePresentationBindings,
|
||||||
_entityEffectAdvance,
|
_entityEffectAdvance,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ using AcDream.App.World;
|
||||||
using AcDream.Content;
|
using AcDream.Content;
|
||||||
using AcDream.Core.Audio;
|
using AcDream.Core.Audio;
|
||||||
using AcDream.Core.Physics;
|
using AcDream.Core.Physics;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Entities;
|
using AcDream.Runtime.Entities;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
|
|
@ -62,7 +63,7 @@ internal sealed record IngressShutdownRoots(
|
||||||
// Keeps failed physical UI bindings alive through native-window release.
|
// Keeps failed physical UI bindings alive through native-window release.
|
||||||
UiHost? RetainedUiHost,
|
UiHost? RetainedUiHost,
|
||||||
DevToolsCompositionOwner? DevTools,
|
DevToolsCompositionOwner? DevTools,
|
||||||
LiveSessionController? LiveSession,
|
GameRuntime Runtime,
|
||||||
RuntimeSettingsController Settings,
|
RuntimeSettingsController Settings,
|
||||||
DispatcherMovementInputSource MovementInput,
|
DispatcherMovementInputSource MovementInput,
|
||||||
DispatcherCameraInputSource CameraInput,
|
DispatcherCameraInputSource CameraInput,
|
||||||
|
|
@ -83,12 +84,8 @@ internal sealed record LiveShutdownRoots(
|
||||||
LandblockStreamer? Streamer,
|
LandblockStreamer? Streamer,
|
||||||
EquippedChildRenderController? EquippedChildren,
|
EquippedChildRenderController? EquippedChildren,
|
||||||
LiveEntityRuntime? LiveEntities,
|
LiveEntityRuntime? LiveEntities,
|
||||||
RuntimeEntityObjectLifetime EntityObjects,
|
GameRuntime Runtime,
|
||||||
RuntimeInventoryState Inventory,
|
IDisposable RuntimeHostLease,
|
||||||
RuntimeCharacterState Character,
|
|
||||||
RuntimeCommunicationState Communication,
|
|
||||||
RuntimeActionState Actions,
|
|
||||||
RuntimeLocalPlayerMovementState Movement,
|
|
||||||
RenderSceneShadowRuntime? RenderSceneShadow,
|
RenderSceneShadowRuntime? RenderSceneShadow,
|
||||||
LivePresentationRuntimeBindings? PresentationBindings,
|
LivePresentationRuntimeBindings? PresentationBindings,
|
||||||
DeferredEntityEffectAdvanceSource EffectAdvance,
|
DeferredEntityEffectAdvanceSource EffectAdvance,
|
||||||
|
|
@ -352,7 +349,7 @@ internal static class GameWindowShutdownManifest
|
||||||
Hard("keyboard source", () => ingress.KeyboardSource?.Deactivate()),
|
Hard("keyboard source", () => ingress.KeyboardSource?.Deactivate()),
|
||||||
Hard("retained UI input", ingress.RetailUi.QuiesceInput),
|
Hard("retained UI input", ingress.RetailUi.QuiesceInput),
|
||||||
Hard("developer tools input", () => ingress.DevTools?.DeactivateInput()),
|
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",
|
new ResourceShutdownStage("physical ingress cleanup",
|
||||||
[
|
[
|
||||||
|
|
@ -391,24 +388,6 @@ internal static class GameWindowShutdownManifest
|
||||||
"shadow render scene",
|
"shadow render scene",
|
||||||
() => live.RenderSceneShadow?.Dispose()),
|
() => 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",
|
new ResourceShutdownStage("effect dispatch edges",
|
||||||
[
|
[
|
||||||
Hard("live-presentation bindings", () => live.PresentationBindings?.Dispose()),
|
Hard("live-presentation bindings", () => live.PresentationBindings?.Dispose()),
|
||||||
|
|
@ -456,9 +435,10 @@ internal static class GameWindowShutdownManifest
|
||||||
Hard("sky", () => render.Sky?.Dispose()),
|
Hard("sky", () => render.Sky?.Dispose()),
|
||||||
Hard("particles", () => render.Particles?.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",
|
new ResourceShutdownStage("shared texture owners",
|
||||||
[
|
[
|
||||||
|
|
@ -515,15 +495,14 @@ internal static class GameWindowShutdownManifest
|
||||||
private static ResourceShutdownOperation Soft(string name, Action action) =>
|
private static ResourceShutdownOperation Soft(string name, Action action) =>
|
||||||
new(name, action, ResourceShutdownOperationPolicy.ReportAndContinue);
|
new(name, action, ResourceShutdownOperationPolicy.ReportAndContinue);
|
||||||
|
|
||||||
private static void DisposeLiveSession(LiveSessionController? controller)
|
private static void DisposeGameRuntime(GameRuntime runtime)
|
||||||
{
|
{
|
||||||
if (controller is null)
|
runtime.Dispose();
|
||||||
return;
|
GameRuntimeOwnershipSnapshot ownership = runtime.CaptureOwnership();
|
||||||
controller.Dispose();
|
if (!ownership.IsConverged)
|
||||||
if (!controller.IsDisposalComplete)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
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.Interaction;
|
||||||
using AcDream.App.Net;
|
using AcDream.App.Net;
|
||||||
using AcDream.App.World;
|
|
||||||
using AcDream.Runtime;
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Entities;
|
|
||||||
using AcDream.Runtime.Gameplay;
|
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
using AcDream.Runtime.World;
|
using AcDream.Runtime.World;
|
||||||
using AcDream.UI.Abstractions;
|
using AcDream.UI.Abstractions;
|
||||||
|
|
@ -12,9 +8,9 @@ using AcDream.UI.Abstractions;
|
||||||
namespace AcDream.App.Runtime;
|
namespace AcDream.App.Runtime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// App composition root for Slice J's borrowed runtime boundary. Focused
|
/// Synchronous graphical command adapter over one canonical
|
||||||
/// adapters project the existing canonical owners; this root owns only those
|
/// <see cref="GameRuntime"/>. It owns only its host lease and observer
|
||||||
/// adapters and never owns or copies gameplay state.
|
/// subscriptions; every view and mutable owner is borrowed directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class CurrentGameRuntimeAdapter
|
internal sealed class CurrentGameRuntimeAdapter
|
||||||
: IGameRuntimeView,
|
: IGameRuntimeView,
|
||||||
|
|
@ -22,72 +18,80 @@ internal sealed class CurrentGameRuntimeAdapter
|
||||||
IRuntimeEventSource,
|
IRuntimeEventSource,
|
||||||
IDisposable
|
IDisposable
|
||||||
{
|
{
|
||||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
private readonly GameRuntime _runtime;
|
||||||
private readonly CurrentGameRuntimeEventAdapter _events;
|
|
||||||
private readonly CurrentGameRuntimeCommandAdapter _commands;
|
private readonly CurrentGameRuntimeCommandAdapter _commands;
|
||||||
|
private readonly IDisposable _hostLease;
|
||||||
|
private readonly object _subscriptionGate = new();
|
||||||
|
private readonly HashSet<AdapterSubscription> _subscriptions = [];
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public CurrentGameRuntimeAdapter(
|
public CurrentGameRuntimeAdapter(
|
||||||
LiveSessionController session,
|
GameRuntime runtime,
|
||||||
LiveSessionHost sessionHost,
|
LiveSessionHost sessionHost,
|
||||||
ICommandBus commands,
|
ICommandBus commands,
|
||||||
LocalPlayerIdentityState playerIdentity,
|
|
||||||
RuntimeEntityObjectLifetime entityObjects,
|
|
||||||
RuntimeInventoryState inventory,
|
|
||||||
RuntimeCharacterState character,
|
|
||||||
RuntimeCommunicationState communication,
|
|
||||||
RuntimeActionState actions,
|
|
||||||
RuntimeLocalPlayerMovementState movement,
|
|
||||||
RuntimeWorldEnvironmentState environment,
|
|
||||||
RuntimeWorldTransitState worldTransit,
|
|
||||||
IGameRuntimeClock clock,
|
|
||||||
SelectionInteractionController selection)
|
SelectionInteractionController selection)
|
||||||
{
|
{
|
||||||
_view = new CurrentGameRuntimeViewAdapter(
|
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||||
session,
|
ArgumentNullException.ThrowIfNull(sessionHost);
|
||||||
playerIdentity,
|
ArgumentNullException.ThrowIfNull(commands);
|
||||||
entityObjects,
|
ArgumentNullException.ThrowIfNull(selection);
|
||||||
inventory,
|
|
||||||
character,
|
_hostLease = runtime.AcquireHostLease(
|
||||||
communication,
|
"graphical game-runtime command adapter");
|
||||||
actions,
|
try
|
||||||
movement,
|
{
|
||||||
environment,
|
_commands = new CurrentGameRuntimeCommandAdapter(
|
||||||
worldTransit,
|
runtime.Session,
|
||||||
clock);
|
sessionHost,
|
||||||
entityObjects.BindEventContext(
|
commands,
|
||||||
() => _view.Generation,
|
this,
|
||||||
() => clock.FrameNumber);
|
runtime.InventoryOwner,
|
||||||
_events = new CurrentGameRuntimeEventAdapter(
|
runtime.CharacterOwner,
|
||||||
_view,
|
runtime.ActionOwner,
|
||||||
entityObjects,
|
runtime.MovementOwner,
|
||||||
communication);
|
selection,
|
||||||
_commands = new CurrentGameRuntimeCommandAdapter(
|
runtime.EventSink);
|
||||||
session,
|
}
|
||||||
sessionHost,
|
catch
|
||||||
commands,
|
{
|
||||||
_view,
|
_hostLease.Dispose();
|
||||||
inventory,
|
throw;
|
||||||
character,
|
}
|
||||||
actions,
|
|
||||||
movement,
|
|
||||||
selection,
|
|
||||||
_events);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RuntimeGenerationToken Generation => _view.Generation;
|
private bool IsActive =>
|
||||||
public RuntimeLifecycleSnapshot Lifecycle => _view.Lifecycle;
|
!_disposed
|
||||||
public IGameRuntimeClock Clock => _view.Clock;
|
&& !_runtime.Session.IsDisposalComplete;
|
||||||
public IRuntimeEntityView Entities => _view.Entities;
|
|
||||||
public IRuntimeInventoryView Inventory => _view.Inventory;
|
public RuntimeGenerationToken Generation => _runtime.Generation;
|
||||||
public IRuntimeInventoryStateView InventoryState => _view.InventoryState;
|
|
||||||
public IRuntimeCharacterView Character => _view.Character;
|
public RuntimeLifecycleSnapshot Lifecycle
|
||||||
public IRuntimeSocialView Social => _view.Social;
|
{
|
||||||
public IRuntimeChatView Chat => _view.Chat;
|
get
|
||||||
public IRuntimeActionView Actions => _view.Actions;
|
{
|
||||||
public IRuntimeMovementView Movement => _view.Movement;
|
RuntimeLifecycleSnapshot current = _runtime.Lifecycle;
|
||||||
public IRuntimeWorldEnvironmentView Environment => _view.Environment;
|
return IsActive
|
||||||
public IRuntimePortalView Portal => _view.Portal;
|
? 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 IRuntimeSessionCommands Session => _commands;
|
||||||
public IRuntimeSelectionCommands Selection => _commands;
|
public IRuntimeSelectionCommands Selection => _commands;
|
||||||
|
|
@ -109,18 +113,64 @@ internal sealed class CurrentGameRuntimeAdapter
|
||||||
public IRuntimeSocialCommands SocialCommands => _commands;
|
public IRuntimeSocialCommands SocialCommands => _commands;
|
||||||
IRuntimeSocialCommands IGameRuntimeCommands.Social => _commands;
|
IRuntimeSocialCommands IGameRuntimeCommands.Social => _commands;
|
||||||
|
|
||||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
public RuntimeStateCheckpoint CaptureCheckpoint()
|
||||||
_view.CaptureCheckpoint();
|
{
|
||||||
|
RuntimeStateCheckpoint checkpoint = _runtime.CaptureCheckpoint();
|
||||||
|
return checkpoint with { Lifecycle = Lifecycle.State };
|
||||||
|
}
|
||||||
|
|
||||||
public IDisposable Subscribe(IRuntimeEventObserver observer) =>
|
public IDisposable Subscribe(IRuntimeEventObserver observer)
|
||||||
_events.Subscribe(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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
lock (_subscriptionGate)
|
||||||
return;
|
{
|
||||||
_disposed = true;
|
if (_disposed)
|
||||||
_view.Deactivate();
|
return;
|
||||||
_events.Dispose();
|
_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 LiveSessionController _session;
|
||||||
private readonly LiveSessionHost _sessionHost;
|
private readonly LiveSessionHost _sessionHost;
|
||||||
private readonly ICommandBus _commands;
|
private readonly ICommandBus _commands;
|
||||||
private readonly CurrentGameRuntimeViewAdapter _view;
|
private readonly IGameRuntimeView _view;
|
||||||
private readonly RuntimeInventoryState _inventory;
|
private readonly RuntimeInventoryState _inventory;
|
||||||
private readonly RuntimeCharacterState _character;
|
private readonly RuntimeCharacterState _character;
|
||||||
private readonly RuntimeActionState _actions;
|
private readonly RuntimeActionState _actions;
|
||||||
private readonly RuntimeLocalPlayerMovementState _movement;
|
private readonly RuntimeLocalPlayerMovementState _movement;
|
||||||
private readonly SelectionInteractionController _selection;
|
private readonly SelectionInteractionController _selection;
|
||||||
private readonly ICurrentGameRuntimeEventSink _events;
|
private readonly IGameRuntimeEventSink _events;
|
||||||
|
|
||||||
public CurrentGameRuntimeCommandAdapter(
|
public CurrentGameRuntimeCommandAdapter(
|
||||||
LiveSessionController session,
|
LiveSessionController session,
|
||||||
LiveSessionHost sessionHost,
|
LiveSessionHost sessionHost,
|
||||||
ICommandBus commands,
|
ICommandBus commands,
|
||||||
CurrentGameRuntimeViewAdapter view,
|
IGameRuntimeView view,
|
||||||
RuntimeInventoryState inventory,
|
RuntimeInventoryState inventory,
|
||||||
RuntimeCharacterState character,
|
RuntimeCharacterState character,
|
||||||
RuntimeActionState actions,
|
RuntimeActionState actions,
|
||||||
RuntimeLocalPlayerMovementState movement,
|
RuntimeLocalPlayerMovementState movement,
|
||||||
SelectionInteractionController selection,
|
SelectionInteractionController selection,
|
||||||
ICurrentGameRuntimeEventSink events)
|
IGameRuntimeEventSink events)
|
||||||
{
|
{
|
||||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||||
_sessionHost = sessionHost ?? throw new ArgumentNullException(nameof(sessionHost));
|
_sessionHost = sessionHost ?? throw new ArgumentNullException(nameof(sessionHost));
|
||||||
|
|
@ -80,7 +80,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
||||||
ToCommandStatus(result.Status),
|
ToCommandStatus(result.Status),
|
||||||
result.CharacterId,
|
result.CharacterId,
|
||||||
result.CharacterName);
|
result.CharacterName);
|
||||||
_events.EmitLifecycle(previous);
|
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
||||||
ToCommandStatus(result.Status),
|
ToCommandStatus(result.Status),
|
||||||
result.CharacterId,
|
result.CharacterId,
|
||||||
result.CharacterName);
|
result.CharacterName);
|
||||||
_events.EmitLifecycle(previous);
|
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
||||||
operation: 2,
|
operation: 2,
|
||||||
acknowledgement.Status,
|
acknowledgement.Status,
|
||||||
text: acknowledgement.Error?.GetType().Name ?? string.Empty);
|
text: acknowledgement.Error?.GetType().Name ?? string.Empty);
|
||||||
_events.EmitLifecycle(previous);
|
_events.EmitLifecycle(previous, _view.Lifecycle.State);
|
||||||
return acknowledgement;
|
return acknowledgement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -674,7 +674,7 @@ internal sealed class CurrentGameRuntimeCommandAdapter
|
||||||
RuntimeGenerationToken expectedGeneration,
|
RuntimeGenerationToken expectedGeneration,
|
||||||
bool requireWorld)
|
bool requireWorld)
|
||||||
{
|
{
|
||||||
if (!_view.IsActive)
|
if (_view.Lifecycle.State == RuntimeLifecycleState.Disposed)
|
||||||
return RuntimeCommandStatus.Inactive;
|
return RuntimeCommandStatus.Inactive;
|
||||||
if (expectedGeneration != _view.Generation)
|
if (expectedGeneration != _view.Generation)
|
||||||
return RuntimeCommandStatus.StaleGeneration;
|
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,
|
: IPhysicsScriptTimeSource,
|
||||||
IGameRuntimeClock
|
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 ulong FrameNumber => _runtime.FrameNumber;
|
||||||
public double SimulationTimeSeconds => _runtime.SimulationTimeSeconds;
|
public double SimulationTimeSeconds => _runtime.SimulationTimeSeconds;
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,8 @@ public sealed class ContentEffectsAudioCompositionTests
|
||||||
Assert.Throws<InvalidOperationException>(() => fixture.Phase().Compose(
|
Assert.Throws<InvalidOperationException>(() => fixture.Phase().Compose(
|
||||||
fixture.Platform,
|
fixture.Platform,
|
||||||
fixture.Host));
|
fixture.Host));
|
||||||
|
Assert.False(
|
||||||
|
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||||
|
|
||||||
Assert.NotNull(fixture.Publication.Audio);
|
Assert.NotNull(fixture.Publication.Audio);
|
||||||
Assert.False(fixture.Publication.Audio!.Engine.IsDisposalComplete);
|
Assert.False(fixture.Publication.Audio!.Engine.IsDisposalComplete);
|
||||||
|
|
@ -127,6 +129,8 @@ public sealed class ContentEffectsAudioCompositionTests
|
||||||
Assert.Throws<InvalidOperationException>(() => fixture.Phase().Compose(
|
Assert.Throws<InvalidOperationException>(() => fixture.Phase().Compose(
|
||||||
fixture.Platform,
|
fixture.Platform,
|
||||||
fixture.Host));
|
fixture.Host));
|
||||||
|
Assert.False(
|
||||||
|
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
Enum.GetValues<ContentEffectsAudioCompositionPoint>()
|
Enum.GetValues<ContentEffectsAudioCompositionPoint>()
|
||||||
|
|
@ -258,7 +262,7 @@ public sealed class ContentEffectsAudioCompositionTests
|
||||||
ResidencyBudgetOptions.Default,
|
ResidencyBudgetOptions.Default,
|
||||||
new PhysicsDataCache(),
|
new PhysicsDataCache(),
|
||||||
false,
|
false,
|
||||||
new RuntimeCharacterState(),
|
GameRuntimeTestFactory.Create(),
|
||||||
Router,
|
Router,
|
||||||
Poses,
|
Poses,
|
||||||
new DeferredEntityEffectAdvanceSource(),
|
new DeferredEntityEffectAdvanceSource(),
|
||||||
|
|
@ -293,7 +297,7 @@ public sealed class ContentEffectsAudioCompositionTests
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Publication.Dispose();
|
Publication.Dispose();
|
||||||
Dependencies.Character.Dispose();
|
Dependencies.Runtime.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using AcDream.App.World;
|
||||||
using AcDream.Core.Combat;
|
using AcDream.Core.Combat;
|
||||||
using AcDream.Core.Items;
|
using AcDream.Core.Items;
|
||||||
using AcDream.Core.Spells;
|
using AcDream.Core.Spells;
|
||||||
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Composition;
|
namespace AcDream.App.Tests.Composition;
|
||||||
|
|
@ -33,7 +34,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void EnabledUiPublishesOneExactResultAfterFrozenConstructionOrder()
|
public void EnabledUiPublishesOneExactResultAfterFrozenConstructionOrder()
|
||||||
{
|
{
|
||||||
var fixture = new Fixture(retailUi: true);
|
using var fixture = new Fixture(retailUi: true);
|
||||||
|
|
||||||
InteractionRetainedUiResult result = fixture.Compose();
|
InteractionRetainedUiResult result = fixture.Compose();
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DisabledUiAcquiresNoRetainedFrontendResource()
|
public void DisabledUiAcquiresNoRetainedFrontendResource()
|
||||||
{
|
{
|
||||||
var fixture = new Fixture(retailUi: false);
|
using var fixture = new Fixture(retailUi: false);
|
||||||
|
|
||||||
InteractionRetainedUiResult result = fixture.Compose();
|
InteractionRetainedUiResult result = fixture.Compose();
|
||||||
|
|
||||||
|
|
@ -81,11 +82,13 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
int pointValue)
|
int pointValue)
|
||||||
{
|
{
|
||||||
var point = (InteractionRetainedUiCompositionPoint)pointValue;
|
var point = (InteractionRetainedUiCompositionPoint)pointValue;
|
||||||
var fixture = new Fixture(retailUi: true, failurePoint: point);
|
using var fixture = new Fixture(retailUi: true, failurePoint: point);
|
||||||
|
|
||||||
Assert.Throws<InvalidOperationException>(fixture.Compose);
|
Assert.Throws<InvalidOperationException>(fixture.Compose);
|
||||||
|
|
||||||
Assert.Equal(point, fixture.Points[^1]);
|
Assert.Equal(point, fixture.Points[^1]);
|
||||||
|
Assert.False(
|
||||||
|
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||||
if (point == InteractionRetainedUiCompositionPoint.ResultPublished)
|
if (point == InteractionRetainedUiCompositionPoint.ResultPublished)
|
||||||
{
|
{
|
||||||
Assert.Empty(fixture.Factory.Releases);
|
Assert.Empty(fixture.Factory.Releases);
|
||||||
|
|
@ -129,9 +132,11 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PublicationFailureRollsBackCompleteUnpublishedPrefix()
|
public void PublicationFailureRollsBackCompleteUnpublishedPrefix()
|
||||||
{
|
{
|
||||||
var fixture = new Fixture(retailUi: true, publicationFailure: true);
|
using var fixture = new Fixture(retailUi: true, publicationFailure: true);
|
||||||
|
|
||||||
Assert.Throws<InvalidOperationException>(fixture.Compose);
|
Assert.Throws<InvalidOperationException>(fixture.Compose);
|
||||||
|
Assert.False(
|
||||||
|
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
[
|
[
|
||||||
|
|
@ -161,7 +166,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
Assert.DoesNotContain("private uint? PickWorldGuidAtCursor(", source);
|
Assert.DoesNotContain("private uint? PickWorldGuidAtCursor(", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class Fixture
|
private sealed class Fixture : IDisposable
|
||||||
{
|
{
|
||||||
private readonly InteractionRetainedUiCompositionPoint? _failurePoint;
|
private readonly InteractionRetainedUiCompositionPoint? _failurePoint;
|
||||||
|
|
||||||
|
|
@ -175,9 +180,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
Publication = new Publication(publicationFailure);
|
Publication = new Publication(publicationFailure);
|
||||||
RuntimeOptions options = RuntimeOptions.Parse("dat", static _ => null)
|
RuntimeOptions options = RuntimeOptions.Parse("dat", static _ => null)
|
||||||
with { RetailUi = retailUi };
|
with { RetailUi = retailUi };
|
||||||
var actions = new RuntimeActionState(
|
GameRuntime runtime = GameRuntimeTestFactory.Create(
|
||||||
new InventoryTransactionState(new ClientObjectTable()),
|
|
||||||
new Spellbook(),
|
|
||||||
new NoopCombatOperations(),
|
new NoopCombatOperations(),
|
||||||
new NoopCombatTargetOperations(),
|
new NoopCombatTargetOperations(),
|
||||||
new NoopCombatModeOperations(),
|
new NoopCombatModeOperations(),
|
||||||
|
|
@ -196,19 +199,15 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
RetainedInputCapture: null!,
|
RetainedInputCapture: null!,
|
||||||
InputDispatcher: null,
|
InputDispatcher: null,
|
||||||
Settings: null!,
|
Settings: null!,
|
||||||
Actions: actions,
|
Runtime: runtime,
|
||||||
CombatAttackOperations: new NoopCombatOperations(),
|
CombatAttackOperations: new NoopCombatOperations(),
|
||||||
CombatTargetOperations: new RuntimeCombatTargetOperationsSlot(),
|
CombatTargetOperations: new RuntimeCombatTargetOperationsSlot(),
|
||||||
SpellCastOperations: new RuntimeSpellCastOperationsSlot(),
|
SpellCastOperations: new RuntimeSpellCastOperationsSlot(),
|
||||||
Inventory: null!,
|
|
||||||
MagicCatalog: null!,
|
MagicCatalog: null!,
|
||||||
Character: null!,
|
|
||||||
Communication: null!,
|
|
||||||
StackSplitQuantity: null!,
|
StackSplitQuantity: null!,
|
||||||
UiRegistry: null,
|
UiRegistry: null,
|
||||||
CombatModeCommands: null!,
|
CombatModeCommands: null!,
|
||||||
PlayerIdentity: null!,
|
PlayerIdentity: null!,
|
||||||
PlayerController: null!,
|
|
||||||
PlayerMode: null!,
|
PlayerMode: null!,
|
||||||
SelectionCameraFactory: static _ => Stub<SelectionCameraSource>(),
|
SelectionCameraFactory: static _ => Stub<SelectionCameraSource>(),
|
||||||
FrameDiagnostics: new DeferredRenderFrameDiagnosticsSource(),
|
FrameDiagnostics: new DeferredRenderFrameDiagnosticsSource(),
|
||||||
|
|
@ -235,6 +234,8 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
if (_failurePoint == point)
|
if (_failurePoint == point)
|
||||||
throw new InvalidOperationException($"fault at {point}");
|
throw new InvalidOperationException($"fault at {point}");
|
||||||
}).Compose();
|
}).Compose();
|
||||||
|
|
||||||
|
public void Dispose() => Dependencies.Runtime.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class FakeFactory : IInteractionRetainedUiCompositionFactory
|
private sealed class FakeFactory : IInteractionRetainedUiCompositionFactory
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,10 @@ public sealed class SessionPlayerCompositionTests
|
||||||
window);
|
window);
|
||||||
Assert.DoesNotContain("IsSpawnClaimUnhydratable", window);
|
Assert.DoesNotContain("IsSpawnClaimUnhydratable", window);
|
||||||
Assert.Contains("LandblockStreamer.CreateForRequests(", phase);
|
Assert.Contains("LandblockStreamer.CreateForRequests(", phase);
|
||||||
Assert.Contains("new LiveSessionController()", phase);
|
Assert.Contains(
|
||||||
|
"LiveSessionController liveSession = d.Runtime.Session;",
|
||||||
|
phase);
|
||||||
|
Assert.DoesNotContain("new LiveSessionController()", phase);
|
||||||
Assert.Contains("new LocalPlayerTeleportController(", phase);
|
Assert.Contains("new LocalPlayerTeleportController(", phase);
|
||||||
Assert.Contains("new DatSpawnClaimHydrationClassifier(", phase);
|
Assert.Contains("new DatSpawnClaimHydrationClassifier(", phase);
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +112,7 @@ public sealed class SessionPlayerCompositionTests
|
||||||
"streamerLease.Resource.Start();",
|
"streamerLease.Resource.Start();",
|
||||||
"new StreamingController(",
|
"new StreamingController(",
|
||||||
"new WorldRevealCoordinator(",
|
"new WorldRevealCoordinator(",
|
||||||
"new LiveSessionController()",
|
"LiveSessionController liveSession = d.Runtime.Session;",
|
||||||
"new LiveEntityHydrationController(",
|
"new LiveEntityHydrationController(",
|
||||||
"new LiveEntityNetworkUpdateController(",
|
"new LiveEntityNetworkUpdateController(",
|
||||||
"new GameplayInputFrameController(",
|
"new GameplayInputFrameController(",
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ public sealed class SettingsDevToolsCompositionTests
|
||||||
SettingsDevToolsResult result = fixture.Compose();
|
SettingsDevToolsResult result = fixture.Compose();
|
||||||
|
|
||||||
Assert.Null(result.DevTools);
|
Assert.Null(result.DevTools);
|
||||||
|
Assert.False(
|
||||||
|
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||||
Assert.Equal(1, fixture.Startup.DisplayCalls);
|
Assert.Equal(1, fixture.Startup.DisplayCalls);
|
||||||
Assert.Equal(1, fixture.Startup.AudioCalls);
|
Assert.Equal(1, fixture.Startup.AudioCalls);
|
||||||
Assert.Equal(0, fixture.Factory.Calls);
|
Assert.Equal(0, fixture.Factory.Calls);
|
||||||
|
|
@ -229,9 +231,7 @@ public sealed class SettingsDevToolsCompositionTests
|
||||||
Settings,
|
Settings,
|
||||||
Startup,
|
Startup,
|
||||||
new HostQuiescenceGate(),
|
new HostQuiescenceGate(),
|
||||||
new RuntimeCommunicationState(),
|
GameRuntimeTestFactory.Create(),
|
||||||
new CombatState(),
|
|
||||||
new LocalPlayerState(new Spellbook()),
|
|
||||||
enabled
|
enabled
|
||||||
? new SettingsDevToolsOptionalDependencies(
|
? new SettingsDevToolsOptionalDependencies(
|
||||||
new Facts(),
|
new Facts(),
|
||||||
|
|
@ -273,6 +273,7 @@ public sealed class SettingsDevToolsCompositionTests
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Dependencies.Runtime.Dispose();
|
||||||
Publication.Owner?.Dispose();
|
Publication.Owner?.Dispose();
|
||||||
_dispatcher.Dispose();
|
_dispatcher.Dispose();
|
||||||
_profiler.Dispose();
|
_profiler.Dispose();
|
||||||
|
|
|
||||||
62
tests/AcDream.App.Tests/GameRuntimeTestFactory.cs
Normal file
62
tests/AcDream.App.Tests/GameRuntimeTestFactory.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
using AcDream.Core.Combat;
|
||||||
|
using AcDream.Core.Items;
|
||||||
|
using AcDream.Core.Spells;
|
||||||
|
using AcDream.Runtime;
|
||||||
|
using AcDream.Runtime.Gameplay;
|
||||||
|
using AcDream.Runtime.Session;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests;
|
||||||
|
|
||||||
|
internal static class GameRuntimeTestFactory
|
||||||
|
{
|
||||||
|
public static GameRuntime Create(
|
||||||
|
IRuntimeCombatAttackOperations? combatAttack = null,
|
||||||
|
IRuntimeCombatTargetOperations? combatTarget = null,
|
||||||
|
IRuntimeCombatModeOperations? combatMode = null,
|
||||||
|
IRuntimeSpellCastOperations? spellCast = null,
|
||||||
|
ILiveSessionOperations? session = null,
|
||||||
|
Func<double>? combatTime = null)
|
||||||
|
{
|
||||||
|
var fallback = new NoopOperations();
|
||||||
|
return new GameRuntime(new GameRuntimeDependencies(
|
||||||
|
combatAttack ?? fallback,
|
||||||
|
combatTarget ?? fallback,
|
||||||
|
combatMode ?? fallback,
|
||||||
|
spellCast ?? fallback,
|
||||||
|
SessionOperations: session,
|
||||||
|
CombatTime: combatTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class NoopOperations :
|
||||||
|
IRuntimeCombatAttackOperations,
|
||||||
|
IRuntimeCombatTargetOperations,
|
||||||
|
IRuntimeCombatModeOperations,
|
||||||
|
IRuntimeSpellCastOperations
|
||||||
|
{
|
||||||
|
public bool CanStartAttack() => false;
|
||||||
|
public void PrepareAttackRequest() { }
|
||||||
|
public bool SendAttack(AttackHeight height, float power) => false;
|
||||||
|
public void SendCancelAttack() { }
|
||||||
|
public bool IsDualWield => false;
|
||||||
|
public bool PlayerReadyForAttack => false;
|
||||||
|
public bool AutoRepeatAttack => false;
|
||||||
|
public bool AutoTarget => false;
|
||||||
|
public uint? SelectClosestTarget() => null;
|
||||||
|
public bool IsInWorld => false;
|
||||||
|
public IReadOnlyList<ClientObject> GetOrderedEquipment() => [];
|
||||||
|
public void NotifyExplicitCombatModeRequest() { }
|
||||||
|
public void SendChangeCombatMode(CombatMode mode) { }
|
||||||
|
public uint LocalPlayerId => 0u;
|
||||||
|
public bool CanSend => false;
|
||||||
|
public bool HasRequiredComponents(uint spellId) => false;
|
||||||
|
public bool IsTargetCompatible(
|
||||||
|
uint targetId,
|
||||||
|
SpellMetadata spell,
|
||||||
|
bool showMessage) => false;
|
||||||
|
public void StopCompletely() { }
|
||||||
|
public void SendUntargeted(uint spellId) { }
|
||||||
|
public void SendTargeted(uint targetId, uint spellId) { }
|
||||||
|
public void DisplayMessage(string message) { }
|
||||||
|
public void IncrementBusy() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,18 +13,22 @@ public sealed class GameWindowLiveSessionOwnershipTests
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic;
|
BindingFlags.Instance | BindingFlags.NonPublic;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowRetainsControllerAndFocusedHostButNoMirroredSession()
|
public void GameWindowRetainsCanonicalRuntimeAndFocusedHostButNoMirroredSession()
|
||||||
{
|
{
|
||||||
FieldInfo[] fields = typeof(GameWindow).GetFields(PrivateInstance);
|
FieldInfo[] fields = typeof(GameWindow).GetFields(PrivateInstance);
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
fields,
|
fields,
|
||||||
field => field.Name == "_liveSessionController"
|
field => field.Name == "_runtime"
|
||||||
&& field.FieldType == typeof(LiveSessionController));
|
&& field.FieldType == typeof(GameRuntime));
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
fields,
|
fields,
|
||||||
field => field.Name == "_liveSessionHost"
|
field => field.Name == "_liveSessionHost"
|
||||||
&& field.FieldType == typeof(LiveSessionHost));
|
&& field.FieldType == typeof(LiveSessionHost));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
fields,
|
||||||
|
field => field.Name == "_liveSessionController"
|
||||||
|
|| field.FieldType == typeof(LiveSessionController));
|
||||||
Assert.DoesNotContain(fields, field => field.Name == "_liveSession");
|
Assert.DoesNotContain(fields, field => field.Name == "_liveSession");
|
||||||
Assert.DoesNotContain(fields, field => field.FieldType == typeof(WorldSession));
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(WorldSession));
|
||||||
Assert.DoesNotContain(fields, field => field.FieldType == typeof(LiveSessionResetPlan));
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(LiveSessionResetPlan));
|
||||||
|
|
@ -54,6 +58,48 @@ public sealed class GameWindowLiveSessionOwnershipTests
|
||||||
|| field.FieldType == typeof(ulong));
|
|| field.FieldType == typeof(ulong));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProductionWindowConstructsOnlyTheCanonicalRuntimeRoot()
|
||||||
|
{
|
||||||
|
string root = FindRepositoryRoot();
|
||||||
|
string source = File.ReadAllText(Path.Combine(
|
||||||
|
root,
|
||||||
|
"src",
|
||||||
|
"AcDream.App",
|
||||||
|
"Rendering",
|
||||||
|
"GameWindow.cs"));
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
1,
|
||||||
|
CountOccurrences(source, "new GameRuntime("));
|
||||||
|
Assert.Contains(
|
||||||
|
"private readonly GameRuntime _runtime;",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"_runtimeHostLease = _runtime.AcquireHostLease(",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
string[] forbidden =
|
||||||
|
[
|
||||||
|
"new RuntimeEntityObjectLifetime(",
|
||||||
|
"new RuntimeInventoryState(",
|
||||||
|
"new RuntimeCharacterState(",
|
||||||
|
"new RuntimeCommunicationState(",
|
||||||
|
"new RuntimeActionState(",
|
||||||
|
"new RuntimeLocalPlayerMovementState(",
|
||||||
|
"new RuntimeWorldTransitState(",
|
||||||
|
"new LiveSessionController(",
|
||||||
|
"new GameRuntimeClock(",
|
||||||
|
];
|
||||||
|
Assert.All(
|
||||||
|
forbidden,
|
||||||
|
value => Assert.DoesNotContain(
|
||||||
|
value,
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("TryStartLiveSession")]
|
[InlineData("TryStartLiveSession")]
|
||||||
[InlineData("ClearInboundEntityState")]
|
[InlineData("ClearInboundEntityState")]
|
||||||
|
|
@ -66,4 +112,32 @@ public sealed class GameWindowLiveSessionOwnershipTests
|
||||||
{
|
{
|
||||||
Assert.Null(typeof(GameWindow).GetMethod(methodName, PrivateInstance));
|
Assert.Null(typeof(GameWindow).GetMethod(methodName, PrivateInstance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int CountOccurrences(string source, string value)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
int cursor = 0;
|
||||||
|
while ((cursor = source.IndexOf(
|
||||||
|
value,
|
||||||
|
cursor,
|
||||||
|
StringComparison.Ordinal)) >= 0)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
cursor += value.Length;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FindRepositoryRoot()
|
||||||
|
{
|
||||||
|
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
if (File.Exists(Path.Combine(current.FullName, "AcDream.slnx")))
|
||||||
|
return current.FullName;
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DirectoryNotFoundException("AcDream.slnx was not found.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
|
"Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
|
||||||
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
||||||
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
|
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
|
||||||
"Hard(\"live session\", () => DisposeLiveSession(ingress.LiveSession))",
|
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
||||||
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
||||||
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
||||||
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
||||||
|
|
@ -469,12 +469,11 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"new ResourceShutdownStage(\"frame borrowers\"",
|
"new ResourceShutdownStage(\"frame borrowers\"",
|
||||||
"new ResourceShutdownStage(\"session dependents\"",
|
"new ResourceShutdownStage(\"session dependents\"",
|
||||||
"new ResourceShutdownStage(\"live entities\"",
|
"new ResourceShutdownStage(\"live entities\"",
|
||||||
"new ResourceShutdownStage(\"runtime entity/object stream\"",
|
|
||||||
"new ResourceShutdownStage(\"effect dispatch edges\"",
|
"new ResourceShutdownStage(\"effect dispatch edges\"",
|
||||||
"new ResourceShutdownStage(\"live entity dependents\"",
|
"new ResourceShutdownStage(\"live entity dependents\"",
|
||||||
"new ResourceShutdownStage(\"submitted GPU work\"",
|
"new ResourceShutdownStage(\"submitted GPU work\"",
|
||||||
"new ResourceShutdownStage(\"render frontends\"",
|
"new ResourceShutdownStage(\"render frontends\"",
|
||||||
"new ResourceShutdownStage(\"runtime communication state\"",
|
"new ResourceShutdownStage(\"game runtime root\"",
|
||||||
"new ResourceShutdownStage(\"shared texture owners\"",
|
"new ResourceShutdownStage(\"shared texture owners\"",
|
||||||
"new ResourceShutdownStage(\"mesh adapter\"",
|
"new ResourceShutdownStage(\"mesh adapter\"",
|
||||||
"new ResourceShutdownStage(\"remaining render owners\"",
|
"new ResourceShutdownStage(\"remaining render owners\"",
|
||||||
|
|
@ -496,7 +495,7 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
||||||
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
|
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
|
||||||
"Hard(\"camera pointer\", () => ingress.CameraPointer?.Deactivate())",
|
"Hard(\"camera pointer\", () => ingress.CameraPointer?.Deactivate())",
|
||||||
"Hard(\"live session\", () => DisposeLiveSession(ingress.LiveSession))",
|
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
||||||
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
||||||
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
||||||
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
||||||
|
|
|
||||||
|
|
@ -762,43 +762,40 @@ public sealed class CurrentGameRuntimeAdapterTests
|
||||||
private readonly ItemInteractionController _items;
|
private readonly ItemInteractionController _items;
|
||||||
private readonly LiveSessionController _session;
|
private readonly LiveSessionController _session;
|
||||||
private readonly IDisposable _combatModeBinding;
|
private readonly IDisposable _combatModeBinding;
|
||||||
|
private readonly GameRuntime _gameRuntime;
|
||||||
|
|
||||||
public Harness()
|
public Harness()
|
||||||
{
|
{
|
||||||
Identity = new LocalPlayerIdentityState();
|
|
||||||
EntityObjects = new RuntimeEntityObjectLifetime();
|
|
||||||
InventoryState = new RuntimeInventoryState(EntityObjects);
|
|
||||||
Character = new RuntimeCharacterState();
|
|
||||||
Entities = new LiveEntityRuntime(
|
|
||||||
new GpuWorldState(),
|
|
||||||
new NoopEntityResources(),
|
|
||||||
EntityObjects);
|
|
||||||
Objects = EntityObjects.Objects;
|
|
||||||
Communication = new RuntimeCommunicationState();
|
|
||||||
CombatAttackOperations = new CombatAttackOperationsSlot();
|
CombatAttackOperations = new CombatAttackOperationsSlot();
|
||||||
CombatModeOperations = new RuntimeCombatModeOperationsSlot();
|
CombatModeOperations = new RuntimeCombatModeOperationsSlot();
|
||||||
SpellCastOperations = new RuntimeSpellCastOperationsSlot();
|
SpellCastOperations = new RuntimeSpellCastOperationsSlot();
|
||||||
Actions = new RuntimeActionState(
|
Options = LiveOptions();
|
||||||
InventoryState.Transactions,
|
Commands = new RecordingCommandRouting();
|
||||||
Character.Spellbook,
|
Transport = new TestTransport();
|
||||||
|
_gameRuntime = GameRuntimeTestFactory.Create(
|
||||||
CombatAttackOperations,
|
CombatAttackOperations,
|
||||||
new RuntimeCombatTargetOperationsSlot(),
|
new RuntimeCombatTargetOperationsSlot(),
|
||||||
CombatModeOperations,
|
CombatModeOperations,
|
||||||
SpellCastOperations,
|
SpellCastOperations,
|
||||||
now: static () => 0d);
|
new SessionOperations(Transport),
|
||||||
|
combatTime: static () => 0d);
|
||||||
|
_session = _gameRuntime.Session;
|
||||||
|
Identity = new LocalPlayerIdentityState(
|
||||||
|
_gameRuntime.PlayerIdentity);
|
||||||
|
Entities = new LiveEntityRuntime(
|
||||||
|
new GpuWorldState(),
|
||||||
|
new NoopEntityResources(),
|
||||||
|
EntityObjects);
|
||||||
CombatMode = new RecordingCombatModeOperations();
|
CombatMode = new RecordingCombatModeOperations();
|
||||||
_combatModeBinding =
|
_combatModeBinding =
|
||||||
CombatModeOperations.BindOwned(CombatMode);
|
CombatModeOperations.BindOwned(CombatMode);
|
||||||
MovementState = new RuntimeLocalPlayerMovementState();
|
|
||||||
Environment = new RuntimeWorldEnvironmentState();
|
|
||||||
MovementInput = new DispatcherMovementInputSource(MovementState);
|
MovementInput = new DispatcherMovementInputSource(MovementState);
|
||||||
GameplayInput = new GameplayInputFrameController(
|
GameplayInput = new GameplayInputFrameController(
|
||||||
dispatcher: null,
|
dispatcher: null,
|
||||||
MovementInput,
|
MovementInput,
|
||||||
mouseLook: null,
|
mouseLook: null,
|
||||||
new NoopCombatInput());
|
new NoopCombatInput());
|
||||||
Clock = new UpdateFrameClock();
|
Clock = new UpdateFrameClock(_gameRuntime.Clock);
|
||||||
WorldTransit = new RuntimeWorldTransitState();
|
|
||||||
WorldReveal = new WorldRevealCoordinator(
|
WorldReveal = new WorldRevealCoordinator(
|
||||||
WorldTransit,
|
WorldTransit,
|
||||||
static (_, _) => true,
|
static (_, _) => true,
|
||||||
|
|
@ -826,50 +823,42 @@ public sealed class CurrentGameRuntimeAdapterTests
|
||||||
new SelectionTransport(() => _session?.IsInWorld == true),
|
new SelectionTransport(() => _session?.IsInWorld == true),
|
||||||
new NoopInteractionMovement());
|
new NoopInteractionMovement());
|
||||||
|
|
||||||
Options = LiveOptions();
|
|
||||||
Commands = new RecordingCommandRouting();
|
|
||||||
Transport = new TestTransport();
|
|
||||||
_session = new LiveSessionController(
|
|
||||||
new SessionOperations(Transport));
|
|
||||||
Host = CreateHost(_session, Commands, Identity);
|
Host = CreateHost(_session, Commands, Identity);
|
||||||
Runtime = new CurrentGameRuntimeAdapter(
|
Runtime = new CurrentGameRuntimeAdapter(
|
||||||
_session,
|
_gameRuntime,
|
||||||
Host,
|
Host,
|
||||||
Commands,
|
Commands,
|
||||||
Identity,
|
|
||||||
EntityObjects,
|
|
||||||
InventoryState,
|
|
||||||
Character,
|
|
||||||
Communication,
|
|
||||||
Actions,
|
|
||||||
MovementState,
|
|
||||||
Environment,
|
|
||||||
WorldTransit,
|
|
||||||
Clock,
|
|
||||||
selectionController);
|
selectionController);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RuntimeOptions Options { get; }
|
public RuntimeOptions Options { get; }
|
||||||
public LocalPlayerIdentityState Identity { get; }
|
public LocalPlayerIdentityState Identity { get; }
|
||||||
public RuntimeEntityObjectLifetime EntityObjects { get; }
|
public RuntimeEntityObjectLifetime EntityObjects =>
|
||||||
public RuntimeInventoryState InventoryState { get; }
|
_gameRuntime.EntityObjects;
|
||||||
public RuntimeCharacterState Character { get; }
|
public RuntimeInventoryState InventoryState =>
|
||||||
|
_gameRuntime.InventoryOwner;
|
||||||
|
public RuntimeCharacterState Character =>
|
||||||
|
_gameRuntime.CharacterOwner;
|
||||||
public LiveEntityRuntime Entities { get; }
|
public LiveEntityRuntime Entities { get; }
|
||||||
public ClientObjectTable Objects { get; }
|
public ClientObjectTable Objects => EntityObjects.Objects;
|
||||||
public RuntimeCommunicationState Communication { get; }
|
public RuntimeCommunicationState Communication =>
|
||||||
|
_gameRuntime.CommunicationOwner;
|
||||||
public ChatLog Chat => Communication.Chat;
|
public ChatLog Chat => Communication.Chat;
|
||||||
public CombatAttackOperationsSlot CombatAttackOperations { get; }
|
public CombatAttackOperationsSlot CombatAttackOperations { get; }
|
||||||
public RuntimeCombatModeOperationsSlot CombatModeOperations { get; }
|
public RuntimeCombatModeOperationsSlot CombatModeOperations { get; }
|
||||||
public RuntimeSpellCastOperationsSlot SpellCastOperations { get; }
|
public RuntimeSpellCastOperationsSlot SpellCastOperations { get; }
|
||||||
public RuntimeActionState Actions { get; }
|
public RuntimeActionState Actions => _gameRuntime.ActionOwner;
|
||||||
public SelectionState Selection => Actions.Selection;
|
public SelectionState Selection => Actions.Selection;
|
||||||
public RuntimeLocalPlayerMovementState MovementState { get; }
|
public RuntimeLocalPlayerMovementState MovementState =>
|
||||||
public RuntimeWorldEnvironmentState Environment { get; }
|
_gameRuntime.MovementOwner;
|
||||||
|
public RuntimeWorldEnvironmentState Environment =>
|
||||||
|
_gameRuntime.EnvironmentOwner;
|
||||||
public DispatcherMovementInputSource MovementInput { get; }
|
public DispatcherMovementInputSource MovementInput { get; }
|
||||||
public GameplayInputFrameController GameplayInput { get; }
|
public GameplayInputFrameController GameplayInput { get; }
|
||||||
public RecordingCombatModeOperations CombatMode { get; }
|
public RecordingCombatModeOperations CombatMode { get; }
|
||||||
public UpdateFrameClock Clock { get; }
|
public UpdateFrameClock Clock { get; }
|
||||||
public RuntimeWorldTransitState WorldTransit { get; }
|
public RuntimeWorldTransitState WorldTransit =>
|
||||||
|
_gameRuntime.TransitOwner;
|
||||||
public WorldRevealCoordinator WorldReveal { get; }
|
public WorldRevealCoordinator WorldReveal { get; }
|
||||||
public RecordingCommandRouting Commands { get; }
|
public RecordingCommandRouting Commands { get; }
|
||||||
public TestTransport Transport { get; }
|
public TestTransport Transport { get; }
|
||||||
|
|
@ -879,15 +868,11 @@ public sealed class CurrentGameRuntimeAdapterTests
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Runtime.Dispose();
|
Runtime.Dispose();
|
||||||
Character.Dispose();
|
|
||||||
Communication.Dispose();
|
|
||||||
_session.Dispose();
|
|
||||||
_items.Dispose();
|
_items.Dispose();
|
||||||
_combatModeBinding.Dispose();
|
_combatModeBinding.Dispose();
|
||||||
Actions.Dispose();
|
|
||||||
InventoryState.Dispose();
|
|
||||||
Entities.Clear();
|
Entities.Clear();
|
||||||
MovementState.Dispose();
|
WorldReveal.ResetSession();
|
||||||
|
_gameRuntime.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ public sealed class RuntimeActionOwnershipTests
|
||||||
string program = ReadAppSource(root, "Program.cs");
|
string program = ReadAppSource(root, "Program.cs");
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"private readonly RuntimeActionState _runtimeActions;",
|
"private readonly GameRuntime _runtime;",
|
||||||
gameWindow,
|
gameWindow,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"_runtimeActions = new RuntimeActionState(",
|
"_runtime = new GameRuntime(new GameRuntimeDependencies(",
|
||||||
gameWindow,
|
gameWindow,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
|
|
@ -73,14 +73,9 @@ public sealed class RuntimeActionOwnershipTests
|
||||||
Assert.Empty(Regex.Matches(
|
Assert.Empty(Regex.Matches(
|
||||||
production,
|
production,
|
||||||
@"\bnew\s+(?:AcDream\.Runtime\.Gameplay\.)?RuntimeSpellCastState\s*\("));
|
@"\bnew\s+(?:AcDream\.Runtime\.Gameplay\.)?RuntimeSpellCastState\s*\("));
|
||||||
Assert.Equal(
|
Assert.Empty(Regex.Matches(
|
||||||
1,
|
production,
|
||||||
Regex.Matches(
|
@"\bnew\s+RuntimeActionState\s*\("));
|
||||||
production,
|
|
||||||
@"\bnew\s+RuntimeActionState\s*\(").Count
|
|
||||||
+ Regex.Matches(
|
|
||||||
production,
|
|
||||||
@"RuntimeActionState\s+\w+\s*=\s*new\s*\(\s*\)").Count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -138,14 +133,13 @@ public sealed class RuntimeActionOwnershipTests
|
||||||
itemInteraction,
|
itemInteraction,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"\"runtime action state\"",
|
"new ResourceShutdownStage(\"game runtime root\"",
|
||||||
shutdown,
|
shutdown,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
AssertAppearsInOrder(
|
Assert.DoesNotContain(
|
||||||
|
"RuntimeActionState Actions",
|
||||||
shutdown,
|
shutdown,
|
||||||
"\"runtime action state\"",
|
StringComparison.Ordinal);
|
||||||
"\"runtime inventory state\"",
|
|
||||||
"\"runtime entity/object lifetime\"");
|
|
||||||
Assert.False(File.Exists(Path.Combine(
|
Assert.False(File.Exists(Path.Combine(
|
||||||
root,
|
root,
|
||||||
"src",
|
"src",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,15 @@ public sealed class RuntimeCharacterOwnershipTests
|
||||||
string source = ReadSource("Rendering", "GameWindow.cs");
|
string source = ReadSource("Rendering", "GameWindow.cs");
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"_runtimeCharacter = new RuntimeCharacterState();",
|
"private readonly GameRuntime _runtime;",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"_runtime = new GameRuntime(new GameRuntimeDependencies(",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"new RuntimeCharacterState(",
|
||||||
source,
|
source,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
|
|
@ -76,7 +84,11 @@ public sealed class RuntimeCharacterOwnershipTests
|
||||||
inventory,
|
inventory,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"\"runtime character state\"",
|
"new ResourceShutdownStage(\"game runtime root\"",
|
||||||
|
shutdown,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"RuntimeCharacterState Character",
|
||||||
shutdown,
|
shutdown,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ public sealed class RuntimeInventoryOwnershipTests
|
||||||
string source = ReadSource("Rendering", "GameWindow.cs");
|
string source = ReadSource("Rendering", "GameWindow.cs");
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"_runtimeInventory = new RuntimeInventoryState(_runtimeEntityObjects);",
|
"private readonly GameRuntime _runtime;",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"new RuntimeInventoryState(",
|
||||||
source,
|
source,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
|
|
@ -77,11 +81,18 @@ public sealed class RuntimeInventoryOwnershipTests
|
||||||
"_domain.Inventory.DesiredComponents",
|
"_domain.Inventory.DesiredComponents",
|
||||||
session,
|
session,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
AssertAppearsInOrder(
|
Assert.Contains(
|
||||||
|
"new ResourceShutdownStage(\"game runtime root\"",
|
||||||
shutdown,
|
shutdown,
|
||||||
"\"runtime action state\"",
|
StringComparison.Ordinal);
|
||||||
"\"runtime inventory state\"",
|
Assert.Contains(
|
||||||
"\"runtime entity/object lifetime\"");
|
"Hard(\"game runtime\", () => DisposeGameRuntime(live.Runtime))",
|
||||||
|
shutdown,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"RuntimeInventoryState Inventory",
|
||||||
|
shutdown,
|
||||||
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,14 @@ public sealed class RuntimeMovementOwnershipTests
|
||||||
app,
|
app,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Empty(Regex.Matches(app, @"\bbool\s+_autoRunActive\b"));
|
Assert.Empty(Regex.Matches(app, @"\bbool\s+_autoRunActive\b"));
|
||||||
Assert.Single(Regex.Matches(
|
Assert.Empty(Regex.Matches(
|
||||||
app,
|
app,
|
||||||
@"RuntimeLocalPlayerMovementState\s+\w+\s*=\s*new\s*\(")
|
@"RuntimeLocalPlayerMovementState\s+\w+\s*=\s*new\s*\(")
|
||||||
.Cast<Match>());
|
.Cast<Match>());
|
||||||
|
Assert.Single(Regex.Matches(
|
||||||
|
runtime,
|
||||||
|
@"new\s+RuntimeLocalPlayerMovementState\s*\(")
|
||||||
|
.Cast<Match>());
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"ACDREAM_RUN_SKILL",
|
"ACDREAM_RUN_SKILL",
|
||||||
runtime,
|
runtime,
|
||||||
|
|
@ -59,10 +63,10 @@ public sealed class RuntimeMovementOwnershipTests
|
||||||
root,
|
root,
|
||||||
"Input",
|
"Input",
|
||||||
"DispatcherMovementInputSource.cs");
|
"DispatcherMovementInputSource.cs");
|
||||||
string view = ReadAppSource(
|
string adapter = ReadAppSource(
|
||||||
root,
|
root,
|
||||||
"Runtime",
|
"Runtime",
|
||||||
"CurrentGameRuntimeViewAdapter.cs");
|
"CurrentGameRuntimeAdapter.cs");
|
||||||
string commands = ReadAppSource(
|
string commands = ReadAppSource(
|
||||||
root,
|
root,
|
||||||
"Runtime",
|
"Runtime",
|
||||||
|
|
@ -73,7 +77,11 @@ public sealed class RuntimeMovementOwnershipTests
|
||||||
"GameWindowLifetime.cs");
|
"GameWindowLifetime.cs");
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"RuntimeLocalPlayerMovementState _playerControllerSlot = new();",
|
"RuntimeLocalPlayerMovementState _playerControllerSlot =>",
|
||||||
|
gameWindow,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"_runtime.MovementOwner;",
|
||||||
gameWindow,
|
gameWindow,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
|
|
@ -90,16 +98,16 @@ public sealed class RuntimeMovementOwnershipTests
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains("_movement.AutoRunActive", input, StringComparison.Ordinal);
|
Assert.Contains("_movement.AutoRunActive", input, StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"movement ?? throw new ArgumentNullException(nameof(movement))).View",
|
"private readonly GameRuntime _runtime;",
|
||||||
view,
|
adapter,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains("_movement.Execute(command)", commands, StringComparison.Ordinal);
|
Assert.Contains("_movement.Execute(command)", commands, StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"RuntimeLocalPlayerMovementState Movement",
|
"GameRuntime Runtime",
|
||||||
lifetime,
|
lifetime,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"\"runtime local movement state\"",
|
"\"game runtime root\"",
|
||||||
lifetime,
|
lifetime,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ public sealed class RuntimeWorldTransitOwnershipTests
|
||||||
"\n",
|
"\n",
|
||||||
Directory.EnumerateFiles(appRoot, "*.cs", SearchOption.AllDirectories)
|
Directory.EnumerateFiles(appRoot, "*.cs", SearchOption.AllDirectories)
|
||||||
.Select(File.ReadAllText));
|
.Select(File.ReadAllText));
|
||||||
|
string runtimeRoot = Path.Combine(root, "src", "AcDream.Runtime");
|
||||||
|
string runtime = string.Join(
|
||||||
|
"\n",
|
||||||
|
Directory.EnumerateFiles(runtimeRoot, "*.cs", SearchOption.AllDirectories)
|
||||||
|
.Select(File.ReadAllText));
|
||||||
|
|
||||||
Assert.False(File.Exists(Path.Combine(
|
Assert.False(File.Exists(Path.Combine(
|
||||||
appRoot,
|
appRoot,
|
||||||
|
|
@ -38,10 +43,14 @@ public sealed class RuntimeWorldTransitOwnershipTests
|
||||||
"record struct WorldRevealLifecycleSnapshot",
|
"record struct WorldRevealLifecycleSnapshot",
|
||||||
app,
|
app,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Single(Regex.Matches(
|
Assert.Empty(Regex.Matches(
|
||||||
app,
|
app,
|
||||||
@"new\s+RuntimeWorldTransitState\s*\(")
|
@"new\s+RuntimeWorldTransitState\s*\(")
|
||||||
.Cast<Match>());
|
.Cast<Match>());
|
||||||
|
Assert.Single(Regex.Matches(
|
||||||
|
runtime,
|
||||||
|
@"new\s+RuntimeWorldTransitState\s*\(")
|
||||||
|
.Cast<Match>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -56,19 +65,17 @@ public sealed class RuntimeWorldTransitOwnershipTests
|
||||||
typeof(RuntimeWorldTransitState),
|
typeof(RuntimeWorldTransitState),
|
||||||
availabilityFields[0].FieldType);
|
availabilityFields[0].FieldType);
|
||||||
|
|
||||||
var viewFields = typeof(CurrentGameRuntimeViewAdapter)
|
var adapterFields = typeof(CurrentGameRuntimeAdapter)
|
||||||
.GetFields(
|
.GetFields(
|
||||||
System.Reflection.BindingFlags.Instance
|
System.Reflection.BindingFlags.Instance
|
||||||
| System.Reflection.BindingFlags.NonPublic);
|
| System.Reflection.BindingFlags.NonPublic);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
viewFields,
|
adapterFields,
|
||||||
field => field.FieldType == typeof(IRuntimePortalView));
|
field => field.FieldType == typeof(GameRuntime));
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
adapterFields,
|
||||||
System.Reflection.BindingFlags.NonPublic),
|
field => field.FieldType == typeof(IRuntimePortalView)
|
||||||
type => type.Name.Contains(
|
|| field.FieldType == typeof(RuntimeWorldTransitState));
|
||||||
"PortalView",
|
|
||||||
StringComparison.Ordinal));
|
|
||||||
|
|
||||||
var coordinatorFields = typeof(WorldRevealCoordinator)
|
var coordinatorFields = typeof(WorldRevealCoordinator)
|
||||||
.GetFields(
|
.GetFields(
|
||||||
|
|
|
||||||
|
|
@ -363,7 +363,7 @@ public sealed class LandblockBuildOriginTests
|
||||||
Assert.True(sessionStage >= 0);
|
Assert.True(sessionStage >= 0);
|
||||||
|
|
||||||
int sessionOperation = source.IndexOf(
|
int sessionOperation = source.IndexOf(
|
||||||
"Hard(\"live session\", () => DisposeLiveSession(ingress.LiveSession))",
|
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
||||||
sessionStage,
|
sessionStage,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.True(sessionOperation > sessionStage);
|
Assert.True(sessionOperation > sessionStage);
|
||||||
|
|
@ -380,25 +380,30 @@ public sealed class LandblockBuildOriginTests
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.True(streamerDispose > dependentStage);
|
Assert.True(streamerDispose > dependentStage);
|
||||||
|
|
||||||
int helper = source.IndexOf(
|
string runtime = File.ReadAllText(Path.Combine(
|
||||||
"private static void DisposeLiveSession(LiveSessionController? controller)",
|
FindRepoRoot(),
|
||||||
|
"src",
|
||||||
|
"AcDream.Runtime",
|
||||||
|
"GameRuntime.cs"));
|
||||||
|
int helper = runtime.IndexOf(
|
||||||
|
"public void StopSession()",
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.True(helper > streamerDispose);
|
Assert.True(helper >= 0);
|
||||||
int sessionDispose = source.IndexOf(
|
int sessionDispose = runtime.IndexOf(
|
||||||
"controller.Dispose()",
|
"Session.Dispose();",
|
||||||
helper,
|
helper,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.True(sessionDispose > helper);
|
Assert.True(sessionDispose > helper);
|
||||||
|
|
||||||
int disposalCompletionBarrier = source.IndexOf(
|
int disposalCompletionBarrier = runtime.IndexOf(
|
||||||
"if (!controller.IsDisposalComplete)",
|
"if (!Session.IsDisposalComplete)",
|
||||||
sessionDispose,
|
sessionDispose,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.True(disposalCompletionBarrier > sessionDispose);
|
Assert.True(disposalCompletionBarrier > sessionDispose);
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"Live-session disposal was deferred by a reentrant lifecycle callback.",
|
"The Runtime session shutdown was deferred by a re-entrant callback.",
|
||||||
source[disposalCompletionBarrier..],
|
runtime[disposalCompletionBarrier..],
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -236,34 +236,18 @@ public sealed class RuntimeEntityOwnershipTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CurrentRuntimeAdapters_DoNotRetainEntityOrInventoryMirrors()
|
public void CurrentRuntimeAdapters_DoNotRetainEntityOrInventoryMirrors()
|
||||||
{
|
{
|
||||||
FieldInfo[] viewFields = typeof(CurrentGameRuntimeViewAdapter)
|
FieldInfo[] adapterFields = typeof(CurrentGameRuntimeAdapter)
|
||||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
|
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
viewFields,
|
adapterFields,
|
||||||
field => field.FieldType == typeof(IRuntimeEntityView));
|
field => field.FieldType == typeof(GameRuntime));
|
||||||
Assert.Contains(
|
|
||||||
viewFields,
|
|
||||||
field => field.FieldType == typeof(IRuntimeInventoryView));
|
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
viewFields,
|
adapterFields,
|
||||||
field => field.FieldType == typeof(LiveEntityRuntime)
|
|
||||||
|| field.FieldType == typeof(ClientObjectTable));
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
|
||||||
BindingFlags.NonPublic),
|
|
||||||
type => type.Name is "EntityView" or "InventoryView");
|
|
||||||
|
|
||||||
FieldInfo[] eventFields = typeof(CurrentGameRuntimeEventAdapter)
|
|
||||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
eventFields,
|
|
||||||
field => field.FieldType == typeof(LiveEntityRuntime)
|
field => field.FieldType == typeof(LiveEntityRuntime)
|
||||||
|| field.FieldType == typeof(ClientObjectTable)
|
|| field.FieldType == typeof(ClientObjectTable)
|
||||||
|| field.FieldType == typeof(RuntimeEventSequencer));
|
|| field.FieldType == typeof(IRuntimeEntityView)
|
||||||
Assert.Contains(
|
|| field.FieldType == typeof(IRuntimeInventoryView)
|
||||||
eventFields,
|
|| field.FieldType == typeof(RuntimeEntityObjectLifetime));
|
||||||
field => field.FieldType
|
|
||||||
== typeof(RuntimeEntityObjectLifetime));
|
|
||||||
|
|
||||||
string root = FindRepositoryRoot();
|
string root = FindRepositoryRoot();
|
||||||
string liveSource = File.ReadAllText(Path.Combine(
|
string liveSource = File.ReadAllText(Path.Combine(
|
||||||
|
|
@ -276,19 +260,34 @@ public sealed class RuntimeEntityOwnershipTests
|
||||||
Assert.DoesNotContain("_directory.TryApply", liveSource);
|
Assert.DoesNotContain("_directory.TryApply", liveSource);
|
||||||
Assert.DoesNotContain("_directory.RemoveActive", liveSource);
|
Assert.DoesNotContain("_directory.RemoveActive", liveSource);
|
||||||
|
|
||||||
string viewSource = File.ReadAllText(Path.Combine(
|
string appRuntimeRoot = Path.Combine(
|
||||||
root,
|
root,
|
||||||
"src",
|
"src",
|
||||||
"AcDream.App",
|
"AcDream.App",
|
||||||
"Runtime",
|
"Runtime");
|
||||||
"CurrentGameRuntimeViewAdapter.cs"));
|
Assert.False(File.Exists(Path.Combine(
|
||||||
|
appRuntimeRoot,
|
||||||
|
"CurrentGameRuntimeViewAdapter.cs")));
|
||||||
|
Assert.False(File.Exists(Path.Combine(
|
||||||
|
appRuntimeRoot,
|
||||||
|
"CurrentGameRuntimeEventAdapter.cs")));
|
||||||
|
|
||||||
|
string rootSource = File.ReadAllText(Path.Combine(
|
||||||
|
root,
|
||||||
|
"src",
|
||||||
|
"AcDream.Runtime",
|
||||||
|
"GameRuntime.cs"));
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"_entityView.MaterializedCount",
|
"public RuntimeEntityObjectLifetime EntityObjects",
|
||||||
viewSource,
|
rootSource,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.DoesNotContain(
|
Assert.Contains(
|
||||||
"_entities.MaterializedCount",
|
"public IRuntimeEntityView Entities => EntityObjects.EntityView;",
|
||||||
viewSource,
|
rootSource,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"public IRuntimeInventoryView Inventory => EntityObjects.InventoryView;",
|
||||||
|
rootSource,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue