diff --git a/src/Spice86/ViewModels/CfgCpuViewModel.cs b/src/Spice86/ViewModels/CfgCpuViewModel.cs index 47579c821d..909db66587 100644 --- a/src/Spice86/ViewModels/CfgCpuViewModel.cs +++ b/src/Spice86/ViewModels/CfgCpuViewModel.cs @@ -1,6 +1,5 @@ namespace Spice86.ViewModels; -using Avalonia.Controls; using Avalonia.Threading; using AvaloniaGraphControl; @@ -23,7 +22,7 @@ public partial class CfgCpuViewModel : ViewModelBase, IInternalDebugger { [ObservableProperty] private Graph? _graph; - + [ObservableProperty] private long _numberOfNodes = 0; @@ -32,64 +31,82 @@ public partial class CfgCpuViewModel : ViewModelBase, IInternalDebugger { private readonly IPauseStatus? _pauseStatus; - public CfgCpuViewModel() { - if(!Design.IsDesignMode) { - throw new InvalidOperationException("This constructor is not for runtime usage"); - } - } - public CfgCpuViewModel(IUIDispatcherTimerFactory dispatcherTimerFactory, IPerformanceMeasurer performanceMeasurer, IPauseStatus pauseStatus) { _pauseStatus = pauseStatus; + _pauseStatus.PropertyChanged += (sender, args) => { + if (args.PropertyName == nameof(IPauseStatus.IsPaused) && !_pauseStatus.IsPaused) { + Graph = null; + } + }; _performanceMeasurer = performanceMeasurer; dispatcherTimerFactory.StartNew(TimeSpan.FromMilliseconds(400), DispatcherPriority.Normal, UpdateCurrentGraph); } - private void UpdateCurrentGraph(object? sender, EventArgs e) { + private async void UpdateCurrentGraph(object? sender, EventArgs e) { if (_pauseStatus?.IsPaused is false or null) { return; } - ICfgNode? nodeRoot = ExecutionContext?.LastExecuted; - if (nodeRoot is null) { + if (Graph is not null) { return; } - NumberOfNodes = 0; - Graph currentGraph = new(); - Queue queue = new(); - queue.Enqueue(nodeRoot); - HashSet visitedNodes = new(); - var stopwatch = Stopwatch.StartNew(); - while (queue.Count > 0) { - ICfgNode node = queue.Dequeue(); - if (visitedNodes.Contains(node)) { - continue; + await Task.Run(async () => { + ICfgNode? nodeRoot = ExecutionContext?.LastExecuted; + if (nodeRoot is null) { + return; } - visitedNodes.Add(node); - stopwatch.Restart(); - foreach (ICfgNode successor in node.Successors) { - currentGraph.Edges.Add(new Edge( - $"{node.Address} {Environment.NewLine} {node.GetType().Name}", - $"{successor.Address} {Environment.NewLine} {successor.GetType().Name}")); - if (!visitedNodes.Contains(successor)) { - queue.Enqueue(successor); + + long localNumberOfNodes = 0; + Graph currentGraph = new(); + Queue queue = new(); + queue.Enqueue(nodeRoot); + HashSet visitedNodes = new(); + HashSet<(string, string)> existingEdges = new(); + Stopwatch stopwatch = new(); + while (queue.Count > 0) { + ICfgNode node = queue.Dequeue(); + if (visitedNodes.Contains(node)) { + continue; } - } - foreach (ICfgNode predecessor in node.Predecessors) { - currentGraph.Edges.Add(new Edge( - $"{predecessor.Address} {Environment.NewLine} {predecessor.GetType().Name}", - $"{node.Address} {Environment.NewLine} {node.GetType().Name}")); - if (!visitedNodes.Contains(predecessor)) { - queue.Enqueue(predecessor); + visitedNodes.Add(node); + stopwatch.Restart(); + foreach (ICfgNode successor in node.Successors) { + var edgeKey = ($"{node.Address}", $"{successor.Address}"); + if (!existingEdges.Contains(edgeKey)) { + currentGraph.Edges.Add(new Edge( + $"{node.Address} {Environment.NewLine} {node.GetType().Name}", + $"{successor.Address} {Environment.NewLine} {successor.GetType().Name}")); + existingEdges.Add(edgeKey); + } + if (!visitedNodes.Contains(successor)) { + queue.Enqueue(successor); + } + } + foreach (ICfgNode predecessor in node.Predecessors) { + var edgeKey = ($"{predecessor.Address}", $"{node.Address}"); + if (!existingEdges.Contains(edgeKey)) { + currentGraph.Edges.Add(new Edge( + $"{predecessor.Address} {Environment.NewLine} {predecessor.GetType().Name}", + $"{node.Address} {Environment.NewLine} {node.GetType().Name}")); + existingEdges.Add(edgeKey); + } + if (!visitedNodes.Contains(predecessor)) { + queue.Enqueue(predecessor); + } } + stopwatch.Stop(); + _performanceMeasurer?.UpdateValue(stopwatch.ElapsedMilliseconds); + localNumberOfNodes++; } - stopwatch.Stop(); - _performanceMeasurer?.UpdateValue(stopwatch.ElapsedMilliseconds); - NumberOfNodes++; - } - Graph = currentGraph; - if (_performanceMeasurer is not null) { - AverageNodeTime = _performanceMeasurer.ValuePerMillisecond; - } + + long averageNodeTime = _performanceMeasurer is not null ? _performanceMeasurer.ValuePerMillisecond : 0; + + await Dispatcher.UIThread.InvokeAsync(() => { + Graph = currentGraph; + NumberOfNodes = localNumberOfNodes; + AverageNodeTime = averageNodeTime; + }); + }).ConfigureAwait(false); } public void Visit(T component) where T : IDebuggableComponent { diff --git a/src/Spice86/ViewModels/DebugWindowViewModel.cs b/src/Spice86/ViewModels/DebugWindowViewModel.cs index f27385d568..b4c755f2a9 100644 --- a/src/Spice86/ViewModels/DebugWindowViewModel.cs +++ b/src/Spice86/ViewModels/DebugWindowViewModel.cs @@ -52,7 +52,7 @@ public partial class DebugWindowViewModel : ViewModelBase, IInternalDebugger { private SoftwareMixerViewModel _softwareMixerViewModel; [ObservableProperty] - private CfgCpuViewModel? _cfgCpuViewModel; + private CfgCpuViewModel _cfgCpuViewModel; public DebugWindowViewModel(ITextClipboard textClipboard, IHostStorageProvider storageProvider, IUIDispatcherTimerFactory uiDispatcherTimerFactory, IPauseStatus pauseStatus, IProgramExecutor programExecutor) { _programExecutor = programExecutor; @@ -108,7 +108,7 @@ public void NewMemoryView() { private void UpdateValues(object? sender, EventArgs e) => _programExecutor.Accept(this); private IEnumerable InternalDebuggers => new IInternalDebugger[] { - PaletteViewModel, CpuViewModel, VideoCardViewModel, MidiViewModel, SoftwareMixerViewModel + PaletteViewModel, CpuViewModel, VideoCardViewModel, MidiViewModel, SoftwareMixerViewModel, CfgCpuViewModel } .Concat(DisassemblyViewModels) .Concat(MemoryViewModels); diff --git a/src/Spice86/ViewModels/MainWindowViewModel.cs b/src/Spice86/ViewModels/MainWindowViewModel.cs index b1b9c5ec66..1cdf3162ad 100644 --- a/src/Spice86/ViewModels/MainWindowViewModel.cs +++ b/src/Spice86/ViewModels/MainWindowViewModel.cs @@ -47,7 +47,21 @@ public sealed partial class MainWindowViewModel : ViewModelBaseWithErrorDialog, private readonly IUIDispatcherTimerFactory _uiDispatcherTimerFactory; private readonly IAvaloniaKeyScanCodeConverter _avaloniaKeyScanCodeConverter; private readonly IWindowService _windowService; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(ShowInternalDebuggerCommand))] + private bool _isProgramExecutorNotNull; + private IProgramExecutor? _programExecutor; + + private IProgramExecutor? ProgramExecutor { + get => _programExecutor; + set { + _programExecutor = value; + Dispatcher.UIThread.Post(() => IsProgramExecutorNotNull = value is not null); + } + } + private SoftwareMixer? _softwareMixer; private ITimeMultiplier? _pit; private DebugWindowViewModel? _debugViewModel; @@ -185,26 +199,26 @@ public bool IsPaused { [RelayCommand(CanExecute = nameof(IsMachineRunning))] public async Task DumpEmulatorStateToFile() { - if (_programExecutor is not null) { - await _hostStorageProvider.DumpEmulatorStateToFile(Configuration, _programExecutor); + if (ProgramExecutor is not null) { + await _hostStorageProvider.DumpEmulatorStateToFile(Configuration, ProgramExecutor); } } [RelayCommand(CanExecute = nameof(IsMachineRunning))] public void Pause() { - if (_programExecutor is null) { + if (ProgramExecutor is null) { return; } - IsPaused = _programExecutor.IsPaused = true; + IsPaused = ProgramExecutor.IsPaused = true; } [RelayCommand(CanExecute = nameof(IsMachineRunning))] public void Play() { - if (_programExecutor is null) { + if (ProgramExecutor is null) { return; } - IsPaused = _programExecutor.IsPaused = false; + IsPaused = ProgramExecutor.IsPaused = false; } private void SetMainTitle() => MainTitle = $"{nameof(Spice86)} {Configuration.Exe}"; @@ -428,7 +442,7 @@ private void Dispose(bool disposing) { } } - private void DisposeEmulator() => _programExecutor?.Dispose(); + private void DisposeEmulator() => ProgramExecutor?.Dispose(); private bool _isInitLogLevelSet; @@ -502,25 +516,25 @@ private void MachineThread() { [ObservableProperty] private bool _isPerformanceVisible; - [RelayCommand] + [RelayCommand(CanExecute = nameof(IsProgramExecutorNotNull))] public async Task ShowInternalDebugger() { - if (_programExecutor is not null) { - _debugViewModel = new DebugWindowViewModel(_textClipboard, _hostStorageProvider, _uiDispatcherTimerFactory, this, _programExecutor); + if (ProgramExecutor is not null) { + _debugViewModel = new DebugWindowViewModel(_textClipboard, _hostStorageProvider, _uiDispatcherTimerFactory, this, ProgramExecutor); await _windowService.ShowDebugWindow(_debugViewModel); } } private void StartProgramExecutor() { (IProgramExecutor ProgramExecutor, SoftwareMixer? SoftwareMixer, ITimeMultiplier? Pit) viewModelEmulatorDependencies = CreateEmulator(); - _programExecutor = viewModelEmulatorDependencies.ProgramExecutor; + ProgramExecutor = viewModelEmulatorDependencies.ProgramExecutor; _softwareMixer = viewModelEmulatorDependencies.SoftwareMixer; _pit = viewModelEmulatorDependencies.Pit; - PerformanceViewModel = new(_uiDispatcherTimerFactory, _programExecutor, new PerformanceMeasurer(), this); + PerformanceViewModel = new(_uiDispatcherTimerFactory, ProgramExecutor, new PerformanceMeasurer(), this); _windowService.CloseDebugWindow(); TimeMultiplier = Configuration.TimeMultiplier; _uiDispatcher.Post(() => IsMachineRunning = true); _uiDispatcher.Post(() => StatusMessage = "Emulator started."); - _programExecutor?.Run(); + ProgramExecutor?.Run(); if (_closeAppOnEmulatorExit) { _uiDispatcher.Post(() => CloseMainWindow?.Invoke(this, EventArgs.Empty)); } diff --git a/src/Spice86/Views/CfgCpuView.axaml b/src/Spice86/Views/CfgCpuView.axaml index a85a6435dc..dc524e2b0c 100644 --- a/src/Spice86/Views/CfgCpuView.axaml +++ b/src/Spice86/Views/CfgCpuView.axaml @@ -15,9 +15,8 @@ - - - + + @@ -27,15 +26,16 @@ - - + + + - - - + + + \ No newline at end of file diff --git a/src/Spice86/Views/MainWindow.axaml b/src/Spice86/Views/MainWindow.axaml index 5e565db4fd..e12180cd6f 100644 --- a/src/Spice86/Views/MainWindow.axaml +++ b/src/Spice86/Views/MainWindow.axaml @@ -100,6 +100,11 @@ - + + + +