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 XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
public sealed class NativeGalaxyCellBudgetTests
{
    private const int DenseCount=24001;
    private const ulong Cell=7;
    private sealed record WireRow(string name,string systemAddress,double[] position,string starClass);
    private static WireRow Row(int index)=>new("Dense fixture "+index,(((ulong)index+1)<<23|Cell).ToString(),[index%1000,5,5],"M");
    private static GalacticSystem[] DenseRows()=>Enumerable.Range(0,DenseCount).Select(i=>
        new GalacticSystem("Dense fixture "+i,new(i%1000,5,5),(long)(((ulong)i+1)<<23|Cell))).ToArray();

    private static object Receipt(long generation,bool done,int total,WireRow[] rows,int rowLimit=131072,int processed=1)=>
        new{generation,done,error="",total,items=rows,scope="requested-cells",requestedCells=1,minimumMassCode=7,
            processedCells=processed,totalCells=1,failedCells=0,truncated=false,complete=done,rowLimit,origin=new[]{0d,0d,0d}};

    private static async Task Serve(int id,Func<JsonElement,object> reply,CancellationToken token)
    {
        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=0;JsonElement request;
        while(true)
        {
            var read=await server.ReadAsync(buffer.AsMemory(count),token);if(read==0)throw new IOException("Empty budget-test request");count+=read;
            var parsed=ParseRequest(buffer,count);if(parsed is{} value){request=value;break;}
            if(count==buffer.Length)throw new IOException("Oversized budget-test request");
        }
        var result=reply(request);var bytes=JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result});
        Check.That(bytes.Length<48000,"An enlarged cell still uses bounded individual IPC pages.");
        await server.WriteAsync(bytes,token);await server.WriteAsync(new byte[]{10},token);
        if(OperatingSystem.IsWindows())server.WaitForPipeDrain();
    }
    private static JsonElement? ParseRequest(byte[] data,int count)
    {
        var reader=new Utf8JsonReader(data.AsSpan(0,count),false,default);
        if(!JsonDocument.TryParseValue(ref reader,out var document))return null;
        using(document)return document.RootElement.Clone();
    }

    [Test] public async Task CompleteDenseSingleCellDownloads24001ActualRowsAcrossBoundedPagesAndPublishesBeforeDone()
    {
        var id=Random.Shared.Next(116000000,117000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(15));
        var offset=0;var pages=0;var nativeDone=false;var publishedBeforeDone=false;
        var published=new HashSet<long>();var duplicateProgress=false;var finalProgress=false;
        var server=Task.Run(async()=>
        {
            await Serve(id,r=>
            {
                Check.Equal("galaxy.cells",r.GetProperty("op").GetString());Check.Equal("7",r.GetProperty("cellAddresses")[0].GetString());
                return Receipt(81,false,0,[],processed:0);
            },stop.Token);
            while(offset<DenseCount)
                await Serve(id,r=>
                {
                    Check.Equal("galaxy.result",r.GetProperty("op").GetString());Check.Equal(81L,r.GetProperty("generation").GetInt64());
                    Check.Equal(offset,r.GetProperty("offset").GetInt32());Check.Equal(96,r.GetProperty("limit").GetInt32());
                    var count=Math.Min(96,DenseCount-offset);var rows=Enumerable.Range(offset,count).Select(Row).ToArray();
                    var first=offset==0;offset+=count;pages++;nativeDone=!first;
                    // The first owning-thread page is still busy. The next
                    // snapshot has finished copying the cell; the client must
                    // nevertheless download all remaining stable pages.
                    return Receipt(81,!first,first?96:DenseCount,rows,processed:first?0:1);
                },stop.Token);
        });
        var catalogue=new NativeGalaxyCatalog(id);
        catalogue.RegionProgress+=progress=>
        {
            Check.Equal(131072,progress.Coverage.RowLimit);
            if(progress.Systems.Count>0&&!nativeDone)publishedBeforeDone=true;
            foreach(var row in progress.Systems)if(!published.Add(row.SystemAddress!.Value))duplicateProgress=true;
            if(progress.Done){finalProgress=true;Check.Equal(DenseCount,progress.DownloadedCount);}
        };
        var result=await catalogue.CellsAsync(new(5,5,5),Enumerable.Repeat(1d,8).ToArray(),new[]{Cell},stop.Token);await server;
        Check.Equal((DenseCount+95)/96,pages);Check.Equal(DenseCount,result.Systems.Count);Check.Equal(DenseCount,published.Count);
        Check.That(publishedBeforeDone&&finalProgress&&!duplicateProgress);
        Check.That(result.WithinReturnedRowBudget&&result.AreRequestedCellsCovered&&!result.Truncated&&!result.IsFullyCovered&&!result.NeedsSubdivision);
        Check.Equal(131072,result.RowLimit);Check.Equal(Row(DenseCount-1).name,result.Systems[^1].Name);
        Check.That(result.Systems.All(s=>GalaxyCellQueryPlan.CellAddress((ulong)s.SystemAddress!.Value)==Cell));
    }

    [Test] public void RowBudgetRejectsNonSingleCellExpansionAndEveryExplicitInvalidLimit()
    {
        static GalaxyRegionResult Read(string text)
        {using var json=JsonDocument.Parse(text);return NativeGalaxyCatalog.ReadCoverage(json.RootElement,[]);}
        const string prefix="{\"scope\":\"requested-cells\",\"requestedCells\":1,\"minimumMassCode\":7,\"processedCells\":1,\"totalCells\":1,\"failedCells\":0,\"complete\":true";
        foreach(var invalid in new[]{"-1","0","24001","131073","1.5","\"131072\"","null","true"})
            Check.Throws<InvalidDataException>(()=>Read(prefix+",\"rowLimit\":"+invalid+"}"));
        Check.Throws<InvalidDataException>(()=>Read("{\"scope\":\"region-sphere\",\"rowLimit\":131072}"));
        Check.Throws<InvalidDataException>(()=>Read("{\"scope\":\"requested-cells\",\"requestedCells\":2,\"totalCells\":2,\"rowLimit\":131072}"));
        Check.Equal(24000,Read(prefix+"}").RowLimit);
        var allowed=Read(prefix+",\"rowLimit\":131072}");Check.That(allowed.AreRequestedCellsCovered&&!allowed.IsFullyCovered);
        var exactCap=allowed with{Systems=Enumerable.Repeat(new GalacticSystem("Budget fixture",new(5,5,5),15),131072).ToArray()};
        Check.That(exactCap.WithinReturnedRowBudget&&exactCap.AreRequestedCellsCovered);
        Check.That(!(exactCap with{Systems=exactCap.Systems.Append(exactCap.Systems[0]).ToArray()}).WithinReturnedRowBudget);
    }

    [Test] public async Task ChangingTheAcceptedRowBudgetRejectsTheGenerationAndRetiresItsQueueBeforeRetry()
    {
        var id=Random.Shared.Next(117000000,118000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var cancelled=false;var drained=false;var server=Task.Run(async()=>
        {
            await Serve(id,_=>Receipt(91,false,0,[],24000,0),stop.Token);
            await Serve(id,_=>Receipt(91,false,0,[],131072,0),stop.Token);
            await Serve(id,r=>
            {cancelled=r.GetProperty("op").GetString()=="galaxy.cancel"&&r.GetProperty("generation").GetInt64()==91;return new{generation=91,done=true};},stop.Token);
            await Serve(id,r=>
            {drained=r.GetProperty("op").GetString()=="galaxy.result"&&r.GetProperty("generation").GetInt64()==91;return new{generation=91,done=true};},stop.Token);
            await Serve(id,r=>
            {Check.Equal("galaxy.cells",r.GetProperty("op").GetString());return Receipt(92,true,0,[]);},stop.Token);
        });
        var catalogue=new NativeGalaxyCatalog(id);var rejected=false;
        try{await catalogue.CellsAsync(new(5,5,5),Enumerable.Repeat(1d,8).ToArray(),new[]{Cell},stop.Token);}
        catch(InvalidDataException){rejected=true;}
        Check.That(rejected);var retry=await catalogue.CellsAsync(new(5,5,5),Enumerable.Repeat(1d,8).ToArray(),new[]{Cell},stop.Token);await server;
        Check.That(cancelled&&drained&&retry.AreRequestedCellsCovered);
    }

    [Test] public async Task CellStreamingCachesTheCompleteDenseSingleCellWithoutSubdivisionOrASecondNativeQuery()
    {
        var centre=new Coordinates(5,5,5);var descriptor=GalaxyOriginalCatalogueLod.Create(centre,0,500,0,1,.001);
        var plan=new GalaxyCellQueryPlan(GalaxyCamera.Create(centre,1600,900,0,0,4),descriptor,new(0,0,0));
        Check.Equal(1,plan.Cells.Count);Check.Equal(Cell,plan.Cells[0].Address);
        using var session=new GalaxyCellStreamingSession(new(){MaximumCachedSystems=50000});var calls=0;var updates=new List<GalaxyCellStreamUpdate>();
        Task<GalaxyRegionResult> Query(Coordinates _,IReadOnlyList<double> radii,IReadOnlyList<ulong> cells,CancellationToken token)
        {
            calls++;Check.Equal(1,cells.Count);Check.Equal(Cell,cells[0]);
            return Task.FromResult(new GalaxyRegionResult(DenseRows(),7,1,1,false,true)
            {Scope="requested-cells",RequestedCells=1,Origin=new(0,0,0),RowLimit=131072});
        }
        await foreach(var update in session.StreamAsync(plan,Query))updates.Add(update);
        Check.Equal(1,calls);Check.Equal(DenseCount,updates.SelectMany(u=>u.Systems).Count());
        Check.That(updates[^1].CoverageComplete&&updates[^1].UnverifiedCells==0&&updates[^1].RefinedBatches==0);
        var cached=new List<GalaxyCellStreamUpdate>();
        await foreach(var update in session.StreamAsync(plan,(_,_,_,_)=>throw new Exception("A complete dense cell must not be queried again")))cached.Add(update);
        Check.That(cached[^1].CoverageComplete&&cached.Any(u=>u.FromCache&&u.Systems.Count==DenseCount));Check.Equal(1,session.CachedCells);
    }
}