Files
XplorePlane/XplorePlane/ViewModels/Debug/SnapshotManagerViewModel.cs
T
2026-05-18 09:38:29 +08:00

294 lines
11 KiB
C#

using Microsoft.Win32;
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Windows;
using System.Windows.Threading;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
using XplorePlane.Views.Debug;
namespace XplorePlane.ViewModels.Debug
{
public class SnapshotViewModel : BindableBase
{
private bool _isSelected;
public StateSnapshot Snapshot { get; set; }
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
}
public class SnapshotManagerViewModel : BindableBase, IDisposable
{
private const int MaxSnapshots = 50;
private readonly IAppStateService _appStateService;
private readonly ILoggerService _logger;
private readonly Dispatcher _dispatcher;
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true };
private SnapshotViewModel _selectedSnapshot;
public ObservableCollection<SnapshotViewModel> Snapshots { get; } = new();
public ObservableCollection<StateNodeViewModel> SnapshotDetails { get; } = new();
public SnapshotViewModel SelectedSnapshot
{
get => _selectedSnapshot;
set
{
if (SetProperty(ref _selectedSnapshot, value))
{
UpdateSnapshotDetails();
}
}
}
public DelegateCommand CaptureSnapshotCommand { get; }
public DelegateCommand CompareSnapshotsCommand { get; }
public DelegateCommand ExportSnapshotCommand { get; }
public DelegateCommand DeleteSnapshotCommand { get; }
public SnapshotManagerViewModel(IAppStateService appStateService, ILoggerService loggerService, Dispatcher dispatcher)
{
_appStateService = appStateService ?? throw new ArgumentNullException(nameof(appStateService));
_logger = (loggerService ?? throw new ArgumentNullException(nameof(loggerService))).ForModule<SnapshotManagerViewModel>();
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
CaptureSnapshotCommand = new DelegateCommand(CaptureSnapshot);
CompareSnapshotsCommand = new DelegateCommand(CompareSnapshots);
ExportSnapshotCommand = new DelegateCommand(ExportSnapshot);
DeleteSnapshotCommand = new DelegateCommand(DeleteSnapshot);
}
public void Dispose()
{
Snapshots.Clear();
SnapshotDetails.Clear();
SelectedSnapshot = null;
}
public void CaptureSnapshot()
{
try
{
var snapshot = new StateSnapshot
{
Id = Guid.NewGuid(),
Timestamp = DateTime.Now,
MotionState = _appStateService.MotionState,
RaySourceState = _appStateService.RaySourceState,
DetectorState = _appStateService.DetectorState,
SystemState = _appStateService.SystemState,
CameraState = _appStateService.CameraState,
LinkedViewState = _appStateService.LinkedViewState,
RecipeExecutionState = _appStateService.RecipeExecutionState,
CalibrationMatrix = _appStateService.CalibrationMatrix
};
var item = new SnapshotViewModel { Snapshot = snapshot };
Snapshots.Insert(0, item);
SelectedSnapshot = item;
while (Snapshots.Count > MaxSnapshots)
{
Snapshots.RemoveAt(Snapshots.Count - 1);
}
_logger.Info("快照已捕获 {Id} | Snapshot captured {Id}", snapshot.Id);
}
catch (Exception ex)
{
_logger.Error(ex, "捕获调试快照失败 | Failed to capture debug snapshot");
}
}
public IReadOnlyList<StateDifference> ComputeDifference(StateSnapshot first, StateSnapshot second)
{
var differences = new List<StateDifference>();
if (first == null || second == null)
{
return differences;
}
CompareObject("MotionState", first.MotionState, second.MotionState, differences);
CompareObject("RaySourceState", first.RaySourceState, second.RaySourceState, differences);
CompareObject("DetectorState", first.DetectorState, second.DetectorState, differences);
CompareObject("SystemState", first.SystemState, second.SystemState, differences);
CompareObject("CameraState", first.CameraState, second.CameraState, differences);
CompareObject("LinkedViewState", first.LinkedViewState, second.LinkedViewState, differences);
CompareObject("RecipeExecutionState", first.RecipeExecutionState, second.RecipeExecutionState, differences);
CompareObject("CalibrationMatrix", first.CalibrationMatrix, second.CalibrationMatrix, differences);
return differences;
}
private void CompareSnapshots()
{
try
{
var selected = Snapshots.Where(s => s.IsSelected).Take(2).ToList();
if (selected.Count != 2)
{
MessageBox.Show("Please select exactly two snapshots to compare.", "Compare Snapshots", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var differences = ComputeDifference(selected[0].Snapshot, selected[1].Snapshot);
var viewModel = new SnapshotDiffViewModel(differences, _logger, _dispatcher);
var window = new SnapshotDiffWindow
{
DataContext = viewModel,
Owner = Application.Current?.MainWindow
};
window.Show();
_logger.Info("快照对比已打开 | Snapshot comparison window opened");
}
catch (Exception ex)
{
_logger.Error(ex, "比较快照失败 | Failed to compare snapshots");
MessageBox.Show($"Failed to compare snapshots: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ExportSnapshot()
{
try
{
if (SelectedSnapshot?.Snapshot == null)
{
MessageBox.Show("Please select a snapshot first.", "Export Snapshot", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var dialog = new SaveFileDialog
{
Title = "Export Snapshot",
Filter = "JSON files (*.json)|*.json",
FileName = $"snapshot-{SelectedSnapshot.Snapshot.Timestamp:yyyyMMdd-HHmmss}.json"
};
if (dialog.ShowDialog() != true)
{
return;
}
File.WriteAllText(dialog.FileName, JsonSerializer.Serialize(SelectedSnapshot.Snapshot, _jsonOptions));
_logger.Info("快照已导出到 {Path} | Snapshot exported to {Path}", dialog.FileName);
}
catch (Exception ex)
{
_logger.Error(ex, "导出快照失败 | Failed to export snapshot");
MessageBox.Show($"Failed to export snapshot: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void DeleteSnapshot()
{
try
{
if (SelectedSnapshot == null)
{
return;
}
var result = MessageBox.Show("Delete selected snapshot?", "Delete Snapshot", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result != MessageBoxResult.Yes)
{
return;
}
var snapshot = SelectedSnapshot;
var index = Snapshots.IndexOf(snapshot);
Snapshots.Remove(snapshot);
SelectedSnapshot = index >= 0 && index < Snapshots.Count ? Snapshots[index] : Snapshots.FirstOrDefault();
}
catch (Exception ex)
{
_logger.Error(ex, "删除快照失败 | Failed to delete snapshot");
}
}
private void UpdateSnapshotDetails()
{
SnapshotDetails.Clear();
if (SelectedSnapshot?.Snapshot == null)
{
return;
}
AddSnapshotRoot("MotionState", SelectedSnapshot.Snapshot.MotionState);
AddSnapshotRoot("RaySourceState", SelectedSnapshot.Snapshot.RaySourceState);
AddSnapshotRoot("DetectorState", SelectedSnapshot.Snapshot.DetectorState);
AddSnapshotRoot("SystemState", SelectedSnapshot.Snapshot.SystemState);
AddSnapshotRoot("CameraState", SelectedSnapshot.Snapshot.CameraState);
AddSnapshotRoot("LinkedViewState", SelectedSnapshot.Snapshot.LinkedViewState);
AddSnapshotRoot("RecipeExecutionState", SelectedSnapshot.Snapshot.RecipeExecutionState);
AddSnapshotRoot("CalibrationMatrix", SelectedSnapshot.Snapshot.CalibrationMatrix);
}
private void AddSnapshotRoot(string category, object value)
{
var root = new StateNodeViewModel
{
Name = category,
Category = category
};
if (value != null)
{
foreach (var property in value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
root.Children.Add(new StateNodeViewModel
{
Name = property.Name,
Category = category,
Value = DebugPanelStateFormatter.FormatValue(property.GetValue(value))
});
}
}
SnapshotDetails.Add(root);
}
private static void CompareObject(string category, object first, object second, ICollection<StateDifference> differences)
{
var type = first?.GetType() ?? second?.GetType();
if (type == null)
{
return;
}
foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var firstValue = first == null ? null : property.GetValue(first);
var secondValue = second == null ? null : property.GetValue(second);
if (Equals(firstValue, secondValue))
{
continue;
}
differences.Add(new StateDifference
{
Category = category,
FieldName = property.Name,
Value1 = DebugPanelStateFormatter.FormatValue(firstValue),
Value2 = DebugPanelStateFormatter.FormatValue(secondValue)
});
}
}
}
}