Debugger: Improved scrolling navigation for disassembly & trace log

This commit is contained in:
Sour 2022-01-05 00:11:08 -05:00
parent a0b9413946
commit bb2b08fffc
17 changed files with 392 additions and 177 deletions

View file

@ -128,8 +128,7 @@ vector<DisassemblyResult> Disassembler::Disassemble(CpuType cpuType, uint8_t ban
AddressInfo relAddress = {};
relAddress.Type = DebugUtilities::GetCpuMemoryType(cpuType);
int32_t maxBank = (_memoryDumper->GetMemorySize(relAddress.Type) - 1) >> 16;
if(bank > maxBank) {
if(bank > GetMaxBank(cpuType)) {
return results;
}
@ -494,11 +493,8 @@ CodeLineData Disassembler::GetLineData(DisassemblyResult& row, CpuType type, Sne
return data;
}
uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount)
int32_t Disassembler::GetMatchingRow(vector<DisassemblyResult>& rows, uint32_t address)
{
uint8_t bank = address >> 16;
vector<DisassemblyResult> rows = Disassemble(type, bank);
int32_t i;
for(i = 0; i < (int32_t)rows.size(); i++) {
if(rows[i].CpuAddress == (int32_t)address) {
@ -510,8 +506,16 @@ uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, Code
break;
}
}
return std::max(0, i);
}
i = std::max(0, i);
uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount)
{
uint16_t bank = address >> 16;
Timer timer;
vector<DisassemblyResult> rows = Disassemble(type, bank);
int32_t i = GetMatchingRow(rows, address);
if(i >= (int32_t)rows.size()) {
return 0;
@ -541,6 +545,70 @@ uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, Code
return row;
}
uint16_t Disassembler::GetMaxBank(CpuType cpuType)
{
AddressInfo relAddress = {};
relAddress.Type = DebugUtilities::GetCpuMemoryType(cpuType);
return (_memoryDumper->GetMemorySize(relAddress.Type) - 1) >> 16;
}
int32_t Disassembler::GetDisassemblyRowAddress(CpuType cpuType, uint32_t address, int32_t rowOffset)
{
uint16_t bank = address >> 16;
vector<DisassemblyResult> rows = Disassemble(cpuType, bank);
int32_t len = (int32_t)rows.size();
if(len == 0) {
return address;
}
uint16_t maxBank = GetMaxBank(cpuType);
int32_t i = GetMatchingRow(rows, address);
if(rowOffset > 0) {
while(len > 0) {
for(; i < len; i++) {
rowOffset--;
if(rowOffset <= 0 && rows[i].CpuAddress >= 0) {
return rows[i].CpuAddress;
}
}
//End of bank, didn't find an appropriate row to jump to, try the next bank
if(bank == maxBank) {
//Reached bottom of last bank, return the bottom row
return rows[len - 1].CpuAddress >= 0 ? rows[len - 1].CpuAddress : address;
}
bank++;
rows = Disassemble(cpuType, bank);
len = (int32_t)rows.size();
i = 0;
}
} else if(rowOffset < 0) {
while(len > 0) {
for(; i >= 0; i--) {
rowOffset++;
if(rowOffset >= 0 && rows[i].CpuAddress >= 0) {
return rows[i].CpuAddress;
}
}
//Start of bank, didn't find an appropriate row to jump to, try the previous bank
if(bank == 0) {
//Reached top of first bank, return the top row
return rows[0].CpuAddress >= 0 ? rows[0].CpuAddress : address;
}
bank--;
rows = Disassemble(cpuType, bank);
len = (int32_t)rows.size();
i = len - 1;
}
}
return address;
}
int32_t Disassembler::SearchDisassembly(CpuType type, const char *searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards)
{
//TODO

View file

@ -36,7 +36,9 @@ private:
DisassemblerSource& GetSource(SnesMemoryType type);
CodeLineData GetLineData(DisassemblyResult& result, CpuType type, SnesMemoryType memType);
int32_t GetMatchingRow(vector<DisassemblyResult>& rows, uint32_t address);
vector<DisassemblyResult> Disassemble(CpuType cpuType, uint8_t bank);
uint16_t GetMaxBank(CpuType cpuType);
public:
Disassembler(IConsole* console, Debugger* debugger);
@ -55,5 +57,6 @@ public:
}
uint32_t GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount);
int32_t GetDisassemblyRowAddress(CpuType type, uint32_t address, int32_t rowOffset);
int32_t SearchDisassembly(CpuType type, const char* searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards);
};

