test: replace frame orchestration source freezes
This commit is contained in:
parent
9bd5d47c47
commit
9b94050229
3 changed files with 501 additions and 466 deletions
|
|
@ -1391,3 +1391,59 @@ Verification:
|
|||
attributed methods, and reduces direct/total source readers from 39/55 to
|
||||
32/46. The remaining 46 reconcile to the 22 approved retained
|
||||
policies/contracts and 24 staged replacements.
|
||||
|
||||
## Batch AD frame orchestration and resource-preparation replacement
|
||||
|
||||
Batch AD converts all fourteen approved source readers in
|
||||
`RenderFrameResourceControllerTests` and `UpdateFrameOrchestratorTests`. No
|
||||
product source changes and no test is removed.
|
||||
|
||||
The three render-resource checks now inspect built call and field-store order.
|
||||
They preserve the texture/dispatcher/environment/portal/clip/terrain/lighting
|
||||
begin sequence, mesh publication before reveal evaluation and particle begin,
|
||||
and weather evaluation before the elapsed-clock commit. The existing recording
|
||||
phase test continues to exercise the public prepare sequence and one GPU-slot
|
||||
read behaviorally; source comments and local expressions are no longer part of
|
||||
the oracle.
|
||||
|
||||
The update-frame replacements retain the full accepted phase graph and its
|
||||
existing behavioral fake trace, then use compiled metadata only for production
|
||||
wiring that cannot safely be invoked without a real session or window:
|
||||
|
||||
- the physics script clock has one typed publisher behind `FrameRootCompositionPhase`
|
||||
and no `GameWindow` publication edge;
|
||||
- live-object, static-animation, effect, particle/script, and spatial-reconcile
|
||||
calls preserve their registered adaptation order and exact-once tails;
|
||||
- streaming precedes the live coordinator, whose inbound session and placement
|
||||
retry precede command and spatial reconciliation on both available and
|
||||
quiesced behavioral paths;
|
||||
- session/frame composition constructs the live-object, spatial, streaming,
|
||||
gameplay-input, teleport, liveness, auto-entry, camera, and update owners,
|
||||
while reflected fields and compiled window calls reject the displaced bodies;
|
||||
- focus loss, player-mode exit, and teleport start retain their typed
|
||||
mouse-look cleanup edges, including freshness validation before input cleanup
|
||||
and Runtime transit commit afterward;
|
||||
- player presentation attachment retains Runtime-publication validation,
|
||||
camera/shadow/host/mode order, and both cleanup routes; mouse filtering and
|
||||
typed movement-input seams are checked from method and constructor metadata;
|
||||
and
|
||||
- camera sampling/reconciliation and the thin `OnUpdate` profile-to-graph
|
||||
handoff remain ordered, while live-session event and world-origin consumers
|
||||
stay behind their focused typed owners without `GameWindow` back-references.
|
||||
|
||||
Where construction happens inside acquisition or transfer delegates, the
|
||||
tests follow the compiled delegate target before inspecting its child edge.
|
||||
This preserves real execution ownership without reverting to lexical source
|
||||
order across nested expressions.
|
||||
|
||||
Verification follows the user-approved proportionate policy for the remaining
|
||||
R3 work:
|
||||
|
||||
- all 34 focused frame/resource methods pass;
|
||||
- the complete `AcDream.App.Tests` hermetic lane passes 5,381/5,381 with zero
|
||||
skips or failures;
|
||||
- the complete locked Release build covers all 44 projects with zero warnings
|
||||
and zero errors; and
|
||||
- a targeted scan confirms neither owning test file retains a source reader or
|
||||
its source-slicing helper. The final full inventory and twelve-assembly
|
||||
Release gate are intentionally deferred to the one R3 closeout run.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using AcDream.App.Diagnostics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.Tests.Architecture;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
|
|
@ -47,36 +56,40 @@ public sealed class RenderFrameResourceControllerTests
|
|||
[Fact]
|
||||
public void Production_begin_resources_preserve_the_exact_frame_order()
|
||||
{
|
||||
string source = ResourceSource();
|
||||
|
||||
// Campaign V slice V4a: the world-HUD and retained-UI TextRenderers no
|
||||
// longer take a per-slot begin call — they now read the shared
|
||||
// IGpuFrame ring reset once per frame by IGpuDevice.BeginFrame() (see
|
||||
// GpuDeviceFrameLifetime), so _worldText/_uiText are gone from this
|
||||
// sequence.
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_textures?.BeginCompositeTextureFrame();",
|
||||
"_textures?.TickCompositeTextureCache();",
|
||||
"_dispatcher?.BeginFrame(gpuSlot);",
|
||||
"_environmentCells?.BeginFrame(gpuSlot);",
|
||||
"_portalDepth?.BeginFrame(gpuSlot);",
|
||||
"_clip?.BeginFrame(gpuSlot);",
|
||||
"_terrain?.BeginFrame(gpuSlot);",
|
||||
"_lighting?.BeginFrame(gpuSlot);");
|
||||
Assert.DoesNotContain("_profiler.FrameBoundary(", source);
|
||||
MethodInfo begin = RequiredMethod(
|
||||
typeof(RuntimeRenderFrameBeginResources),
|
||||
nameof(RuntimeRenderFrameBeginResources.Begin));
|
||||
AssertCallOrder(
|
||||
begin,
|
||||
(typeof(TextureCache), nameof(TextureCache.BeginCompositeTextureFrame)),
|
||||
(typeof(TextureCache), nameof(TextureCache.TickCompositeTextureCache)),
|
||||
(typeof(WbDrawDispatcher), nameof(WbDrawDispatcher.BeginFrame)),
|
||||
(typeof(EnvCellRenderer), nameof(EnvCellRenderer.BeginFrame)),
|
||||
(typeof(PortalDepthMaskRenderer), nameof(PortalDepthMaskRenderer.BeginFrame)),
|
||||
(typeof(ClipFrame), nameof(ClipFrame.BeginFrame)),
|
||||
(typeof(TerrainModernRenderer), nameof(TerrainModernRenderer.BeginFrame)),
|
||||
(typeof(SceneLightingUboBinding), nameof(SceneLightingUboBinding.BeginFrame)));
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.Read(begin),
|
||||
call => call.Target.DeclaringType == typeof(FrameProfiler)
|
||||
&& call.Target.Name == "FrameBoundary");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Production_live_preparation_publishes_meshes_before_reveal_and_particles()
|
||||
{
|
||||
string source = ResourceSource();
|
||||
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_meshes?.Tick();",
|
||||
"_worldReveal?.PrepareAndEvaluate(revealCell);",
|
||||
"_particles?.BeginFrame(gpuSlot);");
|
||||
AssertCallOrder(
|
||||
RequiredMethod(
|
||||
typeof(RuntimeRenderFrameLivePreparation),
|
||||
nameof(RuntimeRenderFrameLivePreparation.Prepare)),
|
||||
(typeof(WbMeshAdapter), nameof(WbMeshAdapter.Tick)),
|
||||
(typeof(WorldRevealCoordinator), nameof(WorldRevealCoordinator.PrepareAndEvaluate)),
|
||||
(typeof(ParticleRenderer), nameof(ParticleRenderer.BeginFrame)));
|
||||
}
|
||||
|
||||
// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase (the raw-GL
|
||||
|
|
@ -101,11 +114,18 @@ public sealed class RenderFrameResourceControllerTests
|
|||
controller.Tick(0.5d);
|
||||
|
||||
Assert.Equal(0.75d, controller.ElapsedSeconds);
|
||||
AssertAppearsInOrder(
|
||||
ResourceSource(),
|
||||
"_weather.Tick(",
|
||||
"nowSeconds: _elapsedSeconds,",
|
||||
"_elapsedSeconds += deltaSeconds;");
|
||||
MethodInfo tick = RequiredMethod(
|
||||
typeof(RenderWeatherFrameController),
|
||||
nameof(RenderWeatherFrameController.Tick));
|
||||
CompiledCall weather = Assert.Single(
|
||||
CompiledCallGraph.Read(tick),
|
||||
call => call.Target.DeclaringType == typeof(WeatherSystem)
|
||||
&& call.Target.Name == nameof(WeatherSystem.Tick));
|
||||
CompiledFieldReference elapsedStore = Assert.Single(
|
||||
CompiledCallGraph.ReadFieldReferences(tick),
|
||||
reference => reference.Field.Name == "_elapsedSeconds"
|
||||
&& reference.OpCode == OpCodes.Stfld);
|
||||
Assert.True(weather.Offset < elapsedStore.Offset);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -127,38 +147,30 @@ public sealed class RenderFrameResourceControllerTests
|
|||
Assert.False(live.IsWaitingForLogin);
|
||||
}
|
||||
|
||||
private static string ResourceSource() => File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"RenderFrameResourceController.cs"));
|
||||
private static MethodInfo RequiredMethod(Type owner, string name) =>
|
||||
owner.GetMethod(
|
||||
name,
|
||||
BindingFlags.Instance | BindingFlags.Static
|
||||
| BindingFlags.Public | BindingFlags.NonPublic)
|
||||
?? throw new MissingMethodException(owner.FullName, name);
|
||||
|
||||
private static void AssertAppearsInOrder(string source, params string[] needles)
|
||||
private static void AssertCallOrder(
|
||||
MethodBase method,
|
||||
params (Type Type, string Method)[] expected)
|
||||
{
|
||||
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
|
||||
int cursor = -1;
|
||||
foreach (string needle in needles)
|
||||
foreach ((Type type, string name) in expected)
|
||||
{
|
||||
int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal);
|
||||
Assert.True(next >= 0, $"Missing expected source fragment: {needle}");
|
||||
Assert.True(next > cursor, $"Out-of-order source fragment: {needle}");
|
||||
cursor = next;
|
||||
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
|
||||
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
|
||||
&& calls[index].Target.Name == name, -1);
|
||||
Assert.True(found > cursor,
|
||||
$"Missing compiled edge after {cursor}: {type.FullName}.{name}.");
|
||||
cursor = found;
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
private sealed class AdvancingSlotSource(int firstSlot) : IRenderFrameSlotSource
|
||||
{
|
||||
public int ReadCount { get; private set; }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
using System.Reflection;
|
||||
using AcDream.App.Composition;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.App.Tests.Architecture;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
|
|
@ -318,104 +321,84 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
[Fact]
|
||||
public void ProductionFrame_PublishesPhysicsScriptTimeExactlyOnce()
|
||||
{
|
||||
string root = FindRepoRoot();
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string adapters = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Update",
|
||||
"UpdateFrameRuntimeAdapters.cs"));
|
||||
string frameRoot = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"FrameRootComposition.cs"));
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||
call => call.Target.Name == nameof(PhysicsScriptClockPublisher.PublishTime));
|
||||
|
||||
Assert.Equal(0, CountOccurrences(source, "PublishTime("));
|
||||
Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime("));
|
||||
Assert.Contains("new PhysicsScriptClockPublisher(", frameRoot,
|
||||
StringComparison.Ordinal);
|
||||
MethodInfo publish = RequiredMethod(
|
||||
typeof(PhysicsScriptClockPublisher),
|
||||
nameof(PhysicsScriptClockPublisher.PublishTime));
|
||||
Assert.Single(
|
||||
CompiledCallGraph.Read(publish),
|
||||
call => call.Target.Name == "PublishTime");
|
||||
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(RequiredMethod(
|
||||
typeof(FrameRootCompositionPhase),
|
||||
"ComposeCore")),
|
||||
call => call.Target.DeclaringType == typeof(PhysicsScriptClockPublisher)
|
||||
&& call.Target.IsConstructor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Update",
|
||||
"LiveObjectFrameController.cs"));
|
||||
MethodInfo live = RequiredMethod(typeof(LiveObjectFrameController), "TickCore");
|
||||
AssertNamedCallOrder(
|
||||
live,
|
||||
("RetailLocalPlayerFrameController", "AdvanceBeforeNetwork"),
|
||||
("SelectionInteractionController", "DrainOutbound"),
|
||||
("LiveEntityAnimationScheduler", "Tick"),
|
||||
("RetailStaticAnimatingObjectScheduler", "Tick"),
|
||||
("LiveEntityAnimationPresenter", "Present"),
|
||||
("EquippedChildRenderController", "Tick"),
|
||||
("RetailStaticAnimatingObjectScheduler", "ProcessHooks"),
|
||||
("LiveEffectFrameController", "Tick"));
|
||||
AssertNamedCallOrder(
|
||||
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
|
||||
("TranslucencyFadeManager", "AdvanceAll"),
|
||||
("AnimationHookFrameQueue", "Drain"),
|
||||
("EntityEffectController", "RefreshLiveOwnerPoses"),
|
||||
("ParticleHookSink", "RefreshAttachedEmitters"),
|
||||
("LiveEntityLightController", "Refresh"),
|
||||
("ParticleVisibilityController", "Apply"),
|
||||
("ParticleSystem", "Tick"),
|
||||
("PhysicsScriptRunner", "Tick"));
|
||||
AssertNamedCallOrder(
|
||||
RequiredMethod(typeof(LiveSpatialPresentationReconciler), "Reconcile"),
|
||||
("EntityEffectController", "RefreshLiveOwnerPoses"),
|
||||
("EquippedChildRenderController", "ReconcileSpatialMutations"),
|
||||
("ParticleHookSink", "RefreshAttachedEmitters"),
|
||||
("LiveEntityLightController", "Refresh"));
|
||||
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_localPlayerFrame.AdvanceBeforeNetwork",
|
||||
"_selectionInteractions?.DrainOutbound",
|
||||
"_animations.Tick",
|
||||
"_staticAnimations.Tick",
|
||||
"_animationPresenter.Present",
|
||||
"_equippedChildren.Tick",
|
||||
"_staticAnimations.ProcessHooks",
|
||||
"_effects.Tick");
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_translucencyFades.AdvanceAll",
|
||||
"_animationHooks.Drain",
|
||||
"_entityEffects.RefreshLiveOwnerPoses",
|
||||
"_particleSink.RefreshAttachedEmitters",
|
||||
"_lights.Refresh",
|
||||
"_particleVisibility.Apply",
|
||||
"_particles.Tick",
|
||||
"_scripts.Tick");
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"public void Reconcile()",
|
||||
"_entityEffects.RefreshLiveOwnerPoses",
|
||||
"_equippedChildren.ReconcileSpatialMutations",
|
||||
"_particleSink.RefreshAttachedEmitters",
|
||||
"_lights.Refresh");
|
||||
|
||||
Assert.Equal(1, CountOccurrences(source, "_staticAnimations.Tick("));
|
||||
Assert.Equal(1, CountOccurrences(source, "_staticAnimations.ProcessHooks("));
|
||||
Assert.Equal(1, CountOccurrences(source, "_effects.Tick("));
|
||||
Assert.Equal(1, CountOccurrences(source, "_particles.Tick("));
|
||||
Assert.Equal(1, CountOccurrences(source, "_scripts.Tick("));
|
||||
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "Tick");
|
||||
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "ProcessHooks");
|
||||
AssertSingleNamedCall(live, "LiveEffectFrameController", "Tick");
|
||||
AssertSingleNamedCall(
|
||||
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
|
||||
"ParticleSystem",
|
||||
"Tick");
|
||||
AssertSingleNamedCall(
|
||||
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
|
||||
"PhysicsScriptRunner",
|
||||
"Tick");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlacementRetryRunsAfterStreamingAndInboundBeforeCommandReconcile()
|
||||
{
|
||||
string root = FindRepoRoot();
|
||||
string outer = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Update",
|
||||
"UpdateFrameOrchestrator.cs"));
|
||||
string live = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"World",
|
||||
"RetailLiveFrameCoordinator.cs"));
|
||||
|
||||
AssertAppearsInOrder(
|
||||
outer,
|
||||
"_streaming.Tick();",
|
||||
"_liveFrame.Tick(");
|
||||
AssertAppearsInOrder(
|
||||
live,
|
||||
"_session.Tick();",
|
||||
"_placementProjectionRetry?.RetryPending();",
|
||||
"_localPlayer.RunPostNetworkCommandPhase();",
|
||||
"_spatialReconciler.Reconcile();");
|
||||
AssertCallOrder(
|
||||
RequiredMethod(typeof(UpdateFrameOrchestrator), nameof(UpdateFrameOrchestrator.Tick)),
|
||||
(typeof(IStreamingFramePhase), nameof(IStreamingFramePhase.Tick)),
|
||||
(typeof(IRetailLiveFramePhase), nameof(IRetailLiveFramePhase.Tick)));
|
||||
AssertCallOrder(
|
||||
RequiredMethod(typeof(RetailLiveFrameCoordinator), nameof(RetailLiveFrameCoordinator.Tick)),
|
||||
(typeof(IRuntimeLiveSessionFramePhase), nameof(IRuntimeLiveSessionFramePhase.Tick)),
|
||||
(typeof(IRuntimePlacementProjectionRetryPhase),
|
||||
nameof(IRuntimePlacementProjectionRetryPhase.RetryPending)),
|
||||
(typeof(IPostNetworkCommandFramePhase),
|
||||
nameof(IPostNetworkCommandFramePhase.RunPostNetworkCommandPhase)),
|
||||
(typeof(ILiveSpatialReconcilePhase), nameof(ILiveSpatialReconcilePhase.Reconcile)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -451,205 +434,162 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
[Fact]
|
||||
public void GameWindow_ComposesTheLiveFrameOwnersWithoutOwningTheirBodies()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string livePresentation = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"LivePresentationComposition.cs"));
|
||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
string frameRoot = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"FrameRootComposition.cs"));
|
||||
IReadOnlyList<CompiledCall> session = CompiledCallGraph.Read(
|
||||
RequiredMethod(typeof(SessionPlayerCompositionPhase), "CompleteSessionPlayer"));
|
||||
Assert.Contains(session, call =>
|
||||
call.Target.DeclaringType == typeof(LiveObjectFrameController)
|
||||
&& call.Target.IsConstructor);
|
||||
Assert.Contains(session, call =>
|
||||
call.Target.DeclaringType == typeof(LiveSpatialPresentationReconciler)
|
||||
&& call.Target.IsConstructor);
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(RequiredMethod(
|
||||
typeof(FrameRootCompositionPhase),
|
||||
"ComposeCore")),
|
||||
call => call.Target.DeclaringType == typeof(RetailLiveFrameCoordinator)
|
||||
&& call.Target.IsConstructor);
|
||||
|
||||
Assert.Contains("new LiveObjectFrameController(", sessionPlayer);
|
||||
Assert.Contains("new LiveSpatialPresentationReconciler(", sessionPlayer);
|
||||
Assert.Contains("new RetailLiveFrameCoordinator(", frameRoot);
|
||||
Assert.DoesNotContain("AdvanceLiveObjectRuntime", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ReconcileLiveObjectSpatialPresentation", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ILiveAnimationPresentationContext", source,
|
||||
StringComparison.Ordinal);
|
||||
IReadOnlyList<CompiledCall> windowCalls =
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||
Assert.DoesNotContain(windowCalls, call =>
|
||||
call.Target.Name is "AdvanceLiveObjectRuntime"
|
||||
or "ReconcileLiveObjectSpatialPresentation");
|
||||
Assert.DoesNotContain(
|
||||
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.FieldType == typeof(ILiveAnimationPresentationContext));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameWindow_DelegatesTheCompleteStreamingFrameBody()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
Assert.Contains("new StreamingFrameController(", sessionPlayer);
|
||||
Assert.DoesNotContain("_streamingFrame", source, StringComparison.Ordinal);
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
source,
|
||||
"_frameGraphs.Tick("));
|
||||
Assert.DoesNotContain("_streamingController.Tick(observerCx", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("DungeonStreamingGate.Compute", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("DrainRescued()", source, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(RequiredMethod(
|
||||
typeof(SessionPlayerCompositionPhase),
|
||||
"CompleteSessionPlayer")),
|
||||
call => call.Target.DeclaringType == typeof(StreamingFrameController)
|
||||
&& call.Target.IsConstructor);
|
||||
Assert.DoesNotContain(
|
||||
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.Name == "_streamingFrame");
|
||||
|
||||
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
|
||||
Assert.Single(
|
||||
CompiledCallGraph.Read(onUpdate),
|
||||
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
|
||||
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||
call => call.Target.Name is "Compute" or "DrainRescued"
|
||||
|| call.Target.DeclaringType == typeof(StreamingController)
|
||||
&& call.Target.Name == nameof(StreamingController.Tick));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameWindow_DelegatesTheCompleteGameplayInputFrameBody()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(RequiredMethod(
|
||||
typeof(SessionPlayerCompositionPhase),
|
||||
"CompleteSessionPlayer")),
|
||||
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
|
||||
&& call.Target.IsConstructor);
|
||||
|
||||
Assert.Contains("new GameplayInputFrameController(", sessionPlayer);
|
||||
Assert.DoesNotContain("_gameplayInputFrame!.Tick", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_inputDispatcher?.Tick()", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("TryTakeRawSample", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_combatAttackController?.Tick()", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("CaptureMovementInput", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("EndMouseLookAndRestoreCursor", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("HideCursorForMouseLook", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("RestoreCursorAfterMouseLook", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("CanStartLiveCombatAttack", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("SendLiveCombatAttack", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("PreparePlayerForAttackRequest", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("DumpMovementTruthOutbound", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("DumpMovementTruthServerEcho", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("wantCaptureMouse: ()", source,
|
||||
StringComparison.Ordinal);
|
||||
HashSet<string> displacedWindowCalls =
|
||||
[
|
||||
"TryTakeRawSample",
|
||||
"CaptureMovementInput",
|
||||
"EndMouseLookAndRestoreCursor",
|
||||
"HideCursorForMouseLook",
|
||||
"RestoreCursorAfterMouseLook",
|
||||
"CanStartLiveCombatAttack",
|
||||
"SendLiveCombatAttack",
|
||||
"PreparePlayerForAttackRequest",
|
||||
"DumpMovementTruthOutbound",
|
||||
"DumpMovementTruthServerEcho",
|
||||
"EndMouseLook",
|
||||
];
|
||||
Assert.DoesNotContain(
|
||||
"_gameplayInputFrame?.EndMouseLook",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
string pointerSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"CameraPointerInputController.cs"));
|
||||
Assert.Contains("_gameplayFrame?.EndMouseLook();", pointerSource,
|
||||
StringComparison.Ordinal);
|
||||
string teleportSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Streaming",
|
||||
"LocalPlayerTeleportController.cs"));
|
||||
AssertAppearsInOrder(
|
||||
teleportSource,
|
||||
"_transit.CanQueueTeleportStart(teleportSequence)",
|
||||
"_input.EndMouseLook();",
|
||||
"_transit.TryQueueTeleportStart(teleportSequence)");
|
||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"PlayerModeController.cs"));
|
||||
Assert.Contains("_input.EndMouseLook();", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"private void OnFocusChanged(bool focused)",
|
||||
"_cameraPointerInput?.HandleFocusChanged(focused);");
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||
call => displacedWindowCalls.Contains(call.Target.Name)
|
||||
|| call.Target.DeclaringType == typeof(GameplayInputFrameController)
|
||||
&& call.Target.Name == nameof(GameplayInputFrameController.Tick));
|
||||
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(RequiredMethod(
|
||||
typeof(CameraPointerInputController),
|
||||
nameof(CameraPointerInputController.HandleFocusChanged))),
|
||||
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
|
||||
&& call.Target.Name == nameof(GameplayInputFrameController.EndMouseLook));
|
||||
AssertCallOrder(
|
||||
RequiredMethod(
|
||||
typeof(LocalPlayerTeleportController),
|
||||
nameof(LocalPlayerTeleportController.OnTeleportStarted)),
|
||||
(typeof(RuntimeWorldTransitState),
|
||||
nameof(RuntimeWorldTransitState.CanQueueTeleportStart)),
|
||||
(typeof(ILocalPlayerTeleportInputLifetime),
|
||||
nameof(ILocalPlayerTeleportInputLifetime.EndMouseLook)),
|
||||
(typeof(RuntimeWorldTransitState),
|
||||
nameof(RuntimeWorldTransitState.TryQueueTeleportStart)));
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(RequiredMethod(typeof(PlayerModeController), "Exit")),
|
||||
call => call.Target.Name == "EndMouseLook");
|
||||
|
||||
MethodInfo focus = RequiredMethod(typeof(GameWindow), "OnFocusChanged");
|
||||
Assert.Single(
|
||||
CompiledCallGraph.Read(focus),
|
||||
call => call.Target.DeclaringType == typeof(CameraPointerInputController)
|
||||
&& call.Target.Name == nameof(CameraPointerInputController.HandleFocusChanged));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
|
||||
{
|
||||
string root = FindRepoRoot();
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string networkSource = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Physics",
|
||||
"LiveEntityNetworkUpdateController.cs"));
|
||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs"));
|
||||
string frameRoot = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"FrameRootComposition.cs"));
|
||||
MethodInfo complete = RequiredMethod(
|
||||
typeof(SessionPlayerCompositionPhase),
|
||||
"CompleteSessionPlayer");
|
||||
MethodBase presentationFactory = ReferencedMethodConstructing(
|
||||
complete,
|
||||
typeof(LocalPlayerTeleportPresentation));
|
||||
Assert.NotNull(ReferencedMethodConstructing(
|
||||
presentationFactory,
|
||||
typeof(LocalPlayerTeleportController)));
|
||||
|
||||
Assert.Contains(
|
||||
"new LocalPlayerTeleportController(",
|
||||
sessionPlayer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_localPlayerTeleport!.Tick", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_teleportTransit", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_teleportAnim", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_teleportViewPlane", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_pendingTeleport", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("AimTeleportDestination", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ResetTeleportTransitState", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("PlaceTeleportArrival", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("TryActivatePendingTeleportPresentation", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Action<WorldSession.EntityPositionUpdate>", networkSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
frameRoot,
|
||||
"new LiveEntityLivenessFramePhase(",
|
||||
"session.LocalTeleport,",
|
||||
"new PlayerModeAutoEntryFramePhase(",
|
||||
"cameraFrame,",
|
||||
"live.WorldAvailability);");
|
||||
HashSet<string> removedFields =
|
||||
[
|
||||
"_teleportTransit",
|
||||
"_teleportAnim",
|
||||
"_teleportViewPlane",
|
||||
"_pendingTeleport",
|
||||
];
|
||||
Assert.DoesNotContain(
|
||||
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => removedFields.Contains(field.Name));
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||
call => call.Target.DeclaringType == typeof(LocalPlayerTeleportController)
|
||||
&& call.Target.Name == nameof(LocalPlayerTeleportController.Tick)
|
||||
|| call.Target.Name is "AimTeleportDestination"
|
||||
or "ResetTeleportTransitState"
|
||||
or "PlaceTeleportArrival"
|
||||
or "TryActivatePendingTeleportPresentation");
|
||||
|
||||
FieldInfo teleport = Assert.Single(
|
||||
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.FieldType == typeof(ILocalPlayerTeleportNetworkSink));
|
||||
Assert.Equal(typeof(ILocalPlayerTeleportNetworkSink), teleport.FieldType);
|
||||
Assert.DoesNotContain(
|
||||
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
||||
|
||||
AssertCallOrder(
|
||||
RequiredMethod(typeof(FrameRootCompositionPhase), "ComposeCore"),
|
||||
(typeof(LiveEntityLivenessFramePhase), ".ctor"),
|
||||
(typeof(SessionPlayerResult), "get_LocalTeleport"),
|
||||
(typeof(PlayerModeAutoEntryFramePhase), ".ctor"),
|
||||
(typeof(LivePresentationResult), "get_WorldAvailability"),
|
||||
(typeof(UpdateFrameOrchestrator), ".ctor"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -734,12 +674,6 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
||||
|
||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"PlayerModeController.cs"));
|
||||
// C3c (clause 4 — sealed-setter lifecycle routing): the movement
|
||||
// controller, physics body, host, and committed placement are
|
||||
// Runtime-owned, published by the first-entry conductor's
|
||||
|
|
@ -750,40 +684,45 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
// CommitPreparedPosition / `_controllerSlot.Controller =`) were
|
||||
// exactly the App-side controller construction+commit this flip
|
||||
// deleted.
|
||||
AssertAppearsInOrder(
|
||||
playerModeSource,
|
||||
"controller.IsRuntimePublished",
|
||||
"_camera.EnterChaseMode(legacyCamera, retailCamera);",
|
||||
"_shadow.SyncPose(",
|
||||
"_hostSlot.Host = playerHost;",
|
||||
"_mode.IsPlayerMode = true;");
|
||||
Assert.Contains("_shadow.Restore(playerEntity, priorShadow);", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_camera.RestoreState(priorCamera);", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
MethodInfo attach = RequiredMethod(typeof(PlayerModeController), "BuildControllerAndCamera");
|
||||
AssertNamedCallOrder(
|
||||
attach,
|
||||
("PlayerMovementController", "get_IsRuntimePublished"),
|
||||
("CameraController", "EnterChaseMode"),
|
||||
("LocalPlayerShadowSynchronizer", "SyncPose"),
|
||||
("LocalPlayerPhysicsHostSlot", "set_Host"),
|
||||
("LocalPlayerModeState", "set_IsPlayerMode"));
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(attach),
|
||||
call => call.Target.DeclaringType?.Name == "LocalPlayerShadowSynchronizer"
|
||||
&& call.Target.Name == "Restore");
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(attach),
|
||||
call => call.Target.DeclaringType?.Name == "CameraController"
|
||||
&& call.Target.Name == "RestoreState");
|
||||
|
||||
string mouseLookSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"MouseLookController.cs"));
|
||||
AssertAppearsInOrder(
|
||||
mouseLookSource,
|
||||
"controller?.StopMouseDrift",
|
||||
"retail.FilterMouseDelta",
|
||||
"_state.ApplyDelta");
|
||||
AssertNamedCallOrder(
|
||||
RequiredMethod(typeof(MouseLookController), nameof(MouseLookController.Tick)),
|
||||
("PlayerMovementController", "StopMouseDrift"),
|
||||
("RetailChaseCamera", "FilterMouseDelta"),
|
||||
("MouseLookState", "ApplyDelta"));
|
||||
|
||||
string localPlayerSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"RetailLocalPlayerFrameController.cs"));
|
||||
Assert.DoesNotContain("Func<MovementInput>", localPlayerSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("IMovementInputSource", localPlayerSource,
|
||||
StringComparison.Ordinal);
|
||||
ConstructorInfo[] localFrameConstructors =
|
||||
typeof(RetailLocalPlayerFrameController).GetConstructors(
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
Assert.NotEmpty(localFrameConstructors);
|
||||
Assert.All(
|
||||
localFrameConstructors,
|
||||
constructor =>
|
||||
{
|
||||
Type[] parameters = constructor.GetParameters()
|
||||
.Select(parameter => parameter.ParameterType)
|
||||
.ToArray();
|
||||
Assert.Contains(typeof(IMovementInputSource), parameters);
|
||||
Assert.DoesNotContain(parameters, type =>
|
||||
type.IsGenericType
|
||||
&& type.GetGenericTypeDefinition() == typeof(Func<>));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -809,66 +748,56 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
||||
}
|
||||
|
||||
string root = FindRepoRoot();
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
source,
|
||||
"_frameGraphs.Tick("));
|
||||
Assert.DoesNotContain("CanAdvanceLocalPlayer", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("GetCombatCameraTargetPoint()", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_cameraController.Fly.Update(", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_localPlayerFrame.TryGetPresentationAfterNetwork", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_cameraFrame", source, StringComparison.Ordinal);
|
||||
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
|
||||
Assert.Single(
|
||||
CompiledCallGraph.Read(onUpdate),
|
||||
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
|
||||
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
|
||||
HashSet<string> displacedCalls =
|
||||
[
|
||||
"CanAdvanceLocalPlayer",
|
||||
"GetCombatCameraTargetPoint",
|
||||
"TryGetPresentationAfterNetwork",
|
||||
];
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||
call => displacedCalls.Contains(call.Target.Name)
|
||||
|| call.Target.DeclaringType?.Name == "FlyCamera"
|
||||
&& call.Target.Name == "Update");
|
||||
Assert.DoesNotContain(
|
||||
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.Name == "_cameraFrame");
|
||||
|
||||
string cameraSource = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"CameraFrameController.cs"));
|
||||
AssertAppearsInOrder(
|
||||
cameraSource,
|
||||
"_localFrame.TryGetPresentationAfterNetwork",
|
||||
"_spatialReconciler.Reconcile();",
|
||||
"legacy.Update(",
|
||||
"retail?.Update(");
|
||||
AssertNamedCallOrder(
|
||||
RequiredMethod(typeof(CameraFrameController), nameof(CameraFrameController.Tick)),
|
||||
("RetailLocalPlayerFrameController", "TryGetPresentationAfterNetwork"),
|
||||
("ILiveSpatialReconcilePhase", "Reconcile"),
|
||||
("ChaseCamera", "Update"),
|
||||
("RetailChaseCamera", "Update"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
|
||||
{
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
source,
|
||||
"_frameGraphs.Tick("));
|
||||
Assert.DoesNotContain("_updateFrameClock.Advance(", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveFrameCoordinator", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveEntityLiveness?.Tick", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_playerModeAutoEntry?.TryEnter", source,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"private void OnUpdate(double dt)",
|
||||
"_frameProfiler.BeginStage(",
|
||||
"_frameGraphs.Tick(",
|
||||
"private void OnRender(double deltaSeconds)");
|
||||
MethodInfo update = RequiredMethod(typeof(GameWindow), "OnUpdate");
|
||||
AssertCallOrder(
|
||||
update,
|
||||
(typeof(AcDream.App.Diagnostics.FrameProfiler), "BeginStage"),
|
||||
(typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.Tick)));
|
||||
Assert.Single(
|
||||
CompiledCallGraph.Read(update),
|
||||
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
|
||||
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.Read(update),
|
||||
call => call.Target.DeclaringType == typeof(UpdateFrameClock)
|
||||
&& call.Target.Name == nameof(UpdateFrameClock.Advance)
|
||||
|| call.Target.Name is "TryEnter"
|
||||
|| call.Target.DeclaringType == typeof(LiveEntityLivenessFramePhase)
|
||||
&& call.Target.Name == nameof(LiveEntityLivenessFramePhase.Tick));
|
||||
Assert.DoesNotContain(
|
||||
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.Name == "_liveFrameCoordinator");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -931,49 +860,50 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
typeof(AcDream.App.Input.ILocalPlayerIdentitySource),
|
||||
originIdentity.FieldType);
|
||||
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string livePresentation = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Composition",
|
||||
"LivePresentationComposition.cs"));
|
||||
string sessionRuntime = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Net",
|
||||
"LiveSessionRuntimeFactory.cs"));
|
||||
Assert.DoesNotContain("PublishLocalPhysicsTimestamps", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("LoginWorldReady", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("candidate => _liveEntityHydration.OnPrune", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("CreateLiveEntitySessionSink", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private System.Numerics.Vector3 CellLocalForSeed", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("OnPlayScriptReceived", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("d.WorldOrigin.GetCenter", livePresentation,
|
||||
StringComparison.Ordinal);
|
||||
HashSet<string> displacedWindowCalls =
|
||||
[
|
||||
"PublishLocalPhysicsTimestamps",
|
||||
"LoginWorldReady",
|
||||
"OnPrune",
|
||||
"CreateLiveEntitySessionSink",
|
||||
"CreateLiveSessionEventRouter",
|
||||
"CellLocalForSeed",
|
||||
"OnPlayScriptReceived",
|
||||
];
|
||||
Assert.DoesNotContain(
|
||||
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||
call => displacedWindowCalls.Contains(call.Target.Name));
|
||||
Assert.DoesNotContain(
|
||||
typeof(GameWindow).GetMethods(
|
||||
BindingFlags.Instance | BindingFlags.Static
|
||||
| BindingFlags.Public | BindingFlags.NonPublic),
|
||||
method => displacedWindowCalls.Contains(method.Name));
|
||||
MethodBase presentationFactory = ReferencedMethodConstructing(
|
||||
RequiredMethod(typeof(LivePresentationCompositionPhase), "ComposeCore"),
|
||||
typeof(LiveEntityPresentationController));
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.ReadMethodReferences(presentationFactory),
|
||||
call => call.Target.DeclaringType == typeof(LiveWorldOriginState)
|
||||
&& call.Target.Name == nameof(LiveWorldOriginState.GetCenter));
|
||||
// C4 route 4b-3 deleted the sole consumer of
|
||||
// d.WorldOrigin.CellLocalForSeed in this file — the standalone
|
||||
// RemoteTeleportController construction. LiveWorldOriginState.
|
||||
// CellLocalForSeed itself is not deleted (LocalPlayerTeleportController
|
||||
// still uses it).
|
||||
Assert.DoesNotContain("CreateLiveSessionEventRouter", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_world.EntitySession.CreateSink()", sessionRuntime,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("GameWindow", sessionRuntime,
|
||||
StringComparison.Ordinal);
|
||||
MethodInfo eventRouter = RequiredMethod(typeof(LiveSessionRuntimeFactory), "CreateEventRouter");
|
||||
Assert.Contains(
|
||||
CompiledCallGraph.Read(eventRouter),
|
||||
call => call.Target.DeclaringType == typeof(LiveEntitySessionController)
|
||||
&& call.Target.Name == nameof(LiveEntitySessionController.CreateSink));
|
||||
Assert.DoesNotContain(
|
||||
typeof(LiveSessionRuntimeFactory).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.FieldType == typeof(GameWindow));
|
||||
Assert.DoesNotContain(
|
||||
typeof(LiveSessionRuntimeFactory).GetConstructors(
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.SelectMany(constructor => constructor.GetParameters()),
|
||||
parameter => parameter.ParameterType == typeof(GameWindow));
|
||||
}
|
||||
|
||||
private static UpdateFrameOrchestrator Create(
|
||||
|
|
@ -1151,31 +1081,68 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
public List<UpdateFrameTiming> Camera { get; } = [];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
private static MethodInfo RequiredMethod(Type owner, string name) =>
|
||||
owner.GetMethod(
|
||||
name,
|
||||
BindingFlags.Instance | BindingFlags.Static
|
||||
| BindingFlags.Public | BindingFlags.NonPublic)
|
||||
?? throw new MissingMethodException(owner.FullName, name);
|
||||
|
||||
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
||||
}
|
||||
private static MethodBase ReferencedMethodConstructing(
|
||||
MethodBase owner,
|
||||
Type constructed) =>
|
||||
Assert.Single(
|
||||
CompiledCallGraph.ReadMethodReferences(owner)
|
||||
.Select(reference => reference.Target)
|
||||
.Where(method => method.GetMethodBody() is not null)
|
||||
.Distinct(),
|
||||
method => Constructs(method, constructed));
|
||||
|
||||
private static void AssertAppearsInOrder(string source, params string[] markers)
|
||||
private static bool Constructs(MethodBase method, Type type) =>
|
||||
method.GetMethodBody() is not null
|
||||
&& CompiledCallGraph.Read(method).Any(call =>
|
||||
call.Target.DeclaringType == type && call.Target.IsConstructor);
|
||||
|
||||
private static void AssertCallOrder(
|
||||
MethodBase method,
|
||||
params (Type Type, string Method)[] expected)
|
||||
{
|
||||
int previous = -1;
|
||||
foreach (string marker in markers)
|
||||
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
|
||||
int cursor = -1;
|
||||
foreach ((Type type, string name) in expected)
|
||||
{
|
||||
int current = source.IndexOf(marker, previous + 1, StringComparison.Ordinal);
|
||||
Assert.True(current >= 0, $"Missing source marker: {marker}");
|
||||
Assert.True(current > previous, $"Out-of-order source marker: {marker}");
|
||||
previous = current;
|
||||
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
|
||||
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
|
||||
&& calls[index].Target.Name == name, -1);
|
||||
Assert.True(found > cursor,
|
||||
$"Missing compiled edge after {cursor}: {type.FullName}.{name}.");
|
||||
cursor = found;
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountOccurrences(string source, string marker) =>
|
||||
source.Split(marker, StringSplitOptions.None).Length - 1;
|
||||
private static void AssertNamedCallOrder(
|
||||
MethodBase method,
|
||||
params (string Type, string Method)[] expected)
|
||||
{
|
||||
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
|
||||
int cursor = -1;
|
||||
foreach ((string type, string name) in expected)
|
||||
{
|
||||
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
|
||||
.FirstOrDefault(index => calls[index].Target.DeclaringType?.Name == type
|
||||
&& calls[index].Target.Name == name, -1);
|
||||
Assert.True(found > cursor,
|
||||
$"Missing compiled edge after {cursor}: {type}.{name}.");
|
||||
cursor = found;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertSingleNamedCall(
|
||||
MethodBase method,
|
||||
string type,
|
||||
string name) =>
|
||||
Assert.Single(
|
||||
CompiledCallGraph.Read(method),
|
||||
call => call.Target.DeclaringType?.Name == type
|
||||
&& call.Target.Name == name);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue