diff --git a/src/Spice86/DependencyInjection/ServiceCollectionExtensions.cs b/src/Spice86/DependencyInjection/ServiceCollectionExtensions.cs index 84d2e65528..8a99a41208 100644 --- a/src/Spice86/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Spice86/DependencyInjection/ServiceCollectionExtensions.cs @@ -4,13 +4,17 @@ namespace Spice86.DependencyInjection; using Avalonia.Platform.Storage; using Avalonia.Threading; +using CommunityToolkit.Mvvm.Messaging; + using Microsoft.Extensions.DependencyInjection; using Spice86.Core.CLI; +using Spice86.Core.Emulator; using Spice86.Infrastructure; using Spice86.Interfaces; using Spice86.Logging; using Spice86.Shared.Interfaces; +using Spice86.ViewModels; public static class ServiceCollectionExtensions { public static void AddConfiguration(this IServiceCollection serviceCollection, string[] args) { @@ -42,5 +46,19 @@ public static void AddGuiInfrastructure(this IServiceCollection serviceCollectio serviceCollection.AddSingleton(); serviceCollection.AddSingleton(_ => new TextClipboard(mainWindow.Clipboard)); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(_ => WeakReferenceMessenger.Default); + } + + public static void AddEmulatorServices(this IServiceCollection serviceCollection) { + serviceCollection.AddScoped((serviceProvider) => { + Configuration configuration = serviceProvider.GetRequiredService(); + ILoggerService loggerService = serviceProvider.GetRequiredService(); + return new ProgramExecutor(configuration, loggerService, serviceProvider.GetService()); + }); + } + + public static void AddViewModels(this IServiceCollection serviceCollection) { + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); } } \ No newline at end of file diff --git a/src/Spice86/Interfaces/IPauseStatus.cs b/src/Spice86/Interfaces/IPauseStatus.cs deleted file mode 100644 index a18828e7dc..0000000000 --- a/src/Spice86/Interfaces/IPauseStatus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Spice86.Interfaces; - -using System.ComponentModel; - -public interface IPauseStatus : INotifyPropertyChanged { - bool IsPaused { get; set; } -} \ No newline at end of file diff --git a/src/Spice86/Program.cs b/src/Spice86/Program.cs index abf73023e6..ad3de992f2 100644 --- a/src/Spice86/Program.cs +++ b/src/Spice86/Program.cs @@ -53,8 +53,11 @@ public static void Main(string[] args) { ClassicDesktopStyleApplicationLifetime desktop = CreateDesktopApp(); MainWindow mainWindow = new(); serviceCollection.AddGuiInfrastructure(mainWindow); + serviceCollection.AddEmulatorServices(); + serviceCollection.AddViewModels(); //We need to rebuild the service provider after adding new services to the collection - using MainWindowViewModel mainWindowViewModel = serviceCollection.BuildServiceProvider().GetRequiredService(); + serviceProvider = serviceCollection.BuildServiceProvider(); + using MainWindowViewModel mainWindowViewModel = serviceProvider.GetRequiredService(); StartGraphicalUserInterface(desktop, mainWindowViewModel, mainWindow, args); } } @@ -78,7 +81,6 @@ private static ServiceCollection InjectCommonServices(string[] args) { serviceCollection.AddLogging(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); return serviceCollection; } diff --git a/src/Spice86/ViewModels/CfgCpuViewModel.cs b/src/Spice86/ViewModels/CfgCpuViewModel.cs index d16744e88f..a96351bdad 100644 --- a/src/Spice86/ViewModels/CfgCpuViewModel.cs +++ b/src/Spice86/ViewModels/CfgCpuViewModel.cs @@ -5,6 +5,7 @@ using AvaloniaGraphControl; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; using Spice86.Core.Emulator.CPU.CfgCpu.ControlFlowGraph; using Spice86.Core.Emulator.CPU.CfgCpu.Linker; @@ -12,10 +13,10 @@ using Spice86.Core.Emulator.CPU.CfgCpu.ParsedInstruction.SelfModifying; using Spice86.Core.Emulator.InternalDebugger; using Spice86.Infrastructure; -using Spice86.Interfaces; using Spice86.Shared.Diagnostics; using Spice86.Shared.Emulator.Memory; using Spice86.Shared.Interfaces; +using Spice86.ViewModels.Messages; using System.Diagnostics; @@ -35,15 +36,18 @@ public partial class CfgCpuViewModel : ViewModelBase, IInternalDebugger { [ObservableProperty] private long _averageNodeTime = 0; - private readonly IPauseStatus? _pauseStatus; + private readonly IMessenger _messenger; - public CfgCpuViewModel(IUIDispatcherTimerFactory dispatcherTimerFactory, IPerformanceMeasurer performanceMeasurer, IPauseStatus pauseStatus) { - _pauseStatus = pauseStatus; - _pauseStatus.PropertyChanged += (sender, args) => { - if (args.PropertyName == nameof(IPauseStatus.IsPaused) && !_pauseStatus.IsPaused) { + private bool _isPaused; + + public CfgCpuViewModel(IMessenger messenger, IUIDispatcherTimerFactory dispatcherTimerFactory, IPerformanceMeasurer performanceMeasurer) { + _messenger = messenger; + _messenger.Register(this, (_, message) => { + _isPaused = message.IsPaused; + if(!message.IsPaused) { Graph = null; } - }; + }); _performanceMeasurer = performanceMeasurer; dispatcherTimerFactory.StartNew(TimeSpan.FromMilliseconds(400), DispatcherPriority.Normal, UpdateCurrentGraph); } @@ -53,7 +57,7 @@ partial void OnMaxNodesToDisplayChanging(int value) { } private async void UpdateCurrentGraph(object? sender, EventArgs e) { - if (_pauseStatus?.IsPaused is false or null) { + if (!_isPaused) { return; } if (Graph is not null) { @@ -81,7 +85,7 @@ await Task.Run(async () => { visitedNodes.Add(node); stopwatch.Restart(); foreach (ICfgNode successor in node.Successors) { - var edgeKey = GenerateEdgeKey(node, successor); + (int, int) edgeKey = GenerateEdgeKey(node, successor); if (!existingEdges.Contains(edgeKey)) { currentGraph.Edges.Add(CreateEdge(node, successor)); existingEdges.Add(edgeKey); @@ -91,7 +95,7 @@ await Task.Run(async () => { } } foreach (ICfgNode predecessor in node.Predecessors) { - var edgeKey = GenerateEdgeKey(predecessor, node); + (int, int) edgeKey = GenerateEdgeKey(predecessor, node); if (!existingEdges.Contains(edgeKey)) { currentGraph.Edges.Add(CreateEdge(predecessor, node)); existingEdges.Add(edgeKey); diff --git a/src/Spice86/ViewModels/CpuViewModel.cs b/src/Spice86/ViewModels/CpuViewModel.cs index 70640bf4ff..9bdc83665b 100644 --- a/src/Spice86/ViewModels/CpuViewModel.cs +++ b/src/Spice86/ViewModels/CpuViewModel.cs @@ -3,19 +3,21 @@ namespace Spice86.ViewModels; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; using Spice86.Core.Emulator.CPU; using Spice86.Core.Emulator.InternalDebugger; using Spice86.Infrastructure; -using Spice86.Interfaces; using Spice86.Models.Debugging; +using Spice86.ViewModels.Messages; using System.ComponentModel; using System.Reflection; public partial class CpuViewModel : ViewModelBase, IInternalDebugger { - private readonly IPauseStatus _pauseStatus; + private readonly IMessenger _messenger; private State? _cpuState; + private bool _isPaused; [ObservableProperty] private StateInfo _state = new(); @@ -23,8 +25,9 @@ public partial class CpuViewModel : ViewModelBase, IInternalDebugger { [ObservableProperty] private CpuFlagsInfo _flags = new(); - public CpuViewModel(IUIDispatcherTimerFactory dispatcherTimerFactory, IPauseStatus pauseStatus) { - _pauseStatus = pauseStatus; + public CpuViewModel(IMessenger messenger, IUIDispatcherTimerFactory dispatcherTimerFactory) { + _messenger = messenger; + _messenger.Register(this, (_, message) => _isPaused = message.IsPaused); dispatcherTimerFactory.StartNew(TimeSpan.FromMilliseconds(400), DispatcherPriority.Normal, UpdateValues); } @@ -40,14 +43,12 @@ public void Visit(T component) where T : IDebuggableComponent { _cpuState ??= component as State; } - private bool IsPaused => _pauseStatus.IsPaused; - private void VisitCpuState(State state) { - if (!IsPaused) { + if (!_isPaused) { UpdateCpuState(state); } - if (IsPaused) { + if (_isPaused) { State.PropertyChanged -= OnStatePropertyChanged; State.PropertyChanged += OnStatePropertyChanged; Flags.PropertyChanged -= OnStatePropertyChanged; @@ -60,7 +61,7 @@ private void VisitCpuState(State state) { return; void OnStatePropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (sender is null || e.PropertyName == null || !IsPaused) { + if (sender is null || e.PropertyName == null || !_isPaused) { return; } PropertyInfo? originalPropertyInfo = state.GetType().GetProperty(e.PropertyName); diff --git a/src/Spice86/ViewModels/DebugWindowViewModel.cs b/src/Spice86/ViewModels/DebugWindowViewModel.cs index 9cad8126bd..d6d909c1ab 100644 --- a/src/Spice86/ViewModels/DebugWindowViewModel.cs +++ b/src/Spice86/ViewModels/DebugWindowViewModel.cs @@ -5,22 +5,25 @@ namespace Spice86.ViewModels; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using Spice86.Core.Emulator; using Spice86.Core.Emulator.InternalDebugger; +using Spice86.Core.Emulator.Memory; using Spice86.Infrastructure; using Spice86.Interfaces; using Spice86.Shared.Diagnostics; +using Spice86.ViewModels.Messages; using System.ComponentModel; public partial class DebugWindowViewModel : ViewModelBase, IInternalDebugger { - private readonly IPauseStatus _pauseStatus; - private readonly IProgramExecutor _programExecutor; + private readonly IDebuggableComponent _programExecutor; private readonly IHostStorageProvider _storageProvider; private readonly IUIDispatcherTimerFactory _uiDispatcherTimerFactory; private readonly ITextClipboard _textClipboard; private readonly IStructureViewModelFactory _structureViewModelFactory; + private readonly IMessenger _messenger; [ObservableProperty] private DateTime? _lastUpdate; @@ -55,28 +58,36 @@ public partial class DebugWindowViewModel : ViewModelBase, IInternalDebugger { [ObservableProperty] private CfgCpuViewModel _cfgCpuViewModel; - public DebugWindowViewModel(ITextClipboard textClipboard, IHostStorageProvider storageProvider, IUIDispatcherTimerFactory uiDispatcherTimerFactory, IPauseStatus pauseStatus, IProgramExecutor programExecutor, IStructureViewModelFactory structureViewModelFactory) { + public DebugWindowViewModel(IMessenger messenger, ITextClipboard textClipboard, IHostStorageProvider storageProvider, IUIDispatcherTimerFactory uiDispatcherTimerFactory, IProgramExecutor programExecutor, IStructureViewModelFactory structureViewModelFactory) { + _messenger = messenger; + _messenger.Register(this, (_, message) => HandlePauseStatusChanged(message.IsPaused)); _programExecutor = programExecutor; _structureViewModelFactory = structureViewModelFactory; _storageProvider = storageProvider; _textClipboard = textClipboard; _uiDispatcherTimerFactory = uiDispatcherTimerFactory; - _pauseStatus = pauseStatus; - IsPaused = _programExecutor.IsPaused; - _pauseStatus.PropertyChanged += OnPauseStatusChanged; + IsPaused = programExecutor.IsPaused; uiDispatcherTimerFactory.StartNew(TimeSpan.FromSeconds(1.0 / 30.0), DispatcherPriority.Normal, UpdateValues); - var disassemblyVm = new DisassemblyViewModel(this, uiDispatcherTimerFactory, pauseStatus); + var disassemblyVm = new DisassemblyViewModel(_messenger, programExecutor.IsPaused, false, uiDispatcherTimerFactory); DisassemblyViewModels.Add(disassemblyVm); PaletteViewModel = new(uiDispatcherTimerFactory); SoftwareMixerViewModel = new(uiDispatcherTimerFactory); VideoCardViewModel = new(uiDispatcherTimerFactory); - CpuViewModel = new(uiDispatcherTimerFactory, pauseStatus); + CpuViewModel = new(_messenger, uiDispatcherTimerFactory); MidiViewModel = new(uiDispatcherTimerFactory); - MemoryViewModels.Add(new(this, textClipboard, uiDispatcherTimerFactory, storageProvider, pauseStatus, 0, _structureViewModelFactory)); - CfgCpuViewModel = new(uiDispatcherTimerFactory, new PerformanceMeasurer(), pauseStatus); + MemoryViewModels.Add(new(this, _messenger, textClipboard, uiDispatcherTimerFactory, storageProvider, programExecutor.IsPaused, false, 0, A20Gate.EndOfHighMemoryArea, _structureViewModelFactory)); + CfgCpuViewModel = new(_messenger, uiDispatcherTimerFactory, new PerformanceMeasurer()); Dispatcher.UIThread.Post(ForceUpdate, DispatcherPriority.Background); + _messenger.Register>(this, (_, _) => NewMemoryViewCommand.Execute(null)); + _messenger.Register>(this, (_, _) => NewDisassemblyViewCommand.Execute(null)); + _messenger.Register>(this, (_, message) => CloseTab(message.Sender)); + _messenger.Register>(this, (_, message) => CloseTab(message.Sender)); } + private void HandlePauseStatusChanged(bool isPaused) => IsPaused = isPaused; + + private void NotifyViaMessageAboutPauseStatus(bool isPaused) => _messenger.Send(new PauseStatusChangedMessage(isPaused)); + internal void CloseTab(IInternalDebugger internalDebuggerViewModel) { switch (internalDebuggerViewModel) { case MemoryViewModel memoryViewModel: @@ -91,20 +102,24 @@ internal void CloseTab(IInternalDebugger internalDebuggerViewModel) { } [RelayCommand(CanExecute = nameof(IsPaused))] - public void NewMemoryView() { - MemoryViewModels.Add(new MemoryViewModel(this, _textClipboard, _uiDispatcherTimerFactory, _storageProvider, _pauseStatus, 0, _structureViewModelFactory)); - } - + public void NewMemoryView() => + MemoryViewModels.Add(new MemoryViewModel(this, _messenger, _textClipboard, _uiDispatcherTimerFactory, _storageProvider, IsPaused, true, 0, A20Gate.EndOfHighMemoryArea, _structureViewModelFactory)); + [RelayCommand(CanExecute = nameof(IsPaused))] - public void NewDisassemblyView() => DisassemblyViewModels.Add(new DisassemblyViewModel(this, _uiDispatcherTimerFactory, _pauseStatus)); + public void NewDisassemblyView() => + DisassemblyViewModels.Add(new DisassemblyViewModel(_messenger, true, IsPaused, _uiDispatcherTimerFactory)); [RelayCommand] - public void Pause() => _pauseStatus.IsPaused = _programExecutor.IsPaused = IsPaused = true; + public void Pause() { + IsPaused = true; + NotifyViaMessageAboutPauseStatus(IsPaused); + } [RelayCommand(CanExecute = nameof(IsPaused))] - public void Continue() => _pauseStatus.IsPaused = _programExecutor.IsPaused = IsPaused = false; - - private void OnPauseStatusChanged(object? sender, PropertyChangedEventArgs e) => IsPaused = _pauseStatus.IsPaused; + public void Continue() { + IsPaused = false; + NotifyViaMessageAboutPauseStatus(IsPaused); + } [RelayCommand] public void ForceUpdate() => UpdateValues(this, EventArgs.Empty); diff --git a/src/Spice86/ViewModels/DisassemblyViewModel.cs b/src/Spice86/ViewModels/DisassemblyViewModel.cs index 4e87c914d1..69296fbf1a 100644 --- a/src/Spice86/ViewModels/DisassemblyViewModel.cs +++ b/src/Spice86/ViewModels/DisassemblyViewModel.cs @@ -5,6 +5,7 @@ namespace Spice86.ViewModels; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using Iced.Intel; @@ -13,17 +14,13 @@ namespace Spice86.ViewModels; using Spice86.Core.Emulator.InternalDebugger; using Spice86.Core.Emulator.Memory; using Spice86.Infrastructure; -using Spice86.Interfaces; using Spice86.MemoryWrappers; using Spice86.Models.Debugging; using Spice86.Shared.Utils; - -using System.Collections.Specialized; -using System.ComponentModel; +using Spice86.ViewModels.Messages; public partial class DisassemblyViewModel : ViewModelBase, IInternalDebugger { - private readonly IPauseStatus _pauseStatus; - private readonly DebugWindowViewModel _debugWindowViewModel; + private readonly IMessenger _messenger; private bool _needToUpdateDisassembly = true; private IMemory? _memory; private State? _state; @@ -62,29 +59,16 @@ public uint? StartAddress { [NotifyCanExecuteChangedFor(nameof(CloseTabCommand))] private bool _canCloseTab; - public DisassemblyViewModel(DebugWindowViewModel debugWindowViewModel, IUIDispatcherTimerFactory dispatcherTimerFactory, IPauseStatus pauseStatus) { - _debugWindowViewModel = debugWindowViewModel; - _pauseStatus = pauseStatus; - IsPaused = pauseStatus.IsPaused; - _pauseStatus.PropertyChanged += OnPauseStatusChanged; + public DisassemblyViewModel(IMessenger messenger, bool isPaused, bool canCloseTab, IUIDispatcherTimerFactory dispatcherTimerFactory) { + IsPaused = isPaused; + CanCloseTab = canCloseTab; + _messenger = messenger; + _messenger.Register(this, HandlePauseStatusMessage); dispatcherTimerFactory.StartNew(TimeSpan.FromMilliseconds(400), DispatcherPriority.Normal, UpdateValues); - UpdateCanCloseTabProperty(); - debugWindowViewModel.DisassemblyViewModels.CollectionChanged += OnDebugViewModelCollectionChanged; } - private void UpdateCanCloseTabProperty() { - CanCloseTab = _debugWindowViewModel.DisassemblyViewModels.Count > 1; - } - - private void OnDebugViewModelCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - UpdateCanCloseTabProperty(); - } - [RelayCommand(CanExecute = nameof(CanCloseTab))] - private void CloseTab() { - _debugWindowViewModel.CloseTab(this); - UpdateCanCloseTabProperty(); - } + private void CloseTab() => _messenger.Send(new RemoveViewModelMessage(this)); private void UpdateValues(object? sender, EventArgs e) { if (_needToUpdateDisassembly && IsPaused) { @@ -92,9 +76,8 @@ private void UpdateValues(object? sender, EventArgs e) { } } - private void OnPauseStatusChanged(object? sender, PropertyChangedEventArgs e) { - IsPaused = _pauseStatus.IsPaused; - UpdateCanCloseTabProperty(); + private void HandlePauseStatusMessage(object recipient, PauseStatusChangedMessage message) { + IsPaused = message.IsPaused; if (!IsPaused) { return; } @@ -124,9 +107,7 @@ public void Visit(T component) where T : IDebuggableComponent { } [RelayCommand(CanExecute = nameof(IsPaused))] - public void NewDisassemblyView() { - _debugWindowViewModel.NewDisassemblyViewCommand.Execute(null); - } + public void NewDisassemblyView() => _messenger.Send(new AddViewModelMessage()); [RelayCommand(CanExecute = nameof(IsPaused))] public void StepInstruction() => _programExecutor?.StepInstruction(); diff --git a/src/Spice86/ViewModels/MainWindowViewModel.cs b/src/Spice86/ViewModels/MainWindowViewModel.cs index ebe3acb96a..41c2cf4802 100644 --- a/src/Spice86/ViewModels/MainWindowViewModel.cs +++ b/src/Spice86/ViewModels/MainWindowViewModel.cs @@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using Serilog.Events; @@ -23,6 +24,7 @@ using Spice86.Shared.Emulator.Mouse; using Spice86.Shared.Emulator.Video; using Spice86.Shared.Interfaces; +using Spice86.ViewModels.Messages; using System.Threading; @@ -31,7 +33,7 @@ using Timer = System.Timers.Timer; /// -public sealed partial class MainWindowViewModel : ViewModelBaseWithErrorDialog, IPauseStatus, IGui, IDisposable { +public sealed partial class MainWindowViewModel : ViewModelBaseWithErrorDialog, IGui, IDisposable { private const double ScreenRefreshHz = 60; private readonly ILoggerService _loggerService; private readonly IHostStorageProvider _hostStorageProvider; @@ -40,6 +42,8 @@ public sealed partial class MainWindowViewModel : ViewModelBaseWithErrorDialog, private readonly IUIDispatcherTimerFactory _uiDispatcherTimerFactory; private readonly IAvaloniaKeyScanCodeConverter _avaloniaKeyScanCodeConverter; private readonly IWindowService _windowService; + private readonly IMessenger _messenger; + private readonly DebugWindowViewModel _debugViewModel; private readonly IStructureViewModelFactory _structureViewModelFactory; [ObservableProperty] @@ -58,7 +62,6 @@ private IProgramExecutor? ProgramExecutor { private SoftwareMixer? _softwareMixer; private ITimeMultiplier? _pit; - private DebugWindowViewModel? _debugViewModel; [ObservableProperty] private Configuration _configuration; @@ -81,7 +84,8 @@ private IProgramExecutor? ProgramExecutor { public event EventHandler? MouseButtonDown; public event EventHandler? MouseButtonUp; - public MainWindowViewModel(IWindowService windowService, IAvaloniaKeyScanCodeConverter avaloniaKeyScanCodeConverter, IProgramExecutorFactory programExecutorFactory, IUIDispatcher uiDispatcher, IHostStorageProvider hostStorageProvider, ITextClipboard textClipboard, IUIDispatcherTimerFactory uiDispatcherTimerFactory, Configuration configuration, ILoggerService loggerService, IStructureViewModelFactory structureViewModelFactory) : base(textClipboard) { + public MainWindowViewModel(IMessenger messenger, IWindowService windowService, IAvaloniaKeyScanCodeConverter avaloniaKeyScanCodeConverter, IProgramExecutorFactory programExecutorFactory, IUIDispatcher uiDispatcher, IHostStorageProvider hostStorageProvider, ITextClipboard textClipboard, IUIDispatcherTimerFactory uiDispatcherTimerFactory, Configuration configuration, ILoggerService loggerService, IStructureViewModelFactory structureViewModelFactory, DebugWindowViewModel debugWindowViewModel) : base(textClipboard) { + _debugViewModel = debugWindowViewModel; _avaloniaKeyScanCodeConverter = avaloniaKeyScanCodeConverter; _windowService = windowService; Configuration = configuration; @@ -91,6 +95,18 @@ public MainWindowViewModel(IWindowService windowService, IAvaloniaKeyScanCodeCon _hostStorageProvider = hostStorageProvider; _uiDispatcher = uiDispatcher; _uiDispatcherTimerFactory = uiDispatcherTimerFactory; + _messenger = messenger; + _messenger.Register(this, HandlePauseStatusMessage); + } + + private void HandlePauseStatusMessage(object recipient, PauseStatusChangedMessage message) { + if (message.IsPaused) { + if (PauseCommand.CanExecute(null)) { + PauseInternal(true); + } + } else if(PlayCommand.CanExecute(null)) { + PlayInternal(true); + } } internal void OnMainWindowClosing() => _isAppClosing = true; @@ -155,18 +171,9 @@ internal void OnKeyDown(KeyEventArgs e) { [ObservableProperty] private string _asmOverrideStatus = "ASM Overrides: not used."; + [ObservableProperty] private bool _isPaused; - public bool IsPaused { - get => _isPaused; - set { - SetProperty(ref _isPaused, value); - if (_softwareMixer is not null) { - _softwareMixer.IsPaused = value; - } - } - } - public int Width { get; private set; } public int Height { get; private set; } @@ -191,19 +198,39 @@ public async Task DumpEmulatorStateToFile() { [RelayCommand(CanExecute = nameof(IsEmulatorRunning))] public void Pause() { + PauseInternal(); + } + + private void PauseInternal(bool fromMessage = false) { if (ProgramExecutor is null) { return; } IsPaused = ProgramExecutor.IsPaused = true; + if(!fromMessage) { + NotifyViaMessageAboutPauseStatus(); + } + } + + private void NotifyViaMessageAboutPauseStatus() { + if (_softwareMixer is not null) { + _softwareMixer.IsPaused = IsPaused; + } + _messenger.Send(new PauseStatusChangedMessage(IsPaused)); } [RelayCommand(CanExecute = nameof(IsEmulatorRunning))] public void Play() { + PlayInternal(); + } + + private void PlayInternal(bool fromMessage = false) { if (ProgramExecutor is null) { return; } - IsPaused = ProgramExecutor.IsPaused = false; + if(!fromMessage) { + NotifyViaMessageAboutPauseStatus(); + } } private void SetMainTitle() => MainTitle = $"{nameof(Spice86)} {Configuration.Exe}"; @@ -431,7 +458,6 @@ private void EmulatorThread() { [RelayCommand(CanExecute = nameof(IsProgramExecutorNotNull))] public async Task ShowInternalDebugger() { if (ProgramExecutor is not null) { - _debugViewModel = new DebugWindowViewModel(_textClipboard, _hostStorageProvider, _uiDispatcherTimerFactory, this, ProgramExecutor, _structureViewModelFactory); await _windowService.ShowDebugWindow(_debugViewModel); } } @@ -441,7 +467,7 @@ private void StartProgramExecutor() { ProgramExecutor = viewModelEmulatorDependencies.ProgramExecutor; _softwareMixer = viewModelEmulatorDependencies.SoftwareMixer; _pit = viewModelEmulatorDependencies.Pit; - PerformanceViewModel = new(_uiDispatcherTimerFactory, ProgramExecutor, new PerformanceMeasurer(), this); + PerformanceViewModel = new(_messenger, _uiDispatcherTimerFactory, ProgramExecutor, new PerformanceMeasurer()); _windowService.CloseDebugWindow(); TimeMultiplier = Configuration.TimeMultiplier; _uiDispatcher.Post(() => IsEmulatorRunning = true); diff --git a/src/Spice86/ViewModels/MemoryViewModel.cs b/src/Spice86/ViewModels/MemoryViewModel.cs index 38f3503e87..fd7bc1a2ed 100644 --- a/src/Spice86/ViewModels/MemoryViewModel.cs +++ b/src/Spice86/ViewModels/MemoryViewModel.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using Spice86.Core.Emulator.InternalDebugger; using Spice86.Core.Emulator.Memory; @@ -15,6 +16,7 @@ using Spice86.MemoryWrappers; using Spice86.Shared.Utils; using Spice86.Views; +using Spice86.ViewModels.Messages; using System.Collections.Specialized; using System.ComponentModel; @@ -44,11 +46,7 @@ public partial class MemoryViewModel : ViewModelBaseWithErrorDialog, IInternalDe private bool _canCloseTab; [RelayCommand(CanExecute = nameof(CanCloseTab))] - private void CloseTab() { - _debugWindowViewModel.CloseTab(this); - UpdateCanCloseTabProperty(); - } - + private void CloseTab() => _messenger.Send(new RemoveViewModelMessage(this)); private bool GetIsMemoryRangeValid() { if (_memory is null) { return false; @@ -99,15 +97,16 @@ public uint? EndAddress { public bool IsStructureInfoPresent => _structureViewModelFactory.IsInitialized; - private readonly IPauseStatus _pauseStatus; + private readonly IMessenger _messenger; private readonly IHostStorageProvider _storageProvider; - public MemoryViewModel(DebugWindowViewModel debugWindowViewModel, ITextClipboard textClipboard, IUIDispatcherTimerFactory dispatcherTimerFactory, IHostStorageProvider storageProvider, IPauseStatus pauseStatus, uint startAddress, IStructureViewModelFactory structureViewModelFactory, uint endAddress = 0) : base(textClipboard) { + public MemoryViewModel(DebugWindowViewModel debugWindowViewModel, IMessenger messenger, ITextClipboard textClipboard, IUIDispatcherTimerFactory dispatcherTimerFactory, IHostStorageProvider storageProvider, bool isPaused, bool canCloseTab, uint startAddress, uint endAddress, IStructureViewModelFactory structureViewModelFactory) : base(textClipboard) { _debugWindowViewModel = debugWindowViewModel; - pauseStatus.PropertyChanged += PauseStatus_PropertyChanged; - _pauseStatus = pauseStatus; + _messenger = messenger; + IsPaused = isPaused; + CanCloseTab = canCloseTab; + _messenger.Register(this, HandlePauseStatusMessage); _storageProvider = storageProvider; - IsPaused = _pauseStatus.IsPaused; StartAddress = startAddress; _structureViewModelFactory = structureViewModelFactory; EndAddress = endAddress; @@ -161,21 +160,15 @@ private void UpdateValues(object? sender, EventArgs e) { _needToUpdateBinaryDocument = false; } - private void PauseStatus_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName != nameof(IPauseStatus.IsPaused)) { - return; - } - UpdateCanCloseTabProperty(); - IsPaused = _pauseStatus.IsPaused; + private void HandlePauseStatusMessage(object recipient, PauseStatusChangedMessage message) { + IsPaused = message.IsPaused; if (IsPaused) { _needToUpdateBinaryDocument = true; } } [RelayCommand(CanExecute = nameof(IsPaused))] - public void NewMemoryView() { - _debugWindowViewModel.NewMemoryViewCommand.Execute(null); - } + public void NewMemoryView() => _messenger.Send(new AddViewModelMessage()); [RelayCommand(CanExecute = nameof(IsMemoryRangeValid))] private void UpdateBinaryDocument() { @@ -187,9 +180,7 @@ private void UpdateBinaryDocument() { DataMemoryDocument.MemoryReadInvalidOperation += OnMemoryReadInvalidOperation; } - private void OnMemoryReadInvalidOperation(Exception exception) { - Dispatcher.UIThread.Post(() => ShowError(exception)); - } + private void OnMemoryReadInvalidOperation(Exception exception) => Dispatcher.UIThread.Post(() => ShowError(exception)); [RelayCommand(CanExecute = nameof(IsMemoryRangeValid))] private async Task DumpMemory() { diff --git a/src/Spice86/ViewModels/Messages/AddViewModelMessage.cs b/src/Spice86/ViewModels/Messages/AddViewModelMessage.cs new file mode 100644 index 0000000000..091912c4e4 --- /dev/null +++ b/src/Spice86/ViewModels/Messages/AddViewModelMessage.cs @@ -0,0 +1,4 @@ +namespace Spice86.ViewModels.Messages; +using Spice86.Core.Emulator.InternalDebugger; + +internal record AddViewModelMessage() where T : IInternalDebugger; diff --git a/src/Spice86/ViewModels/Messages/PauseStatusMessage.cs b/src/Spice86/ViewModels/Messages/PauseStatusMessage.cs new file mode 100644 index 0000000000..fffbc67905 --- /dev/null +++ b/src/Spice86/ViewModels/Messages/PauseStatusMessage.cs @@ -0,0 +1,3 @@ +namespace Spice86.ViewModels.Messages; + +internal record PauseStatusChangedMessage(bool IsPaused); \ No newline at end of file diff --git a/src/Spice86/ViewModels/Messages/RemoveViewModelMessage.cs b/src/Spice86/ViewModels/Messages/RemoveViewModelMessage.cs new file mode 100644 index 0000000000..90cb485f09 --- /dev/null +++ b/src/Spice86/ViewModels/Messages/RemoveViewModelMessage.cs @@ -0,0 +1,4 @@ +namespace Spice86.ViewModels.Messages; +using Spice86.Core.Emulator.InternalDebugger; + +internal record RemoveViewModelMessage(T Sender) where T : IInternalDebugger; diff --git a/src/Spice86/ViewModels/PaletteViewModel.cs b/src/Spice86/ViewModels/PaletteViewModel.cs index 92d2f9cfa2..e67ffba7b4 100644 --- a/src/Spice86/ViewModels/PaletteViewModel.cs +++ b/src/Spice86/ViewModels/PaletteViewModel.cs @@ -1,7 +1,6 @@ namespace Spice86.ViewModels; using Avalonia.Collections; -using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.Threading; diff --git a/src/Spice86/ViewModels/PerformanceViewModel.cs b/src/Spice86/ViewModels/PerformanceViewModel.cs index fea3d032aa..44cebb5dc4 100644 --- a/src/Spice86/ViewModels/PerformanceViewModel.cs +++ b/src/Spice86/ViewModels/PerformanceViewModel.cs @@ -1,35 +1,37 @@ namespace Spice86.ViewModels; -using Avalonia.Controls; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; using Spice86.Core.Emulator.CPU; using Spice86.Core.Emulator.InternalDebugger; using Spice86.Infrastructure; -using Spice86.Interfaces; using Spice86.Shared.Interfaces; +using Spice86.ViewModels.Messages; using System; public partial class PerformanceViewModel : ViewModelBase, IInternalDebugger { private State? _state; private readonly IPerformanceMeasurer _performanceMeasurer; - private readonly IPauseStatus _pauseStatus; + private readonly IMessenger _messenger; + private bool _isPaused; [ObservableProperty] private double _averageInstructionsPerSecond; - public PerformanceViewModel(IUIDispatcherTimerFactory uiDispatcherTimerFactory, IDebuggableComponent programExecutor, IPerformanceMeasurer performanceMeasurer, IPauseStatus pauseStatus) : base() { - _pauseStatus = pauseStatus; + public PerformanceViewModel(IMessenger messenger, IUIDispatcherTimerFactory uiDispatcherTimerFactory, IDebuggableComponent programExecutor, IPerformanceMeasurer performanceMeasurer) : base() { + _messenger = messenger; + _messenger.Register(this, (_, message) => _isPaused = message.IsPaused); programExecutor.Accept(this); _performanceMeasurer = performanceMeasurer; uiDispatcherTimerFactory.StartNew(TimeSpan.FromSeconds(1.0 / 30.0), DispatcherPriority.MaxValue, UpdatePerformanceInfo); } private void UpdatePerformanceInfo(object? sender, EventArgs e) { - if (_state is null || _pauseStatus.IsPaused) { + if (_state is null || _isPaused) { return; }