View file

@ -50,6 +50,7 @@ extern "C"
DllExport void __stdcall Step(CpuType cpuType, uint32_t count, StepType type) { GetDebugger()->Step(cpuType, count, type); }
DllExport uint32_t __stdcall GetDisassemblyOutput(CpuType type, uint32_t lineIndex, CodeLineData output[], uint32_t rowCount) { return GetDebugger()->GetDisassembler()->GetDisassemblyOutput(type, lineIndex, output, rowCount); }
DllExport uint32_t __stdcall GetDisassemblyRowAddress(CpuType type, uint32_t address, int32_t rowOffset) { return GetDebugger()->GetDisassembler()->GetDisassemblyRowAddress(type, address, rowOffset); }
DllExport int32_t __stdcall SearchDisassembly(CpuType type, const char* searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards) { return GetDebugger()->GetDisassembler()->SearchDisassembly(type, searchString, startPosition, endPosition, searchBackwards); }
DllExport void __stdcall SetTraceOptions(CpuType type, TraceLoggerOptions options) { GetDebugger()->GetTraceLogger(type)->SetOptions(options); }

View file

@ -117,8 +117,8 @@ namespace Mesen.Debugger
EndAddress = (UInt32)info.Address
};
if(info.Type != SnesMemoryType.PrgRom) {
//TODO
if(!info.Type.SupportsCdl() || info.Type.IsRelativeMemory()) {
//Turn on break on read/write for non-ROM types only
breakpoint.BreakOnRead = true;
breakpoint.BreakOnWrite = true;
}

View file

@ -12,9 +12,8 @@ namespace Mesen.Debugger.Controls
{
public class DisassemblyViewer : Control
{
public static readonly StyledProperty<ICodeDataProvider> DataProviderProperty = AvaloniaProperty.Register<DisassemblyViewer, ICodeDataProvider>(nameof(DataProvider));
public static readonly StyledProperty<CodeLineData[]> LinesProperty = AvaloniaProperty.Register<DisassemblyViewer, CodeLineData[]>(nameof(Lines));
public static readonly StyledProperty<ILineStyleProvider> StyleProviderProperty = AvaloniaProperty.Register<DisassemblyViewer, ILineStyleProvider>(nameof(StyleProviderProperty));
public static readonly StyledProperty<int> ScrollPositionProperty = AvaloniaProperty.Register<DisassemblyViewer, int>(nameof(ScrollPosition), 0, false, Avalonia.Data.BindingMode.TwoWay);
public static readonly StyledProperty<string> FontFamilyProperty = AvaloniaProperty.Register<DisassemblyViewer, string>(nameof(FontFamily), DebuggerConfig.MonospaceFontFamily);
public static readonly StyledProperty<float> FontSizeProperty = AvaloniaProperty.Register<DisassemblyViewer, float>(nameof(FontSize), DebuggerConfig.DefaultFontSize);
@ -24,10 +23,10 @@ namespace Mesen.Debugger.Controls
new Point(0, 5), new Point(8, 5), new Point(8, 0), new Point(15, 7), new Point(15, 8), new Point(8, 15), new Point(8, 10), new Point(0, 10),
}, true);
public ICodeDataProvider DataProvider
public CodeLineData[] Lines
{
get { return GetValue(DataProviderProperty); }
set { SetValue(DataProviderProperty, value); }
get { return GetValue(LinesProperty); }
set { SetValue(LinesProperty, value); }
}
public ILineStyleProvider StyleProvider
@ -36,12 +35,6 @@ namespace Mesen.Debugger.Controls
set { SetValue(StyleProviderProperty, value); }
}
public int ScrollPosition
{
get { return GetValue(ScrollPositionProperty); }
set { SetValue(ScrollPositionProperty, value); }
}
public string FontFamily
{
get { return GetValue(FontFamilyProperty); }
@ -63,42 +56,19 @@ namespace Mesen.Debugger.Controls
private Typeface Font { get; set; }
private Size LetterSize { get; set; }
private double RowHeight => this.LetterSize.Height;
private int VisibleRows => (int)(Bounds.Height / RowHeight) - 1;
public int GetVisibleRowCount()
{
InitFontAndLetterSize();
return (int)Math.Ceiling(Bounds.Height / RowHeight);
}
private bool _scrollByAddress = false;
private bool _updatingScroll = false;
public delegate void RowClickedEventHandler(DisassemblyViewer sender, RowClickedEventArgs args);
public event RowClickedEventHandler? RowClicked;
static DisassemblyViewer()
{
AffectsRender<DisassemblyViewer>(FontFamilyProperty, FontSizeProperty);
DataProviderProperty.Changed.AddClassHandler<DisassemblyViewer>((x, e) => {
x.Refresh();
x.InvalidateVisual();
});
ScrollPositionProperty.Changed.AddClassHandler<DisassemblyViewer>((x, e) => {
if(x._updatingScroll) {
return;
}
x.Refresh();
if(x._scrollByAddress) {
x._updatingScroll = true;
CodeLineData[] lines = x._lines;
if(e.OldValue is int oldValue && e.NewValue is int newValue && oldValue < newValue) {
foreach(CodeLineData line in lines) {
if(line.Address >= 0 && newValue < line.Address) {
x.ScrollPosition = line.Address;
break;
}
}
}
x._updatingScroll = false;
}
x.InvalidateVisual();
});
AffectsRender<DisassemblyViewer>(FontFamilyProperty, FontSizeProperty, StyleProviderProperty, ShowByteCodeProperty, LinesProperty);
}
public DisassemblyViewer()
@ -110,30 +80,15 @@ namespace Mesen.Debugger.Controls
{
base.OnPointerPressed(e);
PointerPoint p = e.GetCurrentPoint(this);
if(p.Position.X < 20) {
int rowNumber = (int)(p.Position.Y / LetterSize.Height);
CodeLineData[] lines = _lines;
if(rowNumber < lines.Length) {
CpuType cpuType = lines[rowNumber].CpuType;
AddressInfo relAddress = new AddressInfo() {
Address = lines[rowNumber].Address,
Type = cpuType.ToMemoryType()
};
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
BreakpointManager.ToggleBreakpoint(absAddress.Address < 0 ? relAddress : absAddress, cpuType);
}
int rowNumber = (int)(p.Position.Y / LetterSize.Height);
bool marginClicked = p.Position.X < 20;
if(rowNumber < Lines.Length) {
RowClicked?.Invoke(this, new RowClickedEventArgs(Lines[rowNumber], rowNumber, marginClicked, e.GetCurrentPoint(this).Properties));
}
InvalidateVisual();
}
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
ScrollPosition = Math.Max(0, Math.Min(ScrollPosition - (int)(e.Delta.Y * 3), DataProvider.GetLineCount() - 1));
}
private void InitFontAndLetterSize()
{
this.Font = new Typeface(new FontFamily(this.FontFamily));
@ -141,38 +96,17 @@ namespace Mesen.Debugger.Controls
this.LetterSize = text.Bounds.Size;
}
private CodeLineData[] _lines = new CodeLineData[0];
public void Refresh()
{
ICodeDataProvider dp = this.DataProvider;
int scrollPosition = ScrollPosition;
InitFontAndLetterSize();
_lines = dp?.GetCodeLines(scrollPosition, VisibleRows + 3) ?? new CodeLineData[0];
if(_scrollByAddress) {
foreach(CodeLineData line in _lines) {
if(line.Address >= 0) {
if(ScrollPosition != line.Address) {
_updatingScroll = true;
ScrollPosition = line.Address;
_updatingScroll = false;
}
break;
}
}
}
}
public override void Render(DrawingContext context)
{
if(DataProvider == null) {
CodeLineData[] lines = Lines;
if(lines.Length == 0) {
return;
}
CpuType cpuType = lines[0].CpuType;
InitFontAndLetterSize();
CodeLineData[] lines = _lines;
double y = 0;
var text = new FormattedText("", this.Font, this.FontSize, TextAlignment.Left, TextWrapping.NoWrap, Size.Empty);
var smallText = new FormattedText("", this.Font, this.FontSize - 2, TextAlignment.Left, TextWrapping.NoWrap, Size.Empty);
@ -181,10 +115,10 @@ namespace Mesen.Debugger.Controls
ILineStyleProvider styleProvider = this.StyleProvider;
string addrFormat = "X" + DataProvider.CpuType.GetAddressSize();
string addrFormat = "X" + cpuType.GetAddressSize();
double symbolMargin = 20;
double addressMargin = Math.Floor(LetterSize.Width * DataProvider.CpuType.GetAddressSize() + symbolMargin) + 0.5;
double byteCodeMargin = Math.Floor(LetterSize.Width * (3 * DataProvider.CpuType.GetByteCodeSize()));
double addressMargin = Math.Floor(LetterSize.Width * cpuType.GetAddressSize() + symbolMargin) + 0.5;
double byteCodeMargin = Math.Floor(LetterSize.Width * (3 * cpuType.GetByteCodeSize()));
double codeIndent = Math.Floor(LetterSize.Width * 2) + 0.5;
//Draw margin (address)
@ -295,12 +229,30 @@ namespace Mesen.Debugger.Controls
}
}
public class RowClickedEventArgs
{
public CodeLineData CodeLineData { get; private set; }
public int RowNumber { get; private set; }
public bool MarginClicked { get; private set; }
public PointerPointProperties Properties { get; private set; }
public RowClickedEventArgs(CodeLineData codeLineData, int rowNumber, bool marginClicked, PointerPointProperties properties)
{
this.CodeLineData = codeLineData;
this.RowNumber = rowNumber;
this.MarginClicked = marginClicked;
this.Properties = properties;
}
}
public interface ICodeDataProvider
{
CpuType CpuType { get; }
CodeLineData[] GetCodeLines(int address, int rowCount);
int GetRowAddress(int address, int rowOffset);
int GetLineCount();
int GetNextResult(string searchString, int startPosition, int endPosition, bool searchBackwards);
bool UseOptimizedSearch { get; }

View file

@ -1,10 +1,5 @@
using Mesen.Debugger.Controls;
using Mesen.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mesen.Debugger.Disassembly
{
@ -28,6 +23,11 @@ namespace Mesen.Debugger.Disassembly
return DebugApi.GetDisassemblyOutput(_type, (uint)startPosition, (uint)rowCount);
}
public int GetRowAddress(int address, int rowOffset)
{
return DebugApi.GetDisassemblyRowAddress(_type, (uint)address, rowOffset);
}
public bool UseOptimizedSearch { get { return true; } }
public int GetNextResult(string searchString, int startPosition, int endPosition, bool searchBackwards)

View file

@ -110,5 +110,10 @@ namespace Mesen.Debugger.Disassembly
{
throw new NotImplementedException();
}
public int GetRowAddress(int address, int rowOffset)
{
throw new NotImplementedException();
}
}
}

