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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using CommunityToolkit.Mvvm.ComponentModel;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Navigation;

namespace XFEEDToolBox.ViewModels;

public partial class ToolboxViewModel
{
    [ObservableProperty] public partial IReadOnlyList<GalaxyCurrentTradeRenderFlow> GalaxyCurrentTradeFlows { get;set; }=[];
    [ObservableProperty] public partial IReadOnlyList<GalaxyCommodityGroup> GalaxyCurrentTradeChoices { get;set; }=[];
    [ObservableProperty] public partial string GalaxyCurrentTradeStatus { get;set; }="原当前贸易流已关闭;不会以历史缓存代替。";
    private GalaxyCurrentTradeFlowsSnapshot? galaxyCurrentTradeSnapshot;
    private string galaxyCurrentTradeContext="";
    private string galaxyCurrentTradeRunningContext="";
    private CancellationTokenSource? galaxyCurrentTradeStop;
    private Task? galaxyCurrentTradeTask;
    private readonly Dictionary<long,DateTimeOffset> galaxyCurrentTradeAttempts=[];
    private IReadOnlyList<GalaxyCurrentTradeFlow>? currentTradeRoutesSource;
    private IReadOnlySet<string>? currentTradeRouteSelection;
    private IReadOnlySet<int>? currentTradeCategorySelection;
    private GalaxyTradeFlowSource currentTradeRouteKind;
    private IReadOnlyList<GalaxyCurrentTradeRoute> currentTradeRoutes=[];
    private string CurrentTradeContext()=>GalaxyFilter.ShowCurrentTradeFlows&&!string.IsNullOrWhiteSpace(CurrentPlayer.CommanderId)?ThargoidContext():"";
    private void ClearGalaxyCurrentTrade(string status)
    {
        galaxyCurrentTradeSnapshot=null;galaxyCurrentTradeContext="";currentTradeRoutesSource=null;currentTradeRoutes=[];
        if(GalaxyCurrentTradeFlows.Count>0)GalaxyCurrentTradeFlows=[];
        if(GalaxyCurrentTradeChoices.Count>0)GalaxyCurrentTradeChoices=[];
        GalaxyCurrentTradeStatus=status;
    }
    private GalaxyCurrentTradeProjection RefreshCurrentTradeProjection()
    {
        var snapshot=galaxyCurrentTradeContext==CurrentTradeContext()?galaxyCurrentTradeSnapshot:null;
        if(snapshot is null||!snapshot.Available)return new([],[],[]);
        if(!ReferenceEquals(currentTradeRoutesSource,snapshot.Flows)||currentTradeRouteKind!=GalaxyFilter.CurrentTradeSource||!ReferenceEquals(currentTradeRouteSelection,GalaxyFilter.CurrentTradeCommodities)||!ReferenceEquals(currentTradeCategorySelection,GalaxyFilter.CurrentTradeCategories))
        {
            currentTradeRoutesSource=snapshot.Flows;currentTradeRouteKind=GalaxyFilter.CurrentTradeSource;currentTradeRouteSelection=GalaxyFilter.CurrentTradeCommodities;currentTradeCategorySelection=GalaxyFilter.CurrentTradeCategories;
            currentTradeRoutes=GalaxyCurrentTradeRoutes.Select(snapshot,currentTradeRouteKind,currentTradeRouteSelection,currentTradeCategorySelection);
        }
        var required=currentTradeRoutes.SelectMany(r=>new[]{r.FromSystemAddress,r.ToSystemAddress}).ToHashSet();
        var systems=galaxyCache.Values.Where(s=>s.SystemAddress is {} id&&required.Contains(id)&&s.Position.IsValid).GroupBy(s=>s.SystemAddress!.Value)
            .ToDictionary(g=>g.Key,g=>g.OrderByDescending(s=>s.Source=="游戏银河目录").First());
        var projection=GalaxyCurrentTradeRoutes.Project(currentTradeRoutes,systems);
        if(!GalaxyCurrentTradeFlows.SequenceEqual(projection.Flows))GalaxyCurrentTradeFlows=projection.Flows;
        var unclassified=snapshot.Flows.Count(f=>f.CommodityName is not null&&GalaxyCurrentTradeRoutes.CategoryFor(f.CommodityName) is null);
        GalaxyCurrentTradeStatus=$"当前缓存 · 已解析 {projection.Flows.Count:N0} / {currentTradeRoutes.Count:N0} 条,{projection.MissingSystemAddresses.Count:N0} 个端点待确认。\n商品名 {snapshot.UnresolvedCount:N0} 条、分类 {unclassified:N0} 条待确认;覆盖非完整。";
        return projection;
    }
    private void QueueGalaxyCurrentTradeFlows()
    {
        var context=CurrentTradeContext();
        if(context.Length==0)
        {
            galaxyCurrentTradeStop?.Cancel();ClearGalaxyCurrentTrade(GalaxyFilter.ShowCurrentTradeFlows?"原当前贸易缓存未连接;当前流保持未知。":"原当前贸易流已关闭;不会以历史缓存代替。");return;
        }
        if(galaxyCurrentTradeRunningContext.Length>0&&galaxyCurrentTradeRunningContext!=context)
        {galaxyCurrentTradeStop?.Cancel();galaxyCurrentTradeAttempts.Clear();ClearGalaxyCurrentTrade("游戏会话已变化;旧当前贸易流已清除。");}
        RefreshCurrentTradeProjection();
        if(galaxyCurrentTradeTask is{IsCompleted:false}&&galaxyCurrentTradeStop?.IsCancellationRequested==false)return;
        if(runtime.FlightDriver is not NativeFlightControlDriver{SupportsGalaxyCurrentTradeFlows:true} driver)
        {ClearGalaxyCurrentTrade("原当前贸易缓存接口尚未连接;当前流保持未知。");return;}
        galaxyCurrentTradeStop?.Dispose();galaxyCurrentTradeStop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);
        galaxyCurrentTradeRunningContext=context;
        galaxyCurrentTradeTask=RefreshGalaxyCurrentTradeLoopAsync(context,driver.ProcessId,galaxyCurrentTradeStop);
    }
    private async Task RefreshGalaxyCurrentTradeLoopAsync(string context,int processId,CancellationTokenSource stop)
    {
        var token=stop.Token;
        bool Own()=>ReferenceEquals(galaxyCurrentTradeStop,stop);
        bool Latest()=>Own()&&!token.IsCancellationRequested&&CurrentTradeContext()==context&&runtime.FlightDriver is NativeFlightControlDriver{SupportsGalaxyCurrentTradeFlows:true} driver&&driver.ProcessId==processId;
        try
        {
            while(Latest())
            {
                try
                {
                    var snapshot=await new NativeGalaxyAccountCatalog(processId).ReadCurrentTradeFlowsAsync(token);if(!Latest())return;
                    if(!snapshot.Available)ClearGalaxyCurrentTrade("原当前贸易缓存尚未就绪;不使用历史流替代。");
                    else
                    {
                        // A clock tick alone must not replace every route and
                        // renderer reference when the actual rows are unchanged.
                        var changed=galaxyCurrentTradeSnapshot is null||!galaxyCurrentTradeSnapshot.Flows.SequenceEqual(snapshot.Flows);
                        if(!changed)snapshot=snapshot with{Flows=galaxyCurrentTradeSnapshot!.Flows};
                        galaxyCurrentTradeSnapshot=snapshot;galaxyCurrentTradeContext=context;
                        if(changed)GalaxyCurrentTradeChoices=GalaxyCurrentTradeRoutes.Choices(snapshot.Flows);
                        var projection=RefreshCurrentTradeProjection();
                        if(!NavigationActive&&runtime.FlightDriver is NativeFlightControlDriver{SupportsGalaxyResolveAddress:true}&&NativeGalaxy() is {} catalog)
                        {
                            var now=DateTimeOffset.UtcNow;
                            var pending=projection.MissingSystemAddresses.Where(id=>!galaxyCurrentTradeAttempts.TryGetValue(id,out var last)||now-last>TimeSpan.FromSeconds(30))
                                .OrderBy(id=>galaxyCurrentTradeAttempts.GetValueOrDefault(id)).Take(8).ToArray();
                            foreach(var id in pending)
                            {
                                if(!Latest())return;galaxyCurrentTradeAttempts[id]=DateTimeOffset.UtcNow;
                                try{var system=await catalog.ResolveAddressAsync(id,token);if(!Latest())return;RememberGalaxy(system);}
                                catch(Exception error)when(error is KeyNotFoundException or IOException or TimeoutException or InvalidDataException){if(!Latest())return;}
                            }
                            if(!Latest())return;RefreshCurrentTradeProjection();
                        }
                    }
                }
                catch(OperationCanceledException)when(!token.IsCancellationRequested)
                {if(Latest())ClearGalaxyCurrentTrade("原当前贸易流读取超时;旧当前流已清除。");}
                catch(Exception error)when(error is IOException or TimeoutException or InvalidDataException or InvalidOperationException or System.Text.Json.JsonException or KeyNotFoundException or FormatException)
                {if(Latest())ClearGalaxyCurrentTrade("原当前贸易流尚未确认:"+error.Message);}
                await Task.Delay(2000,token);
            }
        }
        catch(OperationCanceledException){ }
        finally
        {
            // Losing only this capability (same PID/FID) is still a lost
            // current source. An older cancelled loop must never clear a new
            // loop's snapshot when it happens to have the same session key.
            if(Own()&&galaxyCurrentTradeContext==context&&!Latest())ClearGalaxyCurrentTrade("原当前贸易来源已断开;旧当前流已清除。");
        }
    }
}