diff --git a/Source/Core/Core/CheatSearch.cpp b/Source/Core/Core/CheatSearch.cpp index bf60485416..78f206ecfb 100644 --- a/Source/Core/Core/CheatSearch.cpp +++ b/Source/Core/Core/CheatSearch.cpp @@ -614,17 +614,15 @@ std::unique_ptr Cheats::CheatSearchSession::C template std::unique_ptr -Cheats::CheatSearchSession::ClonePartial(const std::vector& result_indices) const +Cheats::CheatSearchSession::ClonePartial(const size_t begin_index, const size_t end_index) const { - const auto& results = m_search_results; - std::vector> partial_results; - partial_results.reserve(result_indices.size()); - for (size_t idx : result_indices) - partial_results.push_back(results[idx]); + if (begin_index == 0 && end_index >= m_search_results.size()) + return Clone(); auto c = std::make_unique>(m_memory_ranges, m_address_space, m_aligned); - c->m_search_results = std::move(partial_results); + c->m_search_results.assign(m_search_results.begin() + begin_index, + m_search_results.begin() + end_index); c->m_compare_type = this->m_compare_type; c->m_filter_type = this->m_filter_type; c->m_value = this->m_value; diff --git a/Source/Core/Core/CheatSearch.h b/Source/Core/Core/CheatSearch.h index 696347fcf3..5f72a0a9a0 100644 --- a/Source/Core/Core/CheatSearch.h +++ b/Source/Core/Core/CheatSearch.h @@ -165,11 +165,11 @@ public: // Create a complete copy of this search session. virtual std::unique_ptr Clone() const = 0; - // Create a partial copy of this search session. Only the results with the passed indices are - // copied. This is useful if you want to run a next search on only partial result data of a + // Create a partial copy of this search session. Only the results with indices in the given range + // are copied. This is useful if you want to run a next search on only partial result data of a // previous search. - virtual std::unique_ptr - ClonePartial(const std::vector& result_indices) const = 0; + virtual std::unique_ptr ClonePartial(size_t begin_index, + size_t end_index) const = 0; }; template @@ -210,8 +210,8 @@ public: bool WasFirstSearchDone() const override; std::unique_ptr Clone() const override; - std::unique_ptr - ClonePartial(const std::vector& result_indices) const override; + std::unique_ptr ClonePartial(size_t begin_index, + size_t end_index) const override; private: std::vector> m_search_results; diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index 68b6564f86..c89ac88f84 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -33,6 +33,7 @@ #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" namespace VideoInterface { @@ -839,6 +840,7 @@ void VideoInterfaceManager::EndField(FieldType field, u64 ticks) OutputField(field, ticks); g_perf_metrics.CountVBlank(); + VIEndFieldEvent::Trigger(); Core::OnFrameEnd(); } diff --git a/Source/Core/DolphinQt/CheatSearchWidget.cpp b/Source/Core/DolphinQt/CheatSearchWidget.cpp index 1f9d226601..d588c078b8 100644 --- a/Source/Core/DolphinQt/CheatSearchWidget.cpp +++ b/Source/Core/DolphinQt/CheatSearchWidget.cpp @@ -61,7 +61,7 @@ CheatSearchWidget::CheatSearchWidget(std::unique_ptrisChecked()); + settings.setValue(QStringLiteral("cheatsearchwidget/autoupdatecurrentvalues"), + m_autoupdate_current_values->isChecked()); + if (m_session->IsIntegerType()) { settings.setValue(QStringLiteral("cheatsearchwidget/parsehex"), @@ -229,16 +232,25 @@ void CheatSearchWidget::CreateWidgets() m_info_label_1 = new QLabel(tr("Waiting for first scan...")); m_info_label_2 = new QLabel(); + auto* const checkboxes_layout = new QHBoxLayout(); m_display_values_in_hex_checkbox = new QCheckBox(tr("Display values in Hex")); m_display_values_in_hex_checkbox->setChecked( settings.value(QStringLiteral("cheatsearchwidget/displayhex")).toBool()); + checkboxes_layout->addWidget(m_display_values_in_hex_checkbox); + checkboxes_layout->setStretchFactor(m_display_values_in_hex_checkbox, 1); + + m_autoupdate_current_values = new QCheckBox(tr("Automatically update Current Values")); + m_autoupdate_current_values->setChecked( + settings.value(QStringLiteral("cheatsearchwidget/autoupdatecurrentvalues"), true).toBool()); + checkboxes_layout->addWidget(m_autoupdate_current_values); + checkboxes_layout->setStretchFactor(m_autoupdate_current_values, 2); QVBoxLayout* layout = new QVBoxLayout(); layout->addWidget(session_info_label); layout->addWidget(instructions_label); layout->addLayout(value_layout); layout->addLayout(button_layout); - layout->addWidget(m_display_values_in_hex_checkbox); + layout->addLayout(checkboxes_layout); layout->addWidget(m_info_label_1); layout->addWidget(m_info_label_2); layout->addWidget(m_address_table); @@ -263,6 +275,7 @@ void CheatSearchWidget::ConnectWidgets() void CheatSearchWidget::OnNextScanClicked() { + Core::CPUThreadGuard guard(Core::System::GetInstance()); const bool had_old_results = m_session->WasFirstSearchDone(); const size_t old_count = m_session->GetResultCount(); @@ -288,8 +301,7 @@ void CheatSearchWidget::OnNextScanClicked() } } - const Cheats::SearchErrorCode error_code = [this] { - Core::CPUThreadGuard guard(Core::System::GetInstance()); + const Cheats::SearchErrorCode error_code = [this, &guard] { return m_session->RunSearch(guard); }(); @@ -347,7 +359,7 @@ void CheatSearchWidget::OnNextScanClicked() m_session->GetResultValueAsString(i, show_in_hex); } - UpdateGuiTable(); + RecreateGUITable(); } else { @@ -371,30 +383,13 @@ void CheatSearchWidget::OnNextScanClicked() } } -bool CheatSearchWidget::RefreshValues() +bool CheatSearchWidget::UpdateTableRows(const Core::CPUThreadGuard& guard, const size_t begin_index, + const size_t end_index, const UpdateSource source) { - const size_t result_count = m_session->GetResultCount(); - if (result_count == 0) - { - m_info_label_1->setText(tr("Cannot refresh without results.")); - return false; - } - - const bool too_many_results = result_count > TABLE_MAX_ROWS; - std::unique_ptr tmp; - if (too_many_results) - { - std::vector value_indices; - value_indices.reserve(TABLE_MAX_ROWS); - for (size_t i = 0; i < TABLE_MAX_ROWS; ++i) - value_indices.push_back(i); - tmp = m_session->ClonePartial(value_indices); - } - else - { - tmp = m_session->Clone(); - } + const bool update_status_text = source == UpdateSource::User; + std::unique_ptr tmp = + m_session->ClonePartial(begin_index, end_index); tmp->SetFilterType(Cheats::FilterType::DoNotFilter); const Cheats::SearchErrorCode error_code = [&tmp] { @@ -404,11 +399,11 @@ bool CheatSearchWidget::RefreshValues() if (error_code != Cheats::SearchErrorCode::Success) { - m_info_label_1->setText(tr("Refresh failed. Please run the game for a bit and try again.")); + if (update_status_text) + m_info_label_1->setText(tr("Refresh failed. Please run the game for a bit and try again.")); return false; } - m_address_table_current_values.clear(); const bool show_in_hex = m_display_values_in_hex_checkbox->isChecked(); const size_t result_count_to_display = tmp->GetResultCount(); for (size_t i = 0; i < result_count_to_display; ++i) @@ -417,22 +412,53 @@ bool CheatSearchWidget::RefreshValues() tmp->GetResultValueAsString(i, show_in_hex); } - UpdateGuiTable(); - m_info_label_1->setText(tr("Refreshed current values.")); + RefreshGUICurrentValues(begin_index, end_index); + if (update_status_text) + m_info_label_1->setText(tr("Refreshed current values.")); return true; } +void CheatSearchWidget::UpdateTableVisibleCurrentValues(const UpdateSource source) +{ + if (source == UpdateSource::Auto && !m_autoupdate_current_values->isChecked()) + return; + + Core::CPUThreadGuard guard(Core::System::GetInstance()); + if (m_address_table->rowCount() == 0) + return; + + UpdateTableRows(guard, GetVisibleRowsBeginIndex(), GetVisibleRowsEndIndex(), source); +} + +bool CheatSearchWidget::UpdateTableAllCurrentValues(const UpdateSource source) +{ + if (source == UpdateSource::Auto && !m_autoupdate_current_values->isChecked()) + return false; + + Core::CPUThreadGuard guard(Core::System::GetInstance()); + const size_t result_count = m_address_table->rowCount(); + if (result_count == 0) + { + if (source == UpdateSource::User) + m_info_label_1->setText(tr("Cannot refresh without results.")); + return false; + } + + return UpdateTableRows(guard, 0, result_count, source); +} + void CheatSearchWidget::OnRefreshClicked() { - RefreshValues(); + UpdateTableAllCurrentValues(UpdateSource::User); } void CheatSearchWidget::OnResetClicked() { + Core::CPUThreadGuard guard(Core::System::GetInstance()); m_session->ResetResults(); m_address_table_current_values.clear(); - UpdateGuiTable(); + RecreateGUITable(); m_info_label_1->setText(tr("Waiting for first scan...")); m_info_label_2->clear(); } @@ -454,6 +480,18 @@ void CheatSearchWidget::OnAddressTableItemChanged(QTableWidgetItem* item) } } +int CheatSearchWidget::GetVisibleRowsBeginIndex() const +{ + return m_address_table->rowAt(0); +} + +int CheatSearchWidget::GetVisibleRowsEndIndex() const +{ + const int row_at_bottom_of_viewport = m_address_table->rowAt(m_address_table->height()); + const int end_index = m_address_table->rowCount(); + return row_at_bottom_of_viewport == -1 ? end_index : row_at_bottom_of_viewport + 1; +} + void CheatSearchWidget::OnAddressTableContextMenu() { if (m_address_table->selectedItems().isEmpty()) @@ -487,12 +525,14 @@ void CheatSearchWidget::OnDisplayHexCheckboxStateChanged() if (!m_session->WasFirstSearchDone()) return; - if (!RefreshValues()) - UpdateGuiTable(); + // If the game is running CheatsManager::OnFrameEnd will update values automatically. + if (Core::GetState() != Core::State::Running) + UpdateTableAllCurrentValues(UpdateSource::User); } void CheatSearchWidget::GenerateARCode() { + Core::CPUThreadGuard guard(Core::System::GetInstance()); if (m_address_table->selectedItems().isEmpty()) return; @@ -524,7 +564,28 @@ void CheatSearchWidget::GenerateARCode() } } -void CheatSearchWidget::UpdateGuiTable() +void CheatSearchWidget::RefreshCurrentValueTableItem( + QTableWidgetItem* const current_value_table_item) +{ + const auto address = current_value_table_item->data(ADDRESS_TABLE_ADDRESS_ROLE).toUInt(); + const auto curr_val_iter = m_address_table_current_values.find(address); + if (curr_val_iter != m_address_table_current_values.end()) + current_value_table_item->setText(QString::fromStdString(curr_val_iter->second)); + else + current_value_table_item->setText(QStringLiteral("---")); +} + +void CheatSearchWidget::RefreshGUICurrentValues(const size_t begin_index, const size_t end_index) +{ + for (size_t i = begin_index; i < end_index; ++i) + { + QTableWidgetItem* const current_value_table_item = + m_address_table->item(static_cast(i), ADDRESS_TABLE_COLUMN_INDEX_CURRENT_VALUE); + RefreshCurrentValueTableItem(current_value_table_item); + } +} + +void CheatSearchWidget::RecreateGUITable() { const QSignalBlocker blocker(m_address_table); @@ -570,13 +631,9 @@ void CheatSearchWidget::UpdateGuiTable() auto* current_value_item = new QTableWidgetItem(); current_value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - const auto curr_val_it = m_address_table_current_values.find(address); - if (curr_val_it != m_address_table_current_values.end()) - current_value_item->setText(QString::fromStdString(curr_val_it->second)); - else - current_value_item->setText(QStringLiteral("---")); current_value_item->setData(ADDRESS_TABLE_ADDRESS_ROLE, address); current_value_item->setData(ADDRESS_TABLE_RESULT_INDEX_ROLE, static_cast(i)); m_address_table->setItem(i, ADDRESS_TABLE_COLUMN_INDEX_CURRENT_VALUE, current_value_item); + RefreshCurrentValueTableItem(current_value_item); } } diff --git a/Source/Core/DolphinQt/CheatSearchWidget.h b/Source/Core/DolphinQt/CheatSearchWidget.h index e885f7a181..5c3ad52423 100644 --- a/Source/Core/DolphinQt/CheatSearchWidget.h +++ b/Source/Core/DolphinQt/CheatSearchWidget.h @@ -39,6 +39,15 @@ public: explicit CheatSearchWidget(std::unique_ptr session); ~CheatSearchWidget() override; + enum class UpdateSource + { + User, + Auto, + }; + + bool UpdateTableAllCurrentValues(UpdateSource source); + void UpdateTableVisibleCurrentValues(UpdateSource source); + signals: void ActionReplayCodeGenerated(const ActionReplay::ARCode& ar_code); void RequestWatch(QString name, u32 address); @@ -56,9 +65,14 @@ private: void OnValueSourceChanged(); void OnDisplayHexCheckboxStateChanged(); - bool RefreshValues(); - void UpdateGuiTable(); + void RefreshCurrentValueTableItem(QTableWidgetItem* current_value_table_item); + void RefreshGUICurrentValues(size_t begin_index, size_t end_index); + bool UpdateTableRows(const Core::CPUThreadGuard& guard, size_t begin_index, size_t end_index, + UpdateSource source); + void RecreateGUITable(); void GenerateARCode(); + int GetVisibleRowsBeginIndex() const; + int GetVisibleRowsEndIndex() const; std::unique_ptr m_session; @@ -79,5 +93,6 @@ private: QPushButton* m_reset_button; QCheckBox* m_parse_values_as_hex_checkbox; QCheckBox* m_display_values_in_hex_checkbox; + QCheckBox* m_autoupdate_current_values; QTableWidget* m_address_table; }; diff --git a/Source/Core/DolphinQt/CheatsManager.cpp b/Source/Core/DolphinQt/CheatsManager.cpp index 743126f574..0b80de7304 100644 --- a/Source/Core/DolphinQt/CheatsManager.cpp +++ b/Source/Core/DolphinQt/CheatsManager.cpp @@ -19,8 +19,10 @@ #include "DolphinQt/CheatSearchWidget.h" #include "DolphinQt/Config/ARCodeWidget.h" #include "DolphinQt/Config/GeckoCodeWidget.h" +#include "DolphinQt/QtUtils/PartiallyClosableTabWidget.h" #include "DolphinQt/Settings.h" -#include "QtUtils/PartiallyClosableTabWidget.h" + +#include "VideoCommon/VideoEvents.h" CheatsManager::CheatsManager(QWidget* parent) : QDialog(parent) { @@ -48,6 +50,52 @@ CheatsManager::~CheatsManager() void CheatsManager::OnStateChanged(Core::State state) { RefreshCodeTabs(state, false); + if (state == Core::State::Paused) + UpdateAllCheatSearchWidgetCurrentValues(); +} + +void CheatsManager::OnFrameEnd() +{ + if (!isVisible()) + return; + + auto* const selected_cheat_search_widget = + qobject_cast(m_tab_widget->currentWidget()); + if (selected_cheat_search_widget != nullptr) + { + selected_cheat_search_widget->UpdateTableVisibleCurrentValues( + CheatSearchWidget::UpdateSource::Auto); + } +} + +void CheatsManager::UpdateAllCheatSearchWidgetCurrentValues() +{ + for (int i = 0; i < m_tab_widget->count(); ++i) + { + auto* const cheat_search_widget = qobject_cast(m_tab_widget->widget(i)); + if (cheat_search_widget != nullptr) + cheat_search_widget->UpdateTableAllCurrentValues(CheatSearchWidget::UpdateSource::Auto); + } +} + +void CheatsManager::RegisterAfterFrameEventCallback() +{ + m_VI_end_field_event = VIEndFieldEvent::Register([this] { OnFrameEnd(); }, "CheatsManager"); +} + +void CheatsManager::RemoveAfterFrameEventCallback() +{ + m_VI_end_field_event.reset(); +} + +void CheatsManager::hideEvent(QHideEvent* event) +{ + RemoveAfterFrameEventCallback(); +} + +void CheatsManager::showEvent(QShowEvent* event) +{ + RegisterAfterFrameEventCallback(); } void CheatsManager::RefreshCodeTabs(Core::State state, bool force) diff --git a/Source/Core/DolphinQt/CheatsManager.h b/Source/Core/DolphinQt/CheatsManager.h index 5e7ddb8bb2..2692999bb7 100644 --- a/Source/Core/DolphinQt/CheatsManager.h +++ b/Source/Core/DolphinQt/CheatsManager.h @@ -11,14 +11,16 @@ #include #include "Common/CommonTypes.h" -#include "DolphinQt/GameList/GameListModel.h" - #include "Core/CheatSearch.h" +#include "DolphinQt/GameList/GameListModel.h" +#include "VideoCommon/VideoEvents.h" class ARCodeWidget; class GeckoCodeWidget; class CheatSearchFactoryWidget; class QDialogButtonBox; +class QHideEvent; +class QShowEvent; class PartiallyClosableTabWidget; namespace Core @@ -38,14 +40,22 @@ signals: void ShowMemory(u32 address); void RequestWatch(QString name, u32 address); +protected: + void hideEvent(QHideEvent* event) override; + void showEvent(QShowEvent* event) override; + private: void CreateWidgets(); void ConnectWidgets(); void OnStateChanged(Core::State state); + void OnFrameEnd(); + void RegisterAfterFrameEventCallback(); + void RemoveAfterFrameEventCallback(); void OnNewSessionCreated(const Cheats::CheatSearchSessionBase& session); void OnTabCloseRequested(int index); void RefreshCodeTabs(Core::State state, bool force); + void UpdateAllCheatSearchWidgetCurrentValues(); std::string m_game_id; std::string m_game_tdb_id; @@ -57,4 +67,6 @@ private: ARCodeWidget* m_ar_code = nullptr; GeckoCodeWidget* m_gecko_code = nullptr; CheatSearchFactoryWidget* m_cheat_search_new = nullptr; + + Common::EventHook m_VI_end_field_event; }; diff --git a/Source/Core/VideoCommon/VideoEvents.h b/Source/Core/VideoCommon/VideoEvents.h index c611925b2c..191db9802c 100644 --- a/Source/Core/VideoCommon/VideoEvents.h +++ b/Source/Core/VideoCommon/VideoEvents.h @@ -81,3 +81,6 @@ using BeforePresentEvent = Common::HookableEvent<"BeforePresent", PresentInfo&>; // An event that is triggered after a frame is presented. // The exact timing of this event depends on backend/driver support. using AfterPresentEvent = Common::HookableEvent<"AfterPresent", PresentInfo&>; + +// An end of frame event that runs on the CPU thread +using VIEndFieldEvent = Common::HookableEvent<"VIEndField">;