XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.IO.Pipes;
using System.Text.Json;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
public class GalaxyStreamingTests
{
    [Test] public async Task UnverifiedEmptyLegacyTilesCanReloadAfterTheGameFinishesLoading()
    {
        using var session=new GalaxyStreamingSession(new(){UnverifiedCacheMilliseconds=0});
        var view=SmallView();var queries=0;
        Task<GalaxyRegionResult> Empty(Coordinates c,double radius,CancellationToken token)
        {queries++;return Task.FromResult(new GalaxyRegionResult([],0,1,1));}
        var first=await Collect(session,view,Empty);var firstCount=queries;
        Task<GalaxyRegionResult> Loaded(Coordinates c,double radius,CancellationToken token)
        {queries++;return Task.FromResult(new GalaxyRegionResult([new("Loaded fixture",new(100,100,100))],0,1,1));}
        var second=await Collect(session,view,Loaded);
        Check.That(first[^1].UnverifiedTiles>0&&!first[^1].CoverageComplete);
        Check.That(queries>firstCount&&second.SelectMany(u=>u.Systems).Any(s=>s.Name=="Loaded fixture"));
        Check.That(!second[^1].CoverageComplete,"A refreshed legacy tile still cannot certify complete coverage.");
    }
    private static GalaxyViewport SmallView(double x = 100) => new(new(x, 100, 100), new(1, 0, 0), new(0, 1, 0), new(0, 0, 1), 7, 7, 7);
    private static async Task<List<GalaxyStreamUpdate>> Collect(GalaxyStreamingSession session, GalaxyViewport view,
        Func<Coordinates, double, CancellationToken, Task<GalaxyRegionResult>> query)
    {
        var updates = new List<GalaxyStreamUpdate>();
        await foreach (var update in session.StreamAsync(view, query)) updates.Add(update);
        return updates;
    }

    [Test] public void FrustumPlanCoversNearAndFarOffAxisStarsBeyondTheOldPivotSphere()
    {
        var camera = GalaxyCamera.Create(new(0, 0, 26000), 1600, 900, .6, -.2, 1);
        var samples = new List<Coordinates>();
        foreach (var depth in new[] { camera.Near * 3, camera.Distance, camera.Far * .98 })
            foreach (var side in new[] { -.9, .9 })
            {
                var offset = camera.Forward * depth + camera.Right * (side * camera.Width / (2 * camera.FocalLength) * depth)
                    + camera.Up * (side * camera.Height / (2 * camera.FocalLength) * depth);
                samples.Add(new(camera.Eye.X + offset.X, camera.Eye.Y + offset.Y, camera.Eye.Z + offset.Z));
            }
        Check.That(samples.All(p => camera.Project(p).IsVisible));
        Check.That(samples.Any(p => p.DistanceTo(camera.Focus) > 700));
        var plan = new GalaxyQueryPlan(camera.ToViewport());
        var remaining = samples.ToList(); var count = 0;
        while (plan.TryTake(out var tile))
        {
            Check.That(tile.Radius <= 700 && GalaxyQueryPlan.Intersects(camera.ToViewport(), tile, 1.12));
            remaining.RemoveAll(tile.Contains);
            count++;
            Check.That(count < 20000, "Frustum construction must be lazy and bounded.");
        }
        Check.Equal(0, remaining.Count);
        Check.Equal(0, plan.OmittedTiles);
    }

    [Test] public async Task TruncatedAndCoarseRegionsRefineInsteadOfBeingCachedAsCovered()
    {
        using var session = new GalaxyStreamingSession();
        var queries = new List<double>();
        Task<GalaxyRegionResult> Query(Coordinates c, double radius, CancellationToken token)
        {
            queries.Add(radius);
            return Task.FromResult(radius > 10
                ? new GalaxyRegionResult([], 2, 8, 17, true, false)
                : GalaxyRegionResult.Covered([]));
        }
        var updates = await Collect(session, SmallView(), Query);
        Check.That(queries.Any(r => r > 10) && queries.Any(r => r <= 10));
        Check.That(updates.Any(u => u.RefinedTiles > 0 && !u.CoverageComplete));
        Check.That(updates[^1].Finished && updates[^1].CoverageComplete && updates[^1].PendingTiles == 0);
        var firstQueries = queries.Count;
        var cached = await Collect(session, SmallView(100.25), Query);
        Check.Equal(firstQueries, queries.Count);
        Check.That(cached.Any(u => u.FromCache) && cached[^1].CoverageComplete);
    }

