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
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue