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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;
using XFEEDToolBox.Core.Models;

namespace XFEEDToolBox.Core.Galaxy;

public sealed partial class NativeGalaxyCatalog
{
    private readonly SemaphoreSlim profileQueries=new(1,1);
    private long latestCatalogueProfileGeneration;
    /// <summary>The native owner reads view mode and chooses/clamps eye or focus before sampling its actual GDM.</summary>
    public Task<GalaxyCatalogueProfile> CatalogueProfileAsync(Coordinates eye,Coordinates focus,CancellationToken token=default)
        =>QueryCatalogueProfileAsync(eye,focus,true,token);
    /// <summary>Return the basic LOD immediately; incomplete auxiliary tables retain unknown absence until independently completed.</summary>
    public Task<GalaxyCatalogueProfile> CatalogueProfileFirstPageAsync(Coordinates eye,Coordinates focus,CancellationToken token=default)
        =>QueryCatalogueProfileAsync(eye,focus,false,token);
    private async Task<GalaxyCatalogueProfile> QueryCatalogueProfileAsync(Coordinates eye,Coordinates focus,bool completePointPages,CancellationToken token)
    {
        if(protocolInspected&&!SupportsCatalogueProfile)throw new NotSupportedException("当前游戏插件尚不支持原目录输入快照。");
        static bool Valid(Coordinates p)=>p is{IsValid:true}&&Math.Max(Math.Abs(p.X),Math.Max(Math.Abs(p.Y),Math.Abs(p.Z)))<=200000;
        if(!Valid(eye)||!Valid(focus))throw new ArgumentException("银河目录输入位置无效。");
        // This independent managed/native queue must not wait for an old cell
        // job: the next profile is needed to decide whether that job is useful.
        await profileQueries.WaitAsync(token);long generation=0;
        using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(8));
        try
        {
            token.ThrowIfCancellationRequested();
            // Capture acceptance independently of caller cancellation, then retire
            // its exact generation. Profile cancellation never cancels a cell job.
            var result=await pipe.CallAsync(new{op="galaxy.catalogue.profile",eye=new[]{eye.X,eye.Y,eye.Z},focus=new[]{focus.X,focus.Y,focus.Z}},deadline.Token);
            generation=result.GetProperty("generation").GetInt64();
            if(generation>0)Volatile.Write(ref latestCatalogueProfileGeneration,generation);
            while(true)
            {
                token.ThrowIfCancellationRequested();
                if(generation<=0||result.GetProperty("generation").GetInt64()!=generation)throw new InvalidDataException("游戏目录输入快照回执已变化。");
                var error=result.TryGetProperty("error",out var e)?e.GetString():"";
                if(!string.IsNullOrEmpty(error))throw new IOException(error);
                if(result.TryGetProperty("cancelled",out var cancelled)&&cancelled.ValueKind==JsonValueKind.True)throw new OperationCanceledException("游戏目录输入快照已取消。",token);
                if(result.GetProperty("done").GetBoolean())
                {
                    if(!completePointPages)return ReadFirstProfilePage(result,generation);
                    using var complete=CancellationTokenSource.CreateLinkedTokenSource(token,deadline.Token);
                    return await ReadCompleteProfileAsync(result,generation,complete.Token);
                }
                using var stop=CancellationTokenSource.CreateLinkedTokenSource(token,deadline.Token);
                await Task.Delay(20,stop.Token);
                result=await pipe.CallAsync(new{op="galaxy.catalogue.profile.result",generation},stop.Token);
            }
        }
        catch
        {
            if(generation>0)
            {
                using var cleanup=new CancellationTokenSource(TimeSpan.FromSeconds(2));
                try{await pipe.CallAsync(new{op="galaxy.catalogue.profile.cancel",generation},cleanup.Token);}catch{}
            }
            if(deadline.IsCancellationRequested&&!token.IsCancellationRequested)throw new TimeoutException("游戏目录输入快照超时,请稍后重试。");
            throw;
        }
        finally{profileQueries.Release();}
    }
    public static GalaxyCatalogueProfile ReadCatalogueProfile(JsonElement result)
    {
        if(result.ValueKind!=JsonValueKind.Object)throw new InvalidDataException("游戏目录输入快照不可用。");
        Coordinates? Position(string name,bool required=false)
        {
            if(!result.TryGetProperty(name,out var v)||v.ValueKind==JsonValueKind.Null)
            {if(required)throw new InvalidDataException("游戏目录输入缺少真实原点。");return null;}
            if(v.ValueKind!=JsonValueKind.Array||v.GetArrayLength()!=3)throw new InvalidDataException("游戏目录输入坐标无效。");
            var p=new Coordinates(v[0].GetDouble(),v[1].GetDouble(),v[2].GetDouble());
            if(!p.IsValid||Math.Max(Math.Abs(p.X),Math.Max(Math.Abs(p.Y),Math.Abs(p.Z)))>1000000)throw new InvalidDataException("游戏目录输入坐标无效。");
            return p;
        }
        double? Number(string name)
        {
            if(!result.TryGetProperty(name,out var v)||v.ValueKind==JsonValueKind.Null)return null;
            if(v.ValueKind!=JsonValueKind.Number||!v.TryGetDouble(out var n)||!double.IsFinite(n)||n<0)throw new InvalidDataException("游戏目录输入数值无效。");
            return n;
        }
        bool? Flag(string name)
        {
            if(!result.TryGetProperty(name,out var v)||v.ValueKind==JsonValueKind.Null)return null;
            if(v.ValueKind is not(JsonValueKind.False or JsonValueKind.True))throw new InvalidDataException("游戏目录输入开关无效。");
            return v.GetBoolean();
        }
        uint? quality=null;int? mode=null;
        if(result.TryGetProperty("qualityStarCount",out var n)&&n.ValueKind!=JsonValueKind.Null)
        {if(n.ValueKind!=JsonValueKind.Number||!n.TryGetUInt32(out var q))throw new InvalidDataException("原图形质量恒星数量无效。");quality=q;}
        if(result.TryGetProperty("originalViewMode",out var m)&&m.ValueKind!=JsonValueKind.Null)
        {if(m.ValueKind!=JsonValueKind.Number||!m.TryGetInt32(out var v)||v<0)throw new InvalidDataException("原星图视角模式无效。");mode=v;}
        var reference=result.TryGetProperty("referenceKind",out var r)&&r.ValueKind==JsonValueKind.String?r.GetString()??"":"";
        if(reference!="camera-eye")throw new InvalidDataException("原目录参考位置语义未知。");
        var centre=Position("queryCentre");var density=Number("surfaceDensity");
        if((centre is not null||density is not null)&&mode is null||density is not null&&centre is null)
            throw new InvalidDataException("原目录输入缺少所采样的视角或位置。");
        var blend=Number("colourBlend");if(blend>1)throw new InvalidDataException("原恒星配色混合值无效。");
        int? routeView=null;int? routeMode=null;
        if(result.TryGetProperty("navRouteViewMode",out var rv)&&rv.ValueKind!=JsonValueKind.Null)
        {if(!rv.TryGetInt32(out var value))throw new InvalidDataException("原导航视图模式无效。");routeView=value;}
        if(result.TryGetProperty("navRouteMembershipMode",out var rm)&&rm.ValueKind!=JsonValueKind.Null)
        {if(!rm.TryGetInt32(out var value)||value is <0 or >1)throw new InvalidDataException("原导航成员模式无效。");routeMode=value;}
        if(routeView is{} rawRoute&&routeMode is{} memberMode&&(rawRoute==1?1:0)!=memberMode||routeView.HasValue!=routeMode.HasValue)
            throw new InvalidDataException("原导航成员上下文不匹配。");
        return new(Position("origin",true)!,centre,density,quality,Number("runtimeRadiusMultiplier"),Number("sparseExpansion"),Flag("smallMode"),mode,Flag("debugEnabled"),reference)
            {ColourBlend=blend,NavRouteViewMode=routeView,NavRouteMembershipMode=routeMode,DustInputs=ReadCatalogueDustInputs(result),CurrentRouteChoices=ReadCatalogueRouteChoices(result),
             PointOverrides=result.TryGetProperty("pointOverrides",out var points)&&points.ValueKind!=JsonValueKind.Null?ReadPointOverridesPages([points]):null};
    }
}