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;
/// The native owner reads view mode and chooses/clamps eye or focus before sampling its actual GDM.
public Task CatalogueProfileAsync(Coordinates eye,Coordinates focus,CancellationToken token=default)
=>QueryCatalogueProfileAsync(eye,focus,true,token);
/// Return the basic LOD immediately; incomplete auxiliary tables retain unknown absence until independently completed.
public Task CatalogueProfileFirstPageAsync(Coordinates eye,Coordinates focus,CancellationToken token=default)
=>QueryCatalogueProfileAsync(eye,focus,false,token);
private async Task 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&¢re 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};
}
}