using System.Numerics; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Physics; using AcDream.Core.Selection; using AcDream.Runtime; using AcDream.Runtime.World; namespace AcDream.App.Tests.Streaming; public sealed class WorldRevealCoordinatorTests { private sealed class State { public RuntimeWorldTransitState Transit { get; private set; } = null!; public bool RenderReady { get; set; } public bool CollisionReady { get; set; } public bool CompositesReady { get; set; } public int InvalidateCount { get; private set; } public int PrepareCount { get; private set; } public WorldRevealCoordinator Build( List? logs = null, IWorldRevealStreamingScheduler? streaming = null, IWorldRevealRenderResourceScheduler? renderResources = null, RuntimeWorldTransitState? transit = null) { transit ??= new RuntimeWorldTransitState( logs is null ? null : new Action(logs.Add)); Transit = transit; return new WorldRevealCoordinator( transit, isRenderNeighborhoodReady: (_, _) => RenderReady, isSpawnCellReady: _ => CollisionReady, isTerrainNeighborhoodReady: (_, _) => CollisionReady, areCompositeTexturesReady: () => CompositesReady, prepareCompositeTextures: (_, _) => PrepareCount++, invalidateCompositeTextures: () => { InvalidateCount++; CompositesReady = false; }, isSpawnClaimUnhydratable: _ => false, streaming: streaming, renderResources: renderResources); } } [Fact] public void Begin_InvalidatesPriorDestinationAndStartsOneGeneration() { var state = new State { CompositesReady = true }; WorldRevealCoordinator coordinator = state.Build(); long generation = coordinator.BeginLogin(0x11340021u); Assert.Equal(1, generation); Assert.Equal(1, state.InvalidateCount); Assert.False(state.CompositesReady); Assert.Equal(RuntimePortalKind.Login, coordinator.Snapshot.Kind); } [Fact] public void PrepareAndEvaluate_UsesOneCanonicalSnapshotForLifecycle() { const uint outdoorCell = 0x11340021u; var state = new State { RenderReady = true, CollisionReady = true, }; WorldRevealCoordinator coordinator = state.Build(); coordinator.BeginLogin(outdoorCell); WorldRevealReadinessSnapshot first = coordinator.PrepareAndEvaluate(outdoorCell); state.CompositesReady = true; WorldRevealReadinessSnapshot ready = coordinator.PrepareAndEvaluate(outdoorCell); Assert.False(first.IsReady); Assert.True(ready.IsReady); Assert.Equal(2, state.PrepareCount); Assert.Equal(ready.DestinationCell, coordinator.Snapshot.DestinationCell); Assert.Equal(ready.IsReady, coordinator.Snapshot.IsReady); } [Fact] public void PortalLifecycle_CountsMaterializationAndCompletesVisibleGeneration() { const uint outdoorCell = 0x3032001Cu; var logs = new List(); var state = new State { RenderReady = true, CollisionReady = true, CompositesReady = true, }; WorldRevealCoordinator coordinator = state.Build(logs); long generation = BeginPortal( coordinator, state.Transit, outdoorCell, sequence: 1); state.CompositesReady = true; Assert.True(coordinator.Evaluate(outdoorCell).IsReady); coordinator.ObserveMaterialized(generation, 1, outdoorCell); coordinator.ObserveMaterialized(generation, 1, outdoorCell); coordinator.ObserveWorldViewportVisible(); coordinator.Complete(); Assert.Equal(1, coordinator.PortalMaterializationCount); Assert.True(coordinator.Snapshot.Materialized); Assert.True(coordinator.Snapshot.WorldViewportObserved); Assert.True(coordinator.Snapshot.Completed); Assert.Equal(0, coordinator.Snapshot.InvariantFailureCount); Assert.Single(logs, line => line.Contains("event=materialized", StringComparison.Ordinal)); } [Fact] public void PortalBegin_WrongSequenceOrCellCannotAcquireHostResources() { const uint destinationCell = 0x3032001Cu; var state = new State { CompositesReady = true }; var scheduler = new RecordingDestinationScheduler(); WorldRevealCoordinator coordinator = state.Build(streaming: scheduler); RuntimeWorldTransitTestDriver.AcceptPortalDestination( state.Transit, destinationCell, sequence: 7); Assert.False(coordinator.TryBeginPortal( 8, destinationCell, out _)); Assert.False(coordinator.TryBeginPortal( 7, 0x11340021u, out _)); Assert.Empty(scheduler.Begins); Assert.Equal(0, state.InvalidateCount); Assert.Equal(0, coordinator.Snapshot.Generation); Assert.True(coordinator.TryBeginPortal( 7, destinationCell, out long generation)); Assert.Equal( (generation, destinationCell, 1), Assert.Single(scheduler.Begins)); Assert.Equal(1, state.InvalidateCount); } [Fact] public void Cancel_PreventsLateDestinationWorkFromMutatingLifecycle() { var state = new State { RenderReady = true, CollisionReady = true, CompositesReady = true, }; WorldRevealCoordinator coordinator = state.Build(); long generation = BeginPortal( coordinator, state.Transit, 0x11340021u, sequence: 1); coordinator.Cancel(); coordinator.PrepareAndEvaluate(0x11340021u); coordinator.ObserveMaterialized( generation, 1, 0x11340021u); coordinator.ObserveWorldViewportVisible(); coordinator.Complete(); Assert.True(coordinator.Snapshot.Cancelled); Assert.Equal(0x11340021u, coordinator.Snapshot.DestinationCell); Assert.False(coordinator.Snapshot.IsReady); Assert.Equal(0, coordinator.PortalMaterializationCount); } [Fact] public void RevealLifetime_QuiescesContinuouslyAndOnlyActiveGenerationReopens() { var transit = new RuntimeWorldTransitState(); var availability = new WorldGenerationAvailabilityState(transit); var world = new GpuWorldState(availability: availability); var audio = new RecordingAudioQuiescence(); var quiescence = new WorldGenerationQuiescence( new SelectionState(), world, _ => null, audio); var coordinator = new WorldRevealCoordinator( transit, isRenderNeighborhoodReady: (_, _) => true, isSpawnCellReady: _ => true, isTerrainNeighborhoodReady: (_, _) => true, areCompositeTexturesReady: () => true, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence); Assert.Equal(1, coordinator.BeginLogin(0x11340021u)); long generation = BeginPortal( coordinator, transit, 0x11340021u, sequence: 1); Assert.Equal(2, generation); Assert.False(availability.IsWorldAvailable); Assert.Equal(2, availability.QuiescedGeneration); Assert.Equal(1, audio.SuspendCalls); Assert.True(coordinator.Evaluate(0x11340021u).IsReady); coordinator.ObserveMaterialized( generation, 1, 0x11340021u); coordinator.Complete(); Assert.True(availability.IsWorldAvailable); Assert.Equal(1, audio.ResumeCalls); } [Fact] public void Materialization_ReopensWorldBeforePortalViewportRelease() { var transit = new RuntimeWorldTransitState(); var availability = new WorldGenerationAvailabilityState(transit); var world = new GpuWorldState(availability: availability); var audio = new RecordingAudioQuiescence(); var quiescence = new WorldGenerationQuiescence( new SelectionState(), world, _ => null, audio); var scheduler = new RecordingDestinationScheduler(); var coordinator = new WorldRevealCoordinator( transit, isRenderNeighborhoodReady: (_, _) => true, isSpawnCellReady: _ => true, isTerrainNeighborhoodReady: (_, _) => true, areCompositeTexturesReady: () => true, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence, streaming: scheduler); const uint destinationCell = 0x3032001Cu; const ushort teleportSequence = 1; long generation = BeginPortal( coordinator, transit, destinationCell, teleportSequence); Assert.False(availability.IsWorldAvailable); Assert.True(coordinator.Evaluate(destinationCell).IsReady); coordinator.ObserveMaterialized( generation, teleportSequence, destinationCell); Assert.True(availability.IsWorldAvailable); Assert.False(coordinator.Snapshot.WorldViewportObserved); Assert.False(coordinator.Snapshot.Completed); Assert.Equal(1, audio.ResumeCalls); Assert.Empty(scheduler.Ends); coordinator.RevealWorldViewport(); coordinator.RevealWorldViewport(); Assert.True(availability.IsWorldAvailable); Assert.False(coordinator.Snapshot.Completed); Assert.Equal(1, audio.ResumeCalls); Assert.Equal([generation], scheduler.Ends); coordinator.Complete(); Assert.True(coordinator.Snapshot.Completed); Assert.Equal(1, audio.ResumeCalls); Assert.Equal([generation], scheduler.Ends); } [Fact] public void TunnelContinue_TicksDestinationObjectsBehindPortalViewport() { var transit = new RuntimeWorldTransitState(); var availability = new WorldGenerationAvailabilityState(transit); var world = new GpuWorldState(availability: availability); var quiescence = new WorldGenerationQuiescence( new SelectionState(), world, _ => null, audio: null); var scheduler = new RecordingDestinationScheduler(); var coordinator = new WorldRevealCoordinator( transit, isRenderNeighborhoodReady: (_, _) => true, isSpawnCellReady: _ => true, isTerrainNeighborhoodReady: (_, _) => true, areCompositeTexturesReady: () => true, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence, streaming: scheduler); var calls = new List(); var frame = new RetailLiveFrameCoordinator( new global::AcDream.App.Tests.TestLiveObjectFramePhase( _ => calls.Add("objects")), world, new global::AcDream.App.Tests.TestLiveSessionFramePhase( () => calls.Add("network")), new global::AcDream.App.Tests.TestPostNetworkCommandFramePhase( () => calls.Add("commands")), new global::AcDream.App.Tests.TestLiveSpatialReconcilePhase( () => calls.Add("spatial")), availability); const uint destinationCell = 0x3032001Cu; long generation = BeginPortal( coordinator, transit, destinationCell, sequence: 1); frame.Tick(1f / 60f); Assert.Equal(["network", "commands"], calls); calls.Clear(); Assert.True(coordinator.Evaluate(destinationCell).IsReady); coordinator.ObserveMaterialized( generation, 1, destinationCell); frame.Tick(1f / 60f); Assert.Equal(["objects", "network", "commands", "spatial"], calls); Assert.Empty(scheduler.Ends); Assert.False(coordinator.Snapshot.WorldViewportObserved); } [Fact] public void SessionReset_ReleasesHostEdgesAndClearsRuntimeSnapshot() { var transit = new RuntimeWorldTransitState(); var availability = new WorldGenerationAvailabilityState(transit); var world = new GpuWorldState(availability: availability); var audio = new RecordingAudioQuiescence(); var quiescence = new WorldGenerationQuiescence( new SelectionState(), world, _ => null, audio); var scheduler = new RecordingDestinationScheduler(); var coordinator = new WorldRevealCoordinator( transit, isRenderNeighborhoodReady: (_, _) => false, isSpawnCellReady: _ => false, isTerrainNeighborhoodReady: (_, _) => false, areCompositeTexturesReady: () => false, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence, streaming: scheduler); long generation = BeginPortal( coordinator, transit, 0x3032001Cu, sequence: 1); coordinator.ResetSession(); Assert.Equal(RuntimePortalSnapshot.Idle, coordinator.Snapshot); Assert.True(availability.IsWorldAvailable); Assert.Equal(1, audio.SuspendCalls); Assert.Equal(1, audio.ResumeCalls); Assert.Equal([generation], scheduler.Ends); } [Fact] public void RevealGeneration_OwnsExactDestinationReservationUntilCompletion() { var state = new State(); var scheduler = new RecordingDestinationScheduler(); WorldRevealCoordinator coordinator = state.Build(streaming: scheduler); long first = BeginPortal( coordinator, state.Transit, 0x11340021u, sequence: 1); state.Transit.EndTeleport(); long second = BeginPortal( coordinator, state.Transit, 0x20210123u, sequence: 2); Assert.Equal( [ (first, 0x11340021u, 1), (second, 0x20210123u, 0), ], scheduler.Begins); Assert.Equal([first], scheduler.Ends); coordinator.Cancel(); Assert.Equal([first, second], scheduler.Ends); } [Fact] public void RevealGeneration_OwnsRenderUploadProfileUntilViewportRelease() { var state = new State(); var resources = new RecordingRenderResourceScheduler(); WorldRevealCoordinator coordinator = state.Build(renderResources: resources); long first = BeginPortal( coordinator, state.Transit, 0x11340021u, sequence: 1); state.Transit.EndTeleport(); long second = BeginPortal( coordinator, state.Transit, 0x20210123u, sequence: 2); Assert.Equal([first, second], resources.Begins); Assert.Equal([first], resources.Ends); coordinator.RevealWorldViewport(); coordinator.Complete(); Assert.Equal([first, second], resources.Ends); } [Fact] public void RegistrationCallback_ReentrantReplacementRetiresExactOldSuffix() { var state = new State(); var scheduler = new RecordingDestinationScheduler(); WorldRevealCoordinator coordinator = null!; long replacement = 0; scheduler.OnBegin = () => { scheduler.OnBegin = null; replacement = coordinator.BeginLogin(0x20210123u); }; coordinator = state.Build(streaming: scheduler); long first = coordinator.BeginLogin(0x11340021u); Assert.NotEqual(first, replacement); Assert.Equal(replacement, coordinator.Snapshot.Generation); Assert.Equal( [ (first, 0x11340021u, 1), (replacement, 0x20210123u, 0), ], scheduler.Begins); Assert.Equal([first], scheduler.Ends); Assert.Equal(1, coordinator.Ownership.HostProjectionCount); Assert.Equal( 0, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.Cancel(); Assert.Equal([first, replacement], scheduler.Ends); Assert.Equal(0, coordinator.Ownership.HostProjectionCount); } [Fact] public void RegistrationFailure_RetriesExactSuffixWithoutReplayingBegin() { var state = new State(); var scheduler = new RecordingDestinationScheduler(); var resources = new RecordingRenderResourceScheduler { ThrowBeginCount = 1, }; WorldRevealCoordinator coordinator = state.Build( streaming: scheduler, renderResources: resources); Assert.Throws( () => coordinator.BeginLogin(0x11340021u)); Assert.Single(scheduler.Begins); Assert.Equal(1, resources.BeginAttempts); Assert.Empty(resources.Begins); Assert.Equal(1, coordinator.Ownership.HostProjectionCount); Assert.Equal( 1, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.RetryPendingHostWork(); Assert.Single(scheduler.Begins); Assert.Equal(2, resources.BeginAttempts); Assert.Single(resources.Begins); Assert.Equal( 0, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.Cancel(); Assert.Equal(0, coordinator.Ownership.HostProjectionCount); } [Fact] public void ReservationReleaseFailure_RetriesOnlyUnfinishedCallback() { var state = new State(); var scheduler = new RecordingDestinationScheduler(); var resources = new RecordingRenderResourceScheduler { ThrowEndCount = 1, }; WorldRevealCoordinator coordinator = state.Build( streaming: scheduler, renderResources: resources); long generation = coordinator.BeginLogin(0x11340021u); Assert.Throws( coordinator.RevealWorldViewport); Assert.Equal([generation], scheduler.Ends); Assert.Equal(1, resources.EndAttempts); Assert.Empty(resources.Ends); Assert.Equal( 1, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.RetryPendingHostWork(); Assert.Equal([generation], scheduler.Ends); Assert.Equal(2, resources.EndAttempts); Assert.Equal([generation], resources.Ends); Assert.Equal( 0, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.Cancel(); } [Fact] public void ResetFailure_RetainsWithdrawalAndConvergesOnRetry() { var state = new State(); var scheduler = new RecordingDestinationScheduler { ThrowEndCount = 1, }; WorldRevealCoordinator coordinator = state.Build(streaming: scheduler); coordinator.BeginLogin(0x11340021u); Assert.Throws(coordinator.ResetSession); Assert.True(coordinator.Snapshot.Cancelled); Assert.Equal(1, coordinator.Ownership.HostProjectionCount); Assert.True( coordinator.Ownership.PendingHostAcknowledgementCount > 0); coordinator.ResetSession(); Assert.Equal(RuntimePortalSnapshot.Idle, coordinator.Snapshot); Assert.True(coordinator.Ownership.IsSessionIdle); Assert.Equal(2, scheduler.EndAttempts); Assert.Single(scheduler.Ends); } [Fact] public void SimulationReleaseFailure_RetriesAudioWithoutReplayingMaterialization() { var transit = new RuntimeWorldTransitState(); var availability = new WorldGenerationAvailabilityState(transit); var world = new GpuWorldState(availability: availability); var audio = new RecordingAudioQuiescence { ThrowResumeCount = 1, }; var quiescence = new WorldGenerationQuiescence( new SelectionState(), world, _ => null, audio); var coordinator = new WorldRevealCoordinator( transit, isRenderNeighborhoodReady: (_, _) => true, isSpawnCellReady: _ => true, isTerrainNeighborhoodReady: (_, _) => true, areCompositeTexturesReady: () => true, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence); const uint cell = 0x3032001Cu; long generation = BeginPortal( coordinator, transit, cell, sequence: 1); Assert.True(coordinator.Evaluate(cell).IsReady); Assert.Throws(() => coordinator.ObserveMaterialized( generation, 1, cell)); Assert.True(coordinator.Snapshot.Materialized); Assert.Equal(1, coordinator.PortalMaterializationCount); Assert.Equal(1, audio.ResumeAttempts); Assert.Equal(0, audio.ResumeCalls); Assert.Equal( 1, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.RetryPendingHostWork(); Assert.Equal(2, audio.ResumeAttempts); Assert.Equal(1, audio.ResumeCalls); Assert.Equal(1, coordinator.PortalMaterializationCount); Assert.Equal( 0, coordinator.Ownership.PendingHostAcknowledgementCount); coordinator.Cancel(); } private static long BeginPortal( WorldRevealCoordinator coordinator, RuntimeWorldTransitState transit, uint destinationCell, ushort sequence) { Assert.True(transit.TryQueueTeleportStart(sequence)); Assert.True(transit.ActivateQueuedTeleport()); Assert.True(transit.OfferTeleportDestination( new RuntimeTeleportDestination( EntityGuid: 0x50000001u, InstanceSequence: 1, PositionSequence: 1, TeleportSequence: sequence, ForcePositionSequence: 1, Position: new Position( destinationCell, Vector3.Zero, Quaternion.Identity)), teleportTimestampAdvanced: true)); Assert.True(coordinator.TryBeginPortal( sequence, destinationCell, out long generation)); return generation; } private sealed class RecordingAudioQuiescence : AcDream.App.Audio.IWorldAudioQuiescence { public int SuspendCalls { get; private set; } public int ResumeCalls { get; private set; } public int SuspendAttempts { get; private set; } public int ResumeAttempts { get; private set; } public int ThrowSuspendCount { get; set; } public int ThrowResumeCount { get; set; } public void SuspendWorldAudio() { SuspendAttempts++; if (ThrowSuspendCount > 0) { ThrowSuspendCount--; throw new InvalidOperationException("suspend failed"); } SuspendCalls++; } public void ResumeWorldAudio() { ResumeAttempts++; if (ThrowResumeCount > 0) { ThrowResumeCount--; throw new InvalidOperationException("resume failed"); } ResumeCalls++; } } private sealed class RecordingDestinationScheduler : IWorldRevealStreamingScheduler { public List<(long Generation, uint Cell, int Radius)> Begins { get; } = []; public List Ends { get; } = []; public int BeginAttempts { get; private set; } public int EndAttempts { get; private set; } public int ThrowBeginCount { get; set; } public int ThrowEndCount { get; set; } public Action? OnBegin { get; set; } public Action? OnEnd { get; set; } public void BeginDestinationReservation( long revealGeneration, uint destinationCell, int requiredRenderRadius) { BeginAttempts++; if (ThrowBeginCount > 0) { ThrowBeginCount--; throw new InvalidOperationException("stream begin failed"); } Begins.Add( (revealGeneration, destinationCell, requiredRenderRadius)); OnBegin?.Invoke(); } public void EndDestinationReservation(long revealGeneration) { EndAttempts++; if (ThrowEndCount > 0) { ThrowEndCount--; throw new InvalidOperationException("stream end failed"); } Ends.Add(revealGeneration); OnEnd?.Invoke(); } } private sealed class RecordingRenderResourceScheduler : IWorldRevealRenderResourceScheduler { public List Begins { get; } = []; public List Ends { get; } = []; public int BeginAttempts { get; private set; } public int EndAttempts { get; private set; } public int ThrowBeginCount { get; set; } public int ThrowEndCount { get; set; } public void BeginDestinationReveal(long revealGeneration) { BeginAttempts++; if (ThrowBeginCount > 0) { ThrowBeginCount--; throw new InvalidOperationException("render begin failed"); } Begins.Add(revealGeneration); } public void EndDestinationReveal(long revealGeneration) { EndAttempts++; if (ThrowEndCount > 0) { ThrowEndCount--; throw new InvalidOperationException("render end failed"); } Ends.Add(revealGeneration); } } }