impr: Calculate hashes in a background thread

This commit is contained in:
WerWolv 2025-12-04 20:57:06 +01:00
parent ab54acb176
commit f2e8d402dd
5 changed files with 143 additions and 64 deletions

View File

@ -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<u8>& 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<u8> 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<u8> m_cache;
};
virtual void draw() { }

View File

@ -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<bool> m_background = true;
std::atomic<bool> m_blocking = false;
std::atomic<bool> m_interrupted = false;
std::atomic<bool> m_finished = false;
std::atomic<bool> 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<Task> m_task;
};

View File

@ -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)

View File

@ -1,6 +1,9 @@
#pragma once
#include <hex/api/task_manager.hpp>
#include <hex/api/content_registry/hashes.hpp>
#include <hex/api/imhex_api/hex_editor.hpp>
#include <hex/providers/memory_provider.hpp>
#include <hex/ui/view.hpp>
@ -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 &region, prv::Provider *provider) {
m_region = { region, provider };
}
void update(std::vector<u8> data) {
m_data = std::move(data);
}
std::vector<u8> 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<u8> m_data;
std::optional<ImHexApi::HexEditor::ProviderRegion> m_region;
ContentRegistry::Hashes::Hash::Function m_hashFunction;
std::vector<u8> 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<std::vector<ContentRegistry::Hashes::Hash::Function>> m_hashFunctions;
PerProvider<std::list<Function>> m_hashFunctions;
};

View File

@ -20,8 +20,8 @@ namespace hex::plugin::hashes {
class PopupTextHash : public Popup<PopupTextHash> {
public:
explicit PopupTextHash(const ContentRegistry::Hashes::Hash::Function &hash)
: hex::Popup<PopupTextHash>(hash.getName(), true, false),
explicit PopupTextHash(const ViewHashes::Function &hash)
: hex::Popup<PopupTextHash>(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<std::string> 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<u8> 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<u32> 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++;
}