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
|
|
@ -109,6 +109,8 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
Assert.Throws<InvalidOperationException>(() => fixture.Phase().Compose(
|
||||
fixture.Platform,
|
||||
fixture.Host));
|
||||
Assert.False(
|
||||
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||
|
||||
Assert.NotNull(fixture.Publication.Audio);
|
||||
Assert.False(fixture.Publication.Audio!.Engine.IsDisposalComplete);
|
||||
|
|
@ -127,6 +129,8 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
Assert.Throws<InvalidOperationException>(() => fixture.Phase().Compose(
|
||||
fixture.Platform,
|
||||
fixture.Host));
|
||||
Assert.False(
|
||||
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||
|
||||
Assert.Equal(
|
||||
Enum.GetValues<ContentEffectsAudioCompositionPoint>()
|
||||
|
|
@ -258,7 +262,7 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
ResidencyBudgetOptions.Default,
|
||||
new PhysicsDataCache(),
|
||||
false,
|
||||
new RuntimeCharacterState(),
|
||||
GameRuntimeTestFactory.Create(),
|
||||
Router,
|
||||
Poses,
|
||||
new DeferredEntityEffectAdvanceSource(),
|
||||
|
|
@ -293,7 +297,7 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
public void Dispose()
|
||||
{
|
||||
Publication.Dispose();
|
||||
Dependencies.Character.Dispose();
|
||||
Dependencies.Runtime.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using AcDream.App.World;
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Spells;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.App.Tests.Composition;
|
||||
|
|
@ -33,7 +34,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
[Fact]
|
||||
public void EnabledUiPublishesOneExactResultAfterFrozenConstructionOrder()
|
||||
{
|
||||
var fixture = new Fixture(retailUi: true);
|
||||
using var fixture = new Fixture(retailUi: true);
|
||||
|
||||
InteractionRetainedUiResult result = fixture.Compose();
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
[Fact]
|
||||
public void DisabledUiAcquiresNoRetainedFrontendResource()
|
||||
{
|
||||
var fixture = new Fixture(retailUi: false);
|
||||
using var fixture = new Fixture(retailUi: false);
|
||||
|
||||
InteractionRetainedUiResult result = fixture.Compose();
|
||||
|
||||
|
|
@ -81,11 +82,13 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
int 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.Equal(point, fixture.Points[^1]);
|
||||
Assert.False(
|
||||
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||
if (point == InteractionRetainedUiCompositionPoint.ResultPublished)
|
||||
{
|
||||
Assert.Empty(fixture.Factory.Releases);
|
||||
|
|
@ -129,9 +132,11 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
[Fact]
|
||||
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.False(
|
||||
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
|
|
@ -161,7 +166,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
Assert.DoesNotContain("private uint? PickWorldGuidAtCursor(", source);
|
||||
}
|
||||
|
||||
private sealed class Fixture
|
||||
private sealed class Fixture : IDisposable
|
||||
{
|
||||
private readonly InteractionRetainedUiCompositionPoint? _failurePoint;
|
||||
|
||||
|
|
@ -175,9 +180,7 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
Publication = new Publication(publicationFailure);
|
||||
RuntimeOptions options = RuntimeOptions.Parse("dat", static _ => null)
|
||||
with { RetailUi = retailUi };
|
||||
var actions = new RuntimeActionState(
|
||||
new InventoryTransactionState(new ClientObjectTable()),
|
||||
new Spellbook(),
|
||||
GameRuntime runtime = GameRuntimeTestFactory.Create(
|
||||
new NoopCombatOperations(),
|
||||
new NoopCombatTargetOperations(),
|
||||
new NoopCombatModeOperations(),
|
||||
|
|
@ -196,19 +199,15 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
RetainedInputCapture: null!,
|
||||
InputDispatcher: null,
|
||||
Settings: null!,
|
||||
Actions: actions,
|
||||
Runtime: runtime,
|
||||
CombatAttackOperations: new NoopCombatOperations(),
|
||||
CombatTargetOperations: new RuntimeCombatTargetOperationsSlot(),
|
||||
SpellCastOperations: new RuntimeSpellCastOperationsSlot(),
|
||||
Inventory: null!,
|
||||
MagicCatalog: null!,
|
||||
Character: null!,
|
||||
Communication: null!,
|
||||
StackSplitQuantity: null!,
|
||||
UiRegistry: null,
|
||||
CombatModeCommands: null!,
|
||||
PlayerIdentity: null!,
|
||||
PlayerController: null!,
|
||||
PlayerMode: null!,
|
||||
SelectionCameraFactory: static _ => Stub<SelectionCameraSource>(),
|
||||
FrameDiagnostics: new DeferredRenderFrameDiagnosticsSource(),
|
||||
|
|
@ -235,6 +234,8 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
if (_failurePoint == point)
|
||||
throw new InvalidOperationException($"fault at {point}");
|
||||
}).Compose();
|
||||
|
||||
public void Dispose() => Dependencies.Runtime.Dispose();
|
||||
}
|
||||
|
||||
private sealed class FakeFactory : IInteractionRetainedUiCompositionFactory
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@ public sealed class SessionPlayerCompositionTests
|
|||
window);
|
||||
Assert.DoesNotContain("IsSpawnClaimUnhydratable", window);
|
||||
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 DatSpawnClaimHydrationClassifier(", phase);
|
||||
}
|
||||
|
|
@ -109,7 +112,7 @@ public sealed class SessionPlayerCompositionTests
|
|||
"streamerLease.Resource.Start();",
|
||||
"new StreamingController(",
|
||||
"new WorldRevealCoordinator(",
|
||||
"new LiveSessionController()",
|
||||
"LiveSessionController liveSession = d.Runtime.Session;",
|
||||
"new LiveEntityHydrationController(",
|
||||
"new LiveEntityNetworkUpdateController(",
|
||||
"new GameplayInputFrameController(",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ public sealed class SettingsDevToolsCompositionTests
|
|||
SettingsDevToolsResult result = fixture.Compose();
|
||||
|
||||
Assert.Null(result.DevTools);
|
||||
Assert.False(
|
||||
fixture.Dependencies.Runtime.CaptureOwnership().IsDisposeRequested);
|
||||
Assert.Equal(1, fixture.Startup.DisplayCalls);
|
||||
Assert.Equal(1, fixture.Startup.AudioCalls);
|
||||
Assert.Equal(0, fixture.Factory.Calls);
|
||||
|
|
@ -229,9 +231,7 @@ public sealed class SettingsDevToolsCompositionTests
|
|||
Settings,
|
||||
Startup,
|
||||
new HostQuiescenceGate(),
|
||||
new RuntimeCommunicationState(),
|
||||
new CombatState(),
|
||||
new LocalPlayerState(new Spellbook()),
|
||||
GameRuntimeTestFactory.Create(),
|
||||
enabled
|
||||
? new SettingsDevToolsOptionalDependencies(
|
||||
new Facts(),
|
||||
|
|
@ -273,6 +273,7 @@ public sealed class SettingsDevToolsCompositionTests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
Dependencies.Runtime.Dispose();
|
||||
Publication.Owner?.Dispose();
|
||||
_dispatcher.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;
|
||||
|
||||
[Fact]
|
||||
public void GameWindowRetainsControllerAndFocusedHostButNoMirroredSession()
|
||||
public void GameWindowRetainsCanonicalRuntimeAndFocusedHostButNoMirroredSession()
|
||||
{
|
||||
FieldInfo[] fields = typeof(GameWindow).GetFields(PrivateInstance);
|
||||
|
||||
Assert.Contains(
|
||||
fields,
|
||||
field => field.Name == "_liveSessionController"
|
||||
&& field.FieldType == typeof(LiveSessionController));
|
||||
field => field.Name == "_runtime"
|
||||
&& field.FieldType == typeof(GameRuntime));
|
||||
Assert.Contains(
|
||||
fields,
|
||||
field => field.Name == "_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.FieldType == typeof(WorldSession));
|
||||
Assert.DoesNotContain(fields, field => field.FieldType == typeof(LiveSessionResetPlan));
|
||||
|
|
@ -54,6 +58,48 @@ public sealed class GameWindowLiveSessionOwnershipTests
|
|||
|| 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]
|
||||
[InlineData("TryStartLiveSession")]
|
||||
[InlineData("ClearInboundEntityState")]
|
||||
|
|
@ -66,4 +112,32 @@ public sealed class GameWindowLiveSessionOwnershipTests
|
|||
{
|
||||
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(\"retained gameplay\", () => ingress.RetainedGameplay?.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\"",
|
||||
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
||||
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
||||
|
|
@ -469,12 +469,11 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
"new ResourceShutdownStage(\"frame borrowers\"",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"new ResourceShutdownStage(\"live entities\"",
|
||||
"new ResourceShutdownStage(\"runtime entity/object stream\"",
|
||||
"new ResourceShutdownStage(\"effect dispatch edges\"",
|
||||
"new ResourceShutdownStage(\"live entity dependents\"",
|
||||
"new ResourceShutdownStage(\"submitted GPU work\"",
|
||||
"new ResourceShutdownStage(\"render frontends\"",
|
||||
"new ResourceShutdownStage(\"runtime communication state\"",
|
||||
"new ResourceShutdownStage(\"game runtime root\"",
|
||||
"new ResourceShutdownStage(\"shared texture owners\"",
|
||||
"new ResourceShutdownStage(\"mesh adapter\"",
|
||||
"new ResourceShutdownStage(\"remaining render owners\"",
|
||||
|
|
@ -496,7 +495,7 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
||||
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.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\"",
|
||||
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
||||
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
||||
|
|
|
|||
|
|
@ -762,43 +762,40 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
private readonly ItemInteractionController _items;
|
||||
private readonly LiveSessionController _session;
|
||||
private readonly IDisposable _combatModeBinding;
|
||||
private readonly GameRuntime _gameRuntime;
|
||||
|
||||
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();
|
||||
CombatModeOperations = new RuntimeCombatModeOperationsSlot();
|
||||
SpellCastOperations = new RuntimeSpellCastOperationsSlot();
|
||||
Actions = new RuntimeActionState(
|
||||
InventoryState.Transactions,
|
||||
Character.Spellbook,
|
||||
Options = LiveOptions();
|
||||
Commands = new RecordingCommandRouting();
|
||||
Transport = new TestTransport();
|
||||
_gameRuntime = GameRuntimeTestFactory.Create(
|
||||
CombatAttackOperations,
|
||||
new RuntimeCombatTargetOperationsSlot(),
|
||||
CombatModeOperations,
|
||||
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();
|
||||
_combatModeBinding =
|
||||
CombatModeOperations.BindOwned(CombatMode);
|
||||
MovementState = new RuntimeLocalPlayerMovementState();
|
||||
Environment = new RuntimeWorldEnvironmentState();
|
||||
MovementInput = new DispatcherMovementInputSource(MovementState);
|
||||
GameplayInput = new GameplayInputFrameController(
|
||||
dispatcher: null,
|
||||
MovementInput,
|
||||
mouseLook: null,
|
||||
new NoopCombatInput());
|
||||
Clock = new UpdateFrameClock();
|
||||
WorldTransit = new RuntimeWorldTransitState();
|
||||
Clock = new UpdateFrameClock(_gameRuntime.Clock);
|
||||
WorldReveal = new WorldRevealCoordinator(
|
||||
WorldTransit,
|
||||
static (_, _) => true,
|
||||
|
|
@ -826,50 +823,42 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
new SelectionTransport(() => _session?.IsInWorld == true),
|
||||
new NoopInteractionMovement());
|
||||
|
||||
Options = LiveOptions();
|
||||
Commands = new RecordingCommandRouting();
|
||||
Transport = new TestTransport();
|
||||
_session = new LiveSessionController(
|
||||
new SessionOperations(Transport));
|
||||
Host = CreateHost(_session, Commands, Identity);
|
||||
Runtime = new CurrentGameRuntimeAdapter(
|
||||
_session,
|
||||
_gameRuntime,
|
||||
Host,
|
||||
Commands,
|
||||
Identity,
|
||||
EntityObjects,
|
||||
InventoryState,
|
||||
Character,
|
||||
Communication,
|
||||
Actions,
|
||||
MovementState,
|
||||
Environment,
|
||||
WorldTransit,
|
||||
Clock,
|
||||
selectionController);
|
||||
}
|
||||
|
||||
public RuntimeOptions Options { get; }
|
||||
public LocalPlayerIdentityState Identity { get; }
|
||||
public RuntimeEntityObjectLifetime EntityObjects { get; }
|
||||
public RuntimeInventoryState InventoryState { get; }
|
||||
public RuntimeCharacterState Character { get; }
|
||||
public RuntimeEntityObjectLifetime EntityObjects =>
|
||||
_gameRuntime.EntityObjects;
|
||||
public RuntimeInventoryState InventoryState =>
|
||||
_gameRuntime.InventoryOwner;
|
||||
public RuntimeCharacterState Character =>
|
||||
_gameRuntime.CharacterOwner;
|
||||
public LiveEntityRuntime Entities { get; }
|
||||
public ClientObjectTable Objects { get; }
|
||||
public RuntimeCommunicationState Communication { get; }
|
||||
public ClientObjectTable Objects => EntityObjects.Objects;
|
||||
public RuntimeCommunicationState Communication =>
|
||||
_gameRuntime.CommunicationOwner;
|
||||
public ChatLog Chat => Communication.Chat;
|
||||
public CombatAttackOperationsSlot CombatAttackOperations { get; }
|
||||
public RuntimeCombatModeOperationsSlot CombatModeOperations { get; }
|
||||
public RuntimeSpellCastOperationsSlot SpellCastOperations { get; }
|
||||
public RuntimeActionState Actions { get; }
|
||||
public RuntimeActionState Actions => _gameRuntime.ActionOwner;
|
||||
public SelectionState Selection => Actions.Selection;
|
||||
public RuntimeLocalPlayerMovementState MovementState { get; }
|
||||
public RuntimeWorldEnvironmentState Environment { get; }
|
||||
public RuntimeLocalPlayerMovementState MovementState =>
|
||||
_gameRuntime.MovementOwner;
|
||||
public RuntimeWorldEnvironmentState Environment =>
|
||||
_gameRuntime.EnvironmentOwner;
|
||||
public DispatcherMovementInputSource MovementInput { get; }
|
||||
public GameplayInputFrameController GameplayInput { get; }
|
||||
public RecordingCombatModeOperations CombatMode { get; }
|
||||
public UpdateFrameClock Clock { get; }
|
||||
public RuntimeWorldTransitState WorldTransit { get; }
|
||||
public RuntimeWorldTransitState WorldTransit =>
|
||||
_gameRuntime.TransitOwner;
|
||||
public WorldRevealCoordinator WorldReveal { get; }
|
||||
public RecordingCommandRouting Commands { get; }
|
||||
public TestTransport Transport { get; }
|
||||
|
|
@ -879,15 +868,11 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
public void Dispose()
|
||||
{
|
||||
Runtime.Dispose();
|
||||
Character.Dispose();
|
||||
Communication.Dispose();
|
||||
_session.Dispose();
|
||||
_items.Dispose();
|
||||
_combatModeBinding.Dispose();
|
||||
Actions.Dispose();
|
||||
InventoryState.Dispose();
|
||||
Entities.Clear();
|
||||
MovementState.Dispose();
|
||||
WorldReveal.ResetSession();
|
||||
_gameRuntime.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ public sealed class RuntimeActionOwnershipTests
|
|||
string program = ReadAppSource(root, "Program.cs");
|
||||
|
||||
Assert.Contains(
|
||||
"private readonly RuntimeActionState _runtimeActions;",
|
||||
"private readonly GameRuntime _runtime;",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_runtimeActions = new RuntimeActionState(",
|
||||
"_runtime = new GameRuntime(new GameRuntimeDependencies(",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
|
|
@ -73,14 +73,9 @@ public sealed class RuntimeActionOwnershipTests
|
|||
Assert.Empty(Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+(?:AcDream\.Runtime\.Gameplay\.)?RuntimeSpellCastState\s*\("));
|
||||
Assert.Equal(
|
||||
1,
|
||||
Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+RuntimeActionState\s*\(").Count
|
||||
+ Regex.Matches(
|
||||
production,
|
||||
@"RuntimeActionState\s+\w+\s*=\s*new\s*\(\s*\)").Count);
|
||||
Assert.Empty(Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+RuntimeActionState\s*\("));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -138,14 +133,13 @@ public sealed class RuntimeActionOwnershipTests
|
|||
itemInteraction,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"\"runtime action state\"",
|
||||
"new ResourceShutdownStage(\"game runtime root\"",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
Assert.DoesNotContain(
|
||||
"RuntimeActionState Actions",
|
||||
shutdown,
|
||||
"\"runtime action state\"",
|
||||
"\"runtime inventory state\"",
|
||||
"\"runtime entity/object lifetime\"");
|
||||
StringComparison.Ordinal);
|
||||
Assert.False(File.Exists(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@ public sealed class RuntimeCharacterOwnershipTests
|
|||
string source = ReadSource("Rendering", "GameWindow.cs");
|
||||
|
||||
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,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
|
|
@ -76,7 +84,11 @@ public sealed class RuntimeCharacterOwnershipTests
|
|||
inventory,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"\"runtime character state\"",
|
||||
"new ResourceShutdownStage(\"game runtime root\"",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"RuntimeCharacterState Character",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ public sealed class RuntimeInventoryOwnershipTests
|
|||
string source = ReadSource("Rendering", "GameWindow.cs");
|
||||
|
||||
Assert.Contains(
|
||||
"_runtimeInventory = new RuntimeInventoryState(_runtimeEntityObjects);",
|
||||
"private readonly GameRuntime _runtime;",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"new RuntimeInventoryState(",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
|
|
@ -77,11 +81,18 @@ public sealed class RuntimeInventoryOwnershipTests
|
|||
"_domain.Inventory.DesiredComponents",
|
||||
session,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
Assert.Contains(
|
||||
"new ResourceShutdownStage(\"game runtime root\"",
|
||||
shutdown,
|
||||
"\"runtime action state\"",
|
||||
"\"runtime inventory state\"",
|
||||
"\"runtime entity/object lifetime\"");
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Hard(\"game runtime\", () => DisposeGameRuntime(live.Runtime))",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"RuntimeInventoryState Inventory",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -36,10 +36,14 @@ public sealed class RuntimeMovementOwnershipTests
|
|||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Empty(Regex.Matches(app, @"\bbool\s+_autoRunActive\b"));
|
||||
Assert.Single(Regex.Matches(
|
||||
Assert.Empty(Regex.Matches(
|
||||
app,
|
||||
@"RuntimeLocalPlayerMovementState\s+\w+\s*=\s*new\s*\(")
|
||||
.Cast<Match>());
|
||||
Assert.Single(Regex.Matches(
|
||||
runtime,
|
||||
@"new\s+RuntimeLocalPlayerMovementState\s*\(")
|
||||
.Cast<Match>());
|
||||
Assert.DoesNotContain(
|
||||
"ACDREAM_RUN_SKILL",
|
||||
runtime,
|
||||
|
|
@ -59,10 +63,10 @@ public sealed class RuntimeMovementOwnershipTests
|
|||
root,
|
||||
"Input",
|
||||
"DispatcherMovementInputSource.cs");
|
||||
string view = ReadAppSource(
|
||||
string adapter = ReadAppSource(
|
||||
root,
|
||||
"Runtime",
|
||||
"CurrentGameRuntimeViewAdapter.cs");
|
||||
"CurrentGameRuntimeAdapter.cs");
|
||||
string commands = ReadAppSource(
|
||||
root,
|
||||
"Runtime",
|
||||
|
|
@ -73,7 +77,11 @@ public sealed class RuntimeMovementOwnershipTests
|
|||
"GameWindowLifetime.cs");
|
||||
|
||||
Assert.Contains(
|
||||
"RuntimeLocalPlayerMovementState _playerControllerSlot = new();",
|
||||
"RuntimeLocalPlayerMovementState _playerControllerSlot =>",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_runtime.MovementOwner;",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
|
|
@ -90,16 +98,16 @@ public sealed class RuntimeMovementOwnershipTests
|
|||
StringComparison.Ordinal);
|
||||
Assert.Contains("_movement.AutoRunActive", input, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"movement ?? throw new ArgumentNullException(nameof(movement))).View",
|
||||
view,
|
||||
"private readonly GameRuntime _runtime;",
|
||||
adapter,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_movement.Execute(command)", commands, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"RuntimeLocalPlayerMovementState Movement",
|
||||
"GameRuntime Runtime",
|
||||
lifetime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"\"runtime local movement state\"",
|
||||
"\"game runtime root\"",
|
||||
lifetime,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ public sealed class RuntimeWorldTransitOwnershipTests
|
|||
"\n",
|
||||
Directory.EnumerateFiles(appRoot, "*.cs", SearchOption.AllDirectories)
|
||||
.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(
|
||||
appRoot,
|
||||
|
|
@ -38,10 +43,14 @@ public sealed class RuntimeWorldTransitOwnershipTests
|
|||
"record struct WorldRevealLifecycleSnapshot",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Single(Regex.Matches(
|
||||
Assert.Empty(Regex.Matches(
|
||||
app,
|
||||
@"new\s+RuntimeWorldTransitState\s*\(")
|
||||
.Cast<Match>());
|
||||
Assert.Single(Regex.Matches(
|
||||
runtime,
|
||||
@"new\s+RuntimeWorldTransitState\s*\(")
|
||||
.Cast<Match>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -56,19 +65,17 @@ public sealed class RuntimeWorldTransitOwnershipTests
|
|||
typeof(RuntimeWorldTransitState),
|
||||
availabilityFields[0].FieldType);
|
||||
|
||||
var viewFields = typeof(CurrentGameRuntimeViewAdapter)
|
||||
var adapterFields = typeof(CurrentGameRuntimeAdapter)
|
||||
.GetFields(
|
||||
System.Reflection.BindingFlags.Instance
|
||||
| System.Reflection.BindingFlags.NonPublic);
|
||||
Assert.Contains(
|
||||
viewFields,
|
||||
field => field.FieldType == typeof(IRuntimePortalView));
|
||||
adapterFields,
|
||||
field => field.FieldType == typeof(GameRuntime));
|
||||
Assert.DoesNotContain(
|
||||
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
||||
System.Reflection.BindingFlags.NonPublic),
|
||||
type => type.Name.Contains(
|
||||
"PortalView",
|
||||
StringComparison.Ordinal));
|
||||
adapterFields,
|
||||
field => field.FieldType == typeof(IRuntimePortalView)
|
||||
|| field.FieldType == typeof(RuntimeWorldTransitState));
|
||||
|
||||
var coordinatorFields = typeof(WorldRevealCoordinator)
|
||||
.GetFields(
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ public sealed class LandblockBuildOriginTests
|
|||
Assert.True(sessionStage >= 0);
|
||||
|
||||
int sessionOperation = source.IndexOf(
|
||||
"Hard(\"live session\", () => DisposeLiveSession(ingress.LiveSession))",
|
||||
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
||||
sessionStage,
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(sessionOperation > sessionStage);
|
||||
|
|
@ -380,25 +380,30 @@ public sealed class LandblockBuildOriginTests
|
|||
StringComparison.Ordinal);
|
||||
Assert.True(streamerDispose > dependentStage);
|
||||
|
||||
int helper = source.IndexOf(
|
||||
"private static void DisposeLiveSession(LiveSessionController? controller)",
|
||||
string runtime = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.Runtime",
|
||||
"GameRuntime.cs"));
|
||||
int helper = runtime.IndexOf(
|
||||
"public void StopSession()",
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(helper > streamerDispose);
|
||||
int sessionDispose = source.IndexOf(
|
||||
"controller.Dispose()",
|
||||
Assert.True(helper >= 0);
|
||||
int sessionDispose = runtime.IndexOf(
|
||||
"Session.Dispose();",
|
||||
helper,
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(sessionDispose > helper);
|
||||
|
||||
int disposalCompletionBarrier = source.IndexOf(
|
||||
"if (!controller.IsDisposalComplete)",
|
||||
int disposalCompletionBarrier = runtime.IndexOf(
|
||||
"if (!Session.IsDisposalComplete)",
|
||||
sessionDispose,
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(disposalCompletionBarrier > sessionDispose);
|
||||
|
||||
Assert.Contains(
|
||||
"Live-session disposal was deferred by a reentrant lifecycle callback.",
|
||||
source[disposalCompletionBarrier..],
|
||||
"The Runtime session shutdown was deferred by a re-entrant callback.",
|
||||
runtime[disposalCompletionBarrier..],
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,34 +236,18 @@ public sealed class RuntimeEntityOwnershipTests
|
|||
[Fact]
|
||||
public void CurrentRuntimeAdapters_DoNotRetainEntityOrInventoryMirrors()
|
||||
{
|
||||
FieldInfo[] viewFields = typeof(CurrentGameRuntimeViewAdapter)
|
||||
FieldInfo[] adapterFields = typeof(CurrentGameRuntimeAdapter)
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.Contains(
|
||||
viewFields,
|
||||
field => field.FieldType == typeof(IRuntimeEntityView));
|
||||
Assert.Contains(
|
||||
viewFields,
|
||||
field => field.FieldType == typeof(IRuntimeInventoryView));
|
||||
adapterFields,
|
||||
field => field.FieldType == typeof(GameRuntime));
|
||||
Assert.DoesNotContain(
|
||||
viewFields,
|
||||
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,
|
||||
adapterFields,
|
||||
field => field.FieldType == typeof(LiveEntityRuntime)
|
||||
|| field.FieldType == typeof(ClientObjectTable)
|
||||
|| field.FieldType == typeof(RuntimeEventSequencer));
|
||||
Assert.Contains(
|
||||
eventFields,
|
||||
field => field.FieldType
|
||||
== typeof(RuntimeEntityObjectLifetime));
|
||||
|| field.FieldType == typeof(IRuntimeEntityView)
|
||||
|| field.FieldType == typeof(IRuntimeInventoryView)
|
||||
|| field.FieldType == typeof(RuntimeEntityObjectLifetime));
|
||||
|
||||
string root = FindRepositoryRoot();
|
||||
string liveSource = File.ReadAllText(Path.Combine(
|
||||
|
|
@ -276,19 +260,34 @@ public sealed class RuntimeEntityOwnershipTests
|
|||
Assert.DoesNotContain("_directory.TryApply", liveSource);
|
||||
Assert.DoesNotContain("_directory.RemoveActive", liveSource);
|
||||
|
||||
string viewSource = File.ReadAllText(Path.Combine(
|
||||
string appRuntimeRoot = Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Runtime",
|
||||
"CurrentGameRuntimeViewAdapter.cs"));
|
||||
"Runtime");
|
||||
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(
|
||||
"_entityView.MaterializedCount",
|
||||
viewSource,
|
||||
"public RuntimeEntityObjectLifetime EntityObjects",
|
||||
rootSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"_entities.MaterializedCount",
|
||||
viewSource,
|
||||
Assert.Contains(
|
||||
"public IRuntimeEntityView Entities => EntityObjects.EntityView;",
|
||||
rootSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"public IRuntimeInventoryView Inventory => EntityObjects.InventoryView;",
|
||||
rootSource,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue