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
|
attributed methods, and reduces direct/total source readers from 39/55 to
|
||||||
32/46. The remaining 46 reconcile to the 22 approved retained
|
32/46. The remaining 46 reconcile to the 22 approved retained
|
||||||
policies/contracts and 24 staged replacements.
|
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;
|
||||||
|
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;
|
namespace AcDream.App.Tests.Rendering;
|
||||||
|
|
||||||
|
|
@ -47,36 +56,40 @@ public sealed class RenderFrameResourceControllerTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Production_begin_resources_preserve_the_exact_frame_order()
|
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
|
// Campaign V slice V4a: the world-HUD and retained-UI TextRenderers no
|
||||||
// longer take a per-slot begin call — they now read the shared
|
// longer take a per-slot begin call — they now read the shared
|
||||||
// IGpuFrame ring reset once per frame by IGpuDevice.BeginFrame() (see
|
// IGpuFrame ring reset once per frame by IGpuDevice.BeginFrame() (see
|
||||||
// GpuDeviceFrameLifetime), so _worldText/_uiText are gone from this
|
// GpuDeviceFrameLifetime), so _worldText/_uiText are gone from this
|
||||||
// sequence.
|
// sequence.
|
||||||
AssertAppearsInOrder(
|
MethodInfo begin = RequiredMethod(
|
||||||
source,
|
typeof(RuntimeRenderFrameBeginResources),
|
||||||
"_textures?.BeginCompositeTextureFrame();",
|
nameof(RuntimeRenderFrameBeginResources.Begin));
|
||||||
"_textures?.TickCompositeTextureCache();",
|
AssertCallOrder(
|
||||||
"_dispatcher?.BeginFrame(gpuSlot);",
|
begin,
|
||||||
"_environmentCells?.BeginFrame(gpuSlot);",
|
(typeof(TextureCache), nameof(TextureCache.BeginCompositeTextureFrame)),
|
||||||
"_portalDepth?.BeginFrame(gpuSlot);",
|
(typeof(TextureCache), nameof(TextureCache.TickCompositeTextureCache)),
|
||||||
"_clip?.BeginFrame(gpuSlot);",
|
(typeof(WbDrawDispatcher), nameof(WbDrawDispatcher.BeginFrame)),
|
||||||
"_terrain?.BeginFrame(gpuSlot);",
|
(typeof(EnvCellRenderer), nameof(EnvCellRenderer.BeginFrame)),
|
||||||
"_lighting?.BeginFrame(gpuSlot);");
|
(typeof(PortalDepthMaskRenderer), nameof(PortalDepthMaskRenderer.BeginFrame)),
|
||||||
Assert.DoesNotContain("_profiler.FrameBoundary(", source);
|
(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]
|
[Fact]
|
||||||
public void Production_live_preparation_publishes_meshes_before_reveal_and_particles()
|
public void Production_live_preparation_publishes_meshes_before_reveal_and_particles()
|
||||||
{
|
{
|
||||||
string source = ResourceSource();
|
AssertCallOrder(
|
||||||
|
RequiredMethod(
|
||||||
AssertAppearsInOrder(
|
typeof(RuntimeRenderFrameLivePreparation),
|
||||||
source,
|
nameof(RuntimeRenderFrameLivePreparation.Prepare)),
|
||||||
"_meshes?.Tick();",
|
(typeof(WbMeshAdapter), nameof(WbMeshAdapter.Tick)),
|
||||||
"_worldReveal?.PrepareAndEvaluate(revealCell);",
|
(typeof(WorldRevealCoordinator), nameof(WorldRevealCoordinator.PrepareAndEvaluate)),
|
||||||
"_particles?.BeginFrame(gpuSlot);");
|
(typeof(ParticleRenderer), nameof(ParticleRenderer.BeginFrame)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase (the raw-GL
|
// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase (the raw-GL
|
||||||
|
|
@ -101,11 +114,18 @@ public sealed class RenderFrameResourceControllerTests
|
||||||
controller.Tick(0.5d);
|
controller.Tick(0.5d);
|
||||||
|
|
||||||
Assert.Equal(0.75d, controller.ElapsedSeconds);
|
Assert.Equal(0.75d, controller.ElapsedSeconds);
|
||||||
AssertAppearsInOrder(
|
MethodInfo tick = RequiredMethod(
|
||||||
ResourceSource(),
|
typeof(RenderWeatherFrameController),
|
||||||
"_weather.Tick(",
|
nameof(RenderWeatherFrameController.Tick));
|
||||||
"nowSeconds: _elapsedSeconds,",
|
CompiledCall weather = Assert.Single(
|
||||||
"_elapsedSeconds += deltaSeconds;");
|
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]
|
[Fact]
|
||||||
|
|
@ -127,38 +147,30 @@ public sealed class RenderFrameResourceControllerTests
|
||||||
Assert.False(live.IsWaitingForLogin);
|
Assert.False(live.IsWaitingForLogin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ResourceSource() => File.ReadAllText(Path.Combine(
|
private static MethodInfo RequiredMethod(Type owner, string name) =>
|
||||||
FindRepoRoot(),
|
owner.GetMethod(
|
||||||
"src",
|
name,
|
||||||
"AcDream.App",
|
BindingFlags.Instance | BindingFlags.Static
|
||||||
"Rendering",
|
| BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
"RenderFrameResourceController.cs"));
|
?? 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;
|
int cursor = -1;
|
||||||
foreach (string needle in needles)
|
foreach ((Type type, string name) in expected)
|
||||||
{
|
{
|
||||||
int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal);
|
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
|
||||||
Assert.True(next >= 0, $"Missing expected source fragment: {needle}");
|
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
|
||||||
Assert.True(next > cursor, $"Out-of-order source fragment: {needle}");
|
&& calls[index].Target.Name == name, -1);
|
||||||
cursor = next;
|
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
|
private sealed class AdvancingSlotSource(int firstSlot) : IRenderFrameSlotSource
|
||||||
{
|
{
|
||||||
public int ReadCount { get; private set; }
|
public int ReadCount { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using AcDream.App.Composition;
|
||||||
|
using AcDream.App.Input;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Net;
|
using AcDream.App.Net;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
using AcDream.App.Update;
|
using AcDream.App.Update;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.Runtime;
|
using AcDream.Runtime;
|
||||||
using AcDream.Runtime.Session;
|
using AcDream.Runtime.Session;
|
||||||
using AcDream.Runtime.World;
|
using AcDream.Runtime.World;
|
||||||
|
|
@ -318,104 +321,84 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ProductionFrame_PublishesPhysicsScriptTimeExactlyOnce()
|
public void ProductionFrame_PublishesPhysicsScriptTimeExactlyOnce()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
Assert.DoesNotContain(
|
||||||
string source = File.ReadAllText(Path.Combine(
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||||
root,
|
call => call.Target.Name == nameof(PhysicsScriptClockPublisher.PublishTime));
|
||||||
"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.Equal(0, CountOccurrences(source, "PublishTime("));
|
MethodInfo publish = RequiredMethod(
|
||||||
Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime("));
|
typeof(PhysicsScriptClockPublisher),
|
||||||
Assert.Contains("new PhysicsScriptClockPublisher(", frameRoot,
|
nameof(PhysicsScriptClockPublisher.PublishTime));
|
||||||
StringComparison.Ordinal);
|
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]
|
[Fact]
|
||||||
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
|
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
MethodInfo live = RequiredMethod(typeof(LiveObjectFrameController), "TickCore");
|
||||||
FindRepoRoot(),
|
AssertNamedCallOrder(
|
||||||
"src",
|
live,
|
||||||
"AcDream.App",
|
("RetailLocalPlayerFrameController", "AdvanceBeforeNetwork"),
|
||||||
"Update",
|
("SelectionInteractionController", "DrainOutbound"),
|
||||||
"LiveObjectFrameController.cs"));
|
("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(
|
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "Tick");
|
||||||
source,
|
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "ProcessHooks");
|
||||||
"_localPlayerFrame.AdvanceBeforeNetwork",
|
AssertSingleNamedCall(live, "LiveEffectFrameController", "Tick");
|
||||||
"_selectionInteractions?.DrainOutbound",
|
AssertSingleNamedCall(
|
||||||
"_animations.Tick",
|
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
|
||||||
"_staticAnimations.Tick",
|
"ParticleSystem",
|
||||||
"_animationPresenter.Present",
|
"Tick");
|
||||||
"_equippedChildren.Tick",
|
AssertSingleNamedCall(
|
||||||
"_staticAnimations.ProcessHooks",
|
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
|
||||||
"_effects.Tick");
|
"PhysicsScriptRunner",
|
||||||
AssertAppearsInOrder(
|
"Tick");
|
||||||
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("));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PlacementRetryRunsAfterStreamingAndInboundBeforeCommandReconcile()
|
public void PlacementRetryRunsAfterStreamingAndInboundBeforeCommandReconcile()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
AssertCallOrder(
|
||||||
string outer = File.ReadAllText(Path.Combine(
|
RequiredMethod(typeof(UpdateFrameOrchestrator), nameof(UpdateFrameOrchestrator.Tick)),
|
||||||
root,
|
(typeof(IStreamingFramePhase), nameof(IStreamingFramePhase.Tick)),
|
||||||
"src",
|
(typeof(IRetailLiveFramePhase), nameof(IRetailLiveFramePhase.Tick)));
|
||||||
"AcDream.App",
|
AssertCallOrder(
|
||||||
"Update",
|
RequiredMethod(typeof(RetailLiveFrameCoordinator), nameof(RetailLiveFrameCoordinator.Tick)),
|
||||||
"UpdateFrameOrchestrator.cs"));
|
(typeof(IRuntimeLiveSessionFramePhase), nameof(IRuntimeLiveSessionFramePhase.Tick)),
|
||||||
string live = File.ReadAllText(Path.Combine(
|
(typeof(IRuntimePlacementProjectionRetryPhase),
|
||||||
root,
|
nameof(IRuntimePlacementProjectionRetryPhase.RetryPending)),
|
||||||
"src",
|
(typeof(IPostNetworkCommandFramePhase),
|
||||||
"AcDream.App",
|
nameof(IPostNetworkCommandFramePhase.RunPostNetworkCommandPhase)),
|
||||||
"World",
|
(typeof(ILiveSpatialReconcilePhase), nameof(ILiveSpatialReconcilePhase.Reconcile)));
|
||||||
"RetailLiveFrameCoordinator.cs"));
|
|
||||||
|
|
||||||
AssertAppearsInOrder(
|
|
||||||
outer,
|
|
||||||
"_streaming.Tick();",
|
|
||||||
"_liveFrame.Tick(");
|
|
||||||
AssertAppearsInOrder(
|
|
||||||
live,
|
|
||||||
"_session.Tick();",
|
|
||||||
"_placementProjectionRetry?.RetryPending();",
|
|
||||||
"_localPlayer.RunPostNetworkCommandPhase();",
|
|
||||||
"_spatialReconciler.Reconcile();");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -451,205 +434,162 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_ComposesTheLiveFrameOwnersWithoutOwningTheirBodies()
|
public void GameWindow_ComposesTheLiveFrameOwnersWithoutOwningTheirBodies()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> session = CompiledCallGraph.Read(
|
||||||
FindRepoRoot(),
|
RequiredMethod(typeof(SessionPlayerCompositionPhase), "CompleteSessionPlayer"));
|
||||||
"src",
|
Assert.Contains(session, call =>
|
||||||
"AcDream.App",
|
call.Target.DeclaringType == typeof(LiveObjectFrameController)
|
||||||
"Rendering",
|
&& call.Target.IsConstructor);
|
||||||
"GameWindow.cs"));
|
Assert.Contains(session, call =>
|
||||||
string livePresentation = File.ReadAllText(Path.Combine(
|
call.Target.DeclaringType == typeof(LiveSpatialPresentationReconciler)
|
||||||
FindRepoRoot(),
|
&& call.Target.IsConstructor);
|
||||||
"src",
|
Assert.Contains(
|
||||||
"AcDream.App",
|
CompiledCallGraph.Read(RequiredMethod(
|
||||||
"Composition",
|
typeof(FrameRootCompositionPhase),
|
||||||
"LivePresentationComposition.cs"));
|
"ComposeCore")),
|
||||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
call => call.Target.DeclaringType == typeof(RetailLiveFrameCoordinator)
|
||||||
FindRepoRoot(),
|
&& call.Target.IsConstructor);
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"SessionPlayerComposition.cs"));
|
|
||||||
string frameRoot = File.ReadAllText(Path.Combine(
|
|
||||||
FindRepoRoot(),
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"FrameRootComposition.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new LiveObjectFrameController(", sessionPlayer);
|
IReadOnlyList<CompiledCall> windowCalls =
|
||||||
Assert.Contains("new LiveSpatialPresentationReconciler(", sessionPlayer);
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
Assert.Contains("new RetailLiveFrameCoordinator(", frameRoot);
|
Assert.DoesNotContain(windowCalls, call =>
|
||||||
Assert.DoesNotContain("AdvanceLiveObjectRuntime", source, StringComparison.Ordinal);
|
call.Target.Name is "AdvanceLiveObjectRuntime"
|
||||||
Assert.DoesNotContain("ReconcileLiveObjectSpatialPresentation", source,
|
or "ReconcileLiveObjectSpatialPresentation");
|
||||||
StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("ILiveAnimationPresentationContext", source,
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
StringComparison.Ordinal);
|
field => field.FieldType == typeof(ILiveAnimationPresentationContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_DelegatesTheCompleteStreamingFrameBody()
|
public void GameWindow_DelegatesTheCompleteStreamingFrameBody()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
Assert.Contains(
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.Read(RequiredMethod(
|
||||||
"src",
|
typeof(SessionPlayerCompositionPhase),
|
||||||
"AcDream.App",
|
"CompleteSessionPlayer")),
|
||||||
"Rendering",
|
call => call.Target.DeclaringType == typeof(StreamingFrameController)
|
||||||
"GameWindow.cs"));
|
&& call.Target.IsConstructor);
|
||||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
Assert.DoesNotContain(
|
||||||
FindRepoRoot(),
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
"src",
|
field => field.Name == "_streamingFrame");
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
|
||||||
"SessionPlayerComposition.cs"));
|
Assert.Single(
|
||||||
Assert.Contains("new StreamingFrameController(", sessionPlayer);
|
CompiledCallGraph.Read(onUpdate),
|
||||||
Assert.DoesNotContain("_streamingFrame", source, StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
|
||||||
Assert.Equal(1, CountOccurrences(
|
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
|
||||||
source,
|
Assert.DoesNotContain(
|
||||||
"_frameGraphs.Tick("));
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||||
Assert.DoesNotContain("_streamingController.Tick(observerCx", source,
|
call => call.Target.Name is "Compute" or "DrainRescued"
|
||||||
StringComparison.Ordinal);
|
|| call.Target.DeclaringType == typeof(StreamingController)
|
||||||
Assert.DoesNotContain("DungeonStreamingGate.Compute", source,
|
&& call.Target.Name == nameof(StreamingController.Tick));
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain("DrainRescued()", source, StringComparison.Ordinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_DelegatesTheCompleteGameplayInputFrameBody()
|
public void GameWindow_DelegatesTheCompleteGameplayInputFrameBody()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
Assert.Contains(
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.Read(RequiredMethod(
|
||||||
"src",
|
typeof(SessionPlayerCompositionPhase),
|
||||||
"AcDream.App",
|
"CompleteSessionPlayer")),
|
||||||
"Rendering",
|
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
|
||||||
"GameWindow.cs"));
|
&& call.Target.IsConstructor);
|
||||||
string sessionPlayer = File.ReadAllText(Path.Combine(
|
|
||||||
FindRepoRoot(),
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"SessionPlayerComposition.cs"));
|
|
||||||
|
|
||||||
Assert.Contains("new GameplayInputFrameController(", sessionPlayer);
|
HashSet<string> displacedWindowCalls =
|
||||||
Assert.DoesNotContain("_gameplayInputFrame!.Tick", source,
|
[
|
||||||
StringComparison.Ordinal);
|
"TryTakeRawSample",
|
||||||
Assert.DoesNotContain("_inputDispatcher?.Tick()", source,
|
"CaptureMovementInput",
|
||||||
StringComparison.Ordinal);
|
"EndMouseLookAndRestoreCursor",
|
||||||
Assert.DoesNotContain("TryTakeRawSample", source,
|
"HideCursorForMouseLook",
|
||||||
StringComparison.Ordinal);
|
"RestoreCursorAfterMouseLook",
|
||||||
Assert.DoesNotContain("_combatAttackController?.Tick()", source,
|
"CanStartLiveCombatAttack",
|
||||||
StringComparison.Ordinal);
|
"SendLiveCombatAttack",
|
||||||
Assert.DoesNotContain("CaptureMovementInput", source,
|
"PreparePlayerForAttackRequest",
|
||||||
StringComparison.Ordinal);
|
"DumpMovementTruthOutbound",
|
||||||
Assert.DoesNotContain("EndMouseLookAndRestoreCursor", source,
|
"DumpMovementTruthServerEcho",
|
||||||
StringComparison.Ordinal);
|
"EndMouseLook",
|
||||||
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);
|
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"_gameplayInputFrame?.EndMouseLook",
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||||
source,
|
call => displacedWindowCalls.Contains(call.Target.Name)
|
||||||
StringComparison.Ordinal);
|
|| call.Target.DeclaringType == typeof(GameplayInputFrameController)
|
||||||
string pointerSource = File.ReadAllText(Path.Combine(
|
&& call.Target.Name == nameof(GameplayInputFrameController.Tick));
|
||||||
FindRepoRoot(),
|
|
||||||
"src",
|
Assert.Contains(
|
||||||
"AcDream.App",
|
CompiledCallGraph.Read(RequiredMethod(
|
||||||
"Input",
|
typeof(CameraPointerInputController),
|
||||||
"CameraPointerInputController.cs"));
|
nameof(CameraPointerInputController.HandleFocusChanged))),
|
||||||
Assert.Contains("_gameplayFrame?.EndMouseLook();", pointerSource,
|
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == nameof(GameplayInputFrameController.EndMouseLook));
|
||||||
string teleportSource = File.ReadAllText(Path.Combine(
|
AssertCallOrder(
|
||||||
FindRepoRoot(),
|
RequiredMethod(
|
||||||
"src",
|
typeof(LocalPlayerTeleportController),
|
||||||
"AcDream.App",
|
nameof(LocalPlayerTeleportController.OnTeleportStarted)),
|
||||||
"Streaming",
|
(typeof(RuntimeWorldTransitState),
|
||||||
"LocalPlayerTeleportController.cs"));
|
nameof(RuntimeWorldTransitState.CanQueueTeleportStart)),
|
||||||
AssertAppearsInOrder(
|
(typeof(ILocalPlayerTeleportInputLifetime),
|
||||||
teleportSource,
|
nameof(ILocalPlayerTeleportInputLifetime.EndMouseLook)),
|
||||||
"_transit.CanQueueTeleportStart(teleportSequence)",
|
(typeof(RuntimeWorldTransitState),
|
||||||
"_input.EndMouseLook();",
|
nameof(RuntimeWorldTransitState.TryQueueTeleportStart)));
|
||||||
"_transit.TryQueueTeleportStart(teleportSequence)");
|
Assert.Contains(
|
||||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
CompiledCallGraph.Read(RequiredMethod(typeof(PlayerModeController), "Exit")),
|
||||||
FindRepoRoot(),
|
call => call.Target.Name == "EndMouseLook");
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
MethodInfo focus = RequiredMethod(typeof(GameWindow), "OnFocusChanged");
|
||||||
"Input",
|
Assert.Single(
|
||||||
"PlayerModeController.cs"));
|
CompiledCallGraph.Read(focus),
|
||||||
Assert.Contains("_input.EndMouseLook();", playerModeSource,
|
call => call.Target.DeclaringType == typeof(CameraPointerInputController)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == nameof(CameraPointerInputController.HandleFocusChanged));
|
||||||
AssertAppearsInOrder(
|
|
||||||
source,
|
|
||||||
"private void OnFocusChanged(bool focused)",
|
|
||||||
"_cameraPointerInput?.HandleFocusChanged(focused);");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
|
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
MethodInfo complete = RequiredMethod(
|
||||||
string source = File.ReadAllText(Path.Combine(
|
typeof(SessionPlayerCompositionPhase),
|
||||||
root,
|
"CompleteSessionPlayer");
|
||||||
"src",
|
MethodBase presentationFactory = ReferencedMethodConstructing(
|
||||||
"AcDream.App",
|
complete,
|
||||||
"Rendering",
|
typeof(LocalPlayerTeleportPresentation));
|
||||||
"GameWindow.cs"));
|
Assert.NotNull(ReferencedMethodConstructing(
|
||||||
string networkSource = File.ReadAllText(Path.Combine(
|
presentationFactory,
|
||||||
root,
|
typeof(LocalPlayerTeleportController)));
|
||||||
"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"));
|
|
||||||
|
|
||||||
Assert.Contains(
|
HashSet<string> removedFields =
|
||||||
"new LocalPlayerTeleportController(",
|
[
|
||||||
sessionPlayer,
|
"_teleportTransit",
|
||||||
StringComparison.Ordinal);
|
"_teleportAnim",
|
||||||
Assert.DoesNotContain("_localPlayerTeleport!.Tick", source,
|
"_teleportViewPlane",
|
||||||
StringComparison.Ordinal);
|
"_pendingTeleport",
|
||||||
Assert.DoesNotContain("_teleportTransit", source, StringComparison.Ordinal);
|
];
|
||||||
Assert.DoesNotContain("_teleportAnim", source, StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("_teleportViewPlane", source, StringComparison.Ordinal);
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
Assert.DoesNotContain("_pendingTeleport", source, StringComparison.Ordinal);
|
field => removedFields.Contains(field.Name));
|
||||||
Assert.DoesNotContain("AimTeleportDestination", source, StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("ResetTeleportTransitState", source, StringComparison.Ordinal);
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||||
Assert.DoesNotContain("PlaceTeleportArrival", source, StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(LocalPlayerTeleportController)
|
||||||
Assert.DoesNotContain("TryActivatePendingTeleportPresentation", source,
|
&& call.Target.Name == nameof(LocalPlayerTeleportController.Tick)
|
||||||
StringComparison.Ordinal);
|
|| call.Target.Name is "AimTeleportDestination"
|
||||||
Assert.DoesNotContain("Action<WorldSession.EntityPositionUpdate>", networkSource,
|
or "ResetTeleportTransitState"
|
||||||
StringComparison.Ordinal);
|
or "PlaceTeleportArrival"
|
||||||
Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource,
|
or "TryActivatePendingTeleportPresentation");
|
||||||
StringComparison.Ordinal);
|
|
||||||
AssertAppearsInOrder(
|
FieldInfo teleport = Assert.Single(
|
||||||
frameRoot,
|
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
|
||||||
"new LiveEntityLivenessFramePhase(",
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
"session.LocalTeleport,",
|
field => field.FieldType == typeof(ILocalPlayerTeleportNetworkSink));
|
||||||
"new PlayerModeAutoEntryFramePhase(",
|
Assert.Equal(typeof(ILocalPlayerTeleportNetworkSink), teleport.FieldType);
|
||||||
"cameraFrame,",
|
Assert.DoesNotContain(
|
||||||
"live.WorldAvailability);");
|
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]
|
[Fact]
|
||||||
|
|
@ -734,12 +674,6 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
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
|
// C3c (clause 4 — sealed-setter lifecycle routing): the movement
|
||||||
// controller, physics body, host, and committed placement are
|
// controller, physics body, host, and committed placement are
|
||||||
// Runtime-owned, published by the first-entry conductor's
|
// Runtime-owned, published by the first-entry conductor's
|
||||||
|
|
@ -750,40 +684,45 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
// CommitPreparedPosition / `_controllerSlot.Controller =`) were
|
// CommitPreparedPosition / `_controllerSlot.Controller =`) were
|
||||||
// exactly the App-side controller construction+commit this flip
|
// exactly the App-side controller construction+commit this flip
|
||||||
// deleted.
|
// deleted.
|
||||||
AssertAppearsInOrder(
|
MethodInfo attach = RequiredMethod(typeof(PlayerModeController), "BuildControllerAndCamera");
|
||||||
playerModeSource,
|
AssertNamedCallOrder(
|
||||||
"controller.IsRuntimePublished",
|
attach,
|
||||||
"_camera.EnterChaseMode(legacyCamera, retailCamera);",
|
("PlayerMovementController", "get_IsRuntimePublished"),
|
||||||
"_shadow.SyncPose(",
|
("CameraController", "EnterChaseMode"),
|
||||||
"_hostSlot.Host = playerHost;",
|
("LocalPlayerShadowSynchronizer", "SyncPose"),
|
||||||
"_mode.IsPlayerMode = true;");
|
("LocalPlayerPhysicsHostSlot", "set_Host"),
|
||||||
Assert.Contains("_shadow.Restore(playerEntity, priorShadow);", playerModeSource,
|
("LocalPlayerModeState", "set_IsPlayerMode"));
|
||||||
StringComparison.Ordinal);
|
Assert.Contains(
|
||||||
Assert.Contains("_camera.RestoreState(priorCamera);", playerModeSource,
|
CompiledCallGraph.Read(attach),
|
||||||
StringComparison.Ordinal);
|
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(
|
AssertNamedCallOrder(
|
||||||
FindRepoRoot(),
|
RequiredMethod(typeof(MouseLookController), nameof(MouseLookController.Tick)),
|
||||||
"src",
|
("PlayerMovementController", "StopMouseDrift"),
|
||||||
"AcDream.App",
|
("RetailChaseCamera", "FilterMouseDelta"),
|
||||||
"Input",
|
("MouseLookState", "ApplyDelta"));
|
||||||
"MouseLookController.cs"));
|
|
||||||
AssertAppearsInOrder(
|
|
||||||
mouseLookSource,
|
|
||||||
"controller?.StopMouseDrift",
|
|
||||||
"retail.FilterMouseDelta",
|
|
||||||
"_state.ApplyDelta");
|
|
||||||
|
|
||||||
string localPlayerSource = File.ReadAllText(Path.Combine(
|
ConstructorInfo[] localFrameConstructors =
|
||||||
FindRepoRoot(),
|
typeof(RetailLocalPlayerFrameController).GetConstructors(
|
||||||
"src",
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
"AcDream.App",
|
Assert.NotEmpty(localFrameConstructors);
|
||||||
"Input",
|
Assert.All(
|
||||||
"RetailLocalPlayerFrameController.cs"));
|
localFrameConstructors,
|
||||||
Assert.DoesNotContain("Func<MovementInput>", localPlayerSource,
|
constructor =>
|
||||||
StringComparison.Ordinal);
|
{
|
||||||
Assert.Contains("IMovementInputSource", localPlayerSource,
|
Type[] parameters = constructor.GetParameters()
|
||||||
StringComparison.Ordinal);
|
.Select(parameter => parameter.ParameterType)
|
||||||
|
.ToArray();
|
||||||
|
Assert.Contains(typeof(IMovementInputSource), parameters);
|
||||||
|
Assert.DoesNotContain(parameters, type =>
|
||||||
|
type.IsGenericType
|
||||||
|
&& type.GetGenericTypeDefinition() == typeof(Func<>));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -809,66 +748,56 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
||||||
}
|
}
|
||||||
|
|
||||||
string root = FindRepoRoot();
|
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
|
||||||
string source = File.ReadAllText(Path.Combine(
|
Assert.Single(
|
||||||
root,
|
CompiledCallGraph.Read(onUpdate),
|
||||||
"src",
|
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
|
||||||
"AcDream.App",
|
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
|
||||||
"Rendering",
|
HashSet<string> displacedCalls =
|
||||||
"GameWindow.cs"));
|
[
|
||||||
Assert.Equal(1, CountOccurrences(
|
"CanAdvanceLocalPlayer",
|
||||||
source,
|
"GetCombatCameraTargetPoint",
|
||||||
"_frameGraphs.Tick("));
|
"TryGetPresentationAfterNetwork",
|
||||||
Assert.DoesNotContain("CanAdvanceLocalPlayer", source, StringComparison.Ordinal);
|
];
|
||||||
Assert.DoesNotContain("GetCombatCameraTargetPoint()", source,
|
Assert.DoesNotContain(
|
||||||
StringComparison.Ordinal);
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||||
Assert.DoesNotContain("_cameraController.Fly.Update(", source,
|
call => displacedCalls.Contains(call.Target.Name)
|
||||||
StringComparison.Ordinal);
|
|| call.Target.DeclaringType?.Name == "FlyCamera"
|
||||||
Assert.DoesNotContain("_localPlayerFrame.TryGetPresentationAfterNetwork", source,
|
&& call.Target.Name == "Update");
|
||||||
StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("_cameraFrame", source, StringComparison.Ordinal);
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
|
field => field.Name == "_cameraFrame");
|
||||||
|
|
||||||
string cameraSource = File.ReadAllText(Path.Combine(
|
AssertNamedCallOrder(
|
||||||
root,
|
RequiredMethod(typeof(CameraFrameController), nameof(CameraFrameController.Tick)),
|
||||||
"src",
|
("RetailLocalPlayerFrameController", "TryGetPresentationAfterNetwork"),
|
||||||
"AcDream.App",
|
("ILiveSpatialReconcilePhase", "Reconcile"),
|
||||||
"Rendering",
|
("ChaseCamera", "Update"),
|
||||||
"CameraFrameController.cs"));
|
("RetailChaseCamera", "Update"));
|
||||||
AssertAppearsInOrder(
|
|
||||||
cameraSource,
|
|
||||||
"_localFrame.TryGetPresentationAfterNetwork",
|
|
||||||
"_spatialReconciler.Reconcile();",
|
|
||||||
"legacy.Update(",
|
|
||||||
"retail?.Update(");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
|
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
MethodInfo update = RequiredMethod(typeof(GameWindow), "OnUpdate");
|
||||||
FindRepoRoot(),
|
AssertCallOrder(
|
||||||
"src",
|
update,
|
||||||
"AcDream.App",
|
(typeof(AcDream.App.Diagnostics.FrameProfiler), "BeginStage"),
|
||||||
"Rendering",
|
(typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.Tick)));
|
||||||
"GameWindow.cs"));
|
Assert.Single(
|
||||||
|
CompiledCallGraph.Read(update),
|
||||||
Assert.Equal(1, CountOccurrences(
|
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
|
||||||
source,
|
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
|
||||||
"_frameGraphs.Tick("));
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("_updateFrameClock.Advance(", source,
|
CompiledCallGraph.Read(update),
|
||||||
StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(UpdateFrameClock)
|
||||||
Assert.DoesNotContain("_liveFrameCoordinator", source,
|
&& call.Target.Name == nameof(UpdateFrameClock.Advance)
|
||||||
StringComparison.Ordinal);
|
|| call.Target.Name is "TryEnter"
|
||||||
Assert.DoesNotContain("_liveEntityLiveness?.Tick", source,
|
|| call.Target.DeclaringType == typeof(LiveEntityLivenessFramePhase)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == nameof(LiveEntityLivenessFramePhase.Tick));
|
||||||
Assert.DoesNotContain("_playerModeAutoEntry?.TryEnter", source,
|
Assert.DoesNotContain(
|
||||||
StringComparison.Ordinal);
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
AssertAppearsInOrder(
|
field => field.Name == "_liveFrameCoordinator");
|
||||||
source,
|
|
||||||
"private void OnUpdate(double dt)",
|
|
||||||
"_frameProfiler.BeginStage(",
|
|
||||||
"_frameGraphs.Tick(",
|
|
||||||
"private void OnRender(double deltaSeconds)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -931,49 +860,50 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
typeof(AcDream.App.Input.ILocalPlayerIdentitySource),
|
typeof(AcDream.App.Input.ILocalPlayerIdentitySource),
|
||||||
originIdentity.FieldType);
|
originIdentity.FieldType);
|
||||||
|
|
||||||
string source = File.ReadAllText(Path.Combine(
|
HashSet<string> displacedWindowCalls =
|
||||||
FindRepoRoot(),
|
[
|
||||||
"src",
|
"PublishLocalPhysicsTimestamps",
|
||||||
"AcDream.App",
|
"LoginWorldReady",
|
||||||
"Rendering",
|
"OnPrune",
|
||||||
"GameWindow.cs"));
|
"CreateLiveEntitySessionSink",
|
||||||
string livePresentation = File.ReadAllText(Path.Combine(
|
"CreateLiveSessionEventRouter",
|
||||||
FindRepoRoot(),
|
"CellLocalForSeed",
|
||||||
"src",
|
"OnPlayScriptReceived",
|
||||||
"AcDream.App",
|
];
|
||||||
"Composition",
|
Assert.DoesNotContain(
|
||||||
"LivePresentationComposition.cs"));
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
|
||||||
string sessionRuntime = File.ReadAllText(Path.Combine(
|
call => displacedWindowCalls.Contains(call.Target.Name));
|
||||||
FindRepoRoot(),
|
Assert.DoesNotContain(
|
||||||
"src",
|
typeof(GameWindow).GetMethods(
|
||||||
"AcDream.App",
|
BindingFlags.Instance | BindingFlags.Static
|
||||||
"Net",
|
| BindingFlags.Public | BindingFlags.NonPublic),
|
||||||
"LiveSessionRuntimeFactory.cs"));
|
method => displacedWindowCalls.Contains(method.Name));
|
||||||
Assert.DoesNotContain("PublishLocalPhysicsTimestamps", source,
|
MethodBase presentationFactory = ReferencedMethodConstructing(
|
||||||
StringComparison.Ordinal);
|
RequiredMethod(typeof(LivePresentationCompositionPhase), "ComposeCore"),
|
||||||
Assert.DoesNotContain("LoginWorldReady", source,
|
typeof(LiveEntityPresentationController));
|
||||||
StringComparison.Ordinal);
|
Assert.Contains(
|
||||||
Assert.DoesNotContain("candidate => _liveEntityHydration.OnPrune", source,
|
CompiledCallGraph.ReadMethodReferences(presentationFactory),
|
||||||
StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(LiveWorldOriginState)
|
||||||
Assert.DoesNotContain("CreateLiveEntitySessionSink", source,
|
&& call.Target.Name == nameof(LiveWorldOriginState.GetCenter));
|
||||||
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);
|
|
||||||
// C4 route 4b-3 deleted the sole consumer of
|
// C4 route 4b-3 deleted the sole consumer of
|
||||||
// d.WorldOrigin.CellLocalForSeed in this file — the standalone
|
// d.WorldOrigin.CellLocalForSeed in this file — the standalone
|
||||||
// RemoteTeleportController construction. LiveWorldOriginState.
|
// RemoteTeleportController construction. LiveWorldOriginState.
|
||||||
// CellLocalForSeed itself is not deleted (LocalPlayerTeleportController
|
// CellLocalForSeed itself is not deleted (LocalPlayerTeleportController
|
||||||
// still uses it).
|
// still uses it).
|
||||||
Assert.DoesNotContain("CreateLiveSessionEventRouter", source,
|
MethodInfo eventRouter = RequiredMethod(typeof(LiveSessionRuntimeFactory), "CreateEventRouter");
|
||||||
StringComparison.Ordinal);
|
Assert.Contains(
|
||||||
Assert.Contains("_world.EntitySession.CreateSink()", sessionRuntime,
|
CompiledCallGraph.Read(eventRouter),
|
||||||
StringComparison.Ordinal);
|
call => call.Target.DeclaringType == typeof(LiveEntitySessionController)
|
||||||
Assert.DoesNotContain("GameWindow", sessionRuntime,
|
&& call.Target.Name == nameof(LiveEntitySessionController.CreateSink));
|
||||||
StringComparison.Ordinal);
|
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(
|
private static UpdateFrameOrchestrator Create(
|
||||||
|
|
@ -1151,31 +1081,68 @@ public sealed class UpdateFrameOrchestratorTests
|
||||||
public List<UpdateFrameTiming> Camera { get; } = [];
|
public List<UpdateFrameTiming> Camera { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindRepoRoot()
|
private static MethodInfo RequiredMethod(Type owner, string name) =>
|
||||||
{
|
owner.GetMethod(
|
||||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
name,
|
||||||
while (directory is not null)
|
BindingFlags.Instance | BindingFlags.Static
|
||||||
{
|
| BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
?? throw new MissingMethodException(owner.FullName, name);
|
||||||
return directory.FullName;
|
|
||||||
directory = directory.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
|
||||||
foreach (string marker in markers)
|
int cursor = -1;
|
||||||
|
foreach ((Type type, string name) in expected)
|
||||||
{
|
{
|
||||||
int current = source.IndexOf(marker, previous + 1, StringComparison.Ordinal);
|
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
|
||||||
Assert.True(current >= 0, $"Missing source marker: {marker}");
|
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
|
||||||
Assert.True(current > previous, $"Out-of-order source marker: {marker}");
|
&& calls[index].Target.Name == name, -1);
|
||||||
previous = current;
|
Assert.True(found > cursor,
|
||||||
|
$"Missing compiled edge after {cursor}: {type.FullName}.{name}.");
|
||||||
|
cursor = found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CountOccurrences(string source, string marker) =>
|
private static void AssertNamedCallOrder(
|
||||||
source.Split(marker, StringSplitOptions.None).Length - 1;
|
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