diff --git a/lib/libimhex/include/hex/api/content_registry/hashes.hpp b/lib/libimhex/include/hex/api/content_registry/hashes.hpp index 0e5d2107a..8bb42f39d 100644 --- a/lib/libimhex/include/hex/api/content_registry/hashes.hpp +++ b/lib/libimhex/include/hex/api/content_registry/hashes.hpp @@ -38,24 +38,14 @@ EXPORT_MODULE namespace hex { [[nodiscard]] const Hash *getType() const { return m_type; } [[nodiscard]] const std::string& getName() const { return m_name; } - const std::vector& get(const Region& region, prv::Provider *provider) { - if (m_cache.empty()) { - m_cache = m_callback(region, provider); - } - - return m_cache; - } - - void reset() { - m_cache.clear(); + std::vector get(const Region& region, prv::Provider *provider) const { + return m_callback(region, provider); } private: Hash *m_type; std::string m_name; Callback m_callback; - - std::vector m_cache; }; virtual void draw() { } diff --git a/lib/libimhex/include/hex/api/task_manager.hpp b/lib/libimhex/include/hex/api/task_manager.hpp index 60bfb27ab..30408d6fc 100644 --- a/lib/libimhex/include/hex/api/task_manager.hpp +++ b/lib/libimhex/include/hex/api/task_manager.hpp @@ -70,6 +70,8 @@ EXPORT_MODULE namespace hex { [[nodiscard]] u64 getValue() const; [[nodiscard]] u64 getMaxValue() const; + void wait() const; + private: void finish(); void interruption(); @@ -87,9 +89,9 @@ EXPORT_MODULE namespace hex { std::atomic m_background = true; std::atomic m_blocking = false; - std::atomic m_interrupted = false; - std::atomic m_finished = false; - std::atomic m_hadException = false; + std::atomic_flag m_interrupted = false; + std::atomic_flag m_finished = false; + std::atomic_flag m_hadException = false; std::string m_exceptionMessage; struct TaskInterruptor { virtual ~TaskInterruptor() = default; }; @@ -114,6 +116,7 @@ EXPORT_MODULE namespace hex { [[nodiscard]] u32 getProgress() const; void interrupt() const; + void wait() const; private: std::weak_ptr m_task; }; diff --git a/lib/libimhex/source/api/task_manager.cpp b/lib/libimhex/source/api/task_manager.cpp index b5b91709c..50f46b8cb 100644 --- a/lib/libimhex/source/api/task_manager.cpp +++ b/lib/libimhex/source/api/task_manager.cpp @@ -82,9 +82,17 @@ namespace hex { m_maxValue = u64(other.m_maxValue); m_currValue = u64(other.m_currValue); - m_finished = bool(other.m_finished); - m_hadException = bool(other.m_hadException); - m_interrupted = bool(other.m_interrupted); + if (other.m_finished.test()) + m_finished.test_and_set(); + if (other.m_hadException.test()) + m_hadException.test_and_set(); + if (other.m_interrupted.test()) + m_interrupted.test_and_set(); + + m_finished.notify_all(); + m_hadException.notify_all(); + m_interrupted.notify_all(); + m_shouldInterrupt = bool(other.m_shouldInterrupt); } @@ -143,11 +151,11 @@ namespace hex { bool Task::isFinished() const { - return m_finished; + return m_finished.test(); } bool Task::hadException() const { - return m_hadException; + return m_hadException.test(); } bool Task::shouldInterrupt() const { @@ -155,11 +163,11 @@ namespace hex { } bool Task::wasInterrupted() const { - return m_interrupted; + return m_interrupted.test(); } void Task::clearException() { - m_hadException = false; + m_hadException.clear(); } std::string Task::getExceptionMessage() const { @@ -180,12 +188,18 @@ namespace hex { return m_maxValue; } + void Task::wait() const { + m_finished.wait(false); + } + void Task::finish() { - m_finished = true; + m_finished.test_and_set(); + m_finished.notify_all(); } void Task::interruption() { - m_interrupted = true; + m_interrupted.test_and_set(); + m_interrupted.notify_all(); } void Task::exception(const char *message) { @@ -193,7 +207,8 @@ namespace hex { // Store information about the caught exception m_exceptionMessage = message; - m_hadException = true; + m_hadException.test_and_set(); + m_hadException.notify_all(); // Call the interrupt callback on the current thread if one is set if (m_interruptCallback) @@ -241,6 +256,14 @@ namespace hex { task->interrupt(); } + void TaskHolder::wait() const { + const auto &task = m_task.lock(); + if (!task) + return; + + task->wait(); + } + u32 TaskHolder::getProgress() const { const auto &task = m_task.lock(); if (!task) diff --git a/plugins/hashes/include/content/views/view_hashes.hpp b/plugins/hashes/include/content/views/view_hashes.hpp index 39083f59c..4f4b61bee 100644 --- a/plugins/hashes/include/content/views/view_hashes.hpp +++ b/plugins/hashes/include/content/views/view_hashes.hpp @@ -1,6 +1,9 @@ #pragma once +#include #include +#include +#include #include @@ -14,6 +17,58 @@ namespace hex::plugin::hashes { void drawContent() override; void drawHelpText() override; + class Function { + public: + explicit Function(ContentRegistry::Hashes::Hash::Function hashFunction) : m_hashFunction(std::move(hashFunction)) { } + + void update(const Region ®ion, prv::Provider *provider) { + m_region = { region, provider }; + } + + void update(std::vector data) { + m_data = std::move(data); + } + + std::vector get() { + if (!m_task.isRunning()) { + if (m_region.has_value()) { + m_lastResult.clear(); + m_task = TaskManager::createBackgroundTask("Updating hash", [this, region = m_region]() { + m_lastResult = m_hashFunction.get(region->getRegion(), region->getProvider()); + }); + + m_region.reset(); + } else if (!m_data.empty()) { + m_lastResult.clear(); + m_task = TaskManager::createBackgroundTask("Updating hash", [this, data = std::move(m_data)]() { + prv::MemoryProvider provider({ data.begin(), data.end() }); + m_lastResult = m_hashFunction.get(Region { 0x00, provider.getActualSize() }, &provider); + }); + + m_region.reset(); + m_data = {}; + } + } + + return m_lastResult; + } + + bool isCalculating() const { + return m_task.isRunning(); + } + + const ContentRegistry::Hashes::Hash::Function& getFunction() const { + return m_hashFunction; + } + + private: + std::vector m_data; + std::optional m_region; + ContentRegistry::Hashes::Hash::Function m_hashFunction; + std::vector m_lastResult; + TaskHolder m_task; + }; + private: bool importHashes(prv::Provider *provider, const nlohmann::json &json); bool exportHashes(prv::Provider *provider, nlohmann::json &json); @@ -24,7 +79,7 @@ namespace hex::plugin::hashes { ContentRegistry::Hashes::Hash *m_selectedHash = nullptr; std::string m_newHashName; - PerProvider> m_hashFunctions; + PerProvider> m_hashFunctions; }; diff --git a/plugins/hashes/source/content/views/view_hashes.cpp b/plugins/hashes/source/content/views/view_hashes.cpp index 62821ddba..151f64964 100644 --- a/plugins/hashes/source/content/views/view_hashes.cpp +++ b/plugins/hashes/source/content/views/view_hashes.cpp @@ -20,8 +20,8 @@ namespace hex::plugin::hashes { class PopupTextHash : public Popup { public: - explicit PopupTextHash(const ContentRegistry::Hashes::Hash::Function &hash) - : hex::Popup(hash.getName(), true, false), + explicit PopupTextHash(const ViewHashes::Function &hash) + : hex::Popup(hash.getFunction().getName(), true, false), m_hash(hash) { } void drawContent() override { @@ -29,16 +29,25 @@ namespace hex::plugin::hashes { ImGui::PushItemWidth(-1); if (ImGui::InputTextMultiline("##input", m_input)) { - prv::MemoryProvider provider({ m_input.begin(), m_input.end() }); - - m_hash.reset(); - auto bytes = m_hash.get(Region { 0x00, provider.getActualSize() }, &provider); - - m_result = crypt::encode16(bytes); + m_hash.update({ m_input.begin(), m_input.end() }); + m_result.reset(); } + ImGui::NewLine(); - ImGui::InputText("##result", m_result, ImGuiInputTextFlags_ReadOnly); + if (m_hash.isCalculating()) { + ImGuiExt::TextSpinner(""); + } else { + if (!m_result.has_value()) { + const auto data = m_hash.get(); + if (!data.empty()) + m_result = crypt::encode16(data); + } + + auto result = m_result.value_or("???"); + ImGui::InputText("##result", result, ImGuiInputTextFlags_ReadOnly); + } + ImGui::PopItemWidth(); if (ImGui::IsKeyPressed(ImGuiKey_Escape)) @@ -50,22 +59,22 @@ namespace hex::plugin::hashes { } ImVec2 getMinSize() const override { - return scaled({ 400, 200 }); + return scaled({ 400, 230 }); } ImVec2 getMaxSize() const override { return this->getMinSize(); } private: std::string m_input; - std::string m_result; - ContentRegistry::Hashes::Hash::Function m_hash; + std::optional m_result; + ViewHashes::Function m_hash; }; ViewHashes::ViewHashes() : View::Window("hex.hashes.view.hashes.name", ICON_VS_KEY) { EventRegionSelected::subscribe(this, [this](const auto &providerRegion) { if (providerRegion.getProvider() != nullptr) for (auto &function : m_hashFunctions.get(providerRegion.getProvider())) - function.reset(); + function.update(providerRegion.getRegion(), providerRegion.getProvider()); }); ImHexApi::HexEditor::addTooltipProvider([this](u64 address, const u8 *data, size_t size) { @@ -92,22 +101,15 @@ namespace hex::plugin::hashes { if (provider == nullptr) continue; - std::vector bytes; - try { - bytes = function.get(*selection, provider); - } catch (const std::exception &) { - continue; - } - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGuiExt::TextFormatted("{}", function.getName()); + ImGuiExt::TextFormatted("{}", function.getFunction().getName()); ImGui::TableNextColumn(); ImGuiExt::TextFormatted(" "); ImGui::TableNextColumn(); - ImGuiExt::TextFormatted("{}", crypt::encode16(bytes)); + ImGuiExt::TextFormatted("{}", crypt::encode16(function.get())); } ImGui::EndTable(); @@ -198,7 +200,7 @@ namespace hex::plugin::hashes { ImGui::BeginDisabled(m_newHashName.empty() || m_selectedHash == nullptr); if (ImGuiExt::DimmedButton("hex.hashes.view.hashes.add"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) { if (m_selectedHash != nullptr) { - m_hashFunctions->push_back(m_selectedHash->create(m_newHashName)); + m_hashFunctions->emplace_back(m_selectedHash->create(m_newHashName)); AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.hashes.achievement.misc.create_hash.name"); ImGui::CloseCurrentPopup(); } @@ -222,10 +224,10 @@ namespace hex::plugin::hashes { auto provider = ImHexApi::Provider::get(); auto selection = ImHexApi::HexEditor::getSelection(); - std::optional indexToRemove; - for (u32 i = 0; i < m_hashFunctions->size(); i++) { - auto &function = (*m_hashFunctions)[i]; - + u32 i = 0; + auto itToRemove = m_hashFunctions->end(); + for (auto it = m_hashFunctions->begin(); it != m_hashFunctions->end(); ++it) { + auto &function = *it; ImGui::PushID(i + 1); ImGui::TableNextRow(); @@ -234,17 +236,17 @@ namespace hex::plugin::hashes { ImGui::PushStyleColor(ImGuiCol_Header, 0x00); ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0x00); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0x00); - ImGui::Selectable(function.getName().c_str(), false); + ImGui::Selectable(function.getFunction().getName().c_str(), false); ImGui::PopStyleColor(3); ImGui::TableNextColumn(); - ImGuiExt::TextFormatted("{}", Lang(function.getType()->getUnlocalizedName())); + ImGuiExt::TextFormatted("{}", Lang(function.getFunction().getType()->getUnlocalizedName())); ImGui::TableNextColumn(); std::string result; if (provider != nullptr && selection.has_value()) { try { - result = crypt::encode16(function.get(*selection, provider)); + result = crypt::encode16(function.get()); } catch (const std::exception &e) { result = e.what(); } @@ -252,7 +254,10 @@ namespace hex::plugin::hashes { result = "???"; } - ImGuiExt::TextFormattedSelectable("{}", result); + if (!function.isCalculating()) + ImGuiExt::TextFormattedSelectable("{}", result); + else + ImGuiExt::TextSpinner(""); ImGui::TableNextColumn(); @@ -262,15 +267,17 @@ namespace hex::plugin::hashes { } ImGui::SameLine(0, 3_scaled); if (ImGuiExt::DimmedIconButton(ICON_VS_CHROME_CLOSE, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { - indexToRemove = i; + itToRemove = it; } ImGui::PopStyleVar(); ImGui::PopID(); + + i += 1; } - if (indexToRemove.has_value()) { - m_hashFunctions->erase(m_hashFunctions->begin() + indexToRemove.value()); + if (itToRemove != m_hashFunctions->end()) { + m_hashFunctions->erase(itToRemove); } ImGui::TableNextRow(); @@ -318,7 +325,7 @@ namespace hex::plugin::hashes { auto newFunction = newHash->create(hash["name"]); newFunction.getType()->load(hash["settings"]); - m_hashFunctions.get(provider).push_back(std::move(newFunction)); + m_hashFunctions.get(provider).emplace_back(std::move(newFunction)); break; } } @@ -331,10 +338,11 @@ namespace hex::plugin::hashes { json["hashes"] = nlohmann::json::array(); size_t index = 0; for (const auto &hashFunction : m_hashFunctions.get(provider)) { + const auto &function = hashFunction.getFunction(); json["hashes"][index] = { - { "name", hashFunction.getName() }, - { "type", hashFunction.getType()->getUnlocalizedName() }, - { "settings", hashFunction.getType()->store() } + { "name", function.getName() }, + { "type", function.getType()->getUnlocalizedName() }, + { "settings", function.getType()->store() } }; index++; }