fix(render): harden portal exit handoff
This commit is contained in:
parent
ddbd7e4096
commit
82e4b4cb6d
17 changed files with 896 additions and 119 deletions
|
|
@ -468,7 +468,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ExitSound_ReleasesDestinationBeforeHidingTunnelButKeepsProtocolActive()
|
||||
public void ExitSound_HidesTunnelBeforeReleasingDestinationAndKeepsProtocolActive()
|
||||
{
|
||||
var order = new List<string>();
|
||||
var harness = new Harness(order: order);
|
||||
|
|
@ -481,6 +481,8 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
|
||||
harness.Controller.Tick(0.016f);
|
||||
|
||||
Assert.True(Index(order, "presentation-exit") < Index(order, "reservation-end"));
|
||||
Assert.True(Index(order, "reservation-end") < Index(order, "exit-cue"));
|
||||
Assert.False(harness.Presentation.IsPortalViewportVisible);
|
||||
Assert.True(harness.Controller.IsActive);
|
||||
Assert.False(harness.Reveal.Snapshot.Completed);
|
||||
|
|
@ -1589,10 +1591,13 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
harness.Controller.Tick(0.016f);
|
||||
Assert.False(harness.Placement.Called);
|
||||
|
||||
// Viewport swap: reservation release + exit cue + tunnel retire
|
||||
// Viewport swap: tunnel retire, reservation release, then exit cue
|
||||
// (gmSmartBoxUI::UseTime, Sound_UI_ExitPortal @ 0x004D7405).
|
||||
order.Clear();
|
||||
harness.Presentation.Enqueue(TeleportAnimEvent.PlayExitSound);
|
||||
harness.Controller.Tick(0.016f);
|
||||
Assert.True(Index(order, "presentation-exit") < Index(order, "reservation-end"));
|
||||
Assert.True(Index(order, "reservation-end") < Index(order, "exit-cue"));
|
||||
Assert.Equal(["enter", "exit"], harness.Presentation.Cues);
|
||||
Assert.False(harness.Presentation.IsPortalViewportVisible);
|
||||
Assert.Single(harness.Streaming.ReservationEnds);
|
||||
|
|
@ -2126,9 +2131,17 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
public void TickTunnel(float deltaSeconds) => _order.Add("tunnel-tick");
|
||||
public readonly List<string> Cues = new();
|
||||
public void PlayEnterCue() => Cues.Add("enter");
|
||||
public void PlayExitCue() => Cues.Add("exit");
|
||||
public void PlayExitCue()
|
||||
{
|
||||
Cues.Add("exit");
|
||||
_order.Add("exit-cue");
|
||||
}
|
||||
public void EnterTunnel() => IsPortalViewportVisible = true;
|
||||
public void ExitTunnel() => IsPortalViewportVisible = false;
|
||||
public void ExitTunnel()
|
||||
{
|
||||
IsPortalViewportVisible = false;
|
||||
_order.Add("presentation-exit");
|
||||
}
|
||||
public void SetWaitCue(bool visible) => WaitCueValues.Add(visible);
|
||||
|
||||
public void Reset()
|
||||
|
|
|
|||
129
tests/AcDream.App.Tests/Streaming/RevealTimingProbeTests.cs
Normal file
129
tests/AcDream.App.Tests/Streaming/RevealTimingProbeTests.cs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.Runtime;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
public sealed class RevealTimingProbeTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResourceSampling_IsLowFrequencyAndCoalescesSamePollEdges()
|
||||
{
|
||||
var resources = new CountingResourceSource();
|
||||
var probe = new RevealTimingProbe(
|
||||
loadedLandblockCount: static () => 4,
|
||||
renderResources: resources);
|
||||
probe.Begin(
|
||||
kind: "Login",
|
||||
generation: 2,
|
||||
destinationCell: 0x1234_0001u,
|
||||
window: new StreamingRevealWindow(NearRadius: 1, FarRadius: 2));
|
||||
|
||||
probe.Observe(
|
||||
default,
|
||||
RuntimePortalSnapshot.Idle with { Generation = 2 });
|
||||
probe.Observe(
|
||||
new WorldRevealReadinessSnapshot(
|
||||
DestinationCell: 0x1234_0001u,
|
||||
IsIndoor: false,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: 2,
|
||||
RequiredNearRadius: 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true),
|
||||
RuntimePortalSnapshot.Idle with
|
||||
{
|
||||
Generation = 2,
|
||||
Materialized = true,
|
||||
WorldViewportObserved = true,
|
||||
});
|
||||
|
||||
// Begin, one coalesced readiness-edge snapshot, and summary. The
|
||||
// ordinary false poll does not touch the renderer resource owners.
|
||||
Assert.Equal(3, resources.CaptureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResourceLineCarriesColdMeshTextureAndProcessAttribution()
|
||||
{
|
||||
MeshStreamResourceDiagnostics mesh =
|
||||
default(MeshStreamResourceDiagnostics) with
|
||||
{
|
||||
RenderData = 12,
|
||||
AtlasArrays = 3,
|
||||
EstimatedBytes = 4_000,
|
||||
GlobalUploadCount = 7,
|
||||
GlobalUploadedBytes = 8_000,
|
||||
FrameUploadCount = 2,
|
||||
FrameUploadBytes = 900,
|
||||
StagedUploadBacklog = 5,
|
||||
StagedUploadBytes = 6_000,
|
||||
GlobalCapacityBytes = 10_000,
|
||||
GlobalPhysicalCapacityBytes = 20_000,
|
||||
GlobalMigrationInProgress = true,
|
||||
PreparedReads = 21,
|
||||
PreparedLoaded = 20,
|
||||
};
|
||||
TextureStreamResourceDiagnostics textures =
|
||||
default(TextureStreamResourceDiagnostics) with
|
||||
{
|
||||
OwnedBindlessTextures = 17,
|
||||
TextureOwners = 15,
|
||||
CachedCompositeTextures = 9,
|
||||
CachedUnownedComposites = 2,
|
||||
CachedUnownedCompositeBytes = 700,
|
||||
CompositeAtlases = 2,
|
||||
CompositeAtlasBytes = 30_000,
|
||||
CompositeWarmupPending = 4,
|
||||
FrameCompositeUploadCount = 3,
|
||||
FrameCompositeUploadBytes = 1_200,
|
||||
};
|
||||
ProcessResourceDiagnostics process = new(
|
||||
ManagedBytes: 40_000,
|
||||
ManagedCommittedBytes: 50_000,
|
||||
TrackedGpuBytes: 60_000,
|
||||
TrackedGpuBuffers: 11,
|
||||
TrackedGpuTextures: 13);
|
||||
var snapshot = new RenderFrameResourceDiagnosticsSnapshot(
|
||||
default,
|
||||
default,
|
||||
mesh,
|
||||
textures,
|
||||
process);
|
||||
|
||||
string line = RevealTimingProbe.FormatResourceLine(
|
||||
"render-ready",
|
||||
"Login",
|
||||
generation: 2,
|
||||
elapsedMilliseconds: 3456,
|
||||
snapshot);
|
||||
|
||||
Assert.StartsWith(
|
||||
"[reveal-resource] checkpoint=render-ready kind=Login gen=2 elapsedMs=3456",
|
||||
line);
|
||||
Assert.Contains("meshData=12", line);
|
||||
Assert.Contains("globalUploads=7", line);
|
||||
Assert.Contains("staged=5", line);
|
||||
Assert.Contains("arenaMigrating=1", line);
|
||||
Assert.Contains("prepared=0/21/20/0/0", line);
|
||||
Assert.Contains("ownedTextures=17", line);
|
||||
Assert.Contains("unownedCompositeBytes=700", line);
|
||||
Assert.Contains("compositePending=4", line);
|
||||
Assert.Contains("frameCompositeUploadBytes=1200", line);
|
||||
Assert.Contains("managedBytes=40000", line);
|
||||
Assert.Contains("trackedGpuBytes=60000", line);
|
||||
}
|
||||
|
||||
private sealed class CountingResourceSource :
|
||||
IRenderFrameResourceDiagnosticsSource
|
||||
{
|
||||
public int CaptureCount { get; private set; }
|
||||
|
||||
public RenderFrameResourceDiagnosticsSnapshot Capture()
|
||||
{
|
||||
CaptureCount++;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using AcDream.App.Streaming;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
public sealed class StreamingDiagnosticsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("", null)]
|
||||
[InlineData("0", null)]
|
||||
[InlineData("garbage", null)]
|
||||
[InlineData("1", StreamingDiagnostics.DefaultTunnelFreezeFrame)]
|
||||
[InlineData("true", StreamingDiagnostics.DefaultTunnelFreezeFrame)]
|
||||
[InlineData("TRUE", StreamingDiagnostics.DefaultTunnelFreezeFrame)]
|
||||
[InlineData("2", 2)]
|
||||
[InlineData("72", 72)]
|
||||
[InlineData("120", 120)]
|
||||
[InlineData("121", null)]
|
||||
public void TunnelFreezeParser_AcceptsTheDefaultAliasOrAnAuthoredFrame(
|
||||
string? raw,
|
||||
int? expected) =>
|
||||
Assert.Equal(expected, StreamingDiagnostics.ParseTunnelFreezeFrame(raw));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue