using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
private NativeGalaxyFilterFactsCatalog? galaxySystemFactsClient;
private int galaxySystemFactsProcess;
private CancellationTokenSource? galaxySystemFactsStop;
private Task? galaxySystemFactsTask;
private long galaxySystemFactsEpoch;
private string galaxySystemFactsBinding="";
private GalaxyViewport? galaxySystemFactsViewport;
private readonly Dictionary<long,DateTimeOffset> galaxySystemFactsReadAt=[];
private readonly Dictionary<long,GalacticSystem> galaxySystemFactsTargets=[];
private string galaxySystemFactsStatus="";
private static bool GalaxyUsesOriginalFilterFacts(GalaxyMapFilter filter)=>filter.IsQueryActive&&(filter.RequireOriginalBasicSystemFacts&&filter.RequestsOriginalBasicSystemFacts||filter.RequireOriginalCivilizationFacts&&filter.RequestsOriginalCivilizationFacts||filter.RequireOriginalServiceFacts&&filter.RequestsOriginalServices||filter.RequireOriginalCarrierFacts&&filter.RequestsOriginalCarrierFacts||filter.RequireOriginalStateFacts&&filter.RequestsOriginalStateFacts||filter.RequireOriginalColonisationOwnershipFacts&&filter.RequestsOriginalColonisationOwnershipFacts||filter.RequiresOriginalCommodity||filter.SizeCategory==GalaxyFilterCategory.Commodity||filter.RequestsOriginalColonisationSize||filter.UsesPowerStatus||filter.UsesPowerOuter||filter.UsesPowerTerritory||filter.UsesPowerActivityCounter||filter.Category is GalaxyFilterCategory.Visited or GalaxyFilterCategory.Exploration or GalaxyFilterCategory.TradeData or GalaxyFilterCategory.Colonisation);
private string GalaxySystemFactsBinding()
{
if(!GalaxyUsesOriginalFilterFacts(GalaxyFilter)||NavigationActive||!CurrentPlayer.InGame||CurrentPlayer.Navigation.JumpInProgress||runtime.FlightDriver is not NativeFlightControlDriver{SupportsGalaxyFilterFacts:true} driver
||string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)||CurrentPlayer.Navigation.CurrentSystemAddress is not >0)return "";
if((GalaxyFilter.Category==GalaxyFilterCategory.Colonisation||GalaxyFilter.RequestsOriginalColonisationSize)&&!driver.SupportsGalaxyColonisationEligibility)return "";
if(GalaxyFilter.RequireOriginalCivilizationFacts&&!driver.SupportsGalaxyCivilizationFacts)return "";
if(GalaxyFilter.RequireOriginalServiceFacts&&!driver.SupportsGalaxyServiceFacts)return "";
if(GalaxyFilter.RequireOriginalCarrierFacts&&!driver.SupportsGalaxyCarrierFacts)return "";
if(GalaxyFilter.RequireOriginalStateFacts&&!driver.SupportsGalaxyStateFacts)return "";
if(GalaxyFilter.RequireOriginalColonisationOwnershipFacts&&!driver.SupportsGalaxyColonisationOwnership)return "";
if(GalaxyFilter.UsesPowerActivityCounter&&(!driver.SupportsGalaxyPowerActivityFacts||!GalaxyFilter.ActivityNativeQueryReady))return "";
if(GalaxyFilter.UsesPowerOuter&&!driver.SupportsGalaxyPowerOuterFacts)return "";
if(GalaxyFilter.UsesPowerTerritory&&!driver.SupportsGalaxyPowerTerritoryFacts)return "";
if(GalaxyFilter.UsesPowerStatus&&!driver.SupportsGalaxyPowerStatusFacts)return "";
if(GalaxyFilter.RequiresOriginalCommodity&&(!driver.SupportsGalaxyCommodityFilterFacts||GalaxyFilter.CommodityRequest is null))return "";
return FormattableString.Invariant($"{driver.ProcessId}|{CurrentPlayer.CommanderId}|{CurrentPlayer.LoadGameAt:O}|{CurrentPlayer.Navigation.CurrentSystemAddress}|{GalaxyFilter.QueryKey}|{GalaxyFilter.IncludeUnknown}|{GalaxyFilter.SizeCategory}|{GalaxyFilter.PopulationMinimum:R}|{GalaxyFilter.PopulationMaximum:R}");
}
// Catalogue streaming and attribute refresh have separate lifetimes. Panning
// updates the wanted addresses without cancelling the first in-flight batch.
private void QueueGalaxyOriginalFilterFacts(GalaxyViewport? viewport=null)
{
var requireBasic=GalaxyFilter.RequestsOriginalBasicSystemFacts&&(GalaxyFilter.Category!=GalaxyFilterCategory.State||GalaxyFilter.SizeCategory==GalaxyFilterCategory.Population)&&!NavigationActive&&CurrentPlayer.InGame&&
!string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)&&CurrentPlayer.Navigation.CurrentSystemAddress is >0&&
runtime.FlightDriver is NativeFlightControlDriver{SupportsGalaxyFilterFacts:true,SupportsGalaxyBasicSystemFacts:true};
var originalContext=!NavigationActive&&CurrentPlayer.InGame&&!string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)&&
CurrentPlayer.Navigation.CurrentSystemAddress is >0&&runtime.FlightDriver is NativeFlightControlDriver{SupportsGalaxyFilterFacts:true};
var requireCivilization=originalContext&&GalaxyFilter.RequestsOriginalCivilizationFacts;
var requireServices=originalContext&&GalaxyFilter.RequestsOriginalServices;
var requireCarrier=originalContext&&GalaxyFilter.RequestsOriginalCarrierFacts;
var requireState=originalContext&&GalaxyFilter.RequestsOriginalStateFacts;
var requireOwnership=originalContext&&GalaxyFilter.RequestsOriginalColonisationOwnershipFacts;
if(GalaxyFilter.RequireOriginalBasicSystemFacts!=requireBasic||GalaxyFilter.RequireOriginalCivilizationFacts!=requireCivilization||GalaxyFilter.RequireOriginalServiceFacts!=requireServices||GalaxyFilter.RequireOriginalCarrierFacts!=requireCarrier||GalaxyFilter.RequireOriginalStateFacts!=requireState||GalaxyFilter.RequireOriginalColonisationOwnershipFacts!=requireOwnership)
{GalaxyFilter=GalaxyFilter with{RequireOriginalBasicSystemFacts=requireBasic,RequireOriginalCivilizationFacts=requireCivilization,RequireOriginalServiceFacts=requireServices,RequireOriginalCarrierFacts=requireCarrier,RequireOriginalStateFacts=requireState,RequireOriginalColonisationOwnershipFacts=requireOwnership};return;}
galaxySystemFactsViewport=viewport??galaxyViewport??galaxyDesiredViewport;
var binding=GalaxySystemFactsBinding();
if(binding!=galaxySystemFactsBinding)
{
galaxySystemFactsStop?.Cancel();galaxySystemFactsStop?.Dispose();galaxySystemFactsStop=null;
galaxySystemFactsBinding=binding;galaxySystemFactsEpoch++;galaxySystemFactsReadAt.Clear();galaxySystemFactsTargets.Clear();
// A disconnected process or changed account/origin cannot retain
// its previous original filter classifications in the static cache.
ClearGalaxyOriginalFilterFacts();
}
if(binding.Length==0)
{
if(GalaxyUsesOriginalFilterFacts(GalaxyFilter))
{
galaxySystemFactsStatus=NavigationActive?"导航中 · 原游戏账号属性刷新已暂停;仅显示当前账号已知记录。":GalaxyFilter.RequiresOriginalCommodity&&GalaxyFilter.CommodityRequest is null?"原商品选择尚未与实际UID定义绑定;缺少资料保持未知。":"原游戏星图属性接口尚未连接 · 缺少资料保持未知。";
GalaxyFilterInformationStatus=galaxySystemFactsStatus;
}
return;
}
UpdateGalaxySystemFactsTargets();
if(galaxySystemFactsTargets.Count==0)
{galaxySystemFactsStatus="当前视野尚无可读取的原生系统属性 · 缺少资料保持未知。";GalaxyFilterInformationStatus=galaxySystemFactsStatus;}
if(galaxySystemFactsTask is {IsCompleted:false}&&galaxySystemFactsStop?.IsCancellationRequested==false)return;
galaxySystemFactsStop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);
var epoch=galaxySystemFactsEpoch;var stop=galaxySystemFactsStop;
galaxySystemFactsTask=RefreshGalaxyOriginalFilterFactsAsync(binding,epoch,stop.Token);
}
private void UpdateGalaxySystemFactsTargets()
{
galaxySystemFactsTargets.Clear();if(galaxySystemFactsViewport is not {} viewport)return;
var rangeLimited=GalaxyFilter.Category is GalaxyFilterCategory.Exploration or GalaxyFilterCategory.TradeData;
var origin=CurrentPlayer.Position;
foreach(var system in galaxyCache.Values)
{
if(system.SystemAddress is not >0||system.Source!="游戏银河目录"||!viewport.Contains(system.Position,1.000001))continue;
// This only avoids unnecessary reads. A skipped/missing address is
// never classified as noDataAvailable; the native predicate owns it.
if(rangeLimited&&origin is {IsValid:true}&&origin.DistanceTo(system.Position)>20.000001)continue;
galaxySystemFactsTargets[system.SystemAddress.Value]=system;
}
}
private void ClearGalaxyOriginalFilterFacts(IReadOnlySet<long>? addresses=null)
{
var changed=false;
foreach(var system in galaxyCache.Values.ToArray())
{
if(addresses is not null&&(system.SystemAddress is not {} address||!addresses.Contains(address)))continue;
var data=system.Data;if(data.NativeFilterFacts is null&&data.Exploration is null&&data.TradeData is null)continue;
galaxyCache[system.Name]=system with{Data=data with{NativeFilterFacts=null,Exploration=null,TradeData=null}};changed=true;
}
if(changed)PublishGalaxyOriginalFilterFacts();
}
private void PublishGalaxyOriginalFilterFacts()
{
if(GalaxyUsesOriginalFilterFacts(GalaxyFilter)&&galaxySystemFactsViewport is {} viewport)
GalaxyQuerySystems=galaxyCache.Values.Where(s=>viewport.Contains(s.Position,1.000001)&&GalaxyFilter.Matches(s)).ToArray();
PublishGalaxy();
}
private async Task RefreshGalaxyOriginalFilterFactsAsync(string binding,long epoch,CancellationToken token)
{
bool Latest()=>!token.IsCancellationRequested&&epoch==galaxySystemFactsEpoch&&binding==galaxySystemFactsBinding&&binding==GalaxySystemFactsBinding();
// Yield before assigning the task so a completed/empty initial batch
// cannot make a second caller start another worker synchronously.
await Task.Yield();
try
{
while(Latest())
{
UpdateGalaxySystemFactsTargets();var now=DateTimeOffset.UtcNow;
var batch=galaxySystemFactsTargets.Values
.Where(s=>!galaxySystemFactsReadAt.TryGetValue(s.SystemAddress!.Value,out var at)||now-at>=TimeSpan.FromSeconds(2))
// Never-read rows precede refreshes: large views make forward
// progress rather than continually revisiting the first page.
.OrderBy(s=>galaxySystemFactsReadAt.GetValueOrDefault(s.SystemAddress!.Value))
.ThenBy(s=>s.Position.DistanceTo(galaxySystemFactsViewport!.Focus))
.Take(256).Select(s=>s.SystemAddress!.Value).ToArray();
if(batch.Length==0)
{
if(galaxySystemFactsTargets.Count==0)
{galaxySystemFactsStatus="当前视野尚无可读取的原生系统属性 · 缺少资料保持未知。";GalaxyFilterInformationStatus=galaxySystemFactsStatus;}
await Task.Delay(500,token);continue;
}
if(runtime.FlightDriver is not NativeFlightControlDriver driver)break;
var process=driver.ProcessId;var fid=CurrentPlayer.CommanderId;var origin=CurrentPlayer.Navigation.CurrentSystemAddress!.Value;
if(galaxySystemFactsClient is null||galaxySystemFactsProcess!=process)
{galaxySystemFactsProcess=process;galaxySystemFactsClient=new(process);}
try
{
var powerRequest=GalaxyFilter.UsesPowerActivityCounter?new GalaxyNativePowerActivityRequest(GalaxyFilter.ActivityAllPowers,GalaxyFilter.ActivityAllPowers?null:GalaxyFilter.ActivityInternalPowerId,GalaxyPowerActivities.OriginalUid(GalaxyFilter.Activity)):null;
var result=await galaxySystemFactsClient.ReadAsync(batch,token,includeColonisation:GalaxyFilter.Category==GalaxyFilterCategory.Colonisation||GalaxyFilter.RequestsOriginalColonisationSize,powerActivity:powerRequest,includePowerOuter:GalaxyFilter.UsesPowerOuter,powerTerritory:GalaxyFilter.PowerTerritoryRequest,includePowerStatus:GalaxyFilter.UsesPowerStatus,commodity:GalaxyFilter.CommodityRequest,includeBasicSystem:GalaxyFilter.RequireOriginalBasicSystemFacts&&GalaxyFilter.RequestsOriginalBasicSystemFacts,
includeCivilization:GalaxyFilter.RequireOriginalCivilizationFacts&&GalaxyFilter.RequestsOriginalCivilizationFacts,includeServices:GalaxyFilter.RequireOriginalServiceFacts&&GalaxyFilter.RequestsOriginalServices,includeCarrier:GalaxyFilter.RequireOriginalCarrierFacts&&GalaxyFilter.RequestsOriginalCarrierFacts,includeState:GalaxyFilter.RequireOriginalStateFacts&&GalaxyFilter.RequestsOriginalStateFacts,includeColonisationOwnership:GalaxyFilter.RequireOriginalColonisationOwnershipFacts&&GalaxyFilter.RequestsOriginalColonisationOwnershipFacts);if(!Latest())break;
if(result.OriginSystemAddress!=origin)throw new InvalidDataException("原游戏筛选资料的参照星系与当前玩家不一致。");
UpdateGalaxySystemFactsTargets();
foreach(var row in result.Rows)
{
galaxySystemFactsReadAt[row.SystemAddress]=DateTimeOffset.UtcNow;
if(!galaxySystemFactsTargets.TryGetValue(row.SystemAddress,out var target)||!galaxyCache.TryGetValue(target.Name,out var current)||current.SystemAddress!=row.SystemAddress)continue;
var activity=row.PowerActivityCounter is {} counter?new GalaxyPowerActivityEvidence(counter.AllPowers?"":GalaxyFilter.ActivityPowerName??"",GalaxyPowerActivities.FromOriginalUid(counter.ActionId)!.Value,counter.RecentActivity,counter.InternalPowerId,counter.AllPowers):null;
var ownerId=row.PowerStatus?.OwnerInternalPowerId??row.PowerTerritory?.Style?.OwnerInternalPowerId;
var owner=ownerId is {} id?GalaxyFilter.OriginalPowerOptions.FirstOrDefault(p=>p.InternalPowerId==id)?.Name:null;
var facts=new GalaxyNativeSystemFacts(fid,process,origin,row.Visited,row.Exploration,row.TradeData,row.WithinDataRange,row.MetadataStatus,row.ColonisationStatus,activity){PowerOuter=row.PowerOuter,PowerTerritory=row.PowerTerritory,PowerTerritoryOwnerName=owner,PowerStatus=row.PowerStatus,Commodity=row.Commodity,BasicSystem=row.BasicSystem,BasicSystemRequested=row.BasicSystemRequested,
Civilization=row.Civilization,CivilizationRequested=row.CivilizationRequested,ServiceMask=row.ServiceMask,ServicesRequested=row.ServicesRequested,ServicesDefaulted=row.ServicesDefaulted,CarrierDockable=row.CarrierDockable,CarrierRequested=row.CarrierRequested,State=row.State,StateRequested=row.StateRequested,ColonisationOwnership=row.ColonisationOwnership,ColonisationOwnershipRequested=row.ColonisationOwnershipRequested,
ColonisationHighlight=row.ColonisationHighlight,ColonisationHighlightRequested=row.ColonisationHighlightRequested,
ColonisationNormalStatus=row.ColonisationNormalStatus,ColonisationNormalStatusRequested=row.ColonisationNormalStatusRequested};
RememberGalaxy(current with{Data=current.Data.Merge(new(){NativeFilterFacts=facts})});
}
if(!Latest())break;
PublishGalaxyOriginalFilterFacts();
var category=GalaxyFilter.Category;
var known=galaxySystemFactsTargets.Values.Count(s=>galaxyCache.TryGetValue(s.Name,out var current)&&!GalaxyFilter.IsUnknown(current));
var pending=galaxySystemFactsTargets.Keys.Count(a=>!galaxySystemFactsReadAt.ContainsKey(a));
galaxySystemFactsStatus=$"原游戏星图属性 · 视野已核实 {known:N0} / {galaxySystemFactsTargets.Count:N0} · 匹配 {GalaxyQuerySystems.Count:N0}"+
(pending>0?$" · 待查 {pending:N0}":" · 每 2 秒刷新")+(GalaxyFilter.RequiresOriginalCommodity?"\n原商品分类使用实际定义及已缓存市场,保留原中心/范围和价格规则;未就绪或缺上下文保持未知。":GalaxyFilter.UsesPowerOuter?"\n原活动视图按原缓存中心/半径筛选;已就绪点用原配色与尺寸,缺样式保留回退。圈层与完整淡出仍待验收。":GalaxyFilter.UsesPowerTerritory?"\n领土区域仅用原metadata/实际势力定义/原counter返回的半径和样式;活动开关与最低强度独立,未知样式不画圈。":GalaxyFilter.RequireOriginalStateFacts?"\n原状态包含游戏特殊覆盖;分类资料尚未就绪时保持未知。":GalaxyFilter.RequireOriginalCarrierFacts?"\n按原游戏航母缓存区分可停靠/无可停靠记录;缺行分类不代表全球无航母。":GalaxyFilter.RequireOriginalServiceFacts?"\n使用原游戏十项服务缓存,已知无匹配严格隐藏,未知保持未知。":GalaxyFilter.RequireOriginalCivilizationFacts?"\n使用原文明分类与实际定义ID,缺少原资料保持未知。":GalaxyFilter.RequireOriginalColonisationOwnershipFacts?"\n殖民归属以游戏当前指挥官资料为准;尚未取得归属资料时保持未知。":GalaxyFilter.RequireOriginalBasicSystemFacts?"\n使用游戏已载入的原分类;资料表就绪且确认无条目时采用原分类默认值,未就绪或未知编号保持未知。":category is GalaxyFilterCategory.Exploration or GalaxyFilterCategory.TradeData?"\n原游戏仅在当前系统 20 ly 范围内判定;范围外保持未知。":category==GalaxyFilterCategory.Colonisation?"\n申领资格取原游戏候选规则;本人归属另外核对 CommanderID。未就绪保持未知。":GalaxyFilter.UsesPowerActivityCounter?"\n领土附加的原近期活动计数;0为有效值,缺缓存保持未知。":"\n未就绪的原游戏访问表保持未知;本机访问日志只补充已访问记录。");
GalaxyFilterInformationStatus=galaxySystemFactsStatus;
}
catch(OperationCanceledException)when(token.IsCancellationRequested){break;}
catch(Exception error)when(error is IOException or TimeoutException or InvalidOperationException or System.Text.Json.JsonException or KeyNotFoundException or FormatException)
{
if(!Latest())break;
foreach(var id in batch)galaxySystemFactsReadAt[id]=DateTimeOffset.UtcNow;
ClearGalaxyOriginalFilterFacts(batch.ToHashSet());
galaxySystemFactsStatus="原游戏星图属性暂未就绪 · 缺少资料保持未知:"+error.Message;GalaxyFilterInformationStatus=galaxySystemFactsStatus;
await Task.Delay(2000,token);
}
}
}
catch(OperationCanceledException)when(token.IsCancellationRequested){ }
finally
{
if(epoch==galaxySystemFactsEpoch&&binding==galaxySystemFactsBinding&&GalaxyUsesOriginalFilterFacts(GalaxyFilter)&&binding!=GalaxySystemFactsBinding())
QueueGalaxyOriginalFilterFacts();
}
}
}
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
private NativeGalaxyFilterFactsCatalog? galaxySystemFactsClient;
private int galaxySystemFactsProcess;
private CancellationTokenSource? galaxySystemFactsStop;
private Task? galaxySystemFactsTask;
private long galaxySystemFactsEpoch;
private string galaxySystemFactsBinding="";
private GalaxyViewport? galaxySystemFactsViewport;
private readonly Dictionary<long,DateTimeOffset> galaxySystemFactsReadAt=[];
private readonly Dictionary<long,GalacticSystem> galaxySystemFactsTargets=[];
private string galaxySystemFactsStatus="";
private static bool GalaxyUsesOriginalFilterFacts(GalaxyMapFilter filter)=>filter.IsQueryActive&&(filter.RequireOriginalBasicSystemFacts&&filter.RequestsOriginalBasicSystemFacts||filter.RequireOriginalCivilizationFacts&&filter.RequestsOriginalCivilizationFacts||filter.RequireOriginalServiceFacts&&filter.RequestsOriginalServices||filter.RequireOriginalCarrierFacts&&filter.RequestsOriginalCarrierFacts||filter.RequireOriginalStateFacts&&filter.RequestsOriginalStateFacts||filter.RequireOriginalColonisationOwnershipFacts&&filter.RequestsOriginalColonisationOwnershipFacts||filter.RequiresOriginalCommodity||filter.SizeCategory==GalaxyFilterCategory.Commodity||filter.RequestsOriginalColonisationSize||filter.UsesPowerStatus||filter.UsesPowerOuter||filter.UsesPowerTerritory||filter.UsesPowerActivityCounter||filter.Category is GalaxyFilterCategory.Visited or GalaxyFilterCategory.Exploration or GalaxyFilterCategory.TradeData or GalaxyFilterCategory.Colonisation);
private string GalaxySystemFactsBinding()
{
if(!GalaxyUsesOriginalFilterFacts(GalaxyFilter)||NavigationActive||!CurrentPlayer.InGame||CurrentPlayer.Navigation.JumpInProgress||runtime.FlightDriver is not NativeFlightControlDriver{SupportsGalaxyFilterFacts:true} driver
||string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)||CurrentPlayer.Navigation.CurrentSystemAddress is not >0)return "";
if((GalaxyFilter.Category==GalaxyFilterCategory.Colonisation||GalaxyFilter.RequestsOriginalColonisationSize)&&!driver.SupportsGalaxyColonisationEligibility)return "";
if(GalaxyFilter.RequireOriginalCivilizationFacts&&!driver.SupportsGalaxyCivilizationFacts)return "";
if(GalaxyFilter.RequireOriginalServiceFacts&&!driver.SupportsGalaxyServiceFacts)return "";
if(GalaxyFilter.RequireOriginalCarrierFacts&&!driver.SupportsGalaxyCarrierFacts)return "";
if(GalaxyFilter.RequireOriginalStateFacts&&!driver.SupportsGalaxyStateFacts)return "";
if(GalaxyFilter.RequireOriginalColonisationOwnershipFacts&&!driver.SupportsGalaxyColonisationOwnership)return "";
if(GalaxyFilter.UsesPowerActivityCounter&&(!driver.SupportsGalaxyPowerActivityFacts||!GalaxyFilter.ActivityNativeQueryReady))return "";
if(GalaxyFilter.UsesPowerOuter&&!driver.SupportsGalaxyPowerOuterFacts)return "";
if(GalaxyFilter.UsesPowerTerritory&&!driver.SupportsGalaxyPowerTerritoryFacts)return "";
if(GalaxyFilter.UsesPowerStatus&&!driver.SupportsGalaxyPowerStatusFacts)return "";
if(GalaxyFilter.RequiresOriginalCommodity&&(!driver.SupportsGalaxyCommodityFilterFacts||GalaxyFilter.CommodityRequest is null))return "";
return FormattableString.Invariant($"{driver.ProcessId}|{CurrentPlayer.CommanderId}|{CurrentPlayer.LoadGameAt:O}|{CurrentPlayer.Navigation.CurrentSystemAddress}|{GalaxyFilter.QueryKey}|{GalaxyFilter.IncludeUnknown}|{GalaxyFilter.SizeCategory}|{GalaxyFilter.PopulationMinimum:R}|{GalaxyFilter.PopulationMaximum:R}");
}
// Catalogue streaming and attribute refresh have separate lifetimes. Panning
// updates the wanted addresses without cancelling the first in-flight batch.
private void QueueGalaxyOriginalFilterFacts(GalaxyViewport? viewport=null)
{
var requireBasic=GalaxyFilter.RequestsOriginalBasicSystemFacts&&(GalaxyFilter.Category!=GalaxyFilterCategory.State||GalaxyFilter.SizeCategory==GalaxyFilterCategory.Population)&&!NavigationActive&&CurrentPlayer.InGame&&
!string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)&&CurrentPlayer.Navigation.CurrentSystemAddress is >0&&
runtime.FlightDriver is NativeFlightControlDriver{SupportsGalaxyFilterFacts:true,SupportsGalaxyBasicSystemFacts:true};
var originalContext=!NavigationActive&&CurrentPlayer.InGame&&!string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)&&
CurrentPlayer.Navigation.CurrentSystemAddress is >0&&runtime.FlightDriver is NativeFlightControlDriver{SupportsGalaxyFilterFacts:true};
var requireCivilization=originalContext&&GalaxyFilter.RequestsOriginalCivilizationFacts;
var requireServices=originalContext&&GalaxyFilter.RequestsOriginalServices;
var requireCarrier=originalContext&&GalaxyFilter.RequestsOriginalCarrierFacts;
var requireState=originalContext&&GalaxyFilter.RequestsOriginalStateFacts;
var requireOwnership=originalContext&&GalaxyFilter.RequestsOriginalColonisationOwnershipFacts;
if(GalaxyFilter.RequireOriginalBasicSystemFacts!=requireBasic||GalaxyFilter.RequireOriginalCivilizationFacts!=requireCivilization||GalaxyFilter.RequireOriginalServiceFacts!=requireServices||GalaxyFilter.RequireOriginalCarrierFacts!=requireCarrier||GalaxyFilter.RequireOriginalStateFacts!=requireState||GalaxyFilter.RequireOriginalColonisationOwnershipFacts!=requireOwnership)
{GalaxyFilter=GalaxyFilter with{RequireOriginalBasicSystemFacts=requireBasic,RequireOriginalCivilizationFacts=requireCivilization,RequireOriginalServiceFacts=requireServices,RequireOriginalCarrierFacts=requireCarrier,RequireOriginalStateFacts=requireState,RequireOriginalColonisationOwnershipFacts=requireOwnership};return;}
galaxySystemFactsViewport=viewport??galaxyViewport??galaxyDesiredViewport;
var binding=GalaxySystemFactsBinding();
if(binding!=galaxySystemFactsBinding)
{
galaxySystemFactsStop?.Cancel();galaxySystemFactsStop?.Dispose();galaxySystemFactsStop=null;
galaxySystemFactsBinding=binding;galaxySystemFactsEpoch++;galaxySystemFactsReadAt.Clear();galaxySystemFactsTargets.Clear();
// A disconnected process or changed account/origin cannot retain
// its previous original filter classifications in the static cache.
ClearGalaxyOriginalFilterFacts();
}
if(binding.Length==0)
{
if(GalaxyUsesOriginalFilterFacts(GalaxyFilter))
{
galaxySystemFactsStatus=NavigationActive?"导航中 · 原游戏账号属性刷新已暂停;仅显示当前账号已知记录。":GalaxyFilter.RequiresOriginalCommodity&&GalaxyFilter.CommodityRequest is null?"原商品选择尚未与实际UID定义绑定;缺少资料保持未知。":"原游戏星图属性接口尚未连接 · 缺少资料保持未知。";
GalaxyFilterInformationStatus=galaxySystemFactsStatus;
}
return;
}
UpdateGalaxySystemFactsTargets();
if(galaxySystemFactsTargets.Count==0)
{galaxySystemFactsStatus="当前视野尚无可读取的原生系统属性 · 缺少资料保持未知。";GalaxyFilterInformationStatus=galaxySystemFactsStatus;}
if(galaxySystemFactsTask is {IsCompleted:false}&&galaxySystemFactsStop?.IsCancellationRequested==false)return;
galaxySystemFactsStop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);
var epoch=galaxySystemFactsEpoch;var stop=galaxySystemFactsStop;
galaxySystemFactsTask=RefreshGalaxyOriginalFilterFactsAsync(binding,epoch,stop.Token);
}
private void UpdateGalaxySystemFactsTargets()
{
galaxySystemFactsTargets.Clear();if(galaxySystemFactsViewport is not {} viewport)return;
var rangeLimited=GalaxyFilter.Category is GalaxyFilterCategory.Exploration or GalaxyFilterCategory.TradeData;
var origin=CurrentPlayer.Position;
foreach(var system in galaxyCache.Values)
{
if(system.SystemAddress is not >0||system.Source!="游戏银河目录"||!viewport.Contains(system.Position,1.000001))continue;
// This only avoids unnecessary reads. A skipped/missing address is
// never classified as noDataAvailable; the native predicate owns it.
if(rangeLimited&&origin is {IsValid:true}&&origin.DistanceTo(system.Position)>20.000001)continue;
galaxySystemFactsTargets[system.SystemAddress.Value]=system;
}
}
private void ClearGalaxyOriginalFilterFacts(IReadOnlySet<long>? addresses=null)
{
var changed=false;
foreach(var system in galaxyCache.Values.ToArray())
{
if(addresses is not null&&(system.SystemAddress is not {} address||!addresses.Contains(address)))continue;
var data=system.Data;if(data.NativeFilterFacts is null&&data.Exploration is null&&data.TradeData is null)continue;
galaxyCache[system.Name]=system with{Data=data with{NativeFilterFacts=null,Exploration=null,TradeData=null}};changed=true;
}
if(changed)PublishGalaxyOriginalFilterFacts();
}
private void PublishGalaxyOriginalFilterFacts()
{
if(GalaxyUsesOriginalFilterFacts(GalaxyFilter)&&galaxySystemFactsViewport is {} viewport)
GalaxyQuerySystems=galaxyCache.Values.Where(s=>viewport.Contains(s.Position,1.000001)&&GalaxyFilter.Matches(s)).ToArray();
PublishGalaxy();
}
private async Task RefreshGalaxyOriginalFilterFactsAsync(string binding,long epoch,CancellationToken token)
{
bool Latest()=>!token.IsCancellationRequested&&epoch==galaxySystemFactsEpoch&&binding==galaxySystemFactsBinding&&binding==GalaxySystemFactsBinding();
// Yield before assigning the task so a completed/empty initial batch
// cannot make a second caller start another worker synchronously.
await Task.Yield();
try
{
while(Latest())
{
UpdateGalaxySystemFactsTargets();var now=DateTimeOffset.UtcNow;
var batch=galaxySystemFactsTargets.Values
.Where(s=>!galaxySystemFactsReadAt.TryGetValue(s.SystemAddress!.Value,out var at)||now-at>=TimeSpan.FromSeconds(2))
// Never-read rows precede refreshes: large views make forward
// progress rather than continually revisiting the first page.
.OrderBy(s=>galaxySystemFactsReadAt.GetValueOrDefault(s.SystemAddress!.Value))
.ThenBy(s=>s.Position.DistanceTo(galaxySystemFactsViewport!.Focus))
.Take(256).Select(s=>s.SystemAddress!.Value).ToArray();
if(batch.Length==0)
{
if(galaxySystemFactsTargets.Count==0)
{galaxySystemFactsStatus="当前视野尚无可读取的原生系统属性 · 缺少资料保持未知。";GalaxyFilterInformationStatus=galaxySystemFactsStatus;}
await Task.Delay(500,token);continue;
}
if(runtime.FlightDriver is not NativeFlightControlDriver driver)break;
var process=driver.ProcessId;var fid=CurrentPlayer.CommanderId;var origin=CurrentPlayer.Navigation.CurrentSystemAddress!.Value;
if(galaxySystemFactsClient is null||galaxySystemFactsProcess!=process)
{galaxySystemFactsProcess=process;galaxySystemFactsClient=new(process);}
try
{
var powerRequest=GalaxyFilter.UsesPowerActivityCounter?new GalaxyNativePowerActivityRequest(GalaxyFilter.ActivityAllPowers,GalaxyFilter.ActivityAllPowers?null:GalaxyFilter.ActivityInternalPowerId,GalaxyPowerActivities.OriginalUid(GalaxyFilter.Activity)):null;
var result=await galaxySystemFactsClient.ReadAsync(batch,token,includeColonisation:GalaxyFilter.Category==GalaxyFilterCategory.Colonisation||GalaxyFilter.RequestsOriginalColonisationSize,powerActivity:powerRequest,includePowerOuter:GalaxyFilter.UsesPowerOuter,powerTerritory:GalaxyFilter.PowerTerritoryRequest,includePowerStatus:GalaxyFilter.UsesPowerStatus,commodity:GalaxyFilter.CommodityRequest,includeBasicSystem:GalaxyFilter.RequireOriginalBasicSystemFacts&&GalaxyFilter.RequestsOriginalBasicSystemFacts,
includeCivilization:GalaxyFilter.RequireOriginalCivilizationFacts&&GalaxyFilter.RequestsOriginalCivilizationFacts,includeServices:GalaxyFilter.RequireOriginalServiceFacts&&GalaxyFilter.RequestsOriginalServices,includeCarrier:GalaxyFilter.RequireOriginalCarrierFacts&&GalaxyFilter.RequestsOriginalCarrierFacts,includeState:GalaxyFilter.RequireOriginalStateFacts&&GalaxyFilter.RequestsOriginalStateFacts,includeColonisationOwnership:GalaxyFilter.RequireOriginalColonisationOwnershipFacts&&GalaxyFilter.RequestsOriginalColonisationOwnershipFacts);if(!Latest())break;
if(result.OriginSystemAddress!=origin)throw new InvalidDataException("原游戏筛选资料的参照星系与当前玩家不一致。");
UpdateGalaxySystemFactsTargets();
foreach(var row in result.Rows)
{
galaxySystemFactsReadAt[row.SystemAddress]=DateTimeOffset.UtcNow;
if(!galaxySystemFactsTargets.TryGetValue(row.SystemAddress,out var target)||!galaxyCache.TryGetValue(target.Name,out var current)||current.SystemAddress!=row.SystemAddress)continue;
var activity=row.PowerActivityCounter is {} counter?new GalaxyPowerActivityEvidence(counter.AllPowers?"":GalaxyFilter.ActivityPowerName??"",GalaxyPowerActivities.FromOriginalUid(counter.ActionId)!.Value,counter.RecentActivity,counter.InternalPowerId,counter.AllPowers):null;
var ownerId=row.PowerStatus?.OwnerInternalPowerId??row.PowerTerritory?.Style?.OwnerInternalPowerId;
var owner=ownerId is {} id?GalaxyFilter.OriginalPowerOptions.FirstOrDefault(p=>p.InternalPowerId==id)?.Name:null;
var facts=new GalaxyNativeSystemFacts(fid,process,origin,row.Visited,row.Exploration,row.TradeData,row.WithinDataRange,row.MetadataStatus,row.ColonisationStatus,activity){PowerOuter=row.PowerOuter,PowerTerritory=row.PowerTerritory,PowerTerritoryOwnerName=owner,PowerStatus=row.PowerStatus,Commodity=row.Commodity,BasicSystem=row.BasicSystem,BasicSystemRequested=row.BasicSystemRequested,
Civilization=row.Civilization,CivilizationRequested=row.CivilizationRequested,ServiceMask=row.ServiceMask,ServicesRequested=row.ServicesRequested,ServicesDefaulted=row.ServicesDefaulted,CarrierDockable=row.CarrierDockable,CarrierRequested=row.CarrierRequested,State=row.State,StateRequested=row.StateRequested,ColonisationOwnership=row.ColonisationOwnership,ColonisationOwnershipRequested=row.ColonisationOwnershipRequested,
ColonisationHighlight=row.ColonisationHighlight,ColonisationHighlightRequested=row.ColonisationHighlightRequested,
ColonisationNormalStatus=row.ColonisationNormalStatus,ColonisationNormalStatusRequested=row.ColonisationNormalStatusRequested};
RememberGalaxy(current with{Data=current.Data.Merge(new(){NativeFilterFacts=facts})});
}
if(!Latest())break;
PublishGalaxyOriginalFilterFacts();
var category=GalaxyFilter.Category;
var known=galaxySystemFactsTargets.Values.Count(s=>galaxyCache.TryGetValue(s.Name,out var current)&&!GalaxyFilter.IsUnknown(current));
var pending=galaxySystemFactsTargets.Keys.Count(a=>!galaxySystemFactsReadAt.ContainsKey(a));
galaxySystemFactsStatus=$"原游戏星图属性 · 视野已核实 {known:N0} / {galaxySystemFactsTargets.Count:N0} · 匹配 {GalaxyQuerySystems.Count:N0}"+
(pending>0?$" · 待查 {pending:N0}":" · 每 2 秒刷新")+(GalaxyFilter.RequiresOriginalCommodity?"\n原商品分类使用实际定义及已缓存市场,保留原中心/范围和价格规则;未就绪或缺上下文保持未知。":GalaxyFilter.UsesPowerOuter?"\n原活动视图按原缓存中心/半径筛选;已就绪点用原配色与尺寸,缺样式保留回退。圈层与完整淡出仍待验收。":GalaxyFilter.UsesPowerTerritory?"\n领土区域仅用原metadata/实际势力定义/原counter返回的半径和样式;活动开关与最低强度独立,未知样式不画圈。":GalaxyFilter.RequireOriginalStateFacts?"\n原状态包含游戏特殊覆盖;分类资料尚未就绪时保持未知。":GalaxyFilter.RequireOriginalCarrierFacts?"\n按原游戏航母缓存区分可停靠/无可停靠记录;缺行分类不代表全球无航母。":GalaxyFilter.RequireOriginalServiceFacts?"\n使用原游戏十项服务缓存,已知无匹配严格隐藏,未知保持未知。":GalaxyFilter.RequireOriginalCivilizationFacts?"\n使用原文明分类与实际定义ID,缺少原资料保持未知。":GalaxyFilter.RequireOriginalColonisationOwnershipFacts?"\n殖民归属以游戏当前指挥官资料为准;尚未取得归属资料时保持未知。":GalaxyFilter.RequireOriginalBasicSystemFacts?"\n使用游戏已载入的原分类;资料表就绪且确认无条目时采用原分类默认值,未就绪或未知编号保持未知。":category is GalaxyFilterCategory.Exploration or GalaxyFilterCategory.TradeData?"\n原游戏仅在当前系统 20 ly 范围内判定;范围外保持未知。":category==GalaxyFilterCategory.Colonisation?"\n申领资格取原游戏候选规则;本人归属另外核对 CommanderID。未就绪保持未知。":GalaxyFilter.UsesPowerActivityCounter?"\n领土附加的原近期活动计数;0为有效值,缺缓存保持未知。":"\n未就绪的原游戏访问表保持未知;本机访问日志只补充已访问记录。");
GalaxyFilterInformationStatus=galaxySystemFactsStatus;
}
catch(OperationCanceledException)when(token.IsCancellationRequested){break;}
catch(Exception error)when(error is IOException or TimeoutException or InvalidOperationException or System.Text.Json.JsonException or KeyNotFoundException or FormatException)
{
if(!Latest())break;
foreach(var id in batch)galaxySystemFactsReadAt[id]=DateTimeOffset.UtcNow;
ClearGalaxyOriginalFilterFacts(batch.ToHashSet());
galaxySystemFactsStatus="原游戏星图属性暂未就绪 · 缺少资料保持未知:"+error.Message;GalaxyFilterInformationStatus=galaxySystemFactsStatus;
await Task.Delay(2000,token);
}
}
}
catch(OperationCanceledException)when(token.IsCancellationRequested){ }
finally
{
if(epoch==galaxySystemFactsEpoch&&binding==galaxySystemFactsBinding&&GalaxyUsesOriginalFilterFacts(GalaxyFilter)&&binding!=GalaxySystemFactsBinding())
QueueGalaxyOriginalFilterFacts();
}
}
}