Skip to content

Commit

Permalink
Merge pull request #762 from OpenRakis/feature/debug_on_program_error
Browse files Browse the repository at this point in the history
Feature/debug on program error
  • Loading branch information
kevinferrare authored Jul 3, 2024
2 parents e33d222 + d0fd6f5 commit 68db988
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 68 deletions.
105 changes: 61 additions & 44 deletions src/Spice86/ViewModels/CfgCpuViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
namespace Spice86.ViewModels;

using Avalonia.Controls;
using Avalonia.Threading;

using AvaloniaGraphControl;
Expand All @@ -23,7 +22,7 @@ public partial class CfgCpuViewModel : ViewModelBase, IInternalDebugger {

[ObservableProperty]
private Graph? _graph;

[ObservableProperty]
private long _numberOfNodes = 0;

Expand All @@ -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<ICfgNode> queue = new();
queue.Enqueue(nodeRoot);
HashSet<ICfgNode> 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<ICfgNode> queue = new();
queue.Enqueue(nodeRoot);
HashSet<ICfgNode> 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>(T component) where T : IDebuggableComponent {
Expand Down
4 changes: 2 additions & 2 deletions src/Spice86/ViewModels/DebugWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -108,7 +108,7 @@ public void NewMemoryView() {
private void UpdateValues(object? sender, EventArgs e) => _programExecutor.Accept(this);

private IEnumerable<IInternalDebugger> InternalDebuggers => new IInternalDebugger[] {
PaletteViewModel, CpuViewModel, VideoCardViewModel, MidiViewModel, SoftwareMixerViewModel
PaletteViewModel, CpuViewModel, VideoCardViewModel, MidiViewModel, SoftwareMixerViewModel, CfgCpuViewModel
}
.Concat(DisassemblyViewModels)
.Concat(MemoryViewModels);
Expand Down
40 changes: 27 additions & 13 deletions src/Spice86/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}";
Expand Down Expand Up @@ -428,7 +442,7 @@ private void Dispose(bool disposing) {
}
}

private void DisposeEmulator() => _programExecutor?.Dispose();
private void DisposeEmulator() => ProgramExecutor?.Dispose();

private bool _isInitLogLevelSet;

Expand Down Expand Up @@ -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));
}
Expand Down
16 changes: 8 additions & 8 deletions src/Spice86/Views/CfgCpuView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
<Design.DataContext>
<viewModels:CfgCpuViewModel />
</Design.DataContext>
<ScrollViewer>
<Grid RowDefinitions="Auto, *">
<UniformGrid Grid.Row="0">
<DockPanel>
<UniformGrid DockPanel.Dock="Top">
<StackPanel Orientation="Vertical">
<Label>Number of nodes</Label>
<TextBlock Text="{Binding NumberOfNodes}" />
Expand All @@ -27,15 +26,16 @@
<TextBlock Text="{Binding AverageNodeTime}" />
</StackPanel>
</UniformGrid>
<Border Grid.Row="1" Background="White">
<agc:GraphPanel Graph="{Binding Graph}" LayoutMethod="SugiyamaScheme">
<Border DockPanel.Dock="Bottom" Background="White">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<agc:GraphPanel Graph="{Binding Graph}" LayoutMethod="SugiyamaScheme">
<agc:GraphPanel.DataTemplates>
<DataTemplate DataType="{x:Type models:CfgNodeInfo}">
<agc:TextSticker Padding="30,10" Shape="RoundedRectangle" Text="{Binding Address}" />
</DataTemplate>
</agc:GraphPanel.DataTemplates>
</agc:GraphPanel>
</Border>
</Grid>
</ScrollViewer>
</ScrollViewer>
</Border>
</DockPanel>
</UserControl>
7 changes: 6 additions & 1 deletion src/Spice86/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
<TextBlock Margin="5,0,0,0" Text="{Binding Configuration.GdbPort, StringFormat='GDB port: {0}'}" IsVisible="{Binding Configuration.GdbPort, Converter={StaticResource CountToBooleanConverter}, ConverterParameter=0}" />
</WrapPanel>
</Border>
<userControls:ErrorModalDialogUserControl Grid.Row="0" Grid.RowSpan="3" IsVisible="{Binding IsDialogVisible, FallbackValue=False}" />
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0" Grid.RowSpan="3" IsVisible="{Binding IsDialogVisible, FallbackValue=False}">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
<userControls:ErrorModalDialogUserControl Grid.Row="0" Grid.Column="0" />
<Button Content="Debug..." Grid.Row="1" Grid.ColumnSpan="2" Command="{Binding ShowInternalDebuggerCommand}" />
</Grid>
</WrapPanel>
</Grid>
</Window>

0 comments on commit 68db988

Please sign in to comment.