test: replace composition source freezes
This commit is contained in:
parent
caa5eb8b2b
commit
80c7b44457
8 changed files with 471 additions and 406 deletions
|
|
@ -1208,3 +1208,60 @@ Verification:
|
||||||
attributed methods, and reduces total direct/helper source readers from 92
|
attributed methods, and reduces total direct/helper source readers from 92
|
||||||
to 83 while direct readers remain 57. The remaining 83 reconcile to the 22
|
to 83 while direct readers remain 57. The remaining 83 reconcile to the 22
|
||||||
approved retained policies/contracts and 61 staged replacements.
|
approved retained policies/contracts and 61 staged replacements.
|
||||||
|
|
||||||
|
## Batch Z composition-root source-freeze replacement
|
||||||
|
|
||||||
|
Batch Z converts 12 approved source-text readers across the seven graphical
|
||||||
|
composition-owner suites and removes one stale backend assertion. No product
|
||||||
|
source changes. The replacement tests use the built call graph, reflected
|
||||||
|
owner metadata, existing transactional fixtures, and typed composition results
|
||||||
|
instead of source fragments.
|
||||||
|
|
||||||
|
The exact decisions are:
|
||||||
|
|
||||||
|
- all seven phase-boundary tests now prove that `GameWindow` constructs exactly
|
||||||
|
one instance of the relevant composition phase and does not directly
|
||||||
|
construct the leaf owners that belong behind it. The phase and snapshot
|
||||||
|
types are also checked for the absence of a retained `GameWindow` owner;
|
||||||
|
- the platform prelude now verifies the compiled `OnLoad` route through
|
||||||
|
`AcquirePlatform` and `GameWindowCompositionPipeline.Run`, plus the single
|
||||||
|
`GameWindowPlatformAcquisition.Acquire` and host/input/camera phase edges;
|
||||||
|
- the prepared-asset contract follows the production factory, world phase,
|
||||||
|
`ObjectMeshManager`, `WbMeshAdapter`, and compiled shutdown manifest. It
|
||||||
|
proves one `IPreparedAssetSource` owner, no DAT-reader constructor seam in the
|
||||||
|
production mesh manager, prepared reads rather than live mesh building, and
|
||||||
|
mesh/prepared-source/DAT release order;
|
||||||
|
- the frame-root test follows the compiled construction order from render
|
||||||
|
resources and comparison state through the world/update roots, graph
|
||||||
|
publication, window publication, and final ownership transfers. The
|
||||||
|
lifecycle snapshot contract now verifies its exact `RenderFrameOutcome`
|
||||||
|
input and its typed live-entity, GPU-memory, profiler, and landblock metrics;
|
||||||
|
- the session phase follows the compiled streamer/start/reveal route and the
|
||||||
|
delegate target that constructs the request streamer. Its completion check
|
||||||
|
follows hydration, inbound routing, input/player mode, portal transfer,
|
||||||
|
session creation, command bindings, action attachment, and final publication
|
||||||
|
in order. Character selection now verifies the graphical selector option,
|
||||||
|
typed connect options, absence of automatic first-character selection, and
|
||||||
|
retained-UI runtime bindings;
|
||||||
|
- the interaction, live-presentation, and world-render boundaries retain their
|
||||||
|
existing behavioral phase fixtures while compiled metadata guards against
|
||||||
|
construction leaking back into `GameWindow`; and
|
||||||
|
- `PortalSpaceIsComposedOnBothBackendArms` is removed as stale. Campaign V
|
||||||
|
deleted the OpenGL backend, so a two-backend-arms source condition is no
|
||||||
|
longer an architectural contract; its negative assertion named an already
|
||||||
|
deleted null presentation. The valuable portal ownership rationale remains
|
||||||
|
in the live/session composition code, the compiled portal transfer order,
|
||||||
|
and the existing teleport/reveal behavior suites.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- all 187 composition tests pass with zero skips or failures;
|
||||||
|
- the complete locked Release build covers all 44 projects with zero warnings
|
||||||
|
and zero errors;
|
||||||
|
- the no-retry complete hermetic Release gate passes 14,346/14,346 with zero
|
||||||
|
skips or failures across all 12 test assemblies. The one-case reduction is
|
||||||
|
the stale two-backend-arms assertion; and
|
||||||
|
- the regenerated 1,254-file inventory parses every file, reports 11,414
|
||||||
|
attributed methods, and reduces direct/total source readers from 57/83 to
|
||||||
|
44/70. The remaining 70 reconcile to the 22 approved retained
|
||||||
|
policies/contracts and 48 staged replacements.
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@ using AcDream.App.Physics;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Rendering.Residency;
|
using AcDream.App.Rendering.Residency;
|
||||||
using AcDream.App.Rendering.Vfx;
|
using AcDream.App.Rendering.Vfx;
|
||||||
|
using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.App.Spells;
|
using AcDream.App.Spells;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.Content;
|
using AcDream.Content;
|
||||||
using AcDream.Content.Vfx;
|
using AcDream.Content.Vfx;
|
||||||
using AcDream.Core.Audio;
|
using AcDream.Core.Audio;
|
||||||
using AcDream.Core.CharGen;
|
using AcDream.Core.CharGen;
|
||||||
using AcDream.Core.Lighting;
|
using AcDream.Core.Lighting;
|
||||||
|
using AcDream.Core.Meshing;
|
||||||
using AcDream.Core.Physics;
|
using AcDream.Core.Physics;
|
||||||
using AcDream.Core.Rendering;
|
using AcDream.Core.Rendering;
|
||||||
using AcDream.Core.Spells;
|
using AcDream.Core.Spells;
|
||||||
|
|
@ -20,6 +23,7 @@ using AcDream.Runtime.Gameplay;
|
||||||
using AcDream.Runtime.Physics;
|
using AcDream.Runtime.Physics;
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
using DatReaderWriter.DBObjs;
|
using DatReaderWriter.DBObjs;
|
||||||
|
using DatReaderWriter.Lib.IO;
|
||||||
using Silk.NET.Input;
|
using Silk.NET.Input;
|
||||||
using Silk.NET.OpenAL;
|
using Silk.NET.OpenAL;
|
||||||
|
|
||||||
|
|
@ -163,84 +167,73 @@ public sealed class ContentEffectsAudioCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowUsesTheProductionPhaseAndNoLongerConstructsItsBodyInline()
|
public void GameWindowUsesTheProductionPhaseAndNoLongerConstructsItsBodyInline()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> calls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new ContentEffectsAudioCompositionPhase(", source,
|
Assert.Single(
|
||||||
StringComparison.Ordinal);
|
calls,
|
||||||
|
call => call.Target.DeclaringType
|
||||||
|
== typeof(ContentEffectsAudioCompositionPhase)
|
||||||
|
&& call.Target.IsConstructor);
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"_dats = RuntimeDatCollectionFactory.OpenReadOnly(_datDir);",
|
calls,
|
||||||
source,
|
call => call.Target.DeclaringType
|
||||||
StringComparison.Ordinal);
|
== typeof(RuntimeDatCollectionFactory)
|
||||||
Assert.DoesNotContain("_hookRouter.Register(_particleSink)", source,
|
&& call.Target.Name == nameof(RuntimeDatCollectionFactory.OpenReadOnly));
|
||||||
StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("new AcDream.App.Audio.OpenAlAudioEngine()", source,
|
calls,
|
||||||
StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(AnimationHookRouter)
|
||||||
|
&& call.Target.Name == nameof(AnimationHookRouter.Register));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(OpenAlAudioEngine)
|
||||||
|
&& call.Target.IsConstructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ProductionRendererConsumesOnlyThePublishedPreparedAssetSource()
|
public void ProductionRendererConsumesOnlyThePublishedPreparedAssetSource()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
MethodInfo openPrepared = typeof(RetailContentEffectsAudioCompositionFactory)
|
||||||
string contentPhase = File.ReadAllText(Path.Combine(
|
.GetMethod(nameof(
|
||||||
root,
|
RetailContentEffectsAudioCompositionFactory.OpenPreparedAssetSource))!;
|
||||||
"src",
|
Assert.Contains(
|
||||||
"AcDream.App",
|
CompiledCallGraph.Read(openPrepared),
|
||||||
"Composition",
|
call => call.Target.DeclaringType == typeof(PakPreparedAssetSource)
|
||||||
"ContentEffectsAudioComposition.cs"));
|
&& call.Target.IsConstructor);
|
||||||
string worldPhase = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"WorldRenderComposition.cs"));
|
|
||||||
string manager = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"Wb",
|
|
||||||
"ObjectMeshManager.cs"));
|
|
||||||
string adapter = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"Wb",
|
|
||||||
"WbMeshAdapter.cs"));
|
|
||||||
string lifetime = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindowLifetime.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new PakPreparedAssetSource(path, dats, diagnostic)",
|
MethodInfo compose = typeof(WorldRenderCompositionPhase)
|
||||||
contentPhase, StringComparison.Ordinal);
|
.GetMethod(nameof(WorldRenderCompositionPhase.Compose))!;
|
||||||
Assert.Contains("content.PreparedAssets", worldPhase,
|
IReadOnlyList<CompiledCall> worldCalls = CompiledCallGraph.Read(compose);
|
||||||
StringComparison.Ordinal);
|
Assert.Contains(
|
||||||
Assert.Contains("_preparedAssets.Read(request.Asset, ct)", manager,
|
worldCalls,
|
||||||
StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(ContentEffectsAudioResult)
|
||||||
Assert.DoesNotContain("MeshExtractor", manager,
|
&& call.Target.Name == "get_PreparedAssets");
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain("IDatReaderWriter", manager,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain("GfxObjMesh.Build", adapter,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
|
|
||||||
int meshStage = lifetime.IndexOf(
|
FieldInfo preparedAssets = Assert.Single(
|
||||||
"new ResourceShutdownStage(\"mesh adapter\"",
|
typeof(ObjectMeshManager).GetFields(
|
||||||
StringComparison.Ordinal);
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
int preparedRelease = lifetime.IndexOf(
|
field => field.FieldType == typeof(IPreparedAssetSource));
|
||||||
"Hard(\"prepared asset source\"",
|
Assert.Equal("_preparedAssets", preparedAssets.Name);
|
||||||
StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
int datRelease = lifetime.IndexOf(
|
typeof(ObjectMeshManager).GetConstructors(),
|
||||||
"Hard(\"DAT collection\"",
|
constructor => constructor.GetParameters().Any(parameter =>
|
||||||
StringComparison.Ordinal);
|
parameter.ParameterType == typeof(IDatReaderWriter)));
|
||||||
|
Assert.Contains(
|
||||||
|
CompiledCallGraph.ReadDeclared(typeof(ObjectMeshManager)),
|
||||||
|
call => call.Target.DeclaringType == typeof(IPreparedAssetSource)
|
||||||
|
&& call.Target.Name == nameof(IPreparedAssetSource.Read));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
CompiledCallGraph.ReadDeclared(typeof(WbMeshAdapter)),
|
||||||
|
call => call.Target.DeclaringType == typeof(GfxObjMesh)
|
||||||
|
&& call.Target.Name == nameof(GfxObjMesh.Build));
|
||||||
|
|
||||||
|
MethodInfo shutdown = typeof(GameWindowShutdownManifest).GetMethod(
|
||||||
|
nameof(GameWindowShutdownManifest.Create))!;
|
||||||
|
List<string> labels =
|
||||||
|
CompiledCallGraph.ReadStringLiterals(shutdown).ToList();
|
||||||
|
int meshStage = labels.IndexOf("mesh adapter");
|
||||||
|
int preparedRelease = labels.IndexOf("prepared asset source");
|
||||||
|
int datRelease = labels.IndexOf("DAT collection");
|
||||||
Assert.True(meshStage >= 0);
|
Assert.True(meshStage >= 0);
|
||||||
Assert.True(preparedRelease > meshStage);
|
Assert.True(preparedRelease > meshStage);
|
||||||
Assert.True(datRelease > preparedRelease);
|
Assert.True(datRelease > preparedRelease);
|
||||||
|
|
@ -564,15 +557,4 @@ public sealed class ContentEffectsAudioCompositionTests
|
||||||
public void CloseDevice(nint device) { }
|
public void CloseDevice(nint device) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using AcDream.App.Composition;
|
using AcDream.App.Composition;
|
||||||
|
using AcDream.App.Diagnostics;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
|
using AcDream.App.Rendering.Scene;
|
||||||
|
using AcDream.App.Rendering.Wb;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
|
using AcDream.App.Update;
|
||||||
|
using AcDream.App.World;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Composition;
|
namespace AcDream.App.Tests.Composition;
|
||||||
|
|
||||||
|
|
@ -28,48 +34,77 @@ public sealed class FrameRootCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ProductionPhasePublishesOnlyAfterBothRootsExist()
|
public void ProductionPhasePublishesOnlyAfterBothRootsExist()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
|
||||||
FindRepoRoot(),
|
"ComposeCore",
|
||||||
"src",
|
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
"AcDream.App",
|
?? throw new MissingMethodException(
|
||||||
"Composition",
|
typeof(FrameRootCompositionPhase).FullName,
|
||||||
"FrameRootComposition.cs"));
|
"ComposeCore");
|
||||||
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
|
||||||
|
|
||||||
AssertAppearsInOrder(
|
int resources = CallIndex(calls, typeof(RenderFrameResourceController), ".ctor");
|
||||||
source,
|
int comparison = CallIndex(
|
||||||
"new RenderFrameResourceController(",
|
calls,
|
||||||
"new RenderSceneShadowComparisonController(",
|
typeof(RenderSceneShadowComparisonController),
|
||||||
"new WorldSceneRenderer(",
|
".ctor",
|
||||||
"new WorldLifecycleAutomationController(",
|
resources + 1);
|
||||||
"\"world lifecycle automation owner\"",
|
int scene = CallIndex(calls, typeof(WorldSceneRenderer), ".ctor", comparison + 1);
|
||||||
"\"world lifecycle automation binding\"",
|
int automation = CallIndex(
|
||||||
"new SerialRenderFramePostDiagnosticsPhase(",
|
calls,
|
||||||
"new RenderFrameOrchestrator(",
|
typeof(WorldLifecycleAutomationController),
|
||||||
"postDiagnostics,",
|
".ctor",
|
||||||
"new RetailLiveFrameCoordinator(",
|
scene + 1);
|
||||||
"new UpdateFrameOrchestrator(",
|
int diagnostics = CallIndex(
|
||||||
"d.FrameGraphs.PublishOwned(",
|
calls,
|
||||||
"_publication.PublishFrameRoots(result);",
|
typeof(SerialRenderFramePostDiagnosticsPhase),
|
||||||
"graphLease.Transfer();",
|
".ctor",
|
||||||
"bindingsLease.Transfer();");
|
automation + 1);
|
||||||
|
int render = CallIndex(calls, typeof(RenderFrameOrchestrator), ".ctor", diagnostics + 1);
|
||||||
|
int live = CallIndex(calls, typeof(RetailLiveFrameCoordinator), ".ctor", render + 1);
|
||||||
|
int update = CallIndex(calls, typeof(UpdateFrameOrchestrator), ".ctor", live + 1);
|
||||||
|
int graph = CallIndex(calls, typeof(GameFrameGraphSlot), "PublishOwned", update + 1);
|
||||||
|
int publish = CallIndex(
|
||||||
|
calls,
|
||||||
|
typeof(IGameWindowFrameRootPublication),
|
||||||
|
"PublishFrameRoots",
|
||||||
|
graph + 1);
|
||||||
|
int firstTransfer = calls
|
||||||
|
.Select((call, index) => (call, index))
|
||||||
|
.First(pair => pair.index > publish
|
||||||
|
&& pair.call.Target.Name == "Transfer")
|
||||||
|
.index;
|
||||||
|
int secondTransfer = calls
|
||||||
|
.Select((call, index) => (call, index))
|
||||||
|
.First(pair => pair.index > firstTransfer
|
||||||
|
&& pair.call.Target.Name == "Transfer")
|
||||||
|
.index;
|
||||||
|
|
||||||
|
Assert.True(secondTransfer > firstTransfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowRetainsOnlyThePhaseBoundaryAndFrameHandoffs()
|
public void GameWindowRetainsOnlyThePhaseBoundaryAndFrameHandoffs()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> calls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new FrameRootCompositionPhase(", source);
|
Assert.Single(
|
||||||
Assert.DoesNotContain("new AcDream.App.Rendering.WorldSceneRenderer(", source);
|
calls,
|
||||||
Assert.DoesNotContain("new AcDream.App.Update.UpdateFrameOrchestrator(", source);
|
call => call.Target.DeclaringType == typeof(FrameRootCompositionPhase)
|
||||||
Assert.DoesNotContain("CaptureWorldLifecycleResourceSnapshot", source);
|
&& call.Target.IsConstructor);
|
||||||
Assert.DoesNotContain("_worldLifecycleAutomation", source);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("_frameGraphs.Publish(", source);
|
calls,
|
||||||
|
call => call.Target.IsConstructor
|
||||||
|
&& call.Target.DeclaringType is { } type
|
||||||
|
&& (type == typeof(WorldSceneRenderer)
|
||||||
|
|| type == typeof(UpdateFrameOrchestrator)
|
||||||
|
|| type == typeof(WorldLifecycleAutomationController)));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType
|
||||||
|
== typeof(WorldLifecycleResourceSnapshotSource)
|
||||||
|
&& call.Target.Name == nameof(
|
||||||
|
WorldLifecycleResourceSnapshotSource.Capture));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -80,19 +115,37 @@ public sealed class FrameRootCompositionTests
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
field => field.FieldType == typeof(GameWindow));
|
field => field.FieldType == typeof(GameWindow));
|
||||||
|
|
||||||
string snapshots = File.ReadAllText(Path.Combine(
|
Assert.DoesNotContain(
|
||||||
FindRepoRoot(),
|
typeof(WorldLifecycleResourceSnapshotSource).GetFields(
|
||||||
"src",
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
"AcDream.App",
|
field => field.FieldType == typeof(GameWindow));
|
||||||
"Diagnostics",
|
|
||||||
"WorldLifecycleResourceSnapshotSource.cs"));
|
MethodInfo capture = typeof(WorldLifecycleResourceSnapshotSource)
|
||||||
Assert.DoesNotContain("GameWindow", snapshots, StringComparison.Ordinal);
|
.GetMethod(nameof(WorldLifecycleResourceSnapshotSource.Capture))!;
|
||||||
Assert.Contains("_liveEntities.PendingTeardownCount", snapshots);
|
Assert.Equal(
|
||||||
Assert.Contains("GpuMemoryTracker.AllocatedBytes", snapshots);
|
typeof(RenderFrameOutcome),
|
||||||
Assert.Contains("_frameProfiler.LastReport", snapshots);
|
Assert.Single(capture.GetParameters()).ParameterType);
|
||||||
Assert.Contains("Capture(RenderFrameOutcome outcome)", snapshots);
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(capture);
|
||||||
Assert.Contains("outcome.World.VisibleLandblocks", snapshots);
|
Assert.Contains(
|
||||||
Assert.Contains("outcome.World.TotalLandblocks", snapshots);
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(LiveEntityRuntime)
|
||||||
|
&& call.Target.Name == "get_PendingTeardownCount");
|
||||||
|
Assert.Contains(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(GpuMemoryTracker)
|
||||||
|
&& call.Target.Name == "get_AllocatedBytes");
|
||||||
|
Assert.Contains(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(FrameProfiler)
|
||||||
|
&& call.Target.Name == "get_LastReport");
|
||||||
|
Assert.Contains(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(WorldRenderFrameOutcome)
|
||||||
|
&& call.Target.Name == "get_VisibleLandblocks");
|
||||||
|
Assert.Contains(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(WorldRenderFrameOutcome)
|
||||||
|
&& call.Target.Name == "get_TotalLandblocks");
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RetryBinding(
|
private sealed class RetryBinding(
|
||||||
|
|
@ -110,27 +163,20 @@ public sealed class FrameRootCompositionTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AssertAppearsInOrder(string source, params string[] values)
|
private static int CallIndex(
|
||||||
|
IReadOnlyList<CompiledCall> calls,
|
||||||
|
Type declaringType,
|
||||||
|
string methodName,
|
||||||
|
int startIndex = 0)
|
||||||
{
|
{
|
||||||
int cursor = 0;
|
int index = CompiledCallGraph.IndexOf(
|
||||||
foreach (string value in values)
|
calls,
|
||||||
{
|
declaringType,
|
||||||
int found = source.IndexOf(value, cursor, StringComparison.Ordinal);
|
methodName,
|
||||||
Assert.True(found >= 0, $"Missing expected source fragment: {value}");
|
startIndex);
|
||||||
cursor = found + value.Length;
|
Assert.True(
|
||||||
}
|
index >= startIndex,
|
||||||
}
|
$"Missing compiled edge {declaringType.FullName}.{methodName}.");
|
||||||
|
return index;
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using AcDream.App.Composition;
|
||||||
using AcDream.App.Input;
|
using AcDream.App.Input;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Rendering.Gpu;
|
using AcDream.App.Rendering.Gpu;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.App.Tests.Rendering.Gpu;
|
using AcDream.App.Tests.Rendering.Gpu;
|
||||||
using AcDream.UI.Abstractions.Input;
|
using AcDream.UI.Abstractions.Input;
|
||||||
using Silk.NET.Input;
|
using Silk.NET.Input;
|
||||||
|
|
@ -67,21 +68,35 @@ public sealed class HostInputCameraCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowUsesTheExactPlatformPreludeAndPhaseOneType()
|
public void GameWindowUsesTheExactPlatformPreludeAndPhaseOneType()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> declaredCalls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("GameWindowPlatformAcquisition.Acquire(", source,
|
Assert.Single(
|
||||||
StringComparison.Ordinal);
|
declaredCalls,
|
||||||
Assert.Contains("new HostInputCameraCompositionPhase(", source,
|
call => call.Target.DeclaringType
|
||||||
StringComparison.Ordinal);
|
== typeof(GameWindowPlatformAcquisition)
|
||||||
Assert.DoesNotContain("_gl = GL.GetApi(_window!)", source,
|
&& call.Target.Name == nameof(GameWindowPlatformAcquisition.Acquire));
|
||||||
StringComparison.Ordinal);
|
Assert.Single(
|
||||||
Assert.DoesNotContain("_input = _window!.CreateInput()", source,
|
declaredCalls,
|
||||||
StringComparison.Ordinal);
|
call => call.Target.DeclaringType
|
||||||
|
== typeof(HostInputCameraCompositionPhase)
|
||||||
|
&& call.Target.IsConstructor);
|
||||||
|
|
||||||
|
MethodInfo onLoad = typeof(GameWindow).GetMethod(
|
||||||
|
"OnLoad",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
?? throw new MissingMethodException(typeof(GameWindow).FullName, "OnLoad");
|
||||||
|
IReadOnlyList<CompiledCall> loadCalls = CompiledCallGraph.Read(onLoad);
|
||||||
|
Assert.Contains(
|
||||||
|
loadCalls,
|
||||||
|
call => call.Target.DeclaringType == typeof(GameWindow)
|
||||||
|
&& call.Target.Name == "AcquirePlatform");
|
||||||
|
Assert.Contains(
|
||||||
|
loadCalls,
|
||||||
|
call => call.Target.DeclaringType == typeof(GameWindowCompositionPipeline)
|
||||||
|
&& call.Target.Name == nameof(GameWindowCompositionPipeline.Run));
|
||||||
|
Assert.DoesNotContain(loadCalls, call => call.Target.Name == "GetApi");
|
||||||
|
Assert.DoesNotContain(loadCalls, call => call.Target.Name == "CreateInput");
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class Fixture : IDisposable
|
private sealed class Fixture : IDisposable
|
||||||
|
|
@ -450,16 +465,4 @@ public sealed class HostInputCameraCompositionTests
|
||||||
public void WriteLine(string message) { }
|
public void WriteLine(string message) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AcDream.App.Combat;
|
using AcDream.App.Combat;
|
||||||
using AcDream.App.Composition;
|
using AcDream.App.Composition;
|
||||||
|
|
@ -8,6 +9,7 @@ using AcDream.Content;
|
||||||
using AcDream.App.UI;
|
using AcDream.App.UI;
|
||||||
using AcDream.App.UI.Layout;
|
using AcDream.App.UI.Layout;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.Core.Combat;
|
using AcDream.Core.Combat;
|
||||||
using AcDream.Core.Items;
|
using AcDream.Core.Items;
|
||||||
using AcDream.Core.Spells;
|
using AcDream.Core.Spells;
|
||||||
|
|
@ -190,19 +192,33 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowUsesPhaseAndContainsNoRetainedUiConstructionBody()
|
public void GameWindowUsesPhaseAndContainsNoRetainedUiConstructionBody()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> calls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new InteractionRetainedUiCompositionPhase(", source);
|
Assert.Single(
|
||||||
Assert.DoesNotContain("new AcDream.App.UI.ItemInteractionController(", source);
|
calls,
|
||||||
Assert.DoesNotContain("_retailUiLease.AcquireHost(", source);
|
call => call.Target.DeclaringType
|
||||||
Assert.DoesNotContain("RetailUiRuntime.CreateUninitialized(", source);
|
== typeof(InteractionRetainedUiCompositionPhase)
|
||||||
Assert.DoesNotContain("private void UseItemByGuid(", source);
|
&& call.Target.IsConstructor);
|
||||||
Assert.DoesNotContain("private uint? PickWorldGuidAtCursor(", source);
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType
|
||||||
|
== typeof(ItemInteractionController)
|
||||||
|
&& call.Target.IsConstructor);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(RetailUiRuntimeLease)
|
||||||
|
&& call.Target.Name == "AcquireHost");
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(RetailUiRuntime)
|
||||||
|
&& call.Target.Name == nameof(RetailUiRuntime.CreateUninitialized));
|
||||||
|
Assert.Null(typeof(GameWindow).GetMethod(
|
||||||
|
"UseItemByGuid",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic));
|
||||||
|
Assert.Null(typeof(GameWindow).GetMethod(
|
||||||
|
"PickWorldGuidAtCursor",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic));
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class Fixture : IDisposable
|
private sealed class Fixture : IDisposable
|
||||||
|
|
@ -422,15 +438,4 @@ public sealed class InteractionRetainedUiCompositionTests
|
||||||
private static T Stub<T>() where T : class =>
|
private static T Stub<T>() where T : class =>
|
||||||
(T)RuntimeHelpers.GetUninitializedObject(typeof(T));
|
(T)RuntimeHelpers.GetUninitializedObject(typeof(T));
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
using System.Reflection;
|
||||||
using AcDream.App.Composition;
|
using AcDream.App.Composition;
|
||||||
using AcDream.App.Physics;
|
using AcDream.App.Physics;
|
||||||
|
using AcDream.App.Rendering;
|
||||||
|
using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
using AcDream.Core.Net;
|
using AcDream.Core.Net;
|
||||||
using AcDream.Core.Physics.Motion;
|
using AcDream.Core.Physics.Motion;
|
||||||
|
|
@ -125,71 +129,29 @@ public sealed class LivePresentationCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowUsesLivePhaseAndContainsNoPhaseSixConstructionBody()
|
public void GameWindowUsesLivePhaseAndContainsNoPhaseSixConstructionBody()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
IReadOnlyList<CompiledCall> windowCalls =
|
||||||
string window = File.ReadAllText(Path.Combine(
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
string phase = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"LivePresentationComposition.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new LivePresentationCompositionPhase(", window);
|
Assert.Single(
|
||||||
Assert.DoesNotContain("new AcDream.App.World.LiveEntityRuntime(", window);
|
windowCalls,
|
||||||
Assert.DoesNotContain("new AcDream.App.Rendering.Wb.WbDrawDispatcher(", window);
|
call => call.Target.DeclaringType
|
||||||
Assert.DoesNotContain("new AcDream.App.Streaming.LandblockRenderPublisher(", window);
|
== typeof(LivePresentationCompositionPhase)
|
||||||
Assert.DoesNotContain("_portalTunnelFallback.AcquirePrepared(", window);
|
&& call.Target.IsConstructor);
|
||||||
Assert.Contains("new LiveEntityRuntime(", phase);
|
|
||||||
Assert.Contains("new WbDrawDispatcher(", phase);
|
|
||||||
Assert.Contains("new LandblockRenderPublisher(", phase);
|
|
||||||
Assert.Contains("d.PortalTunnelFallback.AcquirePrepared(", phase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Campaign V slice V6m: portal space is composed on BOTH arms. It was the
|
|
||||||
/// last renderer this phase built only when a GL context existed, and the
|
|
||||||
/// Vulkan arm's portal-less teleport presentation went with the condition —
|
|
||||||
/// so both the gate and the stand-in are asserted absent here.
|
|
||||||
/// </summary>
|
|
||||||
[Fact]
|
|
||||||
public void PortalSpaceIsComposedOnBothBackendArms()
|
|
||||||
{
|
|
||||||
string root = FindRepoRoot();
|
|
||||||
string phase = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"LivePresentationComposition.cs"));
|
|
||||||
string session = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"SessionPlayerComposition.cs"));
|
|
||||||
|
|
||||||
Assert.Contains(
|
|
||||||
"if (dispatcherLease.Resource is { } portalDispatcher)",
|
|
||||||
phase);
|
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"if (gl is not null && dispatcherLease.Resource is { } portalDispatcher)",
|
windowCalls,
|
||||||
phase);
|
call => call.Target.IsConstructor
|
||||||
Assert.DoesNotContain("NullLocalPlayerTeleportPresentation", session);
|
&& call.Target.DeclaringType is { } type
|
||||||
|
&& (type == typeof(LiveEntityRuntime)
|
||||||
|
|| type == typeof(WbDrawDispatcher)
|
||||||
|
|| type == typeof(LandblockRenderPublisher)));
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"NullLocalPlayerTeleportPresentation",
|
windowCalls,
|
||||||
File.ReadAllText(Path.Combine(
|
call => call.Target.Name == "AcquirePrepared");
|
||||||
root,
|
|
||||||
"src",
|
Assert.DoesNotContain(
|
||||||
"AcDream.App",
|
typeof(LivePresentationCompositionPhase).GetFields(
|
||||||
"Rendering",
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
"Gpu",
|
field => field.FieldType == typeof(GameWindow));
|
||||||
"Vk",
|
|
||||||
"VulkanCompositionFramePhases.cs")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LiveEntityRuntime Runtime() => LiveEntityRuntimeFixture.Create(
|
private static LiveEntityRuntime Runtime() => LiveEntityRuntimeFixture.Create(
|
||||||
|
|
@ -250,15 +212,4 @@ public sealed class LivePresentationCompositionTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using AcDream.App.Combat;
|
||||||
using AcDream.App.Composition;
|
using AcDream.App.Composition;
|
||||||
|
using AcDream.App.Diagnostics;
|
||||||
|
using AcDream.App.Input;
|
||||||
|
using AcDream.App.Net;
|
||||||
|
using AcDream.App.Physics;
|
||||||
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
|
using AcDream.App.UI;
|
||||||
|
using AcDream.App.World;
|
||||||
|
using AcDream.Core.Net.Messages;
|
||||||
|
using AcDream.Runtime.Session;
|
||||||
using DatReaderWriter.DBObjs;
|
using DatReaderWriter.DBObjs;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Composition;
|
namespace AcDream.App.Tests.Composition;
|
||||||
|
|
@ -66,115 +78,124 @@ public sealed class SessionPlayerCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowUsesSessionPhaseAndContainsNoPhaseSevenBody()
|
public void GameWindowUsesSessionPhaseAndContainsNoPhaseSevenBody()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
IReadOnlyList<CompiledCall> windowCalls =
|
||||||
string window = File.ReadAllText(Path.Combine(
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
string phase = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"SessionPlayerComposition.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new SessionPlayerCompositionPhase(", window);
|
Assert.Single(
|
||||||
Assert.DoesNotContain("LandblockStreamer.CreateForRequests(", window);
|
windowCalls,
|
||||||
Assert.DoesNotContain("new AcDream.App.Net.LiveSessionController()", window);
|
call => call.Target.DeclaringType
|
||||||
|
== typeof(SessionPlayerCompositionPhase)
|
||||||
|
&& call.Target.IsConstructor);
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"new AcDream.App.Streaming.LocalPlayerTeleportController(",
|
windowCalls,
|
||||||
window);
|
call => call.Target.DeclaringType == typeof(LandblockStreamer)
|
||||||
Assert.DoesNotContain("IsSpawnClaimUnhydratable", window);
|
&& call.Target.Name == nameof(LandblockStreamer.CreateForRequests));
|
||||||
Assert.Contains("LandblockStreamer.CreateForRequests(", phase);
|
Assert.DoesNotContain(
|
||||||
Assert.Contains(
|
windowCalls,
|
||||||
"LiveSessionController liveSession = d.Runtime.Session;",
|
call => call.Target.IsConstructor
|
||||||
phase);
|
&& call.Target.DeclaringType is { } type
|
||||||
Assert.DoesNotContain("new LiveSessionController()", phase);
|
&& (type == typeof(LiveSessionController)
|
||||||
Assert.Contains("new LocalPlayerTeleportController(", phase);
|
|| type == typeof(LocalPlayerTeleportController)
|
||||||
Assert.Contains("new DatSpawnClaimHydrationClassifier(", phase);
|
|| type == typeof(DatSpawnClaimHydrationClassifier)));
|
||||||
|
|
||||||
|
IReadOnlyList<FieldInfo> phaseFields =
|
||||||
|
typeof(SessionPlayerCompositionPhase).GetFields(
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
phaseFields,
|
||||||
|
field => field.FieldType == typeof(GameWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ProductionPhaseStartsStreamerBeforeSessionAndTransfersPortalLast()
|
public void ProductionPhaseStartsStreamerBeforeSessionAndTransfersPortalLast()
|
||||||
{
|
{
|
||||||
string phase = File.ReadAllText(Path.Combine(
|
MethodInfo composeCore = RequiredMethod(
|
||||||
FindRepoRoot(),
|
typeof(SessionPlayerCompositionPhase),
|
||||||
"src",
|
"ComposeCore");
|
||||||
"AcDream.App",
|
IReadOnlyList<CompiledCall> composeCalls =
|
||||||
"Composition",
|
CompiledCallGraph.Read(composeCore);
|
||||||
"SessionPlayerComposition.cs"));
|
AssertCallOrder(
|
||||||
|
composeCalls,
|
||||||
|
(typeof(LandblockStreamer), nameof(LandblockStreamer.Start)),
|
||||||
|
(typeof(StreamingController), ".ctor"),
|
||||||
|
(typeof(WorldRevealCoordinator), ".ctor"),
|
||||||
|
(typeof(SessionPlayerCompositionPhase), "CompleteSessionPlayer"));
|
||||||
|
|
||||||
AssertAppearsInOrder(
|
IEnumerable<MethodBase> streamerFactories =
|
||||||
phase,
|
CompiledCallGraph.ReadMethodReferences(composeCore)
|
||||||
"LandblockStreamer.CreateForRequests(",
|
.Select(call => call.Target)
|
||||||
"streamerLease.Resource.Start();",
|
.Where(method => method.GetMethodBody() is not null);
|
||||||
"new StreamingController(",
|
MethodBase streamerFactory = Assert.Single(
|
||||||
"new WorldRevealCoordinator(",
|
streamerFactories,
|
||||||
"LiveSessionController liveSession = d.Runtime.Session;",
|
method => CompiledCallGraph.Read(method).Any(call =>
|
||||||
"new LiveEntityHydrationController(",
|
call.Target.DeclaringType == typeof(LandblockStreamer)
|
||||||
"new LiveEntityNetworkUpdateController(",
|
&& call.Target.Name
|
||||||
"new GameplayInputFrameController(",
|
== nameof(LandblockStreamer.CreateForRequests)));
|
||||||
"new PlayerModeController(",
|
Assert.Contains(
|
||||||
"d.PortalTunnelFallback.Transfer(",
|
CompiledCallGraph.Read(streamerFactory),
|
||||||
"d.TeleportSink.BindOwned(localTeleport)",
|
call => call.Target.DeclaringType == typeof(LandblockStreamer)
|
||||||
"LiveSessionHost sessionHost = sessionRuntimeFactory.Create(",
|
&& call.Target.Name == nameof(LandblockStreamer.CreateForRequests));
|
||||||
"d.CombatModeCommands.BindOwned(combatCommand)",
|
|
||||||
"d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics)",
|
|
||||||
"GameplayInputActionRouter.Create(",
|
|
||||||
"gameplayActions.Attach();",
|
|
||||||
"_publication.PublishSessionPlayer(result);");
|
|
||||||
|
|
||||||
Assert.DoesNotContain("GameWindow", File.ReadAllText(Path.Combine(
|
MethodInfo complete = RequiredMethod(
|
||||||
FindRepoRoot(),
|
typeof(SessionPlayerCompositionPhase),
|
||||||
"src",
|
"CompleteSessionPlayer");
|
||||||
"AcDream.App",
|
IReadOnlyList<CompiledCall> completeCalls =
|
||||||
"Net",
|
CompiledCallGraph.Read(complete);
|
||||||
"LiveSessionRuntimeFactory.cs")), StringComparison.Ordinal);
|
AssertCallOrder(
|
||||||
|
completeCalls,
|
||||||
|
(typeof(LiveEntityHydrationController), ".ctor"),
|
||||||
|
(typeof(LiveEntityNetworkUpdateController), ".ctor"),
|
||||||
|
(typeof(GameplayInputFrameController), ".ctor"),
|
||||||
|
(typeof(PlayerModeController), ".ctor"),
|
||||||
|
(typeof(TransferableResourceSlot<>), "Transfer"),
|
||||||
|
(typeof(DeferredLocalPlayerTeleportNetworkSink), "BindOwned"),
|
||||||
|
(typeof(LiveSessionRuntimeFactory), nameof(LiveSessionRuntimeFactory.Create)),
|
||||||
|
(typeof(LiveCombatModeCommandSlot), "BindOwned"),
|
||||||
|
(typeof(RuntimeDiagnosticCommandSlot), "BindOwned"),
|
||||||
|
(typeof(GameplayInputActionRouter), nameof(GameplayInputActionRouter.Create)),
|
||||||
|
(typeof(GameplayInputActionRouter), nameof(GameplayInputActionRouter.Attach)),
|
||||||
|
(typeof(IGameWindowSessionPlayerPublication), "PublishSessionPlayer"));
|
||||||
|
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
typeof(LiveSessionRuntimeFactory).GetFields(
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
|
field => field.FieldType == typeof(GameWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GraphicalCompositionPausesOnlyWhenCharacterSelectorIsAbsent()
|
public void GraphicalCompositionPausesOnlyWhenCharacterSelectorIsAbsent()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
MethodInfo complete = RequiredMethod(
|
||||||
string phase = File.ReadAllText(Path.Combine(
|
typeof(SessionPlayerCompositionPhase),
|
||||||
root,
|
"CompleteSessionPlayer");
|
||||||
"src",
|
IReadOnlyList<CompiledCall> sessionCalls =
|
||||||
"AcDream.App",
|
CompiledCallGraph.Read(complete);
|
||||||
"Composition",
|
|
||||||
"SessionPlayerComposition.cs"));
|
|
||||||
string retainedUi = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"InteractionRetainedUiComposition.cs"));
|
|
||||||
|
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"AwaitCharacterSelection:",
|
sessionCalls,
|
||||||
phase,
|
call => call.Target.DeclaringType == typeof(RuntimeOptions)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == "get_LiveCharacterSelector");
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"d.Options.LiveCharacterSelector is null",
|
sessionCalls,
|
||||||
phase,
|
call => call.Target.DeclaringType == typeof(LiveSessionConnectOptions)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.IsConstructor);
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"CharacterList.TrySelectFirstAvailable",
|
sessionCalls,
|
||||||
phase,
|
call => call.Target.DeclaringType == typeof(CharacterList)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == nameof(CharacterList.TrySelectFirstAvailable));
|
||||||
|
|
||||||
|
MethodInfo retainedUi = typeof(RetailInteractionRetainedUiCompositionFactory)
|
||||||
|
.GetMethod(nameof(
|
||||||
|
RetailInteractionRetainedUiCompositionFactory.CreateRetainedUi))!;
|
||||||
|
IReadOnlyList<CompiledCall> uiCalls = CompiledCallGraph.Read(retainedUi);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"CharacterSelection: d.Options.LiveCharacterSelector is null",
|
uiCalls,
|
||||||
retainedUi,
|
call => call.Target.DeclaringType == typeof(RuntimeOptions)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == "get_LiveCharacterSelector");
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"() => late.GameRuntime.CharacterSelection",
|
uiCalls,
|
||||||
retainedUi,
|
call => call.Target.DeclaringType
|
||||||
StringComparison.Ordinal);
|
== typeof(CharacterSelectionRuntimeBindings)
|
||||||
Assert.Contains(
|
&& call.Target.IsConstructor);
|
||||||
"late.GameRuntime.CharacterSelectionEnter",
|
|
||||||
retainedUi,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RetryBinding(
|
private sealed class RetryBinding(
|
||||||
|
|
@ -192,27 +213,39 @@ public sealed class SessionPlayerCompositionTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AssertAppearsInOrder(string source, params string[] values)
|
private static MethodInfo RequiredMethod(Type type, string name) =>
|
||||||
|
type.GetMethod(
|
||||||
|
name,
|
||||||
|
BindingFlags.Instance | BindingFlags.Static
|
||||||
|
| BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
?? throw new MissingMethodException(type.FullName, name);
|
||||||
|
|
||||||
|
private static void AssertCallOrder(
|
||||||
|
IReadOnlyList<CompiledCall> calls,
|
||||||
|
params (Type DeclaringType, string MethodName)[] expected)
|
||||||
{
|
{
|
||||||
int cursor = 0;
|
int cursor = -1;
|
||||||
foreach (string value in values)
|
foreach ((Type declaringType, string methodName) in expected)
|
||||||
{
|
{
|
||||||
int found = source.IndexOf(value, cursor, StringComparison.Ordinal);
|
int found = calls
|
||||||
Assert.True(found >= 0, $"Missing expected source fragment: {value}");
|
.Select((call, index) => (call, index))
|
||||||
cursor = found + value.Length;
|
.FirstOrDefault(
|
||||||
|
pair => pair.index > cursor
|
||||||
|
&& MatchesType(pair.call.Target.DeclaringType, declaringType)
|
||||||
|
&& pair.call.Target.Name == methodName,
|
||||||
|
defaultValue: (default, -1))
|
||||||
|
.index;
|
||||||
|
Assert.True(
|
||||||
|
found > cursor,
|
||||||
|
$"Missing compiled edge after index {cursor}: "
|
||||||
|
+ $"{declaringType.FullName}.{methodName}");
|
||||||
|
cursor = found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindRepoRoot()
|
private static bool MatchesType(Type? actual, Type expected) =>
|
||||||
{
|
actual == expected
|
||||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
|| expected.IsGenericTypeDefinition
|
||||||
while (directory is not null)
|
&& actual?.IsGenericType == true
|
||||||
{
|
&& actual.GetGenericTypeDefinition() == expected;
|
||||||
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
||||||
return directory.FullName;
|
|
||||||
directory = directory.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using AcDream.App.Rendering.Gpu;
|
||||||
using AcDream.App.Rendering.Wb;
|
using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.App.Rendering.Residency;
|
using AcDream.App.Rendering.Residency;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.App.Tests.Rendering.Gpu;
|
using AcDream.App.Tests.Rendering.Gpu;
|
||||||
using AcDream.Content;
|
using AcDream.Content;
|
||||||
using AcDream.Core.Physics;
|
using AcDream.Core.Physics;
|
||||||
|
|
@ -121,23 +122,21 @@ public sealed class WorldRenderCompositionTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowUsesPhaseAndNoLongerBuildsWorldFoundationInline()
|
public void GameWindowUsesPhaseAndNoLongerBuildsWorldFoundationInline()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> calls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new WorldRenderCompositionPhase(", source,
|
Assert.Single(
|
||||||
StringComparison.Ordinal);
|
calls,
|
||||||
Assert.DoesNotContain("BindlessSupport.TryCreate(_gl", source,
|
call => call.Target.DeclaringType
|
||||||
StringComparison.Ordinal);
|
== typeof(WorldRenderCompositionPhase)
|
||||||
Assert.DoesNotContain("_terrainModernShader = new Shader", source,
|
&& call.Target.IsConstructor);
|
||||||
StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("_wbMeshAdapter = new", source,
|
calls,
|
||||||
StringComparison.Ordinal);
|
call => call.Target.IsConstructor
|
||||||
Assert.DoesNotContain("_textureCache = new TextureCache", source,
|
&& call.Target.DeclaringType is { } type
|
||||||
StringComparison.Ordinal);
|
&& (type == typeof(TerrainModernRenderer)
|
||||||
|
|| type == typeof(WbMeshAdapter)
|
||||||
|
|| type == typeof(TextureCache)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class Fixture
|
private sealed class Fixture
|
||||||
|
|
@ -385,15 +384,4 @@ public sealed class WorldRenderCompositionTests
|
||||||
private static T Stub<T>() where T : class =>
|
private static T Stub<T>() where T : class =>
|
||||||
(T)RuntimeHelpers.GetUninitializedObject(typeof(T));
|
(T)RuntimeHelpers.GetUninitializedObject(typeof(T));
|
||||||
|
|
||||||
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