Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: CPU Stack View #998

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
118 changes: 108 additions & 10 deletions src/Spice86/ViewModels/BreakpointsViewModel.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Comment on lines +91 to +95

Check notice

Code scanning / CodeQL

Nested 'if' statements can be combined Note

These 'if' statements can be combined.
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<BreakpointViewModel>? BreakpointDeleted;
public event Action<BreakpointViewModel>? BreakpointEnabled;
public event Action<BreakpointViewModel>? BreakpointCreated;
public event Action<BreakpointViewModel>? BreakpointDisabled;

[ObservableProperty]
private ObservableCollection<BreakpointViewModel> _breakpoints = new();

Expand All @@ -36,27 +134,27 @@
}

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,
(_) => onReached(),
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);
Expand All @@ -65,7 +163,7 @@
return breakpointViewModel;
}

internal BreakpointViewModel? GetBreakpoint(CpuInstructionInfo instructionInfo) {
public BreakpointViewModel? GetBreakpoint(CpuInstructionInfo instructionInfo) {
return Breakpoints.FirstOrDefault(x => x.IsFor(instructionInfo));
}

Expand All @@ -77,7 +175,7 @@
DeleteBreakpoint(SelectedBreakpoint);
}

internal void RemoveBreakpointInternal(BreakpointViewModel vm) {
public void RemoveBreakpointInternal(BreakpointViewModel vm) {
DeleteBreakpoint(vm);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Spice86/ViewModels/CpuViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 26 additions & 6 deletions src/Spice86/ViewModels/DebugWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public partial class DebugWindowViewModel : ViewModelBase,
IRecipient<RemoveViewModelMessage<DisassemblyViewModel>>, IRecipient<RemoveViewModelMessage<MemoryViewModel>> {

private readonly IMessenger _messenger;
private readonly IUIDispatcher _uiDispatcher;

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ContinueCommand))]
Expand Down Expand Up @@ -72,8 +73,9 @@ public DebugWindowViewModel(State cpuState, Stack stack, IMemory memory, Midi ex
messenger.Register<RemoveViewModelMessage<DisassemblyViewModel>>(this);
messenger.Register<RemoveViewModelMessage<MemoryViewModel>>(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);
Expand All @@ -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<DisassemblyViewModel> message) => DisassemblyViewModels.Add(message.ViewModel);
public void Receive(AddViewModelMessage<MemoryViewModel> message) => MemoryViewModels.Add(message.ViewModel);
Expand Down
1 change: 1 addition & 0 deletions src/Spice86/ViewModels/DisassemblyViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public DisassemblyViewModel(
breakpointsViewModel.BreakpointDeleted += OnBreakPointUpdateFromBreakpointsViewModel;
breakpointsViewModel.BreakpointDisabled += OnBreakPointUpdateFromBreakpointsViewModel;
breakpointsViewModel.BreakpointEnabled += OnBreakPointUpdateFromBreakpointsViewModel;
breakpointsViewModel.BreakpointCreated += OnBreakPointUpdateFromBreakpointsViewModel;
}

private void OnBreakPointUpdateFromBreakpointsViewModel(BreakpointViewModel breakpointViewModel) {
Expand Down
3 changes: 3 additions & 0 deletions src/Spice86/ViewModels/MemoryViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public MemoryViewModel(IMemory memory, BreakpointsViewModel breakpointsViewModel
TryUpdateHeaderAndMemoryDocument();
}

[ObservableProperty]
private string? _title;

public enum MemorySearchDataType {
Binary,
Ascii,
Expand Down
15 changes: 14 additions & 1 deletion src/Spice86/ViewModels/StatusMessageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<StatusMessage> {
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);
});
}
}
1 change: 1 addition & 0 deletions src/Spice86/ViewModels/ViewModelWithErrorDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ protected bool TryParseMemoryAddress(string? memoryAddress, [NotNullWhen(true)]

return false;
}

protected void ShowError(Exception e) {
Exception = e.GetBaseException();
IsDialogVisible = true;
Expand Down
49 changes: 49 additions & 0 deletions src/Spice86/Views/BreakpointsView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -12,6 +15,7 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="New..." Command="{Binding BeginCreateBreakpointCommand}" Margin="5"/>
<Button Content="Remove" Command="{Binding RemoveBreakpointCommand}" Margin="5"/>
<Button HotKey="Ctrl+F9" ToolTip.Tip="Ctrl-F9" Content="Toggle" Command="{Binding ToggleSelectedBreakpointCommand}" Margin="5"/>
</StackPanel>
Expand All @@ -31,5 +35,50 @@
<DataGridTextColumn IsReadOnly="False" Binding="{Binding Comment}" Header="Comment" />
</DataGrid.Columns>
</DataGrid>
<Rectangle Fill="{Binding $parent[Window].Background}" Opacity="0.5" Grid.Row="0" Grid.RowSpan="2"
IsVisible="{Binding CreatingBreakpoint}"/>
<dialogHost:DialogHost
Grid.Row="0" Grid.RowSpan="2"
Background="{Binding $parent[Window].Background}"
CloseOnClickAway="False"
IsOpen="{Binding CreatingBreakpoint}">
<dialogHost:DialogHost.DialogContent>
<Grid RowDefinitions="*,Auto" MinWidth="320" MinHeight="200">
<TabControl Grid.Row="0">
<TabItem Header="Cycles"
IsSelected="{Binding IsCyclesBreakpointSelected}">
<NumericUpDown Text="{Binding CyclesValue}" Margin="5,0,0,0" />
</TabItem>
<TabItem Header="Memory"
IsSelected="{Binding IsMemoryBreakpointSelected}">
<Grid ColumnDefinitions="Auto,*">
<controls:GroupBox Grid.Column="0" Header="Memory access type">
<ComboBox SelectedItem="{Binding SelectedMemoryBreakpointType}"
ItemsSource="{Binding MemoryBreakpointTypes}" />
</controls:GroupBox>
<NumericUpDown Grid.Column="1" Text="{Binding MemoryAddressValue}" Margin="5,0,0,0" />
</Grid>
</TabItem>
<TabItem Header="Execution"
IsSelected="{Binding IsExecutionBreakpointSelected}">
<NumericUpDown Text="{Binding ExecutionAddressValue}" Margin="5,0,0,0" />
</TabItem>
</TabControl>
<StackPanel
Grid.Row="1"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Margin="5"
Command="{Binding ConfirmBreakpointCreationCommand}"
Content="OK"
IsDefault="True" />
<Button Margin="5"
Command="{Binding CancelBreakpointCreationCommand}"
Content="Cancel"
IsCancel="True" />
</StackPanel>
</Grid>
</dialogHost:DialogHost.DialogContent>
</dialogHost:DialogHost>
</Grid>
</UserControl>
Loading
Loading