From 3edb5acccab983b08a181356c75177a635ad3c0b Mon Sep 17 00:00:00 2001 From: TryTwo Date: Mon, 24 Jun 2024 13:57:33 -0700 Subject: [PATCH] MemoryViewWidget: Refactor updates using a dispatch function. Isolate memory reads from table updates. Preparations for auto update while a game is running. --- .../DolphinQt/Debugger/MemoryViewWidget.cpp | 186 ++++++++++++------ .../DolphinQt/Debugger/MemoryViewWidget.h | 16 +- .../Core/DolphinQt/Debugger/MemoryWidget.cpp | 2 +- 3 files changed, 140 insertions(+), 64 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 333bb46496..abe179e96e 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -99,7 +99,10 @@ public: void resizeEvent(QResizeEvent* event) override { QTableWidget::resizeEvent(event); - m_view->CreateTable(); + // Remakes table if vertically resized + const int rows = std::round((height() / static_cast(rowHeight(0))) - 0.25); + if (rows != rowCount()) + m_view->UpdateDispatcher(MemoryViewWidget::UpdateType::Full); } void keyPressEvent(QKeyEvent* event) override @@ -108,24 +111,21 @@ public: { case Qt::Key_Up: m_view->m_address -= m_view->m_bytes_per_row; - m_view->Update(); - return; + break; case Qt::Key_Down: m_view->m_address += m_view->m_bytes_per_row; - m_view->Update(); - return; + break; case Qt::Key_PageUp: m_view->m_address -= this->rowCount() * m_view->m_bytes_per_row; - m_view->Update(); - return; + break; case Qt::Key_PageDown: m_view->m_address += this->rowCount() * m_view->m_bytes_per_row; - m_view->Update(); - return; + break; default: QWidget::keyPressEvent(event); - break; + return; } + m_view->UpdateDispatcher(MemoryViewWidget::UpdateType::Addresses); } void wheelEvent(QWheelEvent* event) override @@ -137,7 +137,7 @@ public: return; m_view->m_address += delta * m_view->m_bytes_per_row; - m_view->Update(); + m_view->UpdateDispatcher(MemoryViewWidget::UpdateType::Addresses); } void mousePressEvent(QMouseEvent* event) override @@ -153,7 +153,6 @@ public: { const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt(); m_view->ToggleBreakpoint(address, true); - m_view->Update(); } else { @@ -181,7 +180,8 @@ public: accessors->WriteU8(guard, address++, c); } } - m_view->Update(); + + m_view->UpdateDispatcher(MemoryViewWidget::UpdateType::Values); } private: @@ -213,13 +213,25 @@ MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent) this->setLayout(layout); connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont); - connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, - qOverload<>(&MemoryViewWidget::UpdateColumns)); connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this, - qOverload<>(&MemoryViewWidget::Update)); - connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, - qOverload<>(&MemoryViewWidget::UpdateColumns)); - connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update); + &MemoryViewWidget::UpdateBreakpointTags); + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { + // UpdateDisasmDialog currently catches pauses, no need to signal it twice. + if (Core::GetState(m_system) != Core::State::Paused) + UpdateDispatcher(UpdateType::Values); + }); + connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, [this] { + // Disasm spam will break updates while running. Only need it for things like steps when paused + // and breaks which trigger a pause. + if (Core::GetState(m_system) != Core::State::Running) + UpdateDispatcher(UpdateType::Values); + }); + + // CPU Thread to Main Thread. + connect(this, &MemoryViewWidget::AutoUpdate, this, + [this] { UpdateDispatcher(UpdateType::Auto); }); + connect(&Settings::Instance(), &Settings::ThemeChanged, this, + [this] { UpdateDispatcher(UpdateType::Full); }); // Also calls create table. UpdateFont(Settings::Instance().GetDebugFont()); @@ -235,7 +247,7 @@ void MemoryViewWidget::UpdateFont(const QFont& font) m_font_width = fm.horizontalAdvance(QLatin1Char('0')); m_table->setFont(font); - CreateTable(); + UpdateDispatcher(UpdateType::Full); } constexpr int GetTypeSize(MemoryViewWidget::Type type) @@ -297,6 +309,33 @@ constexpr int GetCharacterCount(MemoryViewWidget::Type type) } } +void MemoryViewWidget::UpdateDispatcher(UpdateType type) +{ + if (!isVisible()) + return; + + // Check if table needs to be created. + if (m_table->item(2, 1) == nullptr) + type = UpdateType::Full; + + switch (type) + { + case UpdateType::Full: + CreateTable(); + [[fallthrough]]; + case UpdateType::Addresses: + Update(); + [[fallthrough]]; + case UpdateType::Values: + if (Core::GetState(m_system) == Core::State::Paused) + GetValues(); + UpdateColumns(); + break; + default: + break; + } +} + void MemoryViewWidget::CreateTable() { m_table->clearContents(); @@ -401,16 +440,10 @@ void MemoryViewWidget::CreateTable() const int width = m_font_width * GetCharacterCount(m_type); for (int i = start_fill; i < total_columns; i++) m_table->setColumnWidth(i, width); - - Update(); } void MemoryViewWidget::Update() { - // Check if table is created - if (m_table->item(1, 1) == nullptr) - return; - m_table->clearSelection(); // Update addresses @@ -418,6 +451,9 @@ void MemoryViewWidget::Update() u32 row_address = address - (m_table->rowCount() / 2) * m_bytes_per_row; const int data_span = m_bytes_per_row / GetTypeSize(m_type); + m_address_range.first = row_address; + m_address_range.second = row_address + m_table->rowCount() * m_bytes_per_row - 1; + for (int i = 0; i < m_table->rowCount(); i++, row_address += m_bytes_per_row) { auto* bp_item = m_table->item(i, 0); @@ -441,58 +477,84 @@ void MemoryViewWidget::Update() } } - UpdateColumns(); UpdateBreakpointTags(); - - m_table->viewport()->update(); - m_table->update(); - update(); } void MemoryViewWidget::UpdateColumns() { - if (!isVisible()) - return; - - // Check if table is created - if (m_table->item(1, 1) == nullptr) - return; - - if (Core::GetState(m_system) == Core::State::Paused) - { - const Core::CPUThreadGuard guard(m_system); - UpdateColumns(&guard); - } - else - { - // If the core is running, blank out the view of memory instead of reading anything. - UpdateColumns(nullptr); - } -} - -void MemoryViewWidget::UpdateColumns(const Core::CPUThreadGuard* guard) -{ - // Check if table is created - if (m_table->item(1, 1) == nullptr) - return; - for (int i = 0; i < m_table->rowCount(); i++) { for (int c = 0; c < m_data_columns; c++) { auto* cell_item = m_table->item(i, c + MISC_COLUMNS); + if (!cell_item) + return; + const u32 cell_address = cell_item->data(USER_ROLE_CELL_ADDRESS).toUInt(); const Type type = static_cast(cell_item->data(USER_ROLE_VALUE_TYPE).toInt()); + std::optional new_text; - cell_item->setText(guard ? ValueToString(*guard, cell_address, type) : INVALID_MEMORY); + // Dual view auto sets the type of the left-side based on m_type. Only time type and + // m_type differ. + if (type != m_type) + { + new_text = m_values_dual_view.empty() || !m_values_dual_view.contains(cell_address) ? + std::nullopt : + m_values_dual_view.at(cell_address); + } + else + { + new_text = m_values.empty() || !m_values.contains(cell_address) ? std::nullopt : + m_values.at(cell_address); + } // Set search address to selected / colored if (cell_address == m_address_highlight) cell_item->setSelected(true); + + // It gets a bit complicated, because invalid addresses becoming valid should not be + // colored. + if (!new_text.has_value()) + cell_item->setText(INVALID_MEMORY); + else + cell_item->setText(new_text.value()); } } } +void MemoryViewWidget::GetValues() +{ + m_values.clear(); + m_values_dual_view.clear(); + + // Check for dual view type + Type type = Type::Null; + + if (m_dual_view) + { + if (GetTypeSize(m_type) == 1) + type = Type::Hex8; + else if (GetTypeSize(m_type) == 2) + type = Type::Hex16; + else if (GetTypeSize(m_type) == 8) + type = Type::Hex64; + else + type = Type::Hex32; + } + + // Grab memory values as QStrings + Core::CPUThreadGuard guard(m_system); + + for (u32 address = m_address_range.first; address <= m_address_range.second; + address += GetTypeSize(m_type)) + { + m_values.insert(std::pair(address, ValueToString(guard, address, m_type))); + + if (m_dual_view) + m_values_dual_view.insert(std::pair(address, ValueToString(guard, address, type))); + } +} + // May only be called if we have taken on the role of the CPU thread QString MemoryViewWidget::ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type) { @@ -616,7 +678,7 @@ void MemoryViewWidget::SetAddressSpace(AddressSpace::Type address_space) } m_address_space = address_space; - Update(); + UpdateDispatcher(UpdateType::Addresses); } AddressSpace::Type MemoryViewWidget::GetAddressSpace() const @@ -787,7 +849,7 @@ void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, b else m_alignment = alignment; - CreateTable(); + UpdateDispatcher(UpdateType::Full); } void MemoryViewWidget::SetBPType(BPType type) @@ -803,7 +865,7 @@ void MemoryViewWidget::SetAddress(u32 address) m_address = address; - Update(); + UpdateDispatcher(UpdateType::Addresses); } void MemoryViewWidget::SetBPLoggingEnabled(bool enabled) @@ -926,7 +988,7 @@ void MemoryViewWidget::ScrollbarActionTriggered(int action) // User is currently dragging the scrollbar. // Adjust the memory view by the exact drag difference. m_address += difference * m_bytes_per_row; - Update(); + UpdateDispatcher(UpdateType::Addresses); } else { @@ -941,7 +1003,7 @@ void MemoryViewWidget::ScrollbarActionTriggered(int action) m_address += (difference < 0 ? -1 : 1) * m_bytes_per_row * m_table->rowCount(); } - Update(); + UpdateDispatcher(UpdateType::Addresses); // Manually reset the draggable part of the bar back to the center. m_scrollbar->setSliderPosition(SCROLLBAR_CENTER); } diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h index f8620472bc..584ce4fd42 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #include @@ -70,10 +71,20 @@ public: WriteOnly }; + enum class UpdateType + { + Full, + Addresses, + Values, + Auto, + }; + explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr); void CreateTable(); + void UpdateDispatcher(UpdateType type = UpdateType::Addresses); void Update(); + void GetValues(); void UpdateFont(const QFont& font); void ToggleBreakpoint(u32 addr, bool row); @@ -97,7 +108,6 @@ private: void OnCopyHex(u32 addr); void UpdateBreakpointTags(); void UpdateColumns(); - void UpdateColumns(const Core::CPUThreadGuard* guard); void ScrollbarActionTriggered(int action); void ScrollbarSliderReleased(); QString ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type); @@ -111,6 +121,9 @@ private: BPType m_bp_type = BPType::ReadWrite; bool m_do_log = true; u32 m_address = 0x80000000; + std::pair m_address_range; + std::map> m_values; + std::map> m_values_dual_view; u32 m_address_highlight = 0; int m_font_width = 0; int m_font_vspace = 0; @@ -118,6 +131,7 @@ private: int m_alignment = 16; int m_data_columns; bool m_dual_view = false; + QColor m_highlight_color = QColor(120, 255, 255, 100); friend class MemoryViewTable; }; diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp index 847124acda..200219e0b6 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp @@ -352,7 +352,7 @@ void MemoryWidget::Update() if (!isVisible()) return; - m_memory_view->Update(); + m_memory_view->UpdateDispatcher(MemoryViewWidget::UpdateType::Addresses); update(); }