test: replace campaign labels with behavior names
This commit is contained in:
parent
e6fab96f12
commit
9c6b143a03
30 changed files with 135 additions and 97 deletions
|
|
@ -1,738 +0,0 @@
|
|||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary source-shape freeze for the Slice 8 cutover. Each later checkpoint
|
||||
/// replaces the corresponding assertion with functional tests on the extracted
|
||||
/// owner; this file prevents a mechanical move from silently reordering host
|
||||
/// behavior before that owner exists.
|
||||
/// </summary>
|
||||
public sealed class GameWindowSlice8BoundaryTests
|
||||
{
|
||||
[Fact]
|
||||
public void Run_PreservesNativeAttributeAndCallbackOrder()
|
||||
{
|
||||
string body = MethodBody("public void Run()", "private void OnLoad()");
|
||||
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
|
||||
"_displayFramePacing.InitializeStartup(startup.Display.VSync)",
|
||||
"VSync = startupPacing.UseVSync",
|
||||
// Campaign V slice V11: the raw-GL "Samples = ..." window option
|
||||
// is gone — Vulkan takes MSAA as an RHI attachment property, not
|
||||
// a window attribute. _startupQuality carries it forward instead.
|
||||
"_startupQuality = startup.Quality;",
|
||||
"Window.Create(options)",
|
||||
"_displayFramePacing.BindSurface(",
|
||||
"_windowCallbacks = SilkWindowCallbackBinding.Create(",
|
||||
"new WindowCallbackTargets(",
|
||||
"OnLoad,",
|
||||
"OnUpdate,",
|
||||
"OnRender,",
|
||||
"OnClosing,",
|
||||
"OnFocusChanged,",
|
||||
"OnFramebufferResize),",
|
||||
"_displayFramePacing,",
|
||||
"_hostQuiescence);",
|
||||
"_windowCallbacks.Attach();",
|
||||
"_window.Run();");
|
||||
|
||||
Assert.Equal(1, CountOccurrences(body, "SilkWindowCallbackBinding.Create("));
|
||||
Assert.Equal(1, CountOccurrences(body, "_windowCallbacks.Attach();"));
|
||||
Assert.DoesNotContain("_window.Load +=", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_window.Update +=", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_window.Render +=", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_window.Closing +=", body, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionStart_FollowsFrameGraphPublication()
|
||||
{
|
||||
string body = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private void OnUpdate(double dt)");
|
||||
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"GameWindowPlatformResult<GameWindowGraphics, IInputContext> platform = AcquirePlatform();",
|
||||
"GameWindowCompositionPipeline.Run<",
|
||||
"new HostInputCameraCompositionPhase(",
|
||||
"this).Compose(platformResult),",
|
||||
"new ContentEffectsAudioCompositionPhase(",
|
||||
"this).Compose(platformResult, hostInputCamera),",
|
||||
"new SettingsDevToolsCompositionPhase(",
|
||||
".Compose(platformResult, hostInputCamera, contentEffectsAudio),",
|
||||
"new InteractionRetainedUiCompositionPhase(",
|
||||
"this).Compose(",
|
||||
"new LivePresentationCompositionPhase(",
|
||||
"this).Compose(",
|
||||
"new SessionPlayerCompositionPhase(",
|
||||
"new FrameRootCompositionPhase(",
|
||||
"new SessionStartCompositionPhase(",
|
||||
".Start(frameRoots));");
|
||||
|
||||
string framePhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"FrameRootComposition.cs"));
|
||||
AssertAppearsInOrder(
|
||||
framePhase,
|
||||
"new RenderFrameOrchestrator(",
|
||||
"new UpdateFrameOrchestrator(",
|
||||
"d.FrameGraphs.PublishOwned(",
|
||||
"_publication.PublishFrameRoots(result);");
|
||||
|
||||
string sessionPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
AssertAppearsInOrder(
|
||||
sessionPhase,
|
||||
"LiveSessionHost sessionHost = sessionRuntimeFactory.Create(",
|
||||
"d.CombatModeCommands.BindOwned(combatCommand)",
|
||||
"d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics)",
|
||||
"GameplayInputActionRouter.Create(",
|
||||
"gameplayActions.Attach();",
|
||||
"_publication.PublishSessionPlayer(result);");
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
body,
|
||||
".Start(frameRoots));"));
|
||||
string phaseOne = Slice(
|
||||
File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"HostInputCameraComposition.cs")),
|
||||
"private HostInputCameraResult ComposeCore(",
|
||||
"private void Fault(");
|
||||
Assert.Contains("if (firstKeyboard is not null)", phaseOne, StringComparison.Ordinal);
|
||||
Assert.Contains("if (firstMouse is not null)", phaseOne, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"if (keyboard is not null && mouse is not null)",
|
||||
phaseOne,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"if (firstKeyboard is not null && firstMouse is not null)",
|
||||
phaseOne,
|
||||
StringComparison.Ordinal);
|
||||
string startPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionStartComposition.cs"));
|
||||
AssertAppearsInOrder(
|
||||
startPhase,
|
||||
"frame.GameRuntime.Session.Start(frame.GameRuntime.Generation)",
|
||||
"switch (result.Status)",
|
||||
"case RuntimeSessionStartStatus.MissingCredentials:",
|
||||
"case RuntimeSessionStartStatus.Failed:");
|
||||
int start = body.IndexOf(
|
||||
".Start(frameRoots));",
|
||||
StringComparison.Ordinal);
|
||||
string postStart = body[(start + ".Start(frameRoots));".Length)..];
|
||||
Assert.Empty(System.Text.RegularExpressions.Regex.Matches(
|
||||
postStart,
|
||||
@"(?m)^\s+_[A-Za-z]\w*\s*(?:\?\?=|=)"));
|
||||
Assert.DoesNotContain("new ", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Mount(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Bind(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Attach(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".Compose(", postStart, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("PrepareResources(", postStart, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorldEnvironment_IsOwnedAndGameWindowOnlyComposesItsTypedEdges()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private void OnUpdate(double dt)");
|
||||
string sessionFactorySource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Net",
|
||||
"LiveSessionRuntimeFactory.cs"));
|
||||
string sessionFactory = Slice(
|
||||
sessionFactorySource,
|
||||
"private ILiveSessionEventRouting CreateEventRouter(",
|
||||
"private LiveInventorySessionBindings CreateInventoryBindings()");
|
||||
string worldPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"WorldRenderComposition.cs"));
|
||||
|
||||
Assert.Contains(
|
||||
"private readonly AcDream.App.World.WorldEnvironmentController _worldEnvironment;",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"new WorldRenderDependencies(\n _worldEnvironment,",
|
||||
load.Replace("\r\n", "\n", StringComparison.Ordinal),
|
||||
StringComparison.Ordinal);
|
||||
string worldCompose = Slice(
|
||||
worldPhase,
|
||||
"public WorldRenderResult Compose(",
|
||||
"private (BitmapFont? Font, TextRenderer? Text) ComposeOptionalHudResources(");
|
||||
AssertAppearsInOrder(
|
||||
worldCompose,
|
||||
"WorldRegionData region = _factory.LoadRegion(content.Dats);",
|
||||
"_factory.InitializeEnvironment(_dependencies.Environment, region.Region);");
|
||||
Assert.Contains("environment.Initialize(region);", worldPhase, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
sessionFactory,
|
||||
"new LiveEnvironmentSessionSink(",
|
||||
"_world.Environment.ApplyAdminEnvirons,",
|
||||
"_world.Environment.SynchronizeFromServer)");
|
||||
Assert.DoesNotContain("_loadedSkyDesc", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_loadedSkyDayIndex", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void RefreshSkyForCurrentDay()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void OnEnvironChanged(", source, StringComparison.Ordinal);
|
||||
|
||||
Assert.DoesNotContain("private void CycleTimeOfDay()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void CycleWeather()", source, StringComparison.Ordinal);
|
||||
string sessionPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
AssertAppearsInOrder(
|
||||
sessionPhase,
|
||||
"new RuntimeDiagnosticCommandController(",
|
||||
"d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputAction_IsOneTypedOwnerHandoff()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private void OnUpdate(double dt)");
|
||||
string sessionPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
string shutdown = GameWindowLifetimeSource();
|
||||
|
||||
Assert.DoesNotContain("private void OnInputAction(", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void SetInputCombatScope(", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void ToggleLiveCombatMode()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void OnUiDragReleasedOutside(", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("DragReleasedOutsideUi +=", source, StringComparison.Ordinal);
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
sessionPhase,
|
||||
"GameplayInputActionRouter.Create("));
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
sessionPhase,
|
||||
"gameplayActions.Attach();"));
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
LivePresentationSource(),
|
||||
"RetainedUiGameplayBinding.Create("));
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
LivePresentationSource(),
|
||||
"retainedGameplayLease.Resource.Attach();"));
|
||||
AssertAppearsInOrder(
|
||||
shutdown,
|
||||
"Hard(\"combat command slot\", ingress.CombatCommands.Deactivate)",
|
||||
"Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
|
||||
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
||||
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
|
||||
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
||||
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
||||
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
||||
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
||||
"Soft(\"camera pointer\", () => DisposeCameraPointer(ingress.CameraPointer))",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"Hard(\"mouse capture\", () => live.CameraPointer?.ReleaseMouseLookAfterSessionRetirement())");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FramebufferResize_IsOneTypedOwnerHandoff()
|
||||
{
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private void OnUpdate(double dt)");
|
||||
string body = MethodBody(
|
||||
"private void OnFramebufferResize(",
|
||||
"private void OnClosing()");
|
||||
|
||||
Assert.Contains("=> _framebufferResize.Resize(newSize);", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Viewport(", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("SetAspect(", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ResetLayout(", body, StringComparison.Ordinal);
|
||||
string phaseOne = Slice(
|
||||
File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"HostInputCameraComposition.cs")),
|
||||
"private HostInputCameraResult ComposeCore(",
|
||||
"private void Fault(");
|
||||
AssertAppearsInOrder(
|
||||
phaseOne,
|
||||
"_dependencies.FramebufferResize.BindViewport(",
|
||||
"_publication.PublishCameraController(camera)",
|
||||
"_dependencies.FramebufferResize.BindCamera(",
|
||||
"_dependencies.FramebufferResize.Resize(");
|
||||
Assert.DoesNotContain(
|
||||
"_viewportAspect.Update(_window",
|
||||
load,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeSettings_IsOneTwoPhaseOwnerWithoutWindowStateMirrors()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string run = MethodBody("public void Run()", "private void OnLoad()");
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private void OnUpdate(double dt)");
|
||||
string shutdown = GameWindowLifetimeSource();
|
||||
|
||||
Assert.Contains(
|
||||
"private readonly RuntimeSettingsController _runtimeSettings;",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_runtimeSettings = new RuntimeSettingsController(",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("new SettingsStore(", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("new AcDream.UI.Abstractions.Panels.Settings.SettingsStore(", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".LoadDisplay()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".LoadAudio()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".LoadGameplay()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".LoadChat()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".LoadCharacter(", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_persistedDisplay", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_persistedAudio", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_persistedGameplay", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_persistedChat", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_persistedCharacter", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_activeToonKey", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_settingsStore", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_settingsVm", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("LoadAndApplyPersistedSettings", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void ApplyDisplayWindowState", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("public void ReapplyQualityPreset", source, StringComparison.Ordinal);
|
||||
|
||||
AssertAppearsInOrder(
|
||||
run,
|
||||
"RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
|
||||
"_displayFramePacing.InitializeStartup(startup.Display.VSync)",
|
||||
// Campaign V slice V11: Vulkan needs a client-API-less window and
|
||||
// takes neither MSAA nor the stencil bit count as a window
|
||||
// attribute (both are RHI attachment properties instead), so the
|
||||
// raw-GL "Samples = ..." window option this used to assert is
|
||||
// gone. _startupQuality carries MsaaSamples forward instead, into
|
||||
// CreateGraphics' VulkanGraphicsContext.Acquire call.
|
||||
"_startupQuality = startup.Quality;",
|
||||
"Window.Create(options)");
|
||||
AssertAppearsInOrder(
|
||||
load,
|
||||
"GameWindowCompositionPipeline.Run<",
|
||||
"new SettingsDevToolsCompositionPhase(",
|
||||
".Compose(platformResult, hostInputCamera, contentEffectsAudio),",
|
||||
"new WorldRenderCompositionPhase(",
|
||||
"new SessionPlayerCompositionPhase(",
|
||||
"new FrameRootCompositionPhase(",
|
||||
"new SessionStartCompositionPhase(",
|
||||
".Start(frameRoots));");
|
||||
string sessionPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
Assert.Contains(
|
||||
"d.Settings.BindRuntimeTargetsOwned(settingsTargets)",
|
||||
sessionPhase,
|
||||
StringComparison.Ordinal);
|
||||
string settingsPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SettingsDevToolsComposition.cs"));
|
||||
Assert.Contains(
|
||||
"_dependencies.Settings.ApplyStartup(_dependencies.StartupTarget);",
|
||||
settingsPhase,
|
||||
StringComparison.Ordinal);
|
||||
string worldPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"WorldRenderComposition.cs"));
|
||||
AssertAppearsInOrder(
|
||||
worldPhase,
|
||||
"TerrainAtlas.BuildBackendNeutral(device, dats)",
|
||||
"settings.ResolvedQuality.AnisotropicLevel",
|
||||
"_factory.CreateBackendNeutralTerrain(");
|
||||
AssertAppearsInOrder(
|
||||
shutdown,
|
||||
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
||||
"new ResourceShutdownStage(\"frame borrowers\"",
|
||||
"Hard(\"frame-root bindings\", () => frame.FrameBindings?.Dispose())",
|
||||
"Hard(\"retail UI\", () => DisposeRetailUi(live.RetailUi))",
|
||||
"Hard(\"streamer\", () => live.Streamer?.Dispose())",
|
||||
"Hard(\"mesh draw dispatcher\", () => render.DrawDispatcher?.Dispose())",
|
||||
"Hard(\"terrain\", () => render.Terrain?.Dispose())");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateRenderFocusAndCloseRemainNarrowHostEdges()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string update = Slice(
|
||||
source,
|
||||
"private void OnUpdate(double dt)",
|
||||
"private void OnRender(double deltaSeconds)");
|
||||
string render = Slice(
|
||||
source,
|
||||
"private void OnRender(double deltaSeconds)",
|
||||
"private void OnFramebufferResize(");
|
||||
string focus = Slice(
|
||||
source,
|
||||
"private void OnFocusChanged(bool focused)",
|
||||
"public void Dispose()");
|
||||
string close = Slice(
|
||||
source,
|
||||
"private void OnClosing()",
|
||||
"private void OnFocusChanged(bool focused)");
|
||||
|
||||
Assert.Equal(1, CountOccurrences(update, "_frameGraphs.Tick("));
|
||||
Assert.Equal(1, CountOccurrences(render, "_frameGraphs.Render("));
|
||||
AssertAppearsInOrder(
|
||||
render,
|
||||
"Vector2D<int> size = _window!.Size;",
|
||||
"_frameGraphs.Render(",
|
||||
"new AcDream.App.Rendering.RenderFrameInput(");
|
||||
Assert.DoesNotContain("FramebufferSize", render, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"=> _cameraPointerInput?.HandleFocusChanged(focused);",
|
||||
focus,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"private void OnClosing() => CompleteShutdown(releaseNativeWindow: false);",
|
||||
close,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
close,
|
||||
"private void CompleteShutdown(bool releaseNativeWindow)",
|
||||
"if (!_lifetime.HasShutdownRoots)",
|
||||
"_lifetime.PublishShutdownRoots(CaptureShutdownRoots());",
|
||||
"? _lifetime.CompleteAndReleaseNativeWindow()",
|
||||
": _lifetime.TryComplete();");
|
||||
Assert.DoesNotContain("new ResourceShutdownStage(", close, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("CompleteOrThrow", close, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shutdown_PreservesDependencyStagesAndNativeWindowLast()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string program = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Program.cs"));
|
||||
string lifetime = GameWindowLifetimeSource();
|
||||
string manifest = Slice(
|
||||
lifetime,
|
||||
"internal static class GameWindowShutdownManifest",
|
||||
"private static ResourceShutdownOperation Hard");
|
||||
string nativeRelease = Slice(
|
||||
lifetime,
|
||||
"public GameWindowLifetimeReport CompleteAndReleaseNativeWindow()",
|
||||
"private void EnsureTransaction()");
|
||||
int disposeStart = source.IndexOf("public void Dispose()", StringComparison.Ordinal);
|
||||
Assert.True(disposeStart >= 0);
|
||||
string dispose = source[disposeStart..];
|
||||
|
||||
string[] stages =
|
||||
[
|
||||
"new ResourceShutdownStage(\"host and session barriers\"",
|
||||
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
||||
"new ResourceShutdownStage(\"plugin host\"",
|
||||
"new ResourceShutdownStage(\"frame borrowers\"",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"new ResourceShutdownStage(\"live entities\"",
|
||||
"new ResourceShutdownStage(\"effect dispatch edges\"",
|
||||
"new ResourceShutdownStage(\"live entity dependents\"",
|
||||
"new ResourceShutdownStage(\"submitted GPU work\"",
|
||||
"new ResourceShutdownStage(\"render frontends\"",
|
||||
"new ResourceShutdownStage(\"game runtime root\"",
|
||||
"new ResourceShutdownStage(\"shared texture owners\"",
|
||||
"new ResourceShutdownStage(\"mesh adapter\"",
|
||||
"new ResourceShutdownStage(\"remaining render owners\"",
|
||||
"new ResourceShutdownStage(\"dedicated render resources\"",
|
||||
"new ResourceShutdownStage(\"failed render construction cleanup\"",
|
||||
"new ResourceShutdownStage(\"frame flight owner\"",
|
||||
"new ResourceShutdownStage(\"content mappings\"",
|
||||
"new ResourceShutdownStage(\"input context\"",
|
||||
"new ResourceShutdownStage(\"graphics API context\"",
|
||||
];
|
||||
AssertAppearsInOrder(manifest, stages);
|
||||
Assert.Equal(stages.Length, CountOccurrences(manifest, "new ResourceShutdownStage("));
|
||||
foreach (string stage in stages)
|
||||
Assert.Equal(1, CountOccurrences(manifest, stage));
|
||||
AssertAppearsInOrder(
|
||||
manifest,
|
||||
"Hard(\"combat command slot\", ingress.CombatCommands.Deactivate)",
|
||||
"Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
|
||||
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
|
||||
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
|
||||
"Hard(\"camera pointer\", () => ingress.CameraPointer?.Deactivate())",
|
||||
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
||||
"new ResourceShutdownStage(\"physical ingress cleanup\"",
|
||||
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
|
||||
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
|
||||
"Soft(\"camera pointer\", () => DisposeCameraPointer(ingress.CameraPointer))",
|
||||
"Soft(\"native window callbacks\", () => DisposeWindowCallbacks(ingress.WindowCallbacks))",
|
||||
"new ResourceShutdownStage(\"plugin host\"",
|
||||
"Hard(\"plugins\", () => ingress.Plugins?.Dispose())",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"Hard(\"mouse capture\", () => live.CameraPointer?.ReleaseMouseLookAfterSessionRetirement())");
|
||||
Assert.Contains(
|
||||
"new(name, action, ResourceShutdownOperationPolicy.ReportAndContinue)",
|
||||
lifetime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"UiHost? RetainedUiHost,",
|
||||
lifetime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"IDisposable? Plugins,",
|
||||
lifetime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_uiHost,", source, StringComparison.Ordinal);
|
||||
Assert.Contains("_pluginSession,", source, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
program,
|
||||
"GraphicalPluginSession pluginSession = GraphicalPluginSession.Create(",
|
||||
"window.StartPluginHosting(pluginSession);",
|
||||
"window.Run();");
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_pluginSession = pluginSession;",
|
||||
"pluginSession.Start();",
|
||||
"public void Run()");
|
||||
AssertAppearsInOrder(
|
||||
manifest,
|
||||
"Hard(\"plugins\", () => ingress.Plugins?.Dispose())",
|
||||
"Hard(\"retail UI\", () => DisposeRetailUi(live.RetailUi))",
|
||||
"Hard(\"game runtime\", () => DisposeGameRuntime(live.Runtime))");
|
||||
AssertAppearsInOrder(
|
||||
nativeRelease,
|
||||
"TryComplete();",
|
||||
"ReleaseNativeWindow();");
|
||||
AssertAppearsInOrder(
|
||||
dispose,
|
||||
"CompleteShutdown(releaseNativeWindow: true);",
|
||||
"_window = null;");
|
||||
Assert.Equal(1, CountOccurrences(dispose, "CompleteShutdown(releaseNativeWindow: true);"));
|
||||
Assert.DoesNotContain("_window?.Dispose();", dispose, StringComparison.Ordinal);
|
||||
Assert.Equal(1, CountOccurrences(dispose, "_window = null;"));
|
||||
int nativeReleased = dispose.IndexOf("_window = null;", StringComparison.Ordinal)
|
||||
+ "_window = null;".Length;
|
||||
string afterNativeRelease = dispose[nativeReleased..];
|
||||
int fallbackDocumentation = afterNativeRelease.IndexOf(
|
||||
"/// <summary>",
|
||||
StringComparison.Ordinal);
|
||||
if (fallbackDocumentation >= 0)
|
||||
afterNativeRelease = afterNativeRelease[..fallbackDocumentation];
|
||||
Assert.All(
|
||||
afterNativeRelease,
|
||||
character => Assert.True(char.IsWhiteSpace(character) || character == '}'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResourceRootsAndFramePairHaveExplicitAcquireTransferAndReleaseBoundaries()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private void OnUpdate(double dt)");
|
||||
string shutdown = GameWindowLifetimeSource();
|
||||
string terrainAtlas = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"TerrainAtlas.cs"));
|
||||
string worldPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"WorldRenderComposition.cs"));
|
||||
string livePhase = LivePresentationSource();
|
||||
string sessionPhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
string framePhase = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"FrameRootComposition.cs"));
|
||||
|
||||
AssertAppearsInOrder(
|
||||
livePhase,
|
||||
"d.PortalTunnelFallback.AcquirePrepared(",
|
||||
"static tunnel => tunnel.PrepareResources());",
|
||||
"new SkyRenderer(");
|
||||
AssertAppearsInOrder(
|
||||
load,
|
||||
"new WorldRenderCompositionPhase(",
|
||||
"new LivePresentationCompositionPhase(",
|
||||
"new SessionPlayerCompositionPhase(",
|
||||
"new FrameRootCompositionPhase(");
|
||||
AssertAppearsInOrder(
|
||||
framePhase,
|
||||
"new RenderFrameOrchestrator(",
|
||||
"new UpdateFrameOrchestrator(",
|
||||
"d.FrameGraphs.PublishOwned(");
|
||||
AssertAppearsInOrder(
|
||||
sessionPhase,
|
||||
"d.PortalTunnelFallback.Transfer(",
|
||||
"new LocalPlayerTeleportController(",
|
||||
"_publication.PublishSessionPlayer(result);");
|
||||
AssertAppearsInOrder(
|
||||
worldPhase,
|
||||
"lifetime.AcquireTerrainAtlas(",
|
||||
"TerrainAtlas.BuildBackendNeutral(device, dats)",
|
||||
"TerrainModernRenderer CreateBackendNeutralTerrain(",
|
||||
// Campaign V slice V6j: terrain is composed on both arms, so its
|
||||
// acquisition is unconditional. The boundary this test pins — that
|
||||
// the atlas is acquired, then the factory names the renderer, then
|
||||
// the renderer is acquired AND published in one step — is unchanged.
|
||||
// The raw-GL arm (CreateTerrain) was deleted at slice V11.
|
||||
"TerrainModernRenderer terrain = AcquireAndPublish(");
|
||||
AssertAppearsInOrder(
|
||||
shutdown,
|
||||
"frame.FrameGraphPublication?.Dispose()",
|
||||
"frame.FrameBindings?.Dispose()",
|
||||
"DisposeRetailUi(live.RetailUi)",
|
||||
"render.LocalTeleport?.Dispose();",
|
||||
"render.PortalTunnelFallback.ReleaseFallback();",
|
||||
"render.Sky?.Dispose()",
|
||||
"render.Terrain?.Dispose()",
|
||||
"render.DedicatedResources.ReleaseTerrainAtlas",
|
||||
"render.ConstructionCleanup.Dispose",
|
||||
"platform.Graphics?.Dispose()");
|
||||
|
||||
Assert.DoesNotContain(
|
||||
"RetailUiRuntime.Mount(",
|
||||
load,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("gl.GenTexture()", terrainAtlas, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"portalTunnel.PrepareResources();",
|
||||
load,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_portalTunnel =", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_renderFrameOrchestrator", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_updateFrameOrchestrator", source, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_window.Run();",
|
||||
"_constructionCleanup.RetainFrom(failure);",
|
||||
"private GameWindowShutdownRoots CaptureShutdownRoots()",
|
||||
"_constructionCleanup)");
|
||||
Assert.Contains(
|
||||
"Hard(\"resource construction ledger\", render.ConstructionCleanup.Dispose)",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string MethodBody(string start, string end) =>
|
||||
Slice(GameWindowSource(), start, end);
|
||||
|
||||
private static string Slice(string source, string start, string end)
|
||||
{
|
||||
int first = source.IndexOf(start, StringComparison.Ordinal);
|
||||
int last = source.IndexOf(end, first + 1, StringComparison.Ordinal);
|
||||
Assert.True(first >= 0, $"Missing source boundary: {start}");
|
||||
Assert.True(last > first, $"Missing source boundary: {end}");
|
||||
return source[first..last];
|
||||
}
|
||||
|
||||
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 void AssertAppearsInOrder(string source, params string[] fragments)
|
||||
{
|
||||
int cursor = -1;
|
||||
foreach (string fragment in fragments)
|
||||
{
|
||||
int next = source.IndexOf(fragment, cursor + 1, StringComparison.Ordinal);
|
||||
Assert.True(next >= 0, $"Missing expected source fragment: {fragment}");
|
||||
Assert.True(next > cursor, $"Out-of-order source fragment: {fragment}");
|
||||
cursor = next;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GameWindowSource() => File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
|
||||
private static string GameWindowLifetimeSource() => File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindowLifetime.cs"));
|
||||
|
||||
private static string LivePresentationSource() => File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"LivePresentationComposition.cs"));
|
||||
|
||||
private static string FindRepoRoot()
|
||||
{
|
||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
||||
return directory.FullName;
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue