using AcDream.App.Streaming; 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) => 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); } [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 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); } 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); } }