View file

@ -4,9 +4,9 @@ namespace Mesen.Debugger.ViewModels.DebuggerDock
{
public class DisassemblyToolViewModel : Tool
{
public DisassemblyViewerViewModel DisassemblyViewerViewModel { get; set; }
public DisassemblyViewModel DisassemblyViewerViewModel { get; set; }
public DisassemblyToolViewModel(DisassemblyViewerViewModel disassemblyViewerViewModel)
public DisassemblyToolViewModel(DisassemblyViewModel disassemblyViewerViewModel)
{
Id = "DiassemblyTool";
Title = "Disassembly";

View file

@ -24,7 +24,7 @@ namespace Mesen.Debugger.ViewModels
[Reactive] public DebuggerOptionsViewModel Options { get; private set; }
[Reactive] public DisassemblyViewerViewModel Disassembly { get; private set; }
[Reactive] public DisassemblyViewModel Disassembly { get; private set; }
[Reactive] public BreakpointListViewModel BreakpointList { get; private set; }
[Reactive] public WatchListViewModel WatchList { get; private set; }
[Reactive] public LabelListViewModel LabelList { get; private set; }
@ -48,7 +48,7 @@ namespace Mesen.Debugger.ViewModels
Options = new DebuggerOptionsViewModel(Config, CpuType);
Disassembly = new DisassemblyViewerViewModel(ConfigManager.Config.Debug);
Disassembly = new DisassemblyViewModel(ConfigManager.Config.Debug);
BreakpointList = new BreakpointListViewModel();
DockFactory = new DebuggerDockFactory(this);
@ -101,10 +101,8 @@ namespace Mesen.Debugger.ViewModels
internal void UpdateDisassembly()
{
//TODO
Disassembly.DataProvider = new CodeDataProvider(CpuType);
Disassembly.UpdateMaxScroll();
Disassembly.ScrollPosition = (Disassembly.StyleProvider?.ActiveAddress ?? 0);
Disassembly.SetActiveAddress(DebugUtilities.GetProgramCounter(CpuType));
}
public void UpdateCpuPpuState()
@ -114,9 +112,6 @@ namespace Mesen.Debugger.ViewModels
if(DockFactory.CpuStatusTool.StatusViewModel is SnesCpuViewModel snesCpuModel) {
CpuState state = DebugApi.GetCpuState<CpuState>(CpuType);
snesCpuModel.UpdateState(state);
if(Disassembly.StyleProvider != null) {
Disassembly.StyleProvider.ActiveAddress = (state.K << 16) | state.PC;
}
}
if(DockFactory.PpuStatusTool.StatusViewModel is SnesPpuViewModel snesPpuModel) {
@ -128,9 +123,6 @@ namespace Mesen.Debugger.ViewModels
if(DockFactory.CpuStatusTool.StatusViewModel is NesCpuViewModel nesCpuModel) {
NesCpuState state = DebugApi.GetCpuState<NesCpuState>(CpuType);
nesCpuModel.UpdateState(state);
if(Disassembly.StyleProvider != null) {
Disassembly.StyleProvider.ActiveAddress = state.PC;
}
}
if(DockFactory.PpuStatusTool.StatusViewModel is NesPpuViewModel nesPpuModel) {

View file

@ -0,0 +1,119 @@
using Mesen.Config;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Disassembly;
using Mesen.ViewModels;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Reactive;
namespace Mesen.Debugger.ViewModels
{
public class DisassemblyViewModel : ViewModelBase
{
[Reactive] public ICodeDataProvider? DataProvider { get; set; } = null;
[Reactive] public BaseStyleProvider StyleProvider { get; set; } = new BaseStyleProvider();
[Reactive] public int ScrollPosition { get; set; } = 0;
[Reactive] public int MaxScrollPosition { get; private set; } = 10000;
[Reactive] public int TopAddress { get; private set; } = 0;
[Reactive] public CodeLineData[] Lines { get; private set; } = Array.Empty<CodeLineData>();
public DebugConfig Config { get; private set; }
public int VisibleRowCount { get; set; } = 100;
private int _ignoreScrollUpdates = 0;
[Obsolete("For designer only")]
public DisassemblyViewModel(): this(new DebugConfig()) { }
public DisassemblyViewModel(DebugConfig config)
{
Config = config;
this.WhenAnyValue(x => x.DataProvider).Subscribe(x => Refresh());
this.WhenAnyValue(x => x.TopAddress).Subscribe(x => Refresh());
int lastValue = ScrollPosition;
this.WhenAnyValue(x => x.ScrollPosition).Subscribe(scrollPos => {
int gap = scrollPos - lastValue;
lastValue = scrollPos;
if(_ignoreScrollUpdates > 0) {
return;
}
if(gap != 0) {
if(Math.Abs(gap) <= 10) {
Scroll(gap);
} else {
int lineCount = DataProvider?.GetLineCount() ?? 0;
TopAddress = Math.Max(0, Math.Min(lineCount - 1, (int)((double)lineCount / MaxScrollPosition * ScrollPosition)));
}
}
});
}
public void Scroll(int lineNumberOffset)
{
ICodeDataProvider? dp = DataProvider;
if(dp == null) {
return;
}
SetTopAddress(dp.GetRowAddress(TopAddress, lineNumberOffset));
}
public void ScrollToTop()
{
SetTopAddress(0);
}
public void ScrollToBottom()
{
SetTopAddress((DataProvider?.GetLineCount() ?? 0) - 1);
}
public void Refresh()
{
ICodeDataProvider? dp = DataProvider;
if(dp == null) {
return;
}
CodeLineData[] lines = dp.GetCodeLines(TopAddress, VisibleRowCount);
Lines = lines;
if(lines.Length > 0 && lines[0].Address >= 0) {
SetTopAddress(lines[0].Address);
}
}
private void SetTopAddress(int address)
{
int lineCount = DataProvider?.GetLineCount() ?? 0;
address = Math.Max(0, Math.Min(lineCount - 1, address));
_ignoreScrollUpdates++;
TopAddress = address;
ScrollPosition = (int)(TopAddress / (double)lineCount * MaxScrollPosition);
_ignoreScrollUpdates--;
}
public void SetActiveAddress(uint pc)
{
StyleProvider.ActiveAddress = (int)pc;
for(int i = 1; i < VisibleRowCount - 2 && i < Lines.Length; i++) {
if(Lines[i].Address == (int)pc) {
//Row is already visible, don't scroll
return;
}
}
ICodeDataProvider? dp = DataProvider;
if(dp == null) {
return;
}
TopAddress = dp.GetRowAddress((int)pc, -VisibleRowCount / 2 + 1);
}
}
}

View file

@ -1,32 +0,0 @@
using Mesen.Config;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Disassembly;
using Mesen.ViewModels;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace Mesen.Debugger.ViewModels
{
public class DisassemblyViewerViewModel : ViewModelBase
{
[Reactive] public ICodeDataProvider? DataProvider { get; set; } = null;
[Reactive] public BaseStyleProvider? StyleProvider { get; set; } = null;
[Reactive] public int ScrollPosition { get; set; }
[Reactive] public int MaxScrollPosition { get; set; } = 0;
public DebugConfig Config { get; private set; }
//For designer
public DisassemblyViewerViewModel(): this(new DebugConfig()) { }
public DisassemblyViewerViewModel(DebugConfig config)
{
Config = config;
}
public void UpdateMaxScroll()
{
MaxScrollPosition = DataProvider?.GetLineCount() - 1 ?? 0;
}
}
}

View file

@ -16,10 +16,14 @@ namespace Mesen.Debugger.ViewModels
{
public class TraceLoggerViewModel : ViewModelBase
{
public const int TraceLogBufferSize = 30000;
[Reactive] public TraceLoggerCodeDataProvider? DataProvider { get; set; }
[Reactive] public TraceLoggerStyleProvider StyleProvider { get; set; } = new TraceLoggerStyleProvider();
[Reactive] public CodeLineData[] TraceLogLines { get; set; } = Array.Empty<CodeLineData>();
[Reactive] public int VisibleRowCount { get; set; } = 100;
[Reactive] public int ScrollPosition { get; set; } = 0;
[Reactive] public int MaxScrollPosition { get; set; } = 0;
[Reactive] public int MaxScrollPosition { get; set; } = TraceLoggerViewModel.TraceLogBufferSize;
[Reactive] public bool IsLoggingToFile { get; set; } = false;
[Reactive] public List<TraceLoggerOptionTab> Tabs { get; set; } = new List<TraceLoggerOptionTab>();
@ -32,6 +36,14 @@ namespace Mesen.Debugger.ViewModels
DataProvider = new TraceLoggerCodeDataProvider();
UpdateAvailableTabs();
this.WhenAnyValue(x => x.ScrollPosition).Subscribe(x => {
ScrollPosition = Math.Max(0, Math.Min(x, MaxScrollPosition));
UpdateTraceLogLines();
});
this.WhenAnyValue(x => x.MaxScrollPosition).Subscribe(x => {
ScrollPosition = Math.Min(ScrollPosition, MaxScrollPosition);
});
}
public void UpdateAvailableTabs()
@ -77,9 +89,30 @@ namespace Mesen.Debugger.ViewModels
DebugApi.SetTraceOptions(tab.CpuType, options);
}
DataProvider = new TraceLoggerCodeDataProvider();
MaxScrollPosition = DataProvider.GetLineCount();
ScrollPosition = MaxScrollPosition - 0x20;
UpdateTraceLogLines();
ScrollToBottom();
}
private void UpdateTraceLogLines()
{
if(DataProvider != null) {
TraceLogLines = DataProvider.GetCodeLines(ScrollPosition, VisibleRowCount);
}
}
public void Scroll(int offset)
{
ScrollPosition += offset;
}
public void ScrollToTop()
{
ScrollPosition = 0;
}
public void ScrollToBottom()
{
ScrollPosition = MaxScrollPosition;
}
}
@ -103,7 +136,7 @@ namespace Mesen.Debugger.ViewModels
public int GetLineCount()
{
return 30000;
return TraceLoggerViewModel.TraceLogBufferSize;
}
public int GetNextResult(string searchString, int startPosition, int endPosition, bool searchBackwards)
@ -113,7 +146,7 @@ namespace Mesen.Debugger.ViewModels
public CodeLineData[] GetCodeLines(int startIndex, int rowCount)
{
TraceRow[] rows = DebugApi.GetExecutionTrace((uint)(30000 - startIndex), (uint)rowCount);
TraceRow[] rows = DebugApi.GetExecutionTrace((uint)(TraceLoggerViewModel.TraceLogBufferSize - startIndex - rowCount), (uint)rowCount);
List<CodeLineData> lines = new(rowCount);
for(int i = 0; i < rows.Length; i++) {
@ -131,6 +164,11 @@ namespace Mesen.Debugger.ViewModels
return lines.ToArray();
}
public int GetRowAddress(int address, int rowOffset)
{
return Math.Max(0, Math.Min(GetLineCount() - 1, address + rowOffset));
}
}
public class TraceLoggerStyleProvider : ILineStyleProvider

View file

@ -8,32 +8,35 @@
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
x:DataType="vm:DisassemblyViewerViewModel"
x:DataType="vm:DisassemblyViewModel"
x:Class="Mesen.Debugger.Views.DisassemblyView"
>
<Design.DataContext>
<vm:DisassemblyViewerViewModel />
<vm:DisassemblyViewModel />
</Design.DataContext>
<DockPanel>
<ScrollBar
DockPanel.Dock="Right"
Orientation="Vertical"
Minimum="0"
Maximum="{CompiledBinding MaxScrollPosition}"
VerticalAlignment="Stretch"
AllowAutoHide="False"
SmallChange="1"
LargeChange="10"
Minimum="0"
Maximum="{CompiledBinding MaxScrollPosition}"
Value="{CompiledBinding ScrollPosition}"
/>
<dc:DisassemblyViewer
DockPanel.Dock="Left"
DataProvider="{CompiledBinding DataProvider}"
Name="disViewer"
Lines="{CompiledBinding Lines}"
StyleProvider="{CompiledBinding StyleProvider}"
ScrollPosition="{CompiledBinding ScrollPosition}"
FontFamily="{CompiledBinding Config.Font.FontFamily}"
FontSize="{CompiledBinding Config.Font.FontSize}"
ShowByteCode="{CompiledBinding Config.Debugger.ShowByteCode}"
RowClicked="Diassembly_RowClicked"
PointerWheelChanged="Disassembly_PointerWheelChanged"
/>
</DockPanel>
</UserControl>

View file

@ -1,19 +1,63 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Mesen.Debugger.Controls;
using Mesen.Debugger.ViewModels;
using Mesen.Interop;
namespace Mesen.Debugger.Views
{
public class DisassemblyView : UserControl
{
private DisassemblyViewModel Model => (DisassemblyViewModel)DataContext!;
static DisassemblyView()
{
BoundsProperty.Changed.AddClassHandler<DisassemblyView>((x, e) => {
DisassemblyViewer viewer = x.FindControl<DisassemblyViewer>("disViewer");
x.Model.VisibleRowCount = viewer.GetVisibleRowCount();
});
}
public DisassemblyView()
{
InitializeComponent();
Focusable = true;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void Diassembly_RowClicked(DisassemblyViewer sender, RowClickedEventArgs e)
{
if(e.MarginClicked && e.Properties.IsLeftButtonPressed) {
CpuType cpuType = e.CodeLineData.CpuType;
AddressInfo relAddress = new AddressInfo() {
Address = e.CodeLineData.Address,
Type = cpuType.ToMemoryType()
};
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
BreakpointManager.ToggleBreakpoint(absAddress.Address < 0 ? relAddress : absAddress, cpuType);
}
}
public void Disassembly_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
Model.Scroll((int)(-e.Delta.Y * 3));
}
protected override void OnKeyDown(KeyEventArgs e)
{
switch(e.Key) {
case Key.PageDown: Model.Scroll(Model.VisibleRowCount - 2); e.Handled = true; break;
case Key.PageUp: Model.Scroll(-(Model.VisibleRowCount - 2)); e.Handled = true; break;
case Key.Home: Model.ScrollToTop(); e.Handled = true; break;
case Key.End: Model.ScrollToBottom(); e.Handled = true; break;
}
}
}
}

View file

@ -65,17 +65,18 @@
<ScrollBar
DockPanel.Dock="Right"
Orientation="Vertical"
Minimum="0"
Maximum="{CompiledBinding MaxScrollPosition}"
VerticalAlignment="Stretch"
AllowAutoHide="False"
Minimum="0"
Maximum="{CompiledBinding MaxScrollPosition}"
Value="{CompiledBinding ScrollPosition}"
/>
<dc:DisassemblyViewer
DataProvider="{CompiledBinding DataProvider}"
Name="disViewer"
Lines="{CompiledBinding TraceLogLines}"
StyleProvider="{CompiledBinding StyleProvider}"
ScrollPosition="{CompiledBinding ScrollPosition}"
PointerWheelChanged="Disassembly_PointerWheelChanged"
/>
</DockPanel>
</Window>

View file

@ -1,14 +1,15 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Mesen.Config;
using Mesen.Debugger.Controls;
using Mesen.Debugger.ViewModels;
using Mesen.Interop;
using Mesen.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Mesen.Debugger.Windows
@ -19,11 +20,20 @@ namespace Mesen.Debugger.Windows
private TraceLoggerViewModel Model => ((TraceLoggerViewModel)DataContext!);
private int _refreshCounter = 0;
static TraceLoggerWindow()
{
BoundsProperty.Changed.AddClassHandler<TraceLoggerWindow>((x, e) => {
DisassemblyViewer viewer = x.FindControl<DisassemblyViewer>("disViewer");
x.Model.VisibleRowCount = viewer.GetVisibleRowCount() - 1;
x.Model.MaxScrollPosition = TraceLoggerViewModel.TraceLogBufferSize - x.Model.VisibleRowCount;
});
}
public TraceLoggerWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
this.AttachDevTools();
#endif
}
@ -43,6 +53,8 @@ namespace Mesen.Debugger.Windows
{
base.OnClosing(e);
_listener?.Dispose();
DebugApi.StopLogTraceToFile();
Model.SaveConfig();
DataContext = null;
}
@ -55,17 +67,17 @@ namespace Mesen.Debugger.Windows
case ConsoleNotificationType.CodeBreak: {
Dispatcher.UIThread.Post(() => {
Model.UpdateLog();
Model?.UpdateLog();
});
break;
}
case ConsoleNotificationType.PpuFrameDone: {
_refreshCounter++;
if(_refreshCounter == 9) {
//Refresh every 9 frames, ~7fps
if(_refreshCounter == 6) {
//Refresh every 6 frames, 10fps
Dispatcher.UIThread.Post(() => {
Model.UpdateLog();
Model?.UpdateLog();
});
_refreshCounter = 0;
}
@ -89,11 +101,19 @@ namespace Mesen.Debugger.Windows
DebugApi.StopLogTraceToFile();
}
protected override void OnClosed(EventArgs e)
public void Disassembly_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
base.OnClosed(e);
DebugApi.StopLogTraceToFile();
Model.SaveConfig();
Model.Scroll((int)(-e.Delta.Y * 3));
}
protected override void OnKeyDown(KeyEventArgs e)
{
switch(e.Key) {
case Key.PageDown: Model.Scroll(Model.VisibleRowCount - 2); e.Handled = true; break;
case Key.PageUp: Model.Scroll(-(Model.VisibleRowCount - 2)); e.Handled = true; break;
case Key.Home: Model.ScrollToTop(); e.Handled = true; break;
case Key.End: Model.ScrollToBottom(); e.Handled = true; break;
}
}
private void InitializeComponent()

View file

@ -63,6 +63,7 @@ namespace Mesen.Interop
return result;
}
[DllImport(DllPath)] public static extern int GetDisassemblyRowAddress(CpuType type, UInt32 address, int rowOffset);
[DllImport(DllPath)] public static extern int SearchDisassembly(CpuType type, [MarshalAs(UnmanagedType.LPUTF8Str)]string searchString, int startPosition, int endPosition, [MarshalAs(UnmanagedType.I1)]bool searchBackwards);
[DllImport(DllPath)] private static extern void GetCpuState(IntPtr state, CpuType cpuType);
@ -505,7 +506,7 @@ namespace Mesen.Interop
switch(memType) {
case SnesMemoryType.CpuMemory:
case SnesMemoryType.GameboyMemory:
case SnesMemoryType.NesMemory:
case SnesMemoryType.NesMemory:
case SnesMemoryType.PrgRom:
case SnesMemoryType.GbPrgRom:
case SnesMemoryType.NesPrgRom: