using System.Collections.Generic; using AcDream.App.Streaming; using AcDream.Core.World; using DatReaderWriter.DBObjs; using Xunit; namespace AcDream.Core.Tests.Streaming; public class StreamingControllerTests { private sealed class FakeStreamer { public List Loads { get; } = new(); public List Unloads { get; } = new(); public Queue Pending { get; } = new(); public void EnqueueLoad(uint id) => Loads.Add(id); public void EnqueueUnload(uint id) => Unloads.Add(id); public IReadOnlyList DrainCompletions(int max) { var batch = new List(); while (batch.Count < max && Pending.Count > 0) batch.Add(Pending.Dequeue()); return batch; } } [Fact] public void FirstTick_EnqueuesWholeVisibleWindow() { var state = new GpuWorldState(); var fake = new FakeStreamer(); var controller = new StreamingController( enqueueLoad: fake.EnqueueLoad, enqueueUnload: fake.EnqueueUnload, drainCompletions: fake.DrainCompletions, applyTerrain: (_, _) => { }, state: state, radius: 2); // Center at (50, 50); no landblocks loaded yet. controller.Tick(observerCx: 50, observerCy: 50); // 5×5 window = 25 loads enqueued, 0 unloads. Assert.Equal(25, fake.Loads.Count); Assert.Empty(fake.Unloads); } [Fact] public void SecondTick_SamePosition_EnqueuesNothing() { var state = new GpuWorldState(); var fake = new FakeStreamer(); var controller = new StreamingController( fake.EnqueueLoad, fake.EnqueueUnload, fake.DrainCompletions, (_, _) => { }, state, radius: 2); controller.Tick(50, 50); fake.Loads.Clear(); controller.Tick(50, 50); Assert.Empty(fake.Loads); Assert.Empty(fake.Unloads); } [Fact] public void DrainingLoadedResult_AddsToState() { var state = new GpuWorldState(); var fake = new FakeStreamer(); var applied = new List(); var controller = new StreamingController( fake.EnqueueLoad, fake.EnqueueUnload, fake.DrainCompletions, (lb, _) => applied.Add(lb), state, radius: 2); // Note: LoadedLandblock's actual fields are LandblockId, Heightmap, // Entities (positional record). Adjust if the first positional arg // name differs. var lb = new LoadedLandblock(0x32320FFEu, new LandBlock(), System.Array.Empty()); // A.5 T10-T12 follow-up: use a real empty mesh instance instead of // default! so any future test that flows MeshData through the apply // callback gets a non-null reference to inspect rather than an NRE. var stubMesh = new AcDream.Core.Terrain.LandblockMeshData( System.Array.Empty(), System.Array.Empty()); fake.Pending.Enqueue(new LandblockStreamResult.Loaded(0x32320FFEu, LandblockStreamTier.Near, lb, stubMesh)); controller.Tick(50, 50); Assert.Single(applied); Assert.True(state.IsLoaded(0x32320FFEu)); } [Fact] public void DrainingUnloadedResult_RemovesFromState() { var state = new GpuWorldState(); var fake = new FakeStreamer(); var controller = new StreamingController( fake.EnqueueLoad, fake.EnqueueUnload, fake.DrainCompletions, (_, _) => { }, state, radius: 2); var lb = new LoadedLandblock(0x32320FFEu, new LandBlock(), System.Array.Empty()); state.AddLandblock(lb); fake.Pending.Enqueue(new LandblockStreamResult.Unloaded(0x32320FFEu)); controller.Tick(50, 50); Assert.False(state.IsLoaded(0x32320FFEu)); } }