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. */}}
}
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. */}}
}