using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Selection; namespace AcDream.App.Tests.Streaming; public sealed class WorldRevealCoordinatorTests { private sealed class State { 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) => new( isRenderNeighborhoodReady: (_, _) => RenderReady, isSpawnCellReady: _ => CollisionReady, isTerrainNeighborhoodReady: (_, _) => CollisionReady, areCompositeTexturesReady: () => CompositesReady, prepareCompositeTextures: (_, _) => PrepareCount++, invalidateCompositeTextures: () => { InvalidateCount++; CompositesReady = false; }, isSpawnClaimUnhydratable: _ => false, log: logs is null ? null : new Action(logs.Add), streaming: streaming, renderResources: renderResources); } [Fact] public void Begin_InvalidatesPriorDestinationAndStartsOneGeneration() { var state = new State { CompositesReady = true }; WorldRevealCoordinator coordinator = state.Build(); long generation = coordinator.Begin(WorldRevealKind.Login, 0x11340021u); Assert.Equal(1, generation); Assert.Equal(1, state.InvalidateCount); Assert.False(state.CompositesReady); Assert.Equal(WorldRevealKind.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.Begin(WorldRevealKind.Login, 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, coordinator.Snapshot.Readiness); } [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); coordinator.Begin(WorldRevealKind.Portal, outdoorCell); state.CompositesReady = true; Assert.True(coordinator.Evaluate(outdoorCell).IsReady); coordinator.ObserveMaterialized(); coordinator.ObserveMaterialized(); 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 Cancel_PreventsLateDestinationWorkFromMutatingLifecycle() { var state = new State { RenderReady = true, CollisionReady = true, CompositesReady = true, }; WorldRevealCoordinator coordinator = state.Build(); coordinator.Begin(WorldRevealKind.Portal, 0x11340021u); coordinator.Cancel(); coordinator.PrepareAndEvaluate(0x11340021u); coordinator.ObserveMaterialized(); coordinator.ObserveWorldViewportVisible(); coordinator.Complete(); Assert.True(coordinator.Snapshot.Cancelled); Assert.Equal(default, coordinator.Snapshot.Readiness); Assert.Equal(0, coordinator.PortalMaterializationCount); } [Fact] public void RevealLifetime_QuiescesContinuouslyAndOnlyActiveGenerationReopens() { var availability = new WorldGenerationAvailabilityState(); var world = new GpuWorldState(availability: availability); var audio = new RecordingAudioQuiescence(); var quiescence = new WorldGenerationQuiescence( availability, new SelectionState(), world, audio); var coordinator = new WorldRevealCoordinator( isRenderNeighborhoodReady: (_, _) => true, isSpawnCellReady: _ => true, isTerrainNeighborhoodReady: (_, _) => true, areCompositeTexturesReady: () => true, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence); Assert.Equal(1, coordinator.Begin(WorldRevealKind.Login, 0x11340021u)); Assert.Equal(2, coordinator.Begin(WorldRevealKind.Portal, 0x11340021u)); Assert.False(availability.IsWorldAvailable); Assert.Equal(2, availability.QuiescedGeneration); Assert.Equal(1, audio.SuspendCalls); coordinator.Complete(); Assert.True(availability.IsWorldAvailable); Assert.Equal(1, audio.ResumeCalls); } [Fact] public void Materialization_ReopensWorldBeforePortalViewportRelease() { var availability = new WorldGenerationAvailabilityState(); var world = new GpuWorldState(availability: availability); var audio = new RecordingAudioQuiescence(); var quiescence = new WorldGenerationQuiescence( availability, new SelectionState(), world, audio); var scheduler = new RecordingDestinationScheduler(); var coordinator = new WorldRevealCoordinator( isRenderNeighborhoodReady: (_, _) => true, isSpawnCellReady: _ => true, isTerrainNeighborhoodReady: (_, _) => true, areCompositeTexturesReady: () => true, prepareCompositeTextures: (_, _) => { }, invalidateCompositeTextures: () => { }, isSpawnClaimUnhydratable: _ => false, quiescence: quiescence, streaming: scheduler); long generation = coordinator.Begin( WorldRevealKind.Portal, 0x3032001Cu); Assert.False(availability.IsWorldAvailable); coordinator.ObserveMaterialized(); 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 availability = new WorldGenerationAvailabilityState(); var world = new GpuWorldState(availability: availability); var quiescence = new WorldGenerationQuiescence( availability, new SelectionState(), world, audio: null); var scheduler = new RecordingDestinationScheduler(); var coordinator = new WorldRevealCoordinator( 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); coordinator.Begin(WorldRevealKind.Portal, 0x3032001Cu); frame.Tick(1f / 60f); Assert.Equal(["network", "commands"], calls); calls.Clear(); coordinator.ObserveMaterialized(); frame.Tick(1f / 60f); Assert.Equal(["objects", "network", "commands", "spatial"], calls); Assert.Empty(scheduler.Ends); Assert.False(coordinator.Snapshot.WorldViewportObserved); } [Fact] public void RevealGeneration_OwnsExactDestinationReservationUntilCompletion() { var state = new State(); var scheduler = new RecordingDestinationScheduler(); WorldRevealCoordinator coordinator = state.Build(streaming: scheduler); long first = coordinator.Begin( WorldRevealKind.Portal, 0x11340021u); long second = coordinator.Begin( WorldRevealKind.Portal, 0x20210123u); Assert.Equal( [ (first, 0x11340021u, 1), (second, 0x20210123u, 0), ], scheduler.Begins); coordinator.Complete(); Assert.Equal([second], scheduler.Ends); } [Fact] public void RevealGeneration_OwnsRenderUploadProfileUntilViewportRelease() { var state = new State(); var resources = new RecordingRenderResourceScheduler(); WorldRevealCoordinator coordinator = state.Build(renderResources: resources); long first = coordinator.Begin( WorldRevealKind.Portal, 0x11340021u); long second = coordinator.Begin( WorldRevealKind.Portal, 0x20210123u); Assert.Equal([first, second], resources.Begins); coordinator.RevealWorldViewport(); coordinator.Complete(); Assert.Equal([second], resources.Ends); } private sealed class RecordingAudioQuiescence : AcDream.App.Audio.IWorldAudioQuiescence { public int SuspendCalls { get; private set; } public int ResumeCalls { get; private set; } public void SuspendWorldAudio() => SuspendCalls++; public void ResumeWorldAudio() => ResumeCalls++; } private sealed class RecordingDestinationScheduler : IWorldRevealStreamingScheduler { public List<(long Generation, uint Cell, int Radius)> Begins { get; } = []; public List Ends { get; } = []; public void BeginDestinationReservation( long revealGeneration, uint destinationCell, int requiredRenderRadius) => Begins.Add( (revealGeneration, destinationCell, requiredRenderRadius)); public void EndDestinationReservation(long revealGeneration) => Ends.Add(revealGeneration); } private sealed class RecordingRenderResourceScheduler : IWorldRevealRenderResourceScheduler { public List Begins { get; } = []; public List Ends { get; } = []; public void BeginDestinationReveal(long revealGeneration) => Begins.Add(revealGeneration); public void EndDestinationReveal(long revealGeneration) => Ends.Add(revealGeneration); } }