    [Test] public async Task SmallLegacyRepliesRemainUnverifiedAndCacheIsBounded()
    {
        using var session = new GalaxyStreamingSession(new() { MaximumCachedTiles = 3, MaximumCachedSystems = 3 });
        Task<GalaxyRegionResult> Query(Coordinates c, double radius, CancellationToken token)
            => Task.FromResult(new GalaxyRegionResult([new("At " + c, c)], 0, 8, 8));
        var updates = await Collect(session, SmallView(), Query);
        Check.That(updates[^1].Finished && !updates[^1].CoverageComplete && updates[^1].UnverifiedTiles > 0);
        Check.That(session.CachedTiles <= 3);
    }

    [Test] public async Task FailedTerminalTileCanBeRetriedByTheNextView()
    {
        using var session=new GalaxyStreamingSession(new(){MinimumTileLevel=4});var queried=0;
        Task<GalaxyRegionResult> Query(Coordinates c,double radius,CancellationToken token)
        {queried++;return Task.FromResult(new GalaxyRegionResult([],0,1,2,false,false,1){Error="failed cell"});}
        var first=await Collect(session,SmallView(),Query);var before=queried;
        var second=await Collect(session,SmallView(),Query);
        Check.That(before>0&&queried>before&&!first[^1].CoverageComplete&&!second[^1].CoverageComplete);
    }

    [Test] public async Task ANewViewCancelsStaleWorkBeforeItCanPublishOrCoverTiles()
    {
        using var session = new GalaxyStreamingSession();
        var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        async Task<GalaxyRegionResult> OldQuery(Coordinates c, double radius, CancellationToken token)
        {
            started.TrySetResult();
            await Task.Delay(Timeout.Infinite, token);
            return GalaxyRegionResult.Covered([new("Stale", c)]);
        }
        await using var old = session.StreamAsync(SmallView(), OldQuery).GetAsyncEnumerator();
        var oldMove = old.MoveNextAsync().AsTask();
        await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
        var latest = await Collect(session, SmallView(10000), (c, r, t) => Task.FromResult(GalaxyRegionResult.Covered([])));
        var cancelled = false;
        try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
        Check.That(cancelled && latest[^1].CoverageComplete);
        Check.That(latest.SelectMany(u => u.Systems).All(s => s.Name != "Stale"));
    }

    [Test] public async Task OverlappingCameraUpdatesFinishAndReuseAUsefulInflightTile()
    {
        using var session = new GalaxyStreamingSession();
        var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        CancellationToken acceptedToken = default;
        async Task<GalaxyRegionResult> Accepted(Coordinates c, double radius, CancellationToken token)
        {
            acceptedToken = token; started.TrySetResult();
            await release.Task.WaitAsync(token);
            return GalaxyRegionResult.Covered([new("Useful native tile", c, 123)]);
        }
        await using var old = session.StreamAsync(SmallView(), Accepted).GetAsyncEnumerator();
        var oldMove = old.MoveNextAsync().AsTask();
        await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
        var latestTask = Collect(session, SmallView(100.25), (c, r, t) => Task.FromResult(GalaxyRegionResult.Covered([])));
        Check.That(!acceptedToken.IsCancellationRequested, "A useful tile must survive continuous overlapping pan updates.");
        release.TrySetResult();
        var latest = await latestTask;
        var cancelled = false;
        try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
        Check.That(cancelled && latest[^1].CoverageComplete);
        Check.That(latest.SelectMany(u => u.Systems).Any(s => s.Name == "Useful native tile"));
    }

    [Test] public void CoverageReceiptsDistinguishCapsMassLodFailuresAndLegacyUnknown()
    {
        var complete = NativeGalaxyCatalog.ReadCoverage(Check.Json("""{"minimumMassCode":0,"processedCells":17,"totalCells":17,"failedCells":0,"complete":true,"truncated":false}"""), []);
        Check.That(complete.IsFullyCovered);
        var capped = NativeGalaxyCatalog.ReadCoverage(Check.Json("""{"minimumMassCode":0,"processedCells":17,"totalCells":17,"complete":true}"""), [], 24000);
        Check.That(capped.Truncated && !capped.IsFullyCovered && capped.NeedsSubdivision);
        var coarse = NativeGalaxyCatalog.ReadCoverage(Check.Json("""{"minimumMassCode":4,"processedCells":17,"totalCells":17,"complete":true}"""), []);
        Check.That(!coarse.IsFullyCovered && coarse.NeedsSubdivision);
        var failed = NativeGalaxyCatalog.ReadCoverage(Check.Json("""{"minimumMassCode":0,"processedCells":17,"totalCells":17,"failedCells":1,"complete":true}"""), []);
        Check.That(!failed.IsFullyCovered);
        var legacy = NativeGalaxyCatalog.ReadCoverage(Check.Json("{}"), []);
        Check.That(legacy.MinimumMassCode == -1 && !legacy.IsFullyCovered && !legacy.HasCoverageEvidence);
    }

