test: replace streaming source freezes
This commit is contained in:
parent
5a33369074
commit
0ad2ee1cdf
6 changed files with 436 additions and 285 deletions
|
|
@ -38,12 +38,12 @@ pwsh ./tools/audit-test-inventory.ps1
|
||||||
The generated JSON lives under ignored `artifacts/test-audit/`; it is not a
|
The generated JSON lives under ignored `artifacts/test-audit/`; it is not a
|
||||||
second 6.8 MB checked-in source of truth. The script and this reviewed ledger
|
second 6.8 MB checked-in source of truth. The script and this reviewed ledger
|
||||||
are durable, while paths and line numbers regenerate after every batch. The
|
are durable, while paths and line numbers regenerate after every batch. The
|
||||||
inventory refreshed through Batch W reports:
|
inventory refreshed through Batch X reports:
|
||||||
|
|
||||||
| Syntax signal | Current count |
|
| Syntax signal | Current count |
|
||||||
|---|---:|
|
|---|---:|
|
||||||
| Tracked/parsed C# test files | 1,254 / 1,254 |
|
| Tracked/parsed C# test files | 1,254 / 1,254 |
|
||||||
| Attributed test methods (not expanded theory cases) | 11,416 |
|
| Attributed test methods (not expanded theory cases) | 11,417 |
|
||||||
| Exact duplicate-data rows | 0 |
|
| Exact duplicate-data rows | 0 |
|
||||||
| Reviewed body-equivalent groups / methods | 11 / 27 |
|
| Reviewed body-equivalent groups / methods | 11 / 27 |
|
||||||
| Methods containing at least one empty `return;` | 138 |
|
| Methods containing at least one empty `return;` | 138 |
|
||||||
|
|
@ -56,7 +56,7 @@ inventory refreshed through Batch W reports:
|
||||||
| Methods directly using `Thread.Sleep` / `Task.Delay` | 15 / 14 |
|
| Methods directly using `Thread.Sleep` / `Task.Delay` | 15 / 14 |
|
||||||
| Cancellable-infinite-only / elapsed-time methods | 5 / 24 |
|
| Cancellable-infinite-only / elapsed-time methods | 5 / 24 |
|
||||||
| Methods directly reading environment variables | 47 |
|
| Methods directly reading environment variables | 47 |
|
||||||
| Methods directly / through same-file helpers reading `.cs` source text | 63 / 98 |
|
| Methods directly / through same-file helpers reading `.cs` source text | 57 / 92 |
|
||||||
|
|
||||||
The three remaining prerequisite-return candidates are all reviewed branch
|
The three remaining prerequisite-return candidates are all reviewed branch
|
||||||
false positives: the two Windows/Linux factory assertions and the launcher's
|
false positives: the two Windows/Linux factory assertions and the launcher's
|
||||||
|
|
@ -140,7 +140,7 @@ identifiable in the lane report.
|
||||||
| T-009 wall-clock double-click tests | resolved in batch E | Four sleeps were replaced by a deterministic test clock behind an internal factory overload. The production factory still reads `Environment.TickCount64` exactly as before. |
|
| T-009 wall-clock double-click tests | resolved in batch E | Four sleeps were replaced by a deterministic test clock behind an internal factory overload. The production factory still reads `Environment.TickCount64` exactly as before. |
|
||||||
| T-010 two useless cases | high-confidence cleanup batch A | Delete `SmokeTest.TestProject_IsWired` and `ChaseCameraTests.ImplementsICamera`; compilation already proves both claims. |
|
| T-010 two useless cases | high-confidence cleanup batch A | Delete `SmokeTest.TestProject_IsWired` and `ChaseCameraTests.ImplementsICamera`; compilation already proves both claims. |
|
||||||
| T-011 diagnostic-only methods | resolved in batches C, L, and R | The reviewed current set is 82 methods / 103 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Batch R catches investigations whose only assertion validates fixture/DAT availability, which the original mechanical output-only scan could not distinguish from an oracle. |
|
| T-011 diagnostic-only methods | resolved in batches C, L, and R | The reviewed current set is 82 methods / 103 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Batch R catches investigations whose only assertion validates fixture/DAT availability, which the original mechanical output-only scan could not distinguish from an oracle. |
|
||||||
| T-012 source-text freezes | direct-read map completed in batch I, helper-mediated gap corrected in batch U, and staged replacement active | Seventeen whole-tree architecture rules and five cross-artifact contracts stay. Batch W retires the first nine literal implementation freezes beside stronger behavior or compiled-metadata evidence; 76 staged replacements remain. |
|
| T-012 source-text freezes | direct-read map completed in batch I, helper-mediated gap corrected in batch U, and staged replacement active | Seventeen whole-tree architecture rules and five cross-artifact contracts stay. Batches W/X retire the first 15 literal implementation freezes beside stronger behavior or compiled-metadata evidence; 70 staged replacements remain. |
|
||||||
| T-013 controller self-comparison | high-confidence cleanup batch A | Capture the first controller next to the first body and compare every retry with that reference. |
|
| T-013 controller self-comparison | high-confidence cleanup batch A | Capture the first controller next to the first body and compare every retry with that reference. |
|
||||||
| T-014 seven load-sensitive tests | six mechanisms repaired in batch F; one product defect classified | Virtual/wall-clock mixing, tiered-JIT allocation noise, a live controller clock, and a ThreadPool-start timing oracle were removed without weakening behavioral contracts. `DatSoundCache` #321 is a real in-flight-entry race and now runs as `Status=KnownFailure` pending a product fix. |
|
| T-014 seven load-sensitive tests | six mechanisms repaired in batch F; one product defect classified | Virtual/wall-clock mixing, tiered-JIT allocation noise, a live controller clock, and a ThreadPool-start timing oracle were removed without weakening behavioral contracts. `DatSoundCache` #321 is a real in-flight-entry race and now runs as `Status=KnownFailure` pending a product fix. |
|
||||||
| T-015 four non-prerequisite skips | resolved in batches A/B | PVS scaffold deleted with rationale preserved; redundant chat/radar generators deleted in favor of the comprehensive Manual lane; tower oracle is `Status=KnownFailure`. |
|
| T-015 four non-prerequisite skips | resolved in batches A/B | PVS scaffold deleted with rationale preserved; redundant chat/radar generators deleted in favor of the comprehensive Manual lane; tower oracle is `Status=KnownFailure`. |
|
||||||
|
|
@ -1085,3 +1085,67 @@ Verification:
|
||||||
source readers from 67 to 63 and total direct/helper readers from 107 to 98.
|
source readers from 67 to 63 and total direct/helper readers from 107 to 98.
|
||||||
The remaining 98 reconcile to the 22 approved retained policies/contracts
|
The remaining 98 reconcile to the 22 approved retained policies/contracts
|
||||||
and 76 staged replacements.
|
and 76 staged replacements.
|
||||||
|
|
||||||
|
## Batch X streaming publication and live-entity source-freeze replacement
|
||||||
|
|
||||||
|
Batch X converts the four approved streaming-publication freezes and both
|
||||||
|
live-entity composition freezes. No product source changes. The tests now
|
||||||
|
inspect the built dependency graph, execute the real origin publishers, or
|
||||||
|
assert the typed owner/reset calls instead of matching C# fragments.
|
||||||
|
|
||||||
|
The conversion found one materially misleading old assertion. The physics
|
||||||
|
publisher test rejected the literal strings
|
||||||
|
`_physicsDataCache.RemoveCellsForLandblock` and
|
||||||
|
`_physicsDataCache.RemoveBuildingsForLandblock`. Those fragments were absent,
|
||||||
|
but the real publisher correctly performs both removals through
|
||||||
|
`publication.StagingCache`; the test therefore passed without establishing its
|
||||||
|
intended generation-isolation rule. The replacement verifies the compiled
|
||||||
|
`AdvanceBeginOne` path obtains the staging cache before each removal, commits
|
||||||
|
through `RuntimePhysicsState.CommitCollisionGeneration`, does not restart the
|
||||||
|
retained-owner capture, and keeps the old publication bodies out of
|
||||||
|
`GameWindow`.
|
||||||
|
|
||||||
|
The other exact decisions are:
|
||||||
|
|
||||||
|
- the render publisher boundary now scans every compiled GameWindow method and
|
||||||
|
constructor for forbidden terrain, cell-visibility, and EnvCell publication
|
||||||
|
calls, and reflects the removed mesh-preparation/body ownership;
|
||||||
|
- the former umbrella origin test is narrowed to the claim its fixture could
|
||||||
|
not observe: production `LivePresentationCompositionPhase` constructs the
|
||||||
|
render, physics, and static publishers before the one presentation pipeline,
|
||||||
|
while GameWindow retains only that pipeline. Captured origin itself remains
|
||||||
|
covered by the real `LandblockBuildFactory.BuildFar` and
|
||||||
|
`LandblockRenderPublisher.BeginPublication` behavior tests;
|
||||||
|
- recenter ordering is now its own named compiled-call contract: old-window
|
||||||
|
retirement completion precedes `LiveWorldOriginState.Recenter`, which
|
||||||
|
precedes destination commit. The extensive recenter transaction suite still
|
||||||
|
owns retry, replacement, session-reset, and retirement behavior;
|
||||||
|
- the shutdown test reads the compiled operation manifest, including delegate
|
||||||
|
targets, to prove Runtime session stop precedes streamer disposal. It also
|
||||||
|
proves `GameRuntime.StopSession` disposes the live session before checking
|
||||||
|
its completion barrier and retains the re-entrant diagnostic message;
|
||||||
|
- live animation/projectile composition now reflects the exact typed fields
|
||||||
|
and proves `CompletePresentation` constructs `DatProjectileSetupResolver`
|
||||||
|
before `ProjectileController`, with no window closure or remote-motion-view
|
||||||
|
owner; and
|
||||||
|
- the session reset test follows compiled calls through
|
||||||
|
`LiveSessionRuntimeFactory`, `PlayerModeController`, the network-update
|
||||||
|
owner, and `LiveWorldOriginState`, rather than pinning four statement strings.
|
||||||
|
|
||||||
|
`CompiledCallGraph` gained reusable readers for every declared method,
|
||||||
|
delegate method targets, and compiled string literals. These are test-only
|
||||||
|
metadata facilities used when invoking the real process/window graph would
|
||||||
|
mutate external resources.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- the four directly affected owner suites pass 111/111;
|
||||||
|
- the complete locked Release build covers all 44 projects with zero warnings
|
||||||
|
and zero errors;
|
||||||
|
- the no-retry complete hermetic Release gate passes 14,349/14,349 with zero
|
||||||
|
skips or failures across all 12 test assemblies. The one-case increase is the
|
||||||
|
newly separated recenter-order contract; and
|
||||||
|
- the regenerated 1,254-file inventory parses every file, reports 11,417
|
||||||
|
attributed methods, and reduces direct/total source readers from 63/98 to
|
||||||
|
57/92. The remaining 92 reconcile to 22 approved retained
|
||||||
|
policies/contracts and 70 staged replacements.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,69 @@ internal static class CompiledCallGraph
|
||||||
.ToDictionary(opCode => opCode.Value);
|
.ToDictionary(opCode => opCode.Value);
|
||||||
|
|
||||||
public static IReadOnlyList<CompiledCall> Read(MethodBase method)
|
public static IReadOnlyList<CompiledCall> Read(MethodBase method)
|
||||||
|
=> ReadMethodReferences(method, includeDelegateTargets: false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads calls, object construction, and method references used to build
|
||||||
|
/// delegates. The latter lets lifetime tests inspect an operation manifest
|
||||||
|
/// without invoking its real process/window resources.
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyList<CompiledCall> ReadMethodReferences(MethodBase method) =>
|
||||||
|
ReadMethodReferences(method, includeDelegateTargets: true);
|
||||||
|
|
||||||
|
public static IReadOnlyList<CompiledCall> ReadDeclared(Type type)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(type);
|
||||||
|
const BindingFlags flags = BindingFlags.Instance
|
||||||
|
| BindingFlags.Static
|
||||||
|
| BindingFlags.Public
|
||||||
|
| BindingFlags.NonPublic
|
||||||
|
| BindingFlags.DeclaredOnly;
|
||||||
|
return type.GetMethods(flags)
|
||||||
|
.Cast<MethodBase>()
|
||||||
|
.Concat(type.GetConstructors(flags))
|
||||||
|
.Where(method => method.GetMethodBody() is not null)
|
||||||
|
.SelectMany(Read)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> ReadStringLiterals(MethodBase method)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(method);
|
||||||
|
byte[] il = method.GetMethodBody()?.GetILAsByteArray()
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"{method.DeclaringType?.FullName}.{method.Name} has no compiled body.");
|
||||||
|
var literals = new List<string>();
|
||||||
|
|
||||||
|
for (int cursor = 0; cursor < il.Length;)
|
||||||
|
{
|
||||||
|
OpCode opCode = ReadOpCode(il, ref cursor);
|
||||||
|
if (opCode == OpCodes.Ldstr)
|
||||||
|
{
|
||||||
|
int token = BitConverter.ToInt32(il, cursor);
|
||||||
|
literals.Add(method.Module.ResolveString(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor += OperandSize(opCode.OperandType, il, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return literals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int IndexOf(
|
||||||
|
IReadOnlyList<CompiledCall> calls,
|
||||||
|
Type declaringType,
|
||||||
|
string methodName,
|
||||||
|
int startIndex = 0) =>
|
||||||
|
Enumerable.Range(startIndex, calls.Count - startIndex)
|
||||||
|
.FirstOrDefault(
|
||||||
|
index => calls[index].Target.DeclaringType == declaringType
|
||||||
|
&& calls[index].Target.Name == methodName,
|
||||||
|
-1);
|
||||||
|
|
||||||
|
private static IReadOnlyList<CompiledCall> ReadMethodReferences(
|
||||||
|
MethodBase method,
|
||||||
|
bool includeDelegateTargets)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(method);
|
ArgumentNullException.ThrowIfNull(method);
|
||||||
byte[] il = method.GetMethodBody()?.GetILAsByteArray()
|
byte[] il = method.GetMethodBody()?.GetILAsByteArray()
|
||||||
|
|
@ -45,11 +108,13 @@ internal static class CompiledCallGraph
|
||||||
token,
|
token,
|
||||||
declaringArguments,
|
declaringArguments,
|
||||||
methodArguments);
|
methodArguments);
|
||||||
if (target is not null && opCode is { Value: var value }
|
bool invocation = opCode == OpCodes.Call
|
||||||
&& value is 0x28 or 0x6F or 0x73)
|
|| opCode == OpCodes.Callvirt
|
||||||
{
|
|| opCode == OpCodes.Newobj;
|
||||||
|
bool delegateTarget = includeDelegateTargets
|
||||||
|
&& (opCode == OpCodes.Ldftn || opCode == OpCodes.Ldvirtftn);
|
||||||
|
if (target is not null && (invocation || delegateTarget))
|
||||||
calls.Add(new CompiledCall(instructionOffset, target));
|
calls.Add(new CompiledCall(instructionOffset, target));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor += OperandSize(opCode.OperandType, il, cursor);
|
cursor += OperandSize(opCode.OperandType, il, cursor);
|
||||||
|
|
@ -58,17 +123,6 @@ internal static class CompiledCallGraph
|
||||||
return calls;
|
return calls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int IndexOf(
|
|
||||||
IReadOnlyList<CompiledCall> calls,
|
|
||||||
Type declaringType,
|
|
||||||
string methodName,
|
|
||||||
int startIndex = 0) =>
|
|
||||||
Enumerable.Range(startIndex, calls.Count - startIndex)
|
|
||||||
.FirstOrDefault(
|
|
||||||
index => calls[index].Target.DeclaringType == declaringType
|
|
||||||
&& calls[index].Target.Name == methodName,
|
|
||||||
-1);
|
|
||||||
|
|
||||||
private static OpCode ReadOpCode(byte[] il, ref int cursor)
|
private static OpCode ReadOpCode(byte[] il, ref int cursor)
|
||||||
{
|
{
|
||||||
byte first = il[cursor++];
|
byte first = il[cursor++];
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using AcDream.App.Composition;
|
||||||
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Rendering.Wb;
|
using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
|
using AcDream.App.World;
|
||||||
using AcDream.Core.Terrain;
|
using AcDream.Core.Terrain;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
using AcDream.Runtime;
|
||||||
|
using AcDream.Runtime.Session;
|
||||||
using DatReaderWriter.DBObjs;
|
using DatReaderWriter.DBObjs;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Streaming;
|
namespace AcDream.App.Tests.Streaming;
|
||||||
|
|
@ -252,179 +259,136 @@ public sealed class LandblockBuildOriginTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BuildFactoryAndRenderPublisher_UseCapturedOriginWithoutGameWindowFacade()
|
public void ProductionCompositionOwnsPublishersWithoutGameWindowFacade()
|
||||||
{
|
{
|
||||||
string root = FindRepoRoot();
|
MethodInfo[] windowMethods = typeof(GameWindow).GetMethods(
|
||||||
string gameWindowSource = File.ReadAllText(Path.Combine(
|
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
||||||
root,
|
string[] extractedMethodNames =
|
||||||
"src",
|
[
|
||||||
"AcDream.App",
|
|
||||||
"Rendering",
|
|
||||||
"GameWindow.cs"));
|
|
||||||
string livePresentationSource = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"LivePresentationComposition.cs"));
|
|
||||||
string buildSource = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Streaming",
|
|
||||||
"LandblockBuildFactory.cs"));
|
|
||||||
string renderPublisherSource = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Streaming",
|
|
||||||
"LandblockRenderPublisher.cs"));
|
|
||||||
string recenterSource = File.ReadAllText(Path.Combine(
|
|
||||||
root,
|
|
||||||
"src",
|
|
||||||
"AcDream.App",
|
|
||||||
"Streaming",
|
|
||||||
"StreamingOriginRecenterCoordinator.cs"));
|
|
||||||
Assert.Contains("request.Origin", buildSource, StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain("_liveCenterX", buildSource, StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain("_liveCenterY", buildSource, StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
"BuildLandblockForStreaming",
|
"BuildLandblockForStreaming",
|
||||||
gameWindowSource,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
"BuildSceneryEntitiesForStreaming",
|
"BuildSceneryEntitiesForStreaming",
|
||||||
gameWindowSource,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
"BuildInteriorEntitiesForStreaming",
|
"BuildInteriorEntitiesForStreaming",
|
||||||
gameWindowSource,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
"BuildPhysicsDatBundle",
|
"BuildPhysicsDatBundle",
|
||||||
gameWindowSource,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
"ApplyLoadedTerrain",
|
"ApplyLoadedTerrain",
|
||||||
gameWindowSource,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
|
||||||
"PublishLandblockStaticLightingBeforeCollision",
|
"PublishLandblockStaticLightingBeforeCollision",
|
||||||
gameWindowSource,
|
];
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"_landblockPhysicsPublisher!.RemoveLandblock",
|
windowMethods,
|
||||||
gameWindowSource,
|
method => extractedMethodNames.Contains(method.Name, StringComparer.Ordinal));
|
||||||
StringComparison.Ordinal);
|
|
||||||
|
FieldInfo[] windowFields = typeof(GameWindow).GetFields(
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Assert.Single(
|
||||||
|
windowFields,
|
||||||
|
field => field.FieldType == typeof(LandblockPresentationPipeline));
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"_landblockRenderPublisher",
|
windowFields,
|
||||||
gameWindowSource,
|
field => field.FieldType == typeof(LandblockRenderPublisher)
|
||||||
StringComparison.Ordinal);
|
|| field.FieldType == typeof(LandblockPhysicsPublisher)
|
||||||
|
|| field.FieldType == typeof(LandblockStaticPresentationPublisher));
|
||||||
|
|
||||||
|
IReadOnlyList<CompiledCall> windowCalls =
|
||||||
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"_landblockPhysicsPublisher",
|
windowCalls,
|
||||||
gameWindowSource,
|
call => call.Target.DeclaringType == typeof(LiveWorldOriginState)
|
||||||
StringComparison.Ordinal);
|
&& call.Target.Name == nameof(LiveWorldOriginState.Recenter));
|
||||||
Assert.DoesNotContain(
|
|
||||||
"_landblockStaticPresentationPublisher",
|
MethodInfo compose = typeof(LivePresentationCompositionPhase).GetMethod(
|
||||||
gameWindowSource,
|
"CompletePresentation",
|
||||||
StringComparison.Ordinal);
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
Assert.Contains(
|
IReadOnlyList<CompiledCall> composeCalls = CompiledCallGraph.Read(compose);
|
||||||
"_landblockPresentationPipeline",
|
int renderPublisher = RequiredCallIndex(
|
||||||
gameWindowSource,
|
composeCalls,
|
||||||
StringComparison.Ordinal);
|
typeof(LandblockRenderPublisher),
|
||||||
Assert.Contains(
|
".ctor");
|
||||||
"new LandblockRenderPublisher(",
|
int physicsPublisher = RequiredCallIndex(
|
||||||
livePresentationSource,
|
composeCalls,
|
||||||
StringComparison.Ordinal);
|
typeof(LandblockPhysicsPublisher),
|
||||||
Assert.Contains(
|
".ctor");
|
||||||
"new LandblockPhysicsPublisher(",
|
int staticPublisher = RequiredCallIndex(
|
||||||
livePresentationSource,
|
composeCalls,
|
||||||
StringComparison.Ordinal);
|
typeof(LandblockStaticPresentationPublisher),
|
||||||
Assert.Contains(
|
".ctor");
|
||||||
"new LandblockStaticPresentationPublisher(",
|
int pipeline = RequiredCallIndex(
|
||||||
livePresentationSource,
|
composeCalls,
|
||||||
StringComparison.Ordinal);
|
typeof(LandblockPresentationPipeline),
|
||||||
Assert.DoesNotContain("applyTerrain:", gameWindowSource, StringComparison.Ordinal);
|
".ctor");
|
||||||
Assert.DoesNotContain("demoteNearLayer:", gameWindowSource, StringComparison.Ordinal);
|
Assert.True(renderPublisher < physicsPublisher);
|
||||||
Assert.DoesNotContain("retirementCoordinator:", gameWindowSource, StringComparison.Ordinal);
|
Assert.True(physicsPublisher < staticPublisher);
|
||||||
Assert.DoesNotContain(
|
Assert.True(staticPublisher < pipeline);
|
||||||
"_liveWorldOrigin.Recenter(lbX, lbY)",
|
|
||||||
gameWindowSource,
|
// Captured-origin flow itself is behavioral: BuildFar exercises the
|
||||||
StringComparison.Ordinal);
|
// real LandblockBuildFactory and BeginPublication exercises the real
|
||||||
int retirementBarrier = recenterSource.IndexOf(
|
// render publisher. This metadata check protects only production
|
||||||
"IsOriginRecenterRetirementComplete()",
|
// ownership and composition, which those fixtures cannot observe.
|
||||||
StringComparison.Ordinal);
|
}
|
||||||
int originCommit = recenterSource.IndexOf(
|
|
||||||
"_origin.Recenter(",
|
[Fact]
|
||||||
StringComparison.Ordinal);
|
public void OriginRecenterWaitsForRetirementBeforeOriginAndDestinationCommits()
|
||||||
int destinationCommit = recenterSource.IndexOf(
|
{
|
||||||
"_streaming.TryCommitOriginRecenter(",
|
MethodInfo advance = typeof(StreamingOriginRecenterCoordinator).GetMethod(
|
||||||
StringComparison.Ordinal);
|
nameof(StreamingOriginRecenterCoordinator.Advance))!;
|
||||||
Assert.True(retirementBarrier >= 0);
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(advance);
|
||||||
Assert.True(originCommit > retirementBarrier);
|
int retirement = RequiredCallIndex(
|
||||||
Assert.True(destinationCommit > originCommit);
|
calls,
|
||||||
Assert.Contains("ComputeOrigin(landblockId, build.Origin)", renderPublisherSource, StringComparison.Ordinal);
|
typeof(StreamingController),
|
||||||
Assert.DoesNotContain("_liveCenterX", renderPublisherSource, StringComparison.Ordinal);
|
"IsOriginRecenterRetirementComplete");
|
||||||
Assert.DoesNotContain("_liveCenterY", renderPublisherSource, StringComparison.Ordinal);
|
int origin = RequiredCallIndex(
|
||||||
|
calls,
|
||||||
|
typeof(LiveWorldOriginState),
|
||||||
|
nameof(LiveWorldOriginState.Recenter));
|
||||||
|
int destination = RequiredCallIndex(
|
||||||
|
calls,
|
||||||
|
typeof(StreamingController),
|
||||||
|
"TryCommitOriginRecenter");
|
||||||
|
|
||||||
|
Assert.True(retirement < origin);
|
||||||
|
Assert.True(origin < destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindowShutdownKeepsStreamerAliveUntilSessionResetConverges()
|
public void GameWindowShutdownKeepsStreamerAliveUntilSessionResetConverges()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
MethodInfo manifest = typeof(GameWindowShutdownManifest).GetMethod(
|
||||||
FindRepoRoot(),
|
nameof(GameWindowShutdownManifest.Create))!;
|
||||||
"src",
|
IReadOnlyList<string> labels = CompiledCallGraph.ReadStringLiterals(manifest);
|
||||||
"AcDream.App",
|
AssertAppearsInOrder(
|
||||||
"Rendering",
|
labels,
|
||||||
"GameWindowLifetime.cs"));
|
"host and session barriers",
|
||||||
int sessionStage = source.IndexOf(
|
"game runtime session",
|
||||||
"new ResourceShutdownStage(\"host and session barriers\"",
|
"session dependents",
|
||||||
StringComparison.Ordinal);
|
"streamer");
|
||||||
Assert.True(sessionStage >= 0);
|
|
||||||
|
|
||||||
int sessionOperation = source.IndexOf(
|
IReadOnlyList<CompiledCall> references =
|
||||||
"Hard(\"game runtime session\", ingress.Runtime.StopSession)",
|
CompiledCallGraph.ReadMethodReferences(manifest);
|
||||||
sessionStage,
|
int stopSession = RequiredCallIndex(
|
||||||
StringComparison.Ordinal);
|
references,
|
||||||
Assert.True(sessionOperation > sessionStage);
|
typeof(GameRuntime),
|
||||||
|
nameof(GameRuntime.StopSession));
|
||||||
int dependentStage = source.IndexOf(
|
int streamerDispose = Enumerable.Range(stopSession + 1, references.Count - stopSession - 1)
|
||||||
"new ResourceShutdownStage(\"session dependents\"",
|
.FirstOrDefault(
|
||||||
sessionOperation,
|
index => Calls(
|
||||||
StringComparison.Ordinal);
|
references[index].Target,
|
||||||
Assert.True(dependentStage > sessionOperation);
|
typeof(LandblockStreamer),
|
||||||
|
nameof(LandblockStreamer.Dispose)),
|
||||||
int streamerDispose = source.IndexOf(
|
-1);
|
||||||
"Hard(\"streamer\", () => live.Streamer?.Dispose())",
|
Assert.True(streamerDispose > stopSession, "Missing later streamer-disposal operation.");
|
||||||
dependentStage,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.True(streamerDispose > dependentStage);
|
|
||||||
|
|
||||||
string runtime = File.ReadAllText(Path.Combine(
|
|
||||||
FindRepoRoot(),
|
|
||||||
"src",
|
|
||||||
"AcDream.Runtime",
|
|
||||||
"GameRuntime.cs"));
|
|
||||||
int helper = runtime.IndexOf(
|
|
||||||
"public void StopSession()",
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.True(helper >= 0);
|
|
||||||
int sessionDispose = runtime.IndexOf(
|
|
||||||
"Session.Dispose();",
|
|
||||||
helper,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.True(sessionDispose > helper);
|
|
||||||
|
|
||||||
int disposalCompletionBarrier = runtime.IndexOf(
|
|
||||||
"if (!Session.IsDisposalComplete)",
|
|
||||||
sessionDispose,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
Assert.True(disposalCompletionBarrier > sessionDispose);
|
|
||||||
|
|
||||||
|
MethodInfo runtimeStop = typeof(GameRuntime).GetMethod(nameof(GameRuntime.StopSession))!;
|
||||||
|
IReadOnlyList<CompiledCall> runtimeCalls = CompiledCallGraph.Read(runtimeStop);
|
||||||
|
int sessionDispose = RequiredCallIndex(
|
||||||
|
runtimeCalls,
|
||||||
|
typeof(LiveSessionController),
|
||||||
|
nameof(LiveSessionController.Dispose));
|
||||||
|
int completionBarrier = RequiredCallIndex(
|
||||||
|
runtimeCalls,
|
||||||
|
typeof(LiveSessionController),
|
||||||
|
"get_IsDisposalComplete");
|
||||||
|
Assert.True(sessionDispose < completionBarrier);
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
"The Runtime session shutdown was deferred by a re-entrant callback.",
|
"The Runtime session shutdown was deferred by a re-entrant callback.",
|
||||||
runtime[disposalCompletionBarrier..],
|
CompiledCallGraph.ReadStringLiterals(runtimeStop));
|
||||||
StringComparison.Ordinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LandblockBuild EmptyBuild(uint landblockId, LandblockBuildOrigin origin) =>
|
private static LandblockBuild EmptyBuild(uint landblockId, LandblockBuildOrigin origin) =>
|
||||||
|
|
@ -458,16 +422,37 @@ public sealed class LandblockBuildOriginTests
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindRepoRoot()
|
private static int RequiredCallIndex(
|
||||||
|
IReadOnlyList<CompiledCall> calls,
|
||||||
|
Type declaringType,
|
||||||
|
string methodName)
|
||||||
{
|
{
|
||||||
string? dir = AppContext.BaseDirectory;
|
int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName);
|
||||||
while (dir is not null)
|
Assert.True(index >= 0, $"Missing compiled call: {declaringType.Name}.{methodName}");
|
||||||
{
|
return index;
|
||||||
if (File.Exists(Path.Combine(dir, "AcDream.slnx")))
|
}
|
||||||
return dir;
|
|
||||||
dir = Directory.GetParent(dir)?.FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new DirectoryNotFoundException("Could not locate AcDream.slnx.");
|
private static bool Calls(
|
||||||
|
MethodBase method,
|
||||||
|
Type declaringType,
|
||||||
|
string methodName) =>
|
||||||
|
method.GetMethodBody() is not null
|
||||||
|
&& CompiledCallGraph.IndexOf(
|
||||||
|
CompiledCallGraph.Read(method),
|
||||||
|
declaringType,
|
||||||
|
methodName) >= 0;
|
||||||
|
|
||||||
|
private static void AssertAppearsInOrder(
|
||||||
|
IReadOnlyList<string> values,
|
||||||
|
params string[] expected)
|
||||||
|
{
|
||||||
|
int cursor = -1;
|
||||||
|
foreach (string value in expected)
|
||||||
|
{
|
||||||
|
int next = Enumerable.Range(cursor + 1, values.Count - cursor - 1)
|
||||||
|
.FirstOrDefault(index => values[index] == value, -1);
|
||||||
|
Assert.True(next > cursor, $"Missing or out-of-order metadata string: {value}");
|
||||||
|
cursor = next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.Core.Net;
|
using AcDream.Core.Net;
|
||||||
using AcDream.Core.Net.Messages;
|
using AcDream.Core.Net.Messages;
|
||||||
using AcDream.Core.Physics;
|
using AcDream.Core.Physics;
|
||||||
|
|
@ -1047,34 +1049,63 @@ public sealed class LandblockPhysicsPublisherTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_HasNoLandblockPhysicsPublicationBodies()
|
public void GameWindow_HasNoLandblockPhysicsPublicationBodies()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> windowCalls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
AssertNoCall(windowCalls, typeof(PhysicsDataCache), nameof(PhysicsDataCache.CacheCellStruct));
|
||||||
"AcDream.App",
|
AssertNoCall(windowCalls, typeof(PhysicsDataCache), nameof(PhysicsDataCache.CacheBuilding));
|
||||||
"Rendering",
|
AssertNoCall(
|
||||||
"GameWindow.cs"));
|
windowCalls,
|
||||||
|
typeof(ShadowShapeBuilder),
|
||||||
|
nameof(ShadowShapeBuilder.FromLandblockBspParts));
|
||||||
|
AssertNoCall(
|
||||||
|
windowCalls,
|
||||||
|
typeof(ShadowObjectRegistry),
|
||||||
|
nameof(ShadowObjectRegistry.RefloodLandblock));
|
||||||
|
AssertNoCall(
|
||||||
|
windowCalls,
|
||||||
|
typeof(PhysicsEngine),
|
||||||
|
"DemoteLandblockToTerrain");
|
||||||
|
AssertNoCall(windowCalls, typeof(PhysicsEngine), "RemoveLandblock");
|
||||||
|
|
||||||
Assert.DoesNotContain("_physicsDataCache.CacheCellStruct", source, StringComparison.Ordinal);
|
IReadOnlyList<CompiledCall> publisherCalls =
|
||||||
Assert.DoesNotContain("_physicsDataCache.CacheBuilding", source, StringComparison.Ordinal);
|
CompiledCallGraph.ReadDeclared(typeof(LandblockPhysicsPublisher));
|
||||||
Assert.DoesNotContain("ShadowShapeBuilder.FromLandblockBspParts", source, StringComparison.Ordinal);
|
MethodInfo advance = typeof(LandblockPhysicsPublisher).GetMethod(
|
||||||
Assert.DoesNotContain("ShadowObjects.RefloodLandblock", source, StringComparison.Ordinal);
|
"AdvanceBeginOne",
|
||||||
Assert.DoesNotContain("_physicsEngine.DemoteLandblockToTerrain", source, StringComparison.Ordinal);
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
Assert.DoesNotContain("_physicsEngine.RemoveLandblock", source, StringComparison.Ordinal);
|
IReadOnlyList<CompiledCall> advanceCalls = CompiledCallGraph.Read(advance);
|
||||||
|
int stagedCache = CompiledCallGraph.IndexOf(
|
||||||
string publisherSource = File.ReadAllText(Path.Combine(
|
advanceCalls,
|
||||||
FindRepoRoot(),
|
typeof(LandblockPhysicsPublication),
|
||||||
"src",
|
"get_StagingCache");
|
||||||
"AcDream.App",
|
int removeCells = CompiledCallGraph.IndexOf(
|
||||||
"Streaming",
|
advanceCalls,
|
||||||
"LandblockPhysicsPublisher.cs"));
|
typeof(PhysicsDataCache),
|
||||||
Assert.DoesNotContain("_physicsDataCache.RemoveCellsForLandblock", publisherSource, StringComparison.Ordinal);
|
nameof(PhysicsDataCache.RemoveCellsForLandblock));
|
||||||
Assert.DoesNotContain("_physicsDataCache.RemoveBuildingsForLandblock", publisherSource, StringComparison.Ordinal);
|
int removeBuildings = CompiledCallGraph.IndexOf(
|
||||||
Assert.DoesNotContain("_physicsEngine.ShadowObjects.Reflood", publisherSource, StringComparison.Ordinal);
|
advanceCalls,
|
||||||
Assert.Contains("CommitCollisionGeneration(", publisherSource, StringComparison.Ordinal);
|
typeof(PhysicsDataCache),
|
||||||
Assert.DoesNotContain(
|
nameof(PhysicsDataCache.RemoveBuildingsForLandblock));
|
||||||
"RestartCollisionRetainedOwnerCapture(",
|
int secondStagedCache = CompiledCallGraph.IndexOf(
|
||||||
publisherSource,
|
advanceCalls,
|
||||||
StringComparison.Ordinal);
|
typeof(LandblockPhysicsPublication),
|
||||||
|
"get_StagingCache",
|
||||||
|
removeCells + 1);
|
||||||
|
Assert.True(stagedCache >= 0);
|
||||||
|
Assert.True(stagedCache < removeCells);
|
||||||
|
Assert.True(removeCells < secondStagedCache);
|
||||||
|
Assert.True(secondStagedCache < removeBuildings);
|
||||||
|
AssertNoCall(
|
||||||
|
publisherCalls,
|
||||||
|
typeof(ShadowObjectRegistry),
|
||||||
|
nameof(ShadowObjectRegistry.RefloodLandblock));
|
||||||
|
Assert.Contains(
|
||||||
|
publisherCalls,
|
||||||
|
call => call.Target.DeclaringType == typeof(RuntimePhysicsState)
|
||||||
|
&& call.Target.Name == "CommitCollisionGeneration");
|
||||||
|
AssertNoCall(
|
||||||
|
publisherCalls,
|
||||||
|
typeof(RuntimePhysicsState),
|
||||||
|
"RestartCollisionRetainedOwnerCapture");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Publish(
|
private static void Publish(
|
||||||
|
|
@ -1500,15 +1531,14 @@ public sealed class LandblockPhysicsPublisherTests
|
||||||
Height = new byte[81],
|
Height = new byte[81],
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string FindRepoRoot()
|
private static void AssertNoCall(
|
||||||
|
IReadOnlyList<CompiledCall> calls,
|
||||||
|
Type declaringType,
|
||||||
|
string methodName)
|
||||||
{
|
{
|
||||||
string? directory = AppContext.BaseDirectory;
|
Assert.DoesNotContain(
|
||||||
while (directory is not null)
|
calls,
|
||||||
{
|
call => call.Target.DeclaringType == declaringType
|
||||||
if (File.Exists(Path.Combine(directory, "AcDream.slnx")))
|
&& call.Target.Name == methodName);
|
||||||
return directory;
|
|
||||||
directory = Directory.GetParent(directory)?.FullName;
|
|
||||||
}
|
|
||||||
throw new DirectoryNotFoundException("Could not locate repository root.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Collections.Immutable;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
using AcDream.App.Rendering.Wb;
|
using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.Core.Terrain;
|
using AcDream.Core.Terrain;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
using DatReaderWriter.DBObjs;
|
using DatReaderWriter.DBObjs;
|
||||||
|
|
@ -306,18 +307,27 @@ public sealed class LandblockRenderPublisherTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GameWindow_HasNoDirectRenderPublicationBodies()
|
public void GameWindow_HasNoDirectRenderPublicationBodies()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> calls =
|
||||||
FindRepoRoot(),
|
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
|
||||||
"src",
|
Assert.DoesNotContain(
|
||||||
"AcDream.App",
|
calls,
|
||||||
"Rendering",
|
call => call.Target.DeclaringType == typeof(TerrainModernRenderer)
|
||||||
"GameWindow.cs"));
|
&& call.Target.Name == nameof(TerrainModernRenderer.AddLandblockWithMesh));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(CellVisibility)
|
||||||
|
&& call.Target.Name == nameof(CellVisibility.CommitLandblock));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
calls,
|
||||||
|
call => call.Target.DeclaringType == typeof(EnvCellRenderer)
|
||||||
|
&& call.Target.Name == nameof(EnvCellRenderer.CommitLandblock));
|
||||||
|
|
||||||
Assert.DoesNotContain("_terrain.AddLandblockWithMesh", source, StringComparison.Ordinal);
|
Assert.Null(typeof(GameWindow).GetMethod(
|
||||||
Assert.DoesNotContain("_cellVisibility.CommitLandblock", source, StringComparison.Ordinal);
|
"EnsureEnvCellMeshesAfterPin",
|
||||||
Assert.DoesNotContain("_buildingRegistries", source, StringComparison.Ordinal);
|
BindingFlags.Instance | BindingFlags.NonPublic));
|
||||||
Assert.DoesNotContain("_envCellRenderer?.CommitLandblock", source, StringComparison.Ordinal);
|
Assert.DoesNotContain(
|
||||||
Assert.DoesNotContain("EnsureEnvCellMeshesAfterPin", source, StringComparison.Ordinal);
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
|
field => field.Name == "_buildingRegistries");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LandblockRenderPublisher Publisher(
|
private static LandblockRenderPublisher Publisher(
|
||||||
|
|
@ -406,18 +416,6 @@ public sealed class LandblockRenderPublisherTests
|
||||||
private static LandblockMeshData EmptyMesh() =>
|
private static LandblockMeshData EmptyMesh() =>
|
||||||
new(Array.Empty<TerrainVertex>(), Array.Empty<uint>());
|
new(Array.Empty<TerrainVertex>(), Array.Empty<uint>());
|
||||||
|
|
||||||
private static string FindRepoRoot()
|
|
||||||
{
|
|
||||||
string? directory = AppContext.BaseDirectory;
|
|
||||||
while (directory is not null)
|
|
||||||
{
|
|
||||||
if (File.Exists(Path.Combine(directory, "AcDream.slnx")))
|
|
||||||
return directory;
|
|
||||||
directory = Directory.GetParent(directory)?.FullName;
|
|
||||||
}
|
|
||||||
throw new DirectoryNotFoundException("Could not locate repository root.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class RecordingEnvCellPublisher :
|
private sealed class RecordingEnvCellPublisher :
|
||||||
IEnvCellLandblockPublisher
|
IEnvCellLandblockPublisher
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using AcDream.App.Composition;
|
||||||
|
using AcDream.App.Input;
|
||||||
|
using AcDream.App.Net;
|
||||||
using AcDream.App.Physics;
|
using AcDream.App.Physics;
|
||||||
using AcDream.App.Rendering;
|
using AcDream.App.Rendering;
|
||||||
|
using AcDream.App.Tests.Architecture;
|
||||||
using AcDream.App.World;
|
using AcDream.App.World;
|
||||||
using AcDream.Core.Net;
|
using AcDream.Core.Net;
|
||||||
using AcDream.Core.Physics;
|
using AcDream.Core.Physics;
|
||||||
|
|
@ -213,58 +217,74 @@ public sealed class GameWindowLiveEntityCompositionTests
|
||||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType)
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType)
|
||||||
&& !field.Name.Contains("DiagnosticSink", StringComparison.Ordinal));
|
&& !field.Name.Contains("DiagnosticSink", StringComparison.Ordinal));
|
||||||
|
|
||||||
string source = File.ReadAllText(Path.Combine(
|
FieldInfo[] windowFields = typeof(GameWindow).GetFields(
|
||||||
FindRepoRoot(), "src", "AcDream.App", "Rendering", "GameWindow.cs"));
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
string livePresentation = File.ReadAllText(Path.Combine(
|
Assert.Equal(
|
||||||
FindRepoRoot(),
|
typeof(LiveEntityAnimationRuntimeView<LiveEntityAnimationState>),
|
||||||
"src",
|
Assert.Single(windowFields, field => field.Name == "_animatedEntities").FieldType);
|
||||||
"AcDream.App",
|
|
||||||
"Composition",
|
|
||||||
"LivePresentationComposition.cs"));
|
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"new LiveEntityAnimationRuntimeView<LiveEntityAnimationState>(() =>",
|
windowFields,
|
||||||
source,
|
field => field.FieldType.Name.Contains(
|
||||||
StringComparison.Ordinal);
|
"LiveEntityRemoteMotionRuntimeView",
|
||||||
Assert.DoesNotContain(
|
StringComparison.Ordinal));
|
||||||
"LiveEntityRemoteMotionRuntimeView",
|
|
||||||
source,
|
MethodInfo compose = typeof(LivePresentationCompositionPhase).GetMethod(
|
||||||
StringComparison.Ordinal);
|
"ComposeCore",
|
||||||
Assert.Contains("new DatProjectileSetupResolver", livePresentation);
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
|
||||||
|
int resolver = RequiredCallIndex(
|
||||||
|
calls,
|
||||||
|
typeof(DatProjectileSetupResolver),
|
||||||
|
".ctor");
|
||||||
|
int controller = RequiredCallIndex(
|
||||||
|
calls,
|
||||||
|
typeof(ProjectileController),
|
||||||
|
".ctor");
|
||||||
|
Assert.True(resolver < controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SessionReset_ClosesEveryStreamingReadinessOwner()
|
public void SessionReset_CallsPlayerModeNetworkAndWorldOriginOwners()
|
||||||
{
|
{
|
||||||
string source = File.ReadAllText(Path.Combine(
|
MethodInfo resetPlayer = typeof(LiveSessionRuntimeFactory).GetMethod(
|
||||||
FindRepoRoot(), "src", "AcDream.App", "Net", "LiveSessionRuntimeFactory.cs"));
|
"ResetPlayerPresentation",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
|
IReadOnlyList<CompiledCall> playerCalls = CompiledCallGraph.Read(resetPlayer);
|
||||||
|
Assert.True(RequiredCallIndex(
|
||||||
|
playerCalls,
|
||||||
|
typeof(PlayerModeController),
|
||||||
|
nameof(PlayerModeController.ResetSession)) >= 0);
|
||||||
|
|
||||||
Assert.Contains("_interaction.PlayerMode.ResetSession();", source,
|
MethodInfo resetMode = typeof(PlayerModeController).GetMethod(
|
||||||
StringComparison.Ordinal);
|
nameof(PlayerModeController.ResetSession))!;
|
||||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
IReadOnlyList<CompiledCall> modeCalls = CompiledCallGraph.Read(resetMode);
|
||||||
FindRepoRoot(),
|
Assert.True(RequiredCallIndex(
|
||||||
"src",
|
modeCalls,
|
||||||
"AcDream.App",
|
typeof(LocalPlayerModeState),
|
||||||
"Input",
|
nameof(LocalPlayerModeState.ResetSession)) >= 0);
|
||||||
"PlayerModeController.cs"));
|
|
||||||
Assert.Contains("_mode.ResetSession();", playerModeSource,
|
MethodInfo resetIdentity = typeof(LiveSessionRuntimeFactory).GetMethod(
|
||||||
StringComparison.Ordinal);
|
"ResetIdentityPresentation",
|
||||||
Assert.Contains(
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||||
"_world.NetworkUpdates.ResetSessionState();",
|
IReadOnlyList<CompiledCall> identityCalls = CompiledCallGraph.Read(resetIdentity);
|
||||||
source,
|
int network = RequiredCallIndex(
|
||||||
StringComparison.Ordinal);
|
identityCalls,
|
||||||
Assert.Contains("_player.WorldOrigin.Reset();", source, StringComparison.Ordinal);
|
typeof(LiveEntityNetworkUpdateController),
|
||||||
|
nameof(LiveEntityNetworkUpdateController.ResetSessionState));
|
||||||
|
int origin = RequiredCallIndex(
|
||||||
|
identityCalls,
|
||||||
|
typeof(LiveWorldOriginState),
|
||||||
|
nameof(LiveWorldOriginState.Reset));
|
||||||
|
Assert.True(network < origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindRepoRoot()
|
private static int RequiredCallIndex(
|
||||||
|
IReadOnlyList<CompiledCall> calls,
|
||||||
|
Type declaringType,
|
||||||
|
string methodName)
|
||||||
{
|
{
|
||||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName);
|
||||||
while (directory is not null)
|
Assert.True(index >= 0, $"Missing compiled call: {declaringType.Name}.{methodName}");
|
||||||
{
|
return index;
|
||||||
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
||||||
return directory.FullName;
|
|
||||||
directory = directory.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue