using System.Text.Json; using System.Runtime.CompilerServices; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Navigation; namespace XFEEDToolBox.Core.Galaxy; public sealed partial class NativeGalaxyCatalog { private sealed record PointPage(int ProcessId,long Generation,ulong Tick,int? View,bool D50Known,bool NavKnown, int OverrideOffset,int OverrideTotal,int NavOffset,int NavTotal,GalaxyOriginalPointOverride[] Overrides,long[] Members); private sealed record PointContinuation(long Generation,JsonElement FirstPage); private readonly ConditionalWeakTable pointContinuations=new(); private static PointPage ReadPointPage(JsonElement p) { if(p.ValueKind!=JsonValueKind.Object||p.GetProperty("scope").GetString()!="published-renderer-d50+local-navroute") throw new InvalidDataException("原星点覆写来源未知。"); int Number(string name,int max) { if(!p.GetProperty(name).TryGetInt32(out var value)||value<0||value>max)throw new InvalidDataException("原星点覆写分页无效。"); return value; } long Address(JsonElement id)=>id.ValueKind==JsonValueKind.String&&long.TryParse(id.GetString(),out var value)&&value>0&&value<1L<<55 ?value:throw new InvalidDataException("原星点覆写地址无效。"); int? view=null;var mode=p.GetProperty("navRouteViewMode"); if(mode.ValueKind!=JsonValueKind.Null) {if(!mode.TryGetInt32(out var value))throw new InvalidDataException("原星点导航视图无效。");view=value;} var process=Number("processId",int.MaxValue);var generation=p.GetProperty("sourceGeneration").GetInt64();var tick=p.GetProperty("capturedAtTick").GetUInt64(); if(process<=0||generation<=0)throw new InvalidDataException("原星点覆写代际无效。"); var d50=p.GetProperty("d50Complete").GetBoolean();var nav=p.GetProperty("navRouteComplete").GetBoolean(); if(nav&&view is null)throw new InvalidDataException("原导航成员上下文缺失。"); var rows=new List(); foreach(var row in p.GetProperty("overrides").EnumerateArray()) { var id=Address(row.GetProperty("systemAddress"));var minimum=row.GetProperty("minimumExtra").GetSingle(); var position=row.GetProperty("position");if(position.ValueKind!=JsonValueKind.Array||position.GetArrayLength()!=3)throw new InvalidDataException("原星点覆写坐标无效。"); var point=new Coordinates(position[0].GetDouble(),position[1].GetDouble(),position[2].GetDouble()); var name=row.GetProperty("name").ValueKind==JsonValueKind.Null?null:row.GetProperty("name").GetString(); var star=row.GetProperty("starClass").GetString()??"";GalaxyOriginalStellarAppearance? appearance=null; if(row.TryGetProperty("originalAppearance",out var a)&&a.ValueKind!=JsonValueKind.Null) { var type=a.GetProperty("typeRgba").GetUInt32();var raw=a.GetProperty("rawScale").GetUInt32();var classification=a.GetProperty("classification").GetUInt16(); uint? realistic=a.GetProperty("realisticRgba").ValueKind==JsonValueKind.Null?null:a.GetProperty("realisticRgba").GetUInt32(); float? blend=a.GetProperty("colourBlend").ValueKind==JsonValueKind.Null?null:a.GetProperty("colourBlend").GetSingle(); var size=a.GetProperty("size").GetSingle();var small=a.GetProperty("smallSize").GetSingle(); bool? active=a.GetProperty("smallSizeActive").ValueKind==JsonValueKind.Null?null:a.GetProperty("smallSizeActive").GetBoolean(); if(!float.IsFinite(size)||size is <0 or >4||!float.IsFinite(small)||small is <0 or >4|| blend is{} b&&(!float.IsFinite(b)||b is <0 or >1))throw new InvalidDataException("原星点覆写外观无效。"); appearance=new(type,realistic,blend,raw,classification,size,small,active); } if(!float.IsFinite(minimum)||minimum<0||!point.IsValid||Math.Max(Math.Abs(point.X),Math.Max(Math.Abs(point.Y),Math.Abs(point.Z)))>1000000||name is{Length:0 or >159}) throw new InvalidDataException("原星点覆写内容无效。"); rows.Add(new(id,minimum,row.GetProperty("farFade").GetBoolean(),row.GetProperty("pending").GetBoolean(),point,name,star,appearance)); if(rows.Count>64)throw new InvalidDataException("原星点覆写分页超出限制。"); } var members=p.GetProperty("navRouteMembers").EnumerateArray().Select(Address).ToArray(); if(members.Length>512)throw new InvalidDataException("原导航成员分页超出限制。"); var offset=Number("overrideOffset",4096);var total=Number("overrideTotal",4096); var navOffset=Number("navRouteOffset",16384);var navTotal=Number("navRouteTotal",16384); if(offset+rows.Count>total||navOffset+members.Length>navTotal||!d50&&(total!=0||rows.Count!=0)||!nav&&(navTotal!=0||members.Length!=0)) throw new InvalidDataException("原星点覆写完整性不匹配。"); return new(process,generation,tick,view,d50,nav,offset,total,navOffset,navTotal,rows.ToArray(),members); } /// Strictly join pages of one immutable copied snapshot. Missing pages keep absence unknown. public static GalaxyOriginalPointOverrides ReadPointOverridesPages(IReadOnlyList pages) { if(pages.Count is <1 or >96)throw new InvalidDataException("原星点覆写分页数量无效。"); var first=ReadPointPage(pages[0]);List rows=[];List members=[]; foreach(var json in pages) { var p=ReadPointPage(json); if(p.ProcessId!=first.ProcessId||p.Generation!=first.Generation||p.Tick!=first.Tick||p.View!=first.View|| p.D50Known!=first.D50Known||p.NavKnown!=first.NavKnown||p.OverrideTotal!=first.OverrideTotal||p.NavTotal!=first.NavTotal|| p.OverrideOffset!=rows.Count||p.NavOffset!=members.Count) throw new InvalidDataException("原星点覆写分页来源已变化。"); rows.AddRange(p.Overrides);members.AddRange(p.Members); } if(rows.Select(r=>r.SystemAddress).Distinct().Count()!=rows.Count||members.Distinct().Count()!=members.Count) throw new InvalidDataException("原星点覆写分页包含重复地址。"); return new(first.ProcessId,first.Generation,first.Tick,first.View,first.D50Known&&rows.Count==first.OverrideTotal, first.NavKnown&&members.Count==first.NavTotal,rows,members); } private GalaxyCatalogueProfile ReadFirstProfilePage(JsonElement result,long generation) { var profileJson=result.GetProperty("profile");var profile=ReadCatalogueProfile(profileJson); if(!profileJson.TryGetProperty("pointOverrides",out var points)||points.ValueKind==JsonValueKind.Null)return profile; var first=ReadPointPage(points); if(first.ProcessId!=ProcessId||first.Generation!=generation)throw new InvalidDataException("原星点覆写进程或代际已变化。"); if(first.Overrides.Length ReadCompleteProfileAsync(JsonElement result,long generation,CancellationToken token) { var profile=ReadFirstProfilePage(result,generation); var points=await CompletePointOverridesAsync(profile,token); return profile with{PointOverrides=points??profile.PointOverrides}; } /// Complete only this copied profile's auxiliary pages. A superseding profile returns null and is never cancelled by this reader. public async Task CompletePointOverridesAsync(GalaxyCatalogueProfile profile,CancellationToken token=default) { ArgumentNullException.ThrowIfNull(profile); if(profile.PointOverrides is{} firstSnapshot&&(firstSnapshot.ProcessId!=ProcessId|| firstSnapshot.SourceGeneration!=Volatile.Read(ref latestCatalogueProfileGeneration)))return null; if(!pointContinuations.TryGetValue(profile,out var pending))return profile.PointOverrides; var generation=pending.Generation; if(Volatile.Read(ref latestCatalogueProfileGeneration)!=generation)return null; List pages=[pending.FirstPage];var first=ReadPointPage(pending.FirstPage); // Independent IPC serialization: an optional slow page must not hold // the basic-profile or catalogue-cell client's per-call gate. var auxiliaryPipe=new NavigationPipeClient(ProcessId); using var deadline=CancellationTokenSource.CreateLinkedTokenSource(token);deadline.CancelAfter(TimeSpan.FromSeconds(8)); var offset=first.Overrides.Length;var navOffset=first.Members.Length; while(offset96)throw new InvalidDataException("原星点覆写分页超出限制。"); } return Volatile.Read(ref latestCatalogueProfileGeneration)==generation?ReadPointOverridesPages(pages):null; } }