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.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;

namespace XFEEDToolBox.ViewModels;

public partial class ToolboxViewModel
{
    private ShipProtectionMonitor? shipProtection;
    private bool sessionAutomationAllowed,shieldRestarting;
    private long shieldSettingVersion;
    private readonly SemaphoreSlim flightProtectionGate=new(1,1);
    [ObservableProperty] public partial bool ShieldZeroRestartEnabled{get;set;}
    [ObservableProperty] public partial string ShieldRestartStatus{get;set;}="已关闭";
    partial void OnShieldZeroRestartEnabledChanged(bool value)=>_=ConfigureShieldRestartAsync(value);
    private async Task ConfigureShieldRestartAsync(bool enabled)
    {
        await flightProtectionGate.WaitAsync();
        try{await ConfigureShieldRestartCoreAsync(enabled);}
        finally{flightProtectionGate.Release();}
    }
    private async Task ConfigureShieldRestartCoreAsync(bool enabled)
    {
        var version=++shieldSettingVersion;
        var previous=Interlocked.Exchange(ref shipProtection,null);
        if(previous is not null)await previous.DisposeAsync();
        if(!enabled)ShieldRestartStatus="已关闭";
        try
        {
            if(initialized&&sessionAutomationAllowed)await runtime.SaveSettingsAsync(runtime.Settings with{ShieldZeroRestartEnabled=enabled});
            if(!enabled)return;
            if(!sessionAutomationAllowed){ShieldRestartStatus="本次界面验收未启用自动重启";return;}
            var monitor=CreateShipProtectionMonitor(version);
            await monitor.EnableAsync(lifetime.Token);
            if(version!=shieldSettingVersion||!ShieldZeroRestartEnabled){await monitor.DisposeAsync();return;}
            shipProtection=monitor;
        }
        catch(Exception error)when(error is not OperationCanceledException){ShieldRestartStatus="等待游戏就绪 · "+error.Message;}
    }
    private ShipProtectionMonitor CreateShipProtectionMonitor(long version)
    {
        var monitor=new ShipProtectionMonitor(new Services.WindowsGameSessionDriver(()=>runtime.Player),()=>runtime.Player,StopForShipProtection);
        monitor.ExpectedInputFreeJumpLoading=()=>runtime.FlightAutomation.IsAwaitingInputFreeJump(runtime.Player,DateTimeOffset.UtcNow);
        monitor.PauseAutomationForMonitoring=StopForFlightMonitoring;
        monitor.NativeProtectionDiagnostics=diagnostic=>runtime.RecordScoDiagnostic(System.Text.Json.JsonSerializer.SerializeToElement(new
        {
            service="FlightHealthProtection",processId=diagnostic.ProcessId,phase=diagnostic.Phase,issue=diagnostic.Issue,
            entry=runtime.FlightAutomation.ActiveEntry?.ToString(),stage=runtime.FlightAutomation.Stage.ToString(),automaticTradeActive=RunActive,
            sample=new{state=diagnostic.Phase},diagnostic
        }));
        monitor.Reentry+=entry=>ProtectionReentry?.Invoke(entry);
        monitor.Progress+=progress=>dispatcher.TryEnqueue(()=>
        {
            if(version!=shieldSettingVersion)return;
            ShieldRestartStatus=progress.Message;shieldRestarting=progress.EmergencyActive;
            SessionBusy=progress.EmergencyActive;ActiveSessionOperation=progress.EmergencyActive?SessionOperation.Restart:null;
        });return monitor;
    }
    private async Task EnsureNativeFlightProtectionAsync(int pid,string hash,CancellationToken token)
    {
        if(!sessionAutomationAllowed||DemoMode)throw new InvalidOperationException("当前界面预览会话未启用实机驾驶。");
        await flightProtectionGate.WaitAsync(token);
        try
        {
            if(shipProtection?.BoundTarget?.ProcessId!=pid)
            {
                var version=++shieldSettingVersion;var previous=Interlocked.Exchange(ref shipProtection,null);
                if(previous is not null)await previous.DisposeAsync();
                var monitor=CreateShipProtectionMonitor(version);
                try
                {
                    await monitor.EnableAsync(token);
                    if(monitor.BoundTarget?.ProcessId!=pid)throw new InvalidOperationException("自动驾驶与受保护游戏实例不一致。");
                    shipProtection=monitor;
                }
                catch{await monitor.DisposeAsync();throw;}
            }
            using var fresh=CancellationTokenSource.CreateLinkedTokenSource(token);fresh.CancelAfter(TimeSpan.FromSeconds(2));
            try{await shipProtection.AttachNativeAsync(hash,fresh.Token,exitForFailedHandoff:false);}
            catch(OperationCanceledException error)when(!token.IsCancellationRequested){throw new FlightTelemetryUnavailableException("正在等待当前驾驶舱的连续健康观测。",error);}
            shipProtection.RequireHealthyShip(requireContinuous:true);
        }
        finally{flightProtectionGate.Release();}
    }
    private void StopForShipProtection()
    {
        var coordinated=runtime.FlightAutomation.IsActive;
        var protection=shipProtection;
        if(protection?.TestingBlocked==true||protection?.EmergencyActive==true)
            runtime.FlightAutomation.PauseForSafety(protection.LastTrigger?.Reason==ShipProtectionReason.Interdicted?new FlightThreatPauseException():new FlightHealthPauseException());
        else runtime.FlightAutomation.PauseForMonitoring(new InvalidOperationException("飞船保护暂停了自动驾驶;尚未确认保护来源,原行程保留。"));
        // The coordinator cancels its own children after recording the cause.
        // Cancelling UI parent tokens first would mislabel safety as a user stop.
        if(!coordinated)try{navigationStartStop?.Cancel();}catch(ObjectDisposedException){}
        runtime.Navigator.Stop();runtime.Runner.Stop();
        systemNavigator.Stop();if(!coordinated)try{systemFlightStop?.Cancel();}catch(ObjectDisposedException){}
        try{sessionStop?.Cancel();}catch(ObjectDisposedException){}
        if(runtime.FlightDriver is {} driver)_=ReleaseProtectedDriverAsync(driver);
    }
    private void StopForFlightMonitoring(Exception cause)
    {
        // Missing monitoring is an internal failure, never an invented user
        // command. Preserve the original typed cause for the journey owner.
        runtime.FlightAutomation.PauseForMonitoring(cause);
    }
    private async Task ReleaseProtectionForControlledExitAsync()
    {
        await flightProtectionGate.WaitAsync(lifetime.Token);
        try
        {
            if(shipProtection is { } protectedFlight)
            {
                if(protectedFlight.TestingBlocked||protectedFlight.EmergencyActive)
                    throw new InvalidOperationException("实际飞船保护仍在处理受损,不能释放保护以继续自动航程。");
                protectedFlight.RequireHealthyShip();
            }
            var previous=Interlocked.Exchange(ref shipProtection,null);++shieldSettingVersion;
            if(previous is not null)await previous.DisposeAsync();
        }
        finally{flightProtectionGate.Release();}
    }
    private void OnShieldJournal(ObservedJournalEvent observed,PlayerState player)
    {
        if(!sessionAutomationAllowed||lifetime.IsCancellationRequested||DemoMode)return;
        // Damage processing never waits for a busy WinUI dispatcher.
        shipProtection?.Observe(observed,player);
        if(ShieldZeroRestartEnabled&&observed.Data.Text("event")=="LoadGame")
            dispatcher.TryEnqueue(()=>_=ConfigureShieldRestartAsync(true));
    }
    internal bool ProtectionBlocksTesting=>shipProtection?.TestingBlocked==true;
    private static async Task ReleaseProtectedDriverAsync(XFEEDToolBox.Core.Navigation.IFlightControlDriver driver)
    {try{await driver.ReleaseAsync();}catch(Exception){/* Emergency process exit is independent of input release. */}}
}