    [Test] public void RecordedTwentyFourThousandStarGalacticCentreResponseDoesNotProveCoverage()
    {
        using var fixture = JsonDocument.Parse(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "Galaxy", "native-core-700-sample.json")));
        var data = fixture.RootElement;
        var rows = data.GetProperty("sample").EnumerateArray().Select(e =>
        {
            var p = e.GetProperty("position");
            return new GalacticSystem(e.GetProperty("name").GetString()!, new(p[0].GetDouble(), p[1].GetDouble(), p[2].GetDouble()),
                long.Parse(e.GetProperty("systemAddress").GetString()!), e.GetProperty("starClass").GetString()!, "Recorded native Stellar Forge");
        }).ToArray();
        Check.Equal(24000, data.GetProperty("returnedCount").GetInt32());
        Check.Equal(32, rows.Length);
        Check.That(rows.All(s => s.Position.IsValid && s.SystemAddress is > 0));
        var receipt = NativeGalaxyCatalog.ReadCoverage(Check.Json("{}"), rows, data.GetProperty("returnedCount").GetInt32());
        Check.That(receipt.Truncated && receipt.NeedsSubdivision && !receipt.IsFullyCovered);
        Check.That(rows.All(s => s.Position.DistanceTo(new(25.21875, -20.90625, 25899.96875)) <= 700.00001));
    }

    private static async Task Serve(int id, object response, CancellationToken token, Action<JsonElement> inspect)
    {
        await using var server = new NamedPipeServerStream($"XFEEDToolBox.Navigation.{id}", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
        await server.WaitForConnectionAsync(token);
        var buffer = new byte[65536]; var count = await server.ReadAsync(buffer, token);
        using (var request = JsonDocument.Parse(buffer.AsMemory(0, count))) inspect(request.RootElement);
        await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new { ok = true, result = response }), token);
        await server.WriteAsync(new byte[] { 10 }, token);
    }

    [Test] public async Task CancelledNativeRegionRetiresItsGenerationBeforeTheNextQuery()
    {
        var id = Random.Shared.Next(145000000, 150000000);
        using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        using var cancel = new CancellationTokenSource(); var operations = new List<string>();
        void Inspect(JsonElement e) => operations.Add(e.GetProperty("op").GetString()!);
        var server = Task.Run(async () =>
        {
            await Serve(id, new { generation = 70, done = false, error = "", total = 0, items = Array.Empty<object>() }, deadline.Token, e => { Inspect(e); cancel.Cancel(); });
            await Serve(id, new { generation = 70, cancelled = true }, deadline.Token, Inspect);
            await Serve(id, new { generation = 70, done = true, error = "", total = 0, items = Array.Empty<object>() }, deadline.Token, Inspect);
            await Serve(id, new { generation = 71, done = true, error = "", total = 0, items = Array.Empty<object>(), minimumMassCode = 0, processedCells = 9, totalCells = 9, complete = true }, deadline.Token, Inspect);
        });
        var catalog = new NativeGalaxyCatalog(id); var cancelled = false; var cancelledProgress = 0;
        catalog.RegionProgress += _ => { if(cancel.IsCancellationRequested)cancelledProgress++; };
        try { await catalog.RegionPageAsync(new(0, 0, 0), 10, cancel.Token); } catch (OperationCanceledException) { cancelled = true; }
        Check.Equal(0,cancelledProgress);
        var next = await catalog.RegionPageAsync(new(0, 0, 26000), 10, deadline.Token);
        await server;
        Check.That(cancelled && next.IsFullyCovered);
        Check.That(operations.SequenceEqual(new[] { "galaxy.region", "galaxy.cancel", "galaxy.result", "galaxy.region" }));
    }

    [Test] public async Task BusyNativeRegionPublishesActualRowsBeforeDoneWithoutDownloadingThemTwice()
    {
        var id=Random.Shared.Next(151000000,155000000);
        using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var firstProgress=new TaskCompletionSource<GalaxyNativeRegionProgress>(TaskCreationOptions.RunContinuationsAsynchronously);
        var progress=new List<GalaxyNativeRegionProgress>();var offsets=new List<int>();
        object[] Rows(int offset,int count)=>Enumerable.Range(offset,count).Select(i=>(object)new
            {name="Native progress "+i,systemAddress=(9007199254740993L+i).ToString(),position=new[]{(double)i,0d,26000d},starClass="M"}).ToArray();
        void Inspect(JsonElement e)=>offsets.Add(e.GetProperty("offset").GetInt32());
        var server=Task.Run(async()=>
        {
            await Serve(id,new{generation=90,done=false,error="",total=0,items=Array.Empty<object>()},deadline.Token,e=>Check.Equal("galaxy.region",e.GetProperty("op").GetString()));
            await Serve(id,new{generation=90,done=false,error="",total=96,items=Rows(0,96),minimumMassCode=0,processedCells=1,totalCells=9,complete=false},deadline.Token,Inspect);
            // Native work cannot finish in this test until real partial rows
            // have reached the caller; waiting for done would deadlock it.
            var early=await firstProgress.Task.WaitAsync(deadline.Token);
            Check.That(!early.Done&&!early.Coverage.Complete&&early.Systems.Count==96&&early.DownloadedCount==96);
            await Serve(id,new{generation=90,done=false,error="",total=192,items=Rows(96,96),minimumMassCode=0,processedCells=2,totalCells=9,complete=false},deadline.Token,Inspect);
            await Serve(id,new{generation=90,done=true,error="",total=192,items=Array.Empty<object>(),minimumMassCode=0,processedCells=9,totalCells=9,complete=true},deadline.Token,Inspect);
        });
        var catalog=new NativeGalaxyCatalog(id);
        catalog.RegionProgress+=update=>{progress.Add(update);if(update.Systems.Count>0)firstProgress.TrySetResult(update);};
        var result=await catalog.RegionPageAsync(new(0,0,26000),100,deadline.Token);
        await server;
        Check.Equal(192,result.Systems.Count);Check.That(result.IsFullyCovered);
        Check.That(offsets.SequenceEqual(new[]{0,96,192}));
        var published=progress.SelectMany(p=>p.Systems).ToArray();
        Check.Equal(192,published.Length);Check.Equal(192,published.Select(s=>s.SystemAddress).Distinct().Count());
        Check.That(progress.All(p=>p.Generation==90&&p.Systems.Count<=512)&&progress[^1].Done&&progress[^1].Coverage.IsFullyCovered);
    }

    [Test] public async Task LegacyProtocolClampsSmallTilesToFiveLightYearsAndKeepsCoverageUnverified()
    {
        var id=Random.Shared.Next(156000000,160000000);
        using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var radii=new List<double>();
        var server=Task.Run(async()=>
        {
            await Serve(id,new{processId=id,schema=1,name="XFEEDToolBox.Navigation",galaxyRegion=true},deadline.Token,e=>Check.Equal("describe",e.GetProperty("op").GetString()));
            await Serve(id,new{generation=101,done=true,error="",total=0,items=Array.Empty<object>(),minimumMassCode=0,processedCells=8,totalCells=8},deadline.Token,e=>radii.Add(e.GetProperty("radius").GetDouble()));
            await Serve(id,new{generation=102,done=true,error="",total=0,items=Array.Empty<object>(),minimumMassCode=0,processedCells=8,totalCells=8,complete=true},deadline.Token,e=>radii.Add(e.GetProperty("radius").GetDouble()));
        });
        var catalog=new NativeGalaxyCatalog(id);
        await catalog.InspectProtocolAsync(deadline.Token);
        var legacy=await catalog.RegionPageAsync(new(0,0,0),.2,deadline.Token);
        Check.That(!legacy.IsFullyCovered&&!catalog.SupportsRegionCoverage&&catalog.MinimumRegionRadius==5);
        catalog.ApplyProtocolDescription(JsonSerializer.SerializeToElement(new{processId=id,galaxyRegionCoverage=true,galaxyCancellation=true,galaxyMinimumRadius=.05}));
        var modern=await catalog.RegionPageAsync(new(0,0,0),.2,deadline.Token);
        await server;
        Check.That(modern.IsFullyCovered&&catalog.SupportsCancellation&&radii.SequenceEqual(new[]{5d,.2}));
    }

    [Test] public async Task CompletedNativeFailureRetainsFinalRowsAndRequestsAdaptiveRefinement()
    {
        var id=Random.Shared.Next(161000000,165000000);
        using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var rows=new[]{new{name="Actual partial star",systemAddress="12345",position=new[]{1d,2d,3d},starClass="K"}};
        var server=Task.Run(()=>Serve(id,new{generation=111,done=true,error="游戏星区数据在读取时变化",total=1,items=rows,minimumMassCode=0,processedCells=1,totalCells=8,failedCells=1,complete=false},deadline.Token,e=>Check.Equal("galaxy.region",e.GetProperty("op").GetString())));
        var catalog=new NativeGalaxyCatalog(id);var progress=new List<GalaxyNativeRegionProgress>();catalog.RegionProgress+=progress.Add;
        var result=await catalog.RegionPageAsync(new(0,0,0),10,deadline.Token);
        await server;
        Check.Equal(1,result.Systems.Count);
        Check.That(result.Error.Length>0&&result.FailedCells>=1&&result.NeedsSubdivision&&!result.IsFullyCovered);
        Check.That(progress[^1].Done&&progress[^1].Systems.Single().Name=="Actual partial star"&&progress[^1].Coverage.Error.Length>0);
    }

    [Test] public async Task NativeDeadlineRetiresAcceptedGenerationUsingAFreshCleanupDeadline()
    {
        var id=Random.Shared.Next(166000000,170000000);
        using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var retired=false;var retirementRead=false;var operations=new List<string>();
        var server=Task.Run(async()=>
        {
            while(!retirementRead)
            {
                await using var pipe=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{id}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
                await pipe.WaitForConnectionAsync(deadline.Token);var buffer=new byte[65536];var count=await pipe.ReadAsync(buffer,deadline.Token);
                using var request=JsonDocument.Parse(buffer.AsMemory(0,count));var op=request.RootElement.GetProperty("op").GetString()!;operations.Add(op);
                if(op=="galaxy.cancel")retired=true;
                if(op=="galaxy.result"&&retired)retirementRead=true;
                var response=new{ok=true,result=new{generation=121,done=retired,error="",total=0,items=Array.Empty<object>()}};
                try{await pipe.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(response),deadline.Token);await pipe.WriteAsync(new byte[]{10},deadline.Token);}catch(IOException){}
            }
            await Serve(id,new{generation=122,done=true,error="",total=0,items=Array.Empty<object>(),minimumMassCode=0,processedCells=8,totalCells=8,complete=true},deadline.Token,e=>Check.Equal("galaxy.region",e.GetProperty("op").GetString()));
        });
        var catalog=new NativeGalaxyCatalog(id,TimeSpan.FromMilliseconds(120));var timedOut=false;
        try{await catalog.RegionPageAsync(new(0,0,0),10,deadline.Token);}catch(TimeoutException){timedOut=true;}
        var next=await catalog.RegionPageAsync(new(0,0,0),10,deadline.Token);
        await server;
        Check.That(timedOut&&retired&&retirementRead&&next.IsFullyCovered);
        Check.That(operations.Contains("galaxy.cancel")&&operations[^1]=="galaxy.result");
    }

    [Test] public async Task NativeBookmarkAddressResolutionPreservesLargeAddressAndRejectsSubstitution()
    {
        var id=Random.Shared.Next(171000000,175000000);
        using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        const long address=9007199254740993;
        var server=Task.Run(async()=>
        {
            await Serve(id,new{generation=131,done=true,error="",items=new[]{new{name="Original bookmark system",systemAddress=address.ToString(),position=new[]{25.21875,-20.90625,25899.96875},starClass="H"}}},deadline.Token,e=>
            {Check.Equal("galaxy.resolve",e.GetProperty("op").GetString());Check.Equal(address.ToString(),e.GetProperty("systemAddress").GetString());Check.That(!e.TryGetProperty("query",out _));});
            await Serve(id,new{generation=132,done=true,error="",items=new[]{new{name="Unrelated system",systemAddress=(address+1).ToString(),position=new[]{0d,0d,0d},starClass="G"}}},deadline.Token,e=>Check.Equal(address.ToString(),e.GetProperty("systemAddress").GetString()));
        });
        var catalog=new NativeGalaxyCatalog(id);
        var resolved=await catalog.ResolveAddressAsync(address,deadline.Token);
        Check.Equal(address,resolved.SystemAddress);Check.Equal(25899.96875,resolved.Position.Z);
        var rejected=false;try{await catalog.ResolveAddressAsync(address,deadline.Token);}catch(InvalidDataException){rejected=true;}
        await server;Check.That(rejected);
    }
}