diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs index d539a7cfd..51d65500a 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs @@ -59,6 +59,10 @@ public DosInt21Handler(IMemory memory, Cpu cpu, KeyboardInt16Handler keyboardInt FillDispatchTable(); } + public override void Run(int index) { + base.Run(index); + } + private void FillDispatchTable() { AddAction(0x00, QuitWithExitCode); AddAction(0x02, DisplayOutput); diff --git a/src/Spice86/ViewModels/BreakpointsViewModel.cs b/src/Spice86/ViewModels/BreakpointsViewModel.cs index bd2d66642..7b8a0002e 100644 --- a/src/Spice86/ViewModels/BreakpointsViewModel.cs +++ b/src/Spice86/ViewModels/BreakpointsViewModel.cs @@ -1,25 +1,123 @@ namespace Spice86.ViewModels; +using Avalonia.Threading; + using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; using Spice86.Core.Emulator.VM; using Spice86.Core.Emulator.VM.Breakpoint; +using Spice86.Infrastructure; +using Spice86.Messages; using Spice86.Models.Debugging; using System.Collections.ObjectModel; -public partial class BreakpointsViewModel : ViewModelBase { +public partial class BreakpointsViewModel : ViewModelWithErrorDialog { private readonly EmulatorBreakpointsManager _emulatorBreakpointsManager; - - public BreakpointsViewModel(EmulatorBreakpointsManager emulatorBreakpointsManager) { + private readonly IMessenger _messenger; + private readonly IPauseHandler _pauseHandler; + + public BreakpointsViewModel(IPauseHandler pauseHandler, + IMessenger messenger, + EmulatorBreakpointsManager emulatorBreakpointsManager, + IUIDispatcher uiDispatcher, + ITextClipboard textClipboard) : base(uiDispatcher, textClipboard) { _emulatorBreakpointsManager = emulatorBreakpointsManager; + _pauseHandler = pauseHandler; + _messenger = messenger; + } + + [ObservableProperty] + private bool _isExecutionBreakpointSelected; + + [ObservableProperty] + private bool _isMemoryBreakpointSelected; + + [ObservableProperty] + private bool _isCyclesBreakpointSelected; + + [ObservableProperty] + private bool _creatingBreakpoint; + + [RelayCommand] + private void BeginCreateBreakpoint() { + CreatingBreakpoint = true; + } + + [ObservableProperty] + private ulong? _cyclesValue; + + [ObservableProperty] + private uint? _executionAddressValue; + + [ObservableProperty] + private uint? _memoryAddressValue; + + [ObservableProperty] + private BreakPointType _selectedMemoryBreakpointType = BreakPointType.ACCESS; + + public BreakPointType[] MemoryBreakpointTypes => [BreakPointType.ACCESS, BreakPointType.WRITE, BreakPointType.READ]; + + [RelayCommand] + private void ConfirmBreakpointCreation() { + if (IsExecutionBreakpointSelected) { + if (ExecutionAddressValue is null) { + return; + } + uint executionValue = ExecutionAddressValue.Value; + BreakpointViewModel executionVm = AddAddressBreakpoint( + executionValue, + BreakPointType.EXECUTION, + false, + () => { + PauseAndReportAddress(executionValue); + }, "Execution breakpoint"); + BreakpointCreated?.Invoke(executionVm); + } else if (IsMemoryBreakpointSelected) { + if (MemoryAddressValue is null) { + return; + } + uint memValue = MemoryAddressValue.Value; + BreakpointViewModel memoryVm = AddAddressBreakpoint( + memValue, + SelectedMemoryBreakpointType, + false, + () => { + PauseAndReportAddress(memValue); + }, "Memory breakpoint"); + BreakpointCreated?.Invoke(memoryVm); + } else if (IsCyclesBreakpointSelected) { + if (CyclesValue is null) { + return; + } + } + CreatingBreakpoint = false; + } + + private void PauseAndReportAddress(long address) { + string message = $"Execution breakpoint was reached at address {address}."; + Pause(message); + } + + private void Pause(string message) { + _pauseHandler.RequestPause(message); + _uiDispatcher.Post(() => { + _messenger.Send(new StatusMessage(DateTime.Now, this, message)); + }); + } + + [RelayCommand] + private void CancelBreakpointCreation() { + CreatingBreakpoint = false; } public event Action? BreakpointDeleted; public event Action? BreakpointEnabled; + public event Action? BreakpointCreated; public event Action? BreakpointDisabled; - + [ObservableProperty] private ObservableCollection _breakpoints = new(); @@ -36,13 +134,13 @@ private void ToggleSelectedBreakpoint() { } private bool ToggleSelectedBreakpointCanExecute() => SelectedBreakpoint is not null; - + [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(RemoveBreakpointCommand))] [NotifyCanExecuteChangedFor(nameof(ToggleSelectedBreakpointCommand))] private BreakpointViewModel? _selectedBreakpoint; - internal void AddUnconditionalBreakpoint(Action onReached, bool removedOnTrigger) { + public void AddUnconditionalBreakpoint(Action onReached, bool removedOnTrigger) { _emulatorBreakpointsManager.ToggleBreakPoint( new UnconditionalBreakPoint( BreakPointType.EXECUTION, @@ -50,13 +148,13 @@ internal void AddUnconditionalBreakpoint(Action onReached, bool removedOnTrigger removedOnTrigger), on: true); } - internal BreakpointViewModel AddAddressBreakpoint( + public BreakpointViewModel AddAddressBreakpoint( uint address, BreakPointType type, bool isRemovedOnTrigger, Action onReached, string comment = "") { - var breakpointViewModel = new BreakpointViewModel( + var breakpointViewModel = new BreakpointViewModel( this, _emulatorBreakpointsManager, address, type, isRemovedOnTrigger, onReached, comment); @@ -65,7 +163,7 @@ internal BreakpointViewModel AddAddressBreakpoint( return breakpointViewModel; } - internal BreakpointViewModel? GetBreakpoint(CpuInstructionInfo instructionInfo) { + public BreakpointViewModel? GetBreakpoint(CpuInstructionInfo instructionInfo) { return Breakpoints.FirstOrDefault(x => x.IsFor(instructionInfo)); } @@ -77,7 +175,7 @@ private void RemoveBreakpoint() { DeleteBreakpoint(SelectedBreakpoint); } - internal void RemoveBreakpointInternal(BreakpointViewModel vm) { + public void RemoveBreakpointInternal(BreakpointViewModel vm) { DeleteBreakpoint(vm); } diff --git a/src/Spice86/ViewModels/CpuViewModel.cs b/src/Spice86/ViewModels/CpuViewModel.cs index a50a21648..e94085812 100644 --- a/src/Spice86/ViewModels/CpuViewModel.cs +++ b/src/Spice86/ViewModels/CpuViewModel.cs @@ -24,7 +24,7 @@ public partial class CpuViewModel : ViewModelBase { [ObservableProperty] private CpuFlagsInfo _flags = new(); - public CpuViewModel(State state, Stack stack, IMemory memory, IPauseHandler pauseHandler, IUIDispatcher uiDispatcher) { + public CpuViewModel(State state, IMemory memory, IPauseHandler pauseHandler, IUIDispatcher uiDispatcher) { _cpuState = state; _memory = memory; pauseHandler.Pausing += () => uiDispatcher.Post(() => _isPaused = true); diff --git a/src/Spice86/ViewModels/DebugWindowViewModel.cs b/src/Spice86/ViewModels/DebugWindowViewModel.cs index b41efca18..580eab841 100644 --- a/src/Spice86/ViewModels/DebugWindowViewModel.cs +++ b/src/Spice86/ViewModels/DebugWindowViewModel.cs @@ -24,6 +24,7 @@ public partial class DebugWindowViewModel : ViewModelBase, IRecipient>, IRecipient> { private readonly IMessenger _messenger; + private readonly IUIDispatcher _uiDispatcher; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ContinueCommand))] @@ -72,8 +73,9 @@ public DebugWindowViewModel(State cpuState, Stack stack, IMemory memory, Midi ex messenger.Register>(this); messenger.Register>(this); _messenger = messenger; - BreakpointsViewModel = new(emulatorBreakpointsManager); - StatusMessageViewModel = new(_messenger); + _uiDispatcher = uiDispatcher; + BreakpointsViewModel = new(pauseHandler, messenger, emulatorBreakpointsManager, uiDispatcher, textClipboard); + StatusMessageViewModel = new(_uiDispatcher, _messenger); _pauseHandler = pauseHandler; IsPaused = pauseHandler.IsPaused; pauseHandler.Pausing += () => uiDispatcher.Post(() => IsPaused = true); @@ -89,17 +91,35 @@ public DebugWindowViewModel(State cpuState, Stack stack, IMemory memory, Midi ex PaletteViewModel = new(argbPalette, uiDispatcher); SoftwareMixerViewModel = new(softwareMixer); VideoCardViewModel = new(vgaRenderer, videoState); - CpuViewModel = new(cpuState, stack, memory, pauseHandler, uiDispatcher); + CpuViewModel = new(cpuState, memory, pauseHandler, uiDispatcher); MidiViewModel = new(externalMidiDevice); - MemoryViewModels.Add(new(memory, BreakpointsViewModel, pauseHandler, messenger, uiDispatcher, textClipboard, storageProvider, structureViewModelFactory)); + MemoryViewModel mainMemoryViewModel = new(memory, + BreakpointsViewModel, pauseHandler, messenger, + uiDispatcher, textClipboard, storageProvider, structureViewModelFactory); + MemoryViewModel stackMemoryViewModel = new(memory, + BreakpointsViewModel, pauseHandler, messenger, + uiDispatcher, textClipboard, storageProvider, structureViewModelFactory, + canCloseTab: false, startAddress: stack.PhysicalAddress) { + Title = "CPU Stack Memory" + }; + pauseHandler.Pausing += () => UpdateStackMemoryViewModel(stackMemoryViewModel, stack); + MemoryViewModels.Add(mainMemoryViewModel); + MemoryViewModels.Add(stackMemoryViewModel); CfgCpuViewModel = new(executionContextManager, pauseHandler, new PerformanceMeasurer()); } + private void UpdateStackMemoryViewModel(MemoryViewModel stackMemoryViewModel, Stack stack) { + stackMemoryViewModel.StartAddress = stack.PhysicalAddress; + stackMemoryViewModel.EndAddress = A20Gate.EndOfHighMemoryArea; + } + [RelayCommand] - private void Pause() => _pauseHandler.RequestPause("Pause button pressed in debug window"); + private void Pause() => _uiDispatcher.Post(() => { + _pauseHandler.RequestPause("Pause button pressed in debug window"); + }); [RelayCommand(CanExecute = nameof(IsPaused))] - private void Continue() => _pauseHandler.Resume(); + private void Continue() => _uiDispatcher.Post(_pauseHandler.Resume); public void Receive(AddViewModelMessage message) => DisassemblyViewModels.Add(message.ViewModel); public void Receive(AddViewModelMessage message) => MemoryViewModels.Add(message.ViewModel); diff --git a/src/Spice86/ViewModels/DisassemblyViewModel.cs b/src/Spice86/ViewModels/DisassemblyViewModel.cs index 7185d9f96..42cd317dc 100644 --- a/src/Spice86/ViewModels/DisassemblyViewModel.cs +++ b/src/Spice86/ViewModels/DisassemblyViewModel.cs @@ -60,6 +60,7 @@ public DisassemblyViewModel( breakpointsViewModel.BreakpointDeleted += OnBreakPointUpdateFromBreakpointsViewModel; breakpointsViewModel.BreakpointDisabled += OnBreakPointUpdateFromBreakpointsViewModel; breakpointsViewModel.BreakpointEnabled += OnBreakPointUpdateFromBreakpointsViewModel; + breakpointsViewModel.BreakpointCreated += OnBreakPointUpdateFromBreakpointsViewModel; } private void OnBreakPointUpdateFromBreakpointsViewModel(BreakpointViewModel breakpointViewModel) { diff --git a/src/Spice86/ViewModels/MemoryViewModel.cs b/src/Spice86/ViewModels/MemoryViewModel.cs index e8ac36b21..2437f6b5e 100644 --- a/src/Spice86/ViewModels/MemoryViewModel.cs +++ b/src/Spice86/ViewModels/MemoryViewModel.cs @@ -50,6 +50,9 @@ public MemoryViewModel(IMemory memory, BreakpointsViewModel breakpointsViewModel TryUpdateHeaderAndMemoryDocument(); } + [ObservableProperty] + private string? _title; + public enum MemorySearchDataType { Binary, Ascii, diff --git a/src/Spice86/ViewModels/StatusMessageViewModel.cs b/src/Spice86/ViewModels/StatusMessageViewModel.cs index 0d713df04..0e394db1c 100644 --- a/src/Spice86/ViewModels/StatusMessageViewModel.cs +++ b/src/Spice86/ViewModels/StatusMessageViewModel.cs @@ -3,15 +3,28 @@ namespace Spice86.ViewModels; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Spice86.Infrastructure; using Spice86.Messages; public partial class StatusMessageViewModel : ViewModelBase, IRecipient { + private readonly IUIDispatcher _uiDispatcher; + [ObservableProperty] private StatusMessage? _message; - public StatusMessageViewModel(IMessenger messenger) => messenger.Register(this); + [ObservableProperty] + private bool _isVisible; + + public StatusMessageViewModel(IUIDispatcher dispatcher, IMessenger messenger) { + messenger.Register(this); + _uiDispatcher = dispatcher; + } public void Receive(StatusMessage message) { Message = message; + IsVisible = true; + Task.Delay(millisecondsDelay: 5000).ContinueWith(_ => { + _uiDispatcher.Post(() => IsVisible = false); + }); } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs b/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs index ea5c8fdc7..b6619539b 100644 --- a/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs +++ b/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs @@ -61,6 +61,7 @@ protected bool TryParseMemoryAddress(string? memoryAddress, [NotNullWhen(true)] return false; } + protected void ShowError(Exception e) { Exception = e.GetBaseException(); IsDialogVisible = true; diff --git a/src/Spice86/Views/BreakpointsView.axaml b/src/Spice86/Views/BreakpointsView.axaml index 023067ab0..79f192fde 100644 --- a/src/Spice86/Views/BreakpointsView.axaml +++ b/src/Spice86/Views/BreakpointsView.axaml @@ -2,7 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:viewModels="clr-namespace:Spice86.ViewModels" + xmlns:converters="clr-namespace:Spice86.Converters" + xmlns:controls="clr-namespace:Spice86.Controls;assembly=Spice86" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Spice86.Views.BreakpointsView" x:DataType="viewModels:BreakpointsViewModel"> @@ -12,6 +15,7 @@ +