1
0
mirror of synced 2024-11-12 02:00:52 +01:00

nodes: Added support for nested, shareable, custom data processor nodes

This commit is contained in:
WerWolv 2023-02-09 23:07:04 +01:00
parent 303dd28c7c
commit 5cc01ae89d
11 changed files with 646 additions and 185 deletions

View File

@ -31,7 +31,10 @@ namespace hex::dp {
void setUnlocalizedName(const std::string &unlocalizedName) { this->m_unlocalizedName = unlocalizedName; }
[[nodiscard]] const std::string &getUnlocalizedTitle() const { return this->m_unlocalizedTitle; }
void setUnlocalizedTitle(std::string title) { this->m_unlocalizedTitle = std::move(title); }
[[nodiscard]] std::vector<Attribute> &getAttributes() { return this->m_attributes; }
[[nodiscard]] const std::vector<Attribute> &getAttributes() const { return this->m_attributes; }
void setCurrentOverlay(prv::Overlay *overlay) {
this->m_overlay = overlay;
@ -40,8 +43,8 @@ namespace hex::dp {
virtual void drawNode() { }
virtual void process() = 0;
virtual void store(nlohmann::json &j) { hex::unused(j); }
virtual void load(nlohmann::json &j) { hex::unused(j); }
virtual void store(nlohmann::json &j) const { hex::unused(j); }
virtual void load(const nlohmann::json &j) { hex::unused(j); }
struct NodeError {
Node *node;
@ -70,6 +73,14 @@ namespace hex::dp {
Node::s_idCounter = id;
}
std::vector<u8> getBufferOnInput(u32 index);
i128 getIntegerOnInput(u32 index);
long double getFloatOnInput(u32 index);
void setBufferOnOutput(u32 index, const std::vector<u8> &data);
void setIntegerOnOutput(u32 index, i128 integer);
void setFloatOnOutput(u32 index, long double floatingPoint);
private:
int m_id;
std::string m_unlocalizedTitle, m_unlocalizedName;
@ -103,15 +114,14 @@ namespace hex::dp {
throw NodeError { this, message };
}
std::vector<u8> getBufferOnInput(u32 index);
i128 getIntegerOnInput(u32 index);
long double getFloatOnInput(u32 index);
void setBufferOnOutput(u32 index, const std::vector<u8> &data);
void setIntegerOnOutput(u32 index, i128 integer);
void setFloatOnOutput(u32 index, long double floatingPoint);
void setOverlayData(u64 address, const std::vector<u8> &data);
void setAttributes(std::vector<Attribute> attributes) {
this->m_attributes = std::move(attributes);
for (auto &attr : this->m_attributes)
attr.setParentNode(this);
}
};
}

View File

@ -102,6 +102,7 @@ namespace hex::fs {
Inspectors,
Themes,
Libraries,
Nodes,
END
};

View File

@ -2,10 +2,6 @@
namespace hex {
[[noreturn]] inline void unreachable() {
__builtin_unreachable();
}
inline void unused(auto && ... x) {
((void)x, ...);
}

View File

@ -101,7 +101,7 @@ namespace hex::fs {
result = NFD::PickFolder(outPath, defaultPath.empty() ? nullptr : defaultPath.c_str());
break;
default:
hex::unreachable();
std::unreachable();
}
if (result == NFD_OKAY){
@ -271,6 +271,9 @@ namespace hex::fs {
case ImHexPath::Inspectors:
result = appendPath(getDefaultPaths(ImHexPath::Scripts), "inspectors");
break;
case ImHexPath::Nodes:
result = appendPath(getDefaultPaths(ImHexPath::Scripts), "nodes");
break;
case ImHexPath::Themes:
result = appendPath(getDataPaths(), "themes");
break;

View File

@ -63,12 +63,16 @@ namespace hex::plugin::builtin {
std::list<ImHexApi::Bookmarks::Entry> bookmarks;
struct DataProcessor {
std::list<dp::Node*> endNodes;
std::list<std::unique_ptr<dp::Node>> nodes;
std::list<dp::Link> links;
struct Workspace {
std::list<std::unique_ptr<dp::Node>> nodes;
std::list<dp::Node*> endNodes;
std::list<dp::Link> links;
std::vector<hex::prv::Overlay *> dataOverlays;
std::optional<dp::Node::NodeError> currNodeError;
};
std::vector<hex::prv::Overlay *> dataOverlays;
std::optional<dp::Node::NodeError> currNodeError;
Workspace mainWorkspace;
std::vector<Workspace*> workspaceStack;
} dataProcessor;
struct HexEditor {
@ -101,7 +105,7 @@ namespace hex::plugin::builtin {
return get(ImHexApi::Provider::get());
}
static Data& get(hex::prv::Provider *provider) {
static Data& get(const hex::prv::Provider *provider) {
return s_data[provider];
}
@ -115,7 +119,7 @@ namespace hex::plugin::builtin {
private:
ProviderExtraData() = default;
static inline std::map<hex::prv::Provider*, Data> s_data = {};
static inline std::map<const hex::prv::Provider*, Data> s_data = {};
};
}

View File

@ -7,6 +7,8 @@
#include <hex/data_processor/node.hpp>
#include <hex/data_processor/link.hpp>
#include "content/helpers/provider_extra_data.hpp"
#include <array>
#include <string>
@ -14,24 +16,38 @@ namespace hex::plugin::builtin {
class ViewDataProcessor : public View {
public:
using Workspace = ProviderExtraData::Data::DataProcessor::Workspace;
ViewDataProcessor();
~ViewDataProcessor() override;
void drawContent() override;
static nlohmann::json saveNode(const dp::Node *node);
static nlohmann::json saveNodes(const Workspace &workspace);
static std::unique_ptr<dp::Node> loadNode(const nlohmann::json &data);
static void loadNodes(Workspace &workspace, const nlohmann::json &data);
private:
bool m_justSwitchedProvider = false;
static void eraseLink(Workspace &workspace, int id);
static void eraseNodes(Workspace &workspace, const std::vector<int> &ids);
static void processNodes(Workspace &workspace);
void reloadCustomNodes();
private:
bool m_updateNodePositions = false;
int m_rightClickedId = -1;
ImVec2 m_rightClickedCoords;
bool m_continuousEvaluation = false;
void eraseLink(int id);
void eraseNodes(const std::vector<int> &ids);
void processNodes();
struct CustomNode {
std::string name;
nlohmann::json data;
};
std::string saveNodes(prv::Provider *provider);
void loadNodes(prv::Provider *provider, const std::string &data);
std::vector<CustomNode> m_customNodes;
};
}

View File

@ -268,6 +268,15 @@
"hex.builtin.nodes.crypto.aes.key_length": "Key length",
"hex.builtin.nodes.crypto.aes.mode": "Mode",
"hex.builtin.nodes.crypto.aes.nonce": "Nonce",
"hex.builtin.nodes.custom": "Custom",
"hex.builtin.nodes.custom.custom": "New Node",
"hex.builtin.nodes.custom.custom.edit": "Edit",
"hex.builtin.nodes.custom.custom.edit_hint": "Hold down SHIFT to edit",
"hex.builtin.nodes.custom.custom.header": "Custom Node",
"hex.builtin.nodes.custom.input": "Custom Node Input",
"hex.builtin.nodes.custom.input.header": "Node Input",
"hex.builtin.nodes.custom.output": "Custom Node Output",
"hex.builtin.nodes.custom.output.header": "Node Output",
"hex.builtin.nodes.data_access": "Data access",
"hex.builtin.nodes.data_access.read": "Read",
"hex.builtin.nodes.data_access.read.address": "Address",
@ -540,6 +549,7 @@
"hex.builtin.view.data_processor.menu.remove_link": "Remove Link",
"hex.builtin.view.data_processor.menu.remove_node": "Remove Node",
"hex.builtin.view.data_processor.menu.remove_selection": "Remove Selected",
"hex.builtin.view.data_processor.menu.save_node": "Save Node",
"hex.builtin.view.data_processor.name": "Data Processor",
"hex.builtin.view.diff.name": "Diffing",
"hex.builtin.view.disassembler.16bit": "16-bit",

View File

@ -7,6 +7,8 @@
#include <hex/helpers/logger.hpp>
#include <hex/providers/provider.hpp>
#include <content/views/view_data_processor.hpp>
#include <content/helpers/provider_extra_data.hpp>
#include <content/helpers/diagrams.hpp>
@ -18,7 +20,6 @@
#include <imgui.h>
#include <implot.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <fonts/codicons_font.h>
namespace hex::plugin::builtin {
@ -50,14 +51,14 @@ namespace hex::plugin::builtin {
this->setBufferOnOutput(0, this->m_buffer);
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["size"] = this->m_size;
j["data"] = this->m_buffer;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_size = j["size"];
this->m_buffer = j["data"].get<std::vector<u8>>();
}
@ -81,13 +82,13 @@ namespace hex::plugin::builtin {
this->setBufferOnOutput(0, hex::decodeByteString(this->m_value));
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["data"] = this->m_value;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_value = j["data"].get<std::string>();
}
@ -109,13 +110,13 @@ namespace hex::plugin::builtin {
this->setIntegerOnOutput(0, this->m_value);
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["data"] = this->m_value;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_value = j["data"];
}
@ -137,13 +138,13 @@ namespace hex::plugin::builtin {
this->setFloatOnOutput(0, this->m_value);
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["data"] = this->m_value;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_value = j["data"];
}
@ -166,13 +167,13 @@ namespace hex::plugin::builtin {
}
void process() override {
this->setBufferOnOutput(0, hex::toBytes<u8>(this->m_color.Value.x * 0xFF));
this->setBufferOnOutput(1, hex::toBytes<u8>(this->m_color.Value.y * 0xFF));
this->setBufferOnOutput(2, hex::toBytes<u8>(this->m_color.Value.z * 0xFF));
this->setBufferOnOutput(3, hex::toBytes<u8>(this->m_color.Value.w * 0xFF));
this->setBufferOnOutput(0, hex::toBytes<u8>(u8(this->m_color.Value.x * 0xFF)));
this->setBufferOnOutput(1, hex::toBytes<u8>(u8(this->m_color.Value.y * 0xFF)));
this->setBufferOnOutput(2, hex::toBytes<u8>(u8(this->m_color.Value.z * 0xFF)));
this->setBufferOnOutput(3, hex::toBytes<u8>(u8(this->m_color.Value.w * 0xFF)));
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["data"] = nlohmann::json::object();
@ -182,7 +183,7 @@ namespace hex::plugin::builtin {
j["data"]["a"] = this->m_color.Value.w;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_color = ImVec4(j["data"]["r"], j["data"]["g"], j["data"]["b"], j["data"]["a"]);
}
@ -203,13 +204,13 @@ namespace hex::plugin::builtin {
void process() override {
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["comment"] = this->m_comment;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_comment = j["comment"].get<std::string>();
}
@ -878,7 +879,7 @@ namespace hex::plugin::builtin {
this->setBufferOnOutput(4, output);
}
void store(nlohmann::json &j) override {
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["data"] = nlohmann::json::object();
@ -886,7 +887,7 @@ namespace hex::plugin::builtin {
j["data"]["key_length"] = this->m_keyLength;
}
void load(nlohmann::json &j) override {
void load(const nlohmann::json &j) override {
this->m_mode = j["data"]["mode"];
this->m_keyLength = j["data"]["key_length"];
}
@ -1122,10 +1123,301 @@ namespace hex::plugin::builtin {
}
}
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["name"] = this->m_name;
}
void load(const nlohmann::json &j) override {
this->m_name = j["name"].get<std::string>();
}
private:
std::string m_name;
};
class NodeCustomInput : public dp::Node {
public:
NodeCustomInput() : Node("hex.builtin.nodes.custom.input.header", { dp::Attribute(dp::Attribute::IOType::Out, dp::Attribute::Type::Integer, "hex.builtin.nodes.common.input") }) { }
~NodeCustomInput() override = default;
void drawNode() override {
ImGui::PushItemWidth(100_scaled);
if (ImGui::Combo("##type", &this->m_type, "Integer\0Float\0Buffer\0")) {
this->setAttributes({
{ dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.input") }
});
}
if (ImGui::InputText("##name", this->m_name)) {
this->setUnlocalizedTitle(this->m_name);
}
ImGui::PopItemWidth();
}
void setValue(auto value) { this->m_value = std::move(value); }
const std::string &getName() const { return this->m_name; }
dp::Attribute::Type getType() const {
switch (this->m_type) {
default:
case 0: return dp::Attribute::Type::Integer;
case 1: return dp::Attribute::Type::Float;
case 2: return dp::Attribute::Type::Buffer;
}
}
void process() override {
std::visit(overloaded {
[this](i128 value) { this->setIntegerOnOutput(0, value); },
[this](long double value) { this->setFloatOnOutput(0, value); },
[this](const std::vector<u8> &value) { this->setBufferOnOutput(0, value); }
}, this->m_value);
}
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["name"] = this->m_name;
j["type"] = this->m_type;
}
void load(const nlohmann::json &j) override {
this->m_name = j["name"].get<std::string>();
this->m_type = j["type"];
}
private:
std::string m_name = LangEntry(this->getUnlocalizedName());
int m_type = 0;
std::variant<i128, long double, std::vector<u8>> m_value;
};
class NodeCustomOutput : public dp::Node {
public:
NodeCustomOutput() : Node("hex.builtin.nodes.custom.output.header", { dp::Attribute(dp::Attribute::IOType::In, dp::Attribute::Type::Integer, "hex.builtin.nodes.common.output") }) { }
~NodeCustomOutput() override = default;
void drawNode() override {
ImGui::PushItemWidth(100_scaled);
if (ImGui::Combo("##type", &this->m_type, "Integer\0Float\0Buffer\0")) {
this->setAttributes({
{ dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.output") }
});
}
if (ImGui::InputText("##name", this->m_name)) {
this->setUnlocalizedTitle(this->m_name);
}
ImGui::PopItemWidth();
}
const std::string &getName() const { return this->m_name; }
dp::Attribute::Type getType() const {
switch (this->m_type) {
case 0: return dp::Attribute::Type::Integer;
case 1: return dp::Attribute::Type::Float;
case 2: return dp::Attribute::Type::Buffer;
default: return dp::Attribute::Type::Integer;
}
}
void process() override {
switch (this->getType()) {
case dp::Attribute::Type::Integer: this->m_value = this->getIntegerOnInput(0); break;
case dp::Attribute::Type::Float: this->m_value = this->getFloatOnInput(0); break;
case dp::Attribute::Type::Buffer: this->m_value = this->getBufferOnInput(0); break;
}
}
const auto& getValue() const { return this->m_value; }
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["name"] = this->m_name;
j["type"] = this->m_type;
}
void load(const nlohmann::json &j) override {
this->m_name = j["name"].get<std::string>();
this->m_type = j["type"];
}
private:
std::string m_name = LangEntry(this->getUnlocalizedName());
int m_type = 0;
std::variant<i128, long double, std::vector<u8>> m_value;
};
class NodeCustom : public dp::Node {
public:
NodeCustom() : Node("hex.builtin.nodes.custom.custom.header", {}) { }
~NodeCustom() override = default;
void drawNode() override {
if (this->m_requiresAttributeUpdate) {
this->m_requiresAttributeUpdate = false;
this->setAttributes(this->findAttributes());
}
ImGui::PushItemWidth(200_scaled);
bool editing = false;
if (this->m_editable) {
ImGui::InputTextIcon("##name", ICON_VS_SYMBOL_KEY, this->m_name);
editing = ImGui::IsItemActive();
if (ImGui::Button("hex.builtin.nodes.custom.custom.edit"_lang, ImVec2(200_scaled, ImGui::GetTextLineHeightWithSpacing()))) {
ProviderExtraData::getCurrent().dataProcessor.workspaceStack.push_back(&this->m_workspace);
this->m_requiresAttributeUpdate = true;
}
} else {
this->setUnlocalizedTitle(this->m_name);
if (this->getAttributes().empty()) {
ImGui::TextUnformatted("hex.builtin.nodes.custom.custom.edit_hint"_lang);
}
}
this->m_editable = ImGui::GetIO().KeyShift || editing;
ImGui::PopItemWidth();
}
void process() override {
auto indexFromId = [this](u32 id) -> std::optional<u32> {
const auto &attributes = this->getAttributes();
for (u32 i = 0; i < attributes.size(); i++)
if (u32(attributes[i].getId()) == id)
return i;
return std::nullopt;
};
// Forward inputs to input nodes values
for (auto &attribute : this->getAttributes()) {
auto index = indexFromId(attribute.getId());
if (!index.has_value())
continue;
if (auto input = this->findInput(attribute.getUnlocalizedName()); input != nullptr) {
switch (attribute.getType()) {
case dp::Attribute::Type::Integer: {
auto value = this->getIntegerOnInput(*index);
input->setValue(value);
break;
}
case dp::Attribute::Type::Float: {
auto value = this->getFloatOnInput(*index);
input->setValue(value);
break;
}
case dp::Attribute::Type::Buffer: {
auto value = this->getBufferOnInput(*index);
input->setValue(value);
break;
}
}
}
}
// Process all nodes in our workspace
for (auto &endNode : this->m_workspace.endNodes) {
endNode->resetOutputData();
for (auto &node : this->m_workspace.nodes)
node->resetProcessedInputs();
endNode->process();
}
// Forward output node values to outputs
for (auto &attribute : this->getAttributes()) {
auto index = indexFromId(attribute.getId());
if (!index.has_value())
continue;
if (auto output = this->findOutput(attribute.getUnlocalizedName()); output != nullptr) {
switch (attribute.getType()) {
case dp::Attribute::Type::Integer: {
auto value = std::get<i128>(output->getValue());
this->setIntegerOnOutput(*index, value);
break;
}
case dp::Attribute::Type::Float: {
auto value = std::get<long double>(output->getValue());
this->setFloatOnOutput(*index, value);
break;
}
case dp::Attribute::Type::Buffer: {
auto value = std::get<std::vector<u8>>(output->getValue());
this->setBufferOnOutput(*index, value);
break;
}
}
}
}
}
void store(nlohmann::json &j) const override {
j = nlohmann::json::object();
j["nodes"] = ViewDataProcessor::saveNodes(this->m_workspace);
}
void load(const nlohmann::json &j) override {
ViewDataProcessor::loadNodes(this->m_workspace, j["nodes"]);
this->m_name = LangEntry(this->getUnlocalizedTitle()).get();
this->m_requiresAttributeUpdate = true;
}
private:
std::vector<dp::Attribute> findAttributes() {
std::vector<dp::Attribute> result;
for (auto &node : this->m_workspace.nodes) {
if (auto *inputNode = dynamic_cast<NodeCustomInput*>(node.get()); inputNode != nullptr)
result.emplace_back(dp::Attribute::IOType::In, inputNode->getType(), inputNode->getName());
else if (auto *outputNode = dynamic_cast<NodeCustomOutput*>(node.get()); outputNode != nullptr)
result.emplace_back(dp::Attribute::IOType::Out, outputNode->getType(), outputNode->getName());
}
return result;
}
NodeCustomInput* findInput(const std::string &name) {
for (auto &node : this->m_workspace.nodes) {
if (auto *inputNode = dynamic_cast<NodeCustomInput*>(node.get()); inputNode != nullptr && inputNode->getName() == name)
return inputNode;
}
return nullptr;
}
NodeCustomOutput* findOutput(const std::string &name) {
for (auto &node : this->m_workspace.nodes) {
if (auto *outputNode = dynamic_cast<NodeCustomOutput*>(node.get()); outputNode != nullptr && outputNode->getName() == name)
return outputNode;
}
return nullptr;
}
private:
std::string m_name = "hex.builtin.nodes.custom.custom.header"_lang;
bool m_editable = false;
bool m_requiresAttributeUpdate = false;
ProviderExtraData::Data::DataProcessor::Workspace m_workspace;
};
void registerDataProcessorNodes() {
ContentRegistry::DataProcessorNode::add<NodeInteger>("hex.builtin.nodes.constants", "hex.builtin.nodes.constants.int");
ContentRegistry::DataProcessorNode::add<NodeFloat>("hex.builtin.nodes.constants", "hex.builtin.nodes.constants.float");
@ -1188,6 +1480,10 @@ namespace hex::plugin::builtin {
ContentRegistry::DataProcessorNode::add<NodeVisualizerByteDistribution>("hex.builtin.nodes.visualizer", "hex.builtin.nodes.visualizer.byte_distribution");
ContentRegistry::DataProcessorNode::add<NodePatternLanguageOutVariable>("hex.builtin.nodes.pattern_language", "hex.builtin.nodes.pattern_language.out_var");
ContentRegistry::DataProcessorNode::add<NodeCustom>("hex.builtin.nodes.custom", "hex.builtin.nodes.custom.custom");
ContentRegistry::DataProcessorNode::add<NodeCustomInput>("hex.builtin.nodes.custom", "hex.builtin.nodes.custom.input");
ContentRegistry::DataProcessorNode::add<NodeCustomOutput>("hex.builtin.nodes.custom", "hex.builtin.nodes.custom.output");
}
}

View File

@ -159,6 +159,7 @@ namespace hex::plugin::builtin {
{ "Scripts", fs::ImHexPath::Scripts },
{ "Themes", fs::ImHexPath::Themes },
{ "Data inspector scripts", fs::ImHexPath::Inspectors },
{ "Custom data processor nodes", fs::ImHexPath::Nodes },
}
};

View File

@ -3,11 +3,13 @@
#include <hex/api/content_registry.hpp>
#include <hex/helpers/file.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/providers/provider.hpp>
#include <hex/api/project_file_manager.hpp>
#include <imnodes.h>
#include <imnodes_internal.h>
#include <nlohmann/json.hpp>
#include <content/helpers/provider_extra_data.hpp>
@ -20,52 +22,71 @@ namespace hex::plugin::builtin {
.required = false,
.load = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) {
auto save = tar.readString(basePath);
auto &data = ProviderExtraData::get(provider).dataProcessor;
this->loadNodes(provider, save);
ViewDataProcessor::loadNodes(data.mainWorkspace, save);
this->m_updateNodePositions = true;
return true;
},
.store = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) {
tar.write(basePath, this->saveNodes(provider));
.store = [](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) {
auto &data = ProviderExtraData::get(provider).dataProcessor;
tar.write(basePath, ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4));
return true;
}
});
EventManager::subscribe<EventProviderCreated>(this, [](const auto *provider) {
auto &data = ProviderExtraData::get(provider).dataProcessor;
data.mainWorkspace = { };
data.workspaceStack.push_back(&data.mainWorkspace);
});
EventManager::subscribe<EventProviderChanged>(this, [this](const auto &, const auto &) {
auto &data = ProviderExtraData::getCurrent().dataProcessor;
for (auto &node : data.nodes) {
node->setCurrentOverlay(nullptr);
for (auto *workspace : data.workspaceStack) {
for (auto &node : workspace->nodes) {
node->setCurrentOverlay(nullptr);
}
workspace->dataOverlays.clear();
}
data.dataOverlays.clear();
this->m_justSwitchedProvider = true;
this->m_updateNodePositions = true;
});
EventManager::subscribe<EventDataChanged>(this, [this] {
this->processNodes();
EventManager::subscribe<EventDataChanged>(this, [] {
auto &workspace = *ProviderExtraData::getCurrent().dataProcessor.workspaceStack.back();
ViewDataProcessor::processNodes(workspace);
});
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&, this] {
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&] {
bool providerValid = ImHexApi::Provider::isValid();
auto provider = ImHexApi::Provider::get();
auto &data = ProviderExtraData::getCurrent().dataProcessor;
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.load_processor"_lang, nullptr, false, providerValid)) {
fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode"} },
[&, this](const std::fs::path &path) {
fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } },
[&](const std::fs::path &path) {
fs::File file(path, fs::File::Mode::Read);
if (file.isValid())
this->loadNodes(provider, file.readString());
if (file.isValid()) {
ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString());
this->m_updateNodePositions = true;
}
});
}
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.save_processor"_lang, nullptr, false, !data.nodes.empty() && providerValid)) {
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode"} },
[&, this](const std::fs::path &path) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.save_processor"_lang, nullptr, false, !data.workspaceStack.empty() && !data.workspaceStack.back()->nodes.empty() && providerValid)) {
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } },
[&](const std::fs::path &path) {
fs::File file(path, fs::File::Mode::Create);
if (file.isValid())
file.write(this->saveNodes(provider));
file.write(ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4));
});
}
});
@ -74,42 +95,44 @@ namespace hex::plugin::builtin {
fs::File file(path, fs::File::Mode::Read);
if (!file.isValid()) return false;
this->loadNodes(ImHexApi::Provider::get(), file.readString());
auto &data = ProviderExtraData::getCurrent().dataProcessor;
ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString());
this->m_updateNodePositions = true;
return true;
});
}
ViewDataProcessor::~ViewDataProcessor() {
EventManager::unsubscribe<EventProviderCreated>(this);
EventManager::unsubscribe<EventProviderChanged>(this);
EventManager::unsubscribe<RequestChangeTheme>(this);
EventManager::unsubscribe<EventFileLoaded>(this);
EventManager::unsubscribe<EventDataChanged>(this);
}
void ViewDataProcessor::eraseLink(int id) {
auto &data = ProviderExtraData::getCurrent().dataProcessor;
void ViewDataProcessor::eraseLink(ProviderExtraData::Data::DataProcessor::Workspace &workspace, int id) {
auto link = std::find_if(workspace.links.begin(), workspace.links.end(), [&id](auto link) { return link.getId() == id; });
auto link = std::find_if(data.links.begin(), data.links.end(), [&id](auto link) { return link.getId() == id; });
if (link == data.links.end())
if (link == workspace.links.end())
return;
for (auto &node : data.nodes) {
for (auto &node : workspace.nodes) {
for (auto &attribute : node->getAttributes()) {
attribute.removeConnectedAttribute(id);
}
}
data.links.erase(link);
workspace.links.erase(link);
ImHexApi::Provider::markDirty();
}
void ViewDataProcessor::eraseNodes(const std::vector<int> &ids) {
auto &data = ProviderExtraData::getCurrent().dataProcessor;
void ViewDataProcessor::eraseNodes(ProviderExtraData::Data::DataProcessor::Workspace &workspace, const std::vector<int> &ids) {
for (int id : ids) {
auto node = std::find_if(data.nodes.begin(), data.nodes.end(),
auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(),
[&id](const auto &node) {
return node->getId() == id;
});
@ -120,64 +143,81 @@ namespace hex::plugin::builtin {
linksToRemove.push_back(linkId);
for (auto linkId : linksToRemove)
eraseLink(linkId);
eraseLink(workspace, linkId);
}
}
for (int id : ids) {
auto node = std::find_if(data.nodes.begin(), data.nodes.end(), [&id](const auto &node) { return node->getId() == id; });
auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(), [&id](const auto &node) { return node->getId() == id; });
std::erase_if(data.endNodes, [&id](const auto &node) { return node->getId() == id; });
std::erase_if(workspace.endNodes, [&id](const auto &node) { return node->getId() == id; });
data.nodes.erase(node);
workspace.nodes.erase(node);
}
ImHexApi::Provider::markDirty();
}
void ViewDataProcessor::processNodes() {
auto &data = ProviderExtraData::getCurrent().dataProcessor;
if (data.dataOverlays.size() != data.endNodes.size()) {
for (auto overlay : data.dataOverlays)
void ViewDataProcessor::processNodes(ProviderExtraData::Data::DataProcessor::Workspace &workspace) {
if (workspace.dataOverlays.size() != workspace.endNodes.size()) {
for (auto overlay : workspace.dataOverlays)
ImHexApi::Provider::get()->deleteOverlay(overlay);
data.dataOverlays.clear();
workspace.dataOverlays.clear();
for (u32 i = 0; i < data.endNodes.size(); i++)
data.dataOverlays.push_back(ImHexApi::Provider::get()->newOverlay());
for (u32 i = 0; i < workspace.endNodes.size(); i++)
workspace.dataOverlays.push_back(ImHexApi::Provider::get()->newOverlay());
}
u32 overlayIndex = 0;
for (auto endNode : data.endNodes) {
endNode->setCurrentOverlay(data.dataOverlays[overlayIndex]);
for (auto endNode : workspace.endNodes) {
endNode->setCurrentOverlay(workspace.dataOverlays[overlayIndex]);
overlayIndex++;
}
data.currNodeError.reset();
workspace.currNodeError.reset();
try {
for (auto &endNode : data.endNodes) {
for (auto &endNode : workspace.endNodes) {
endNode->resetOutputData();
for (auto &node : data.nodes)
for (auto &node : workspace.nodes)
node->resetProcessedInputs();
endNode->process();
}
} catch (dp::Node::NodeError &e) {
data.currNodeError = e;
workspace.currNodeError = e;
for (auto overlay : data.dataOverlays)
for (auto overlay : workspace.dataOverlays)
ImHexApi::Provider::get()->deleteOverlay(overlay);
data.dataOverlays.clear();
workspace.dataOverlays.clear();
} catch (std::runtime_error &e) {
printf("Node implementation bug! %s\n", e.what());
log::fatal("Node implementation bug! {}\n", e.what());
}
}
void ViewDataProcessor::reloadCustomNodes() {
this->m_customNodes.clear();
for (const auto &basePath : fs::getDefaultPaths(fs::ImHexPath::Nodes)) {
for (const auto &entry : std::fs::recursive_directory_iterator(basePath)) {
if (entry.path().extension() == ".hexnode") {
try {
nlohmann::json nodeJson = nlohmann::json::parse(fs::File(entry.path(), fs::File::Mode::Read).readString());
this->m_customNodes.push_back(CustomNode { LangEntry(nodeJson["name"]), nodeJson });
} catch (nlohmann::json::exception &e) {
continue;
}
}
}
}
}
void ViewDataProcessor::drawContent() {
auto &data = ProviderExtraData::getCurrent().dataProcessor;
auto &workspace = *data.workspaceStack.back();
if (ImGui::Begin(View::toWindowName("hex.builtin.view.data_processor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) {
@ -191,8 +231,10 @@ namespace hex::plugin::builtin {
ImGui::OpenPopup("Node Menu");
else if (ImNodes::IsLinkHovered(&this->m_rightClickedId))
ImGui::OpenPopup("Link Menu");
else
else {
ImGui::OpenPopup("Context Menu");
this->reloadCustomNodes();
}
}
if (ImGui::BeginPopup("Context Menu")) {
@ -204,14 +246,14 @@ namespace hex::plugin::builtin {
ids.resize(ImNodes::NumSelectedNodes());
ImNodes::GetSelectedNodes(ids.data());
this->eraseNodes(ids);
this->eraseNodes(workspace, ids);
ImNodes::ClearNodeSelection();
ids.resize(ImNodes::NumSelectedLinks());
ImNodes::GetSelectedLinks(ids.data());
for (auto id : ids)
this->eraseLink(id);
this->eraseLink(workspace, id);
ImNodes::ClearLinkSelection();
}
}
@ -233,6 +275,17 @@ namespace hex::plugin::builtin {
}
}
if (ImGui::BeginMenu("hex.builtin.nodes.custom"_lang)) {
ImGui::Separator();
for (auto &customNode : this->m_customNodes) {
if (ImGui::MenuItem(customNode.name.c_str())) {
node = loadNode(customNode.data);
}
}
ImGui::EndMenu();
}
if (node != nullptr) {
bool hasOutput = false;
bool hasInput = false;
@ -245,10 +298,10 @@ namespace hex::plugin::builtin {
}
if (hasInput && !hasOutput)
data.endNodes.push_back(node.get());
workspace.endNodes.push_back(node.get());
ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords);
data.nodes.push_back(std::move(node));
workspace.nodes.push_back(std::move(node));
ImHexApi::Provider::markDirty();
}
@ -256,44 +309,64 @@ namespace hex::plugin::builtin {
}
if (ImGui::BeginPopup("Node Menu")) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.save_node"_lang)) {
auto it = std::find_if(workspace.nodes.begin(), workspace.nodes.end(),
[this](const auto &node) {
return node->getId() == this->m_rightClickedId;
});
if (it != workspace.nodes.end()) {
auto &node = *it;
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, [&](const std::fs::path &path){
fs::File outputFile(path, fs::File::Mode::Create);
outputFile.write(ViewDataProcessor::saveNode(node.get()).dump(4));
});
}
}
ImGui::Separator();
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_node"_lang))
this->eraseNodes({ this->m_rightClickedId });
this->eraseNodes(workspace, { this->m_rightClickedId });
ImGui::EndPopup();
}
if (ImGui::BeginPopup("Link Menu")) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_link"_lang))
this->eraseLink(this->m_rightClickedId);
this->eraseLink(workspace, this->m_rightClickedId);
ImGui::EndPopup();
}
{
int nodeId;
if (ImNodes::IsNodeHovered(&nodeId) && data.currNodeError.has_value() && data.currNodeError->node->getId() == nodeId) {
if (ImNodes::IsNodeHovered(&nodeId) && workspace.currNodeError.has_value() && workspace.currNodeError->node->getId() == nodeId) {
ImGui::BeginTooltip();
ImGui::TextUnformatted("hex.builtin.common.error"_lang);
ImGui::Separator();
ImGui::TextUnformatted(data.currNodeError->message.c_str());
ImGui::TextUnformatted(workspace.currNodeError->message.c_str());
ImGui::EndTooltip();
}
}
if (ImGui::BeginChild("##node_editor", ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 1.3))) {
if (ImGui::BeginChild("##node_editor", ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 1.3F))) {
ImNodes::BeginNodeEditor();
for (auto &node : data.nodes) {
const bool hasError = data.currNodeError.has_value() && data.currNodeError->node == node.get();
for (auto &node : workspace.nodes) {
const bool hasError = workspace.currNodeError.has_value() && workspace.currNodeError->node == node.get();
if (hasError)
ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF);
int nodeId = node->getId();
if (!this->m_justSwitchedProvider)
node->setPosition(ImNodes::GetNodeGridSpacePos(nodeId));
else
if (this->m_updateNodePositions) {
this->m_updateNodePositions = false;
ImNodes::SetNodeGridSpacePos(nodeId, node->getPosition());
} else {
if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Nodes, nodeId) >= 0)
node->setPosition(ImNodes::GetNodeGridSpacePos(nodeId));
}
ImNodes::BeginNode(nodeId);
@ -301,7 +374,9 @@ namespace hex::plugin::builtin {
ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle()));
ImNodes::EndNodeTitleBar();
ImGui::PopStyleVar();
node->drawNode();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0F, 1.0F));
for (auto &attribute : node->getAttributes()) {
ImNodesPinShape pinShape;
@ -338,28 +413,50 @@ namespace hex::plugin::builtin {
ImNodes::PopColorStyle();
}
for (const auto &link : data.links)
std::vector<int> linksToRemove;
for (const auto &link : workspace.links) {
if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getFromId()) == -1 ||
ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getToId()) == -1) {
linksToRemove.push_back(link.getId());
}
}
for (auto linkId : linksToRemove)
this->eraseLink(workspace, linkId);
for (const auto &link : workspace.links) {
ImNodes::Link(link.getId(), link.getFromId(), link.getToId());
}
ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight);
if (data.nodes.empty())
if (workspace.nodes.empty())
ImGui::TextFormattedCentered("{}", "hex.builtin.view.data_processor.help_text"_lang);
if (data.workspaceStack.size() > 1) {
ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeightWithSpacing() * 1.2F, ImGui::GetTextLineHeightWithSpacing() * 0.2F));
if (ImGui::IconButton(ICON_VS_CLOSE, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGray))) {
data.workspaceStack.pop_back();
this->m_updateNodePositions = true;
}
}
ImNodes::EndNodeEditor();
}
ImGui::EndChild();
if (ImGui::IconButton(ICON_VS_DEBUG_START, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen)) || this->m_continuousEvaluation)
this->processNodes();
this->processNodes(workspace);
ImGui::SameLine();
ImGui::Checkbox("Continuous evaluation", &this->m_continuousEvaluation);
{
int linkId;
if (ImNodes::IsLinkDestroyed(&linkId)) {
this->eraseLink(linkId);
this->eraseLink(workspace, linkId);
}
}
@ -369,7 +466,7 @@ namespace hex::plugin::builtin {
do {
dp::Attribute *fromAttr = nullptr, *toAttr = nullptr;
for (auto &node : data.nodes) {
for (auto &node : workspace.nodes) {
for (auto &attribute : node->getAttributes()) {
if (attribute.getId() == from)
fromAttr = &attribute;
@ -390,7 +487,7 @@ namespace hex::plugin::builtin {
if (!toAttr->getConnectedAttributes().empty())
break;
auto newLink = data.links.emplace_back(from, to);
auto newLink = workspace.links.emplace_back(from, to);
fromAttr->addConnectedAttribute(newLink.getId(), toAttr);
toAttr->addConnectedAttribute(newLink.getId(), fromAttr);
@ -406,7 +503,7 @@ namespace hex::plugin::builtin {
ImNodes::GetSelectedLinks(selectedLinks.data());
for (const int id : selectedLinks) {
eraseLink(id);
eraseLink(workspace, id);
}
}
}
@ -418,48 +515,52 @@ namespace hex::plugin::builtin {
selectedNodes.resize(static_cast<size_t>(selectedNodeCount));
ImNodes::GetSelectedNodes(selectedNodes.data());
this->eraseNodes(selectedNodes);
this->eraseNodes(workspace, selectedNodes);
}
}
this->m_justSwitchedProvider = false;
}
ImGui::End();
}
std::string ViewDataProcessor::saveNodes(prv::Provider *provider) {
auto &data = ProviderExtraData::get(provider).dataProcessor;
nlohmann::json ViewDataProcessor::saveNode(const dp::Node *node) {
nlohmann::json output;
using json = nlohmann::json;
json output;
output["name"] = node->getUnlocalizedTitle();
output["type"] = node->getUnlocalizedName();
output["nodes"] = json::object();
for (auto &node : data.nodes) {
auto id = node->getId();
auto &currNodeOutput = output["nodes"][std::to_string(id)];
auto pos = node->getPosition();
nlohmann::json nodeData;
node->store(nodeData);
output["data"] = nodeData;
currNodeOutput["type"] = node->getUnlocalizedName();
currNodeOutput["pos"] = {
{"x", pos.x},
{ "y", pos.y}
};
currNodeOutput["attrs"] = json::array();
currNodeOutput["id"] = id;
json nodeData;
node->store(nodeData);
currNodeOutput["data"] = nodeData;
u32 attrIndex = 0;
for (auto &attr : node->getAttributes()) {
currNodeOutput["attrs"][attrIndex] = attr.getId();
attrIndex++;
}
output["attrs"] = nlohmann::json::array();
u32 attrIndex = 0;
for (auto &attr : node->getAttributes()) {
output["attrs"][attrIndex] = attr.getId();
attrIndex++;
}
output["links"] = json::object();
for (auto &link : data.links) {
return output;
}
nlohmann::json ViewDataProcessor::saveNodes(const ViewDataProcessor::Workspace &workspace) {
nlohmann::json output;
output["nodes"] = nlohmann::json::object();
for (auto &node : workspace.nodes) {
auto id = node->getId();
auto &currNodeOutput = output["nodes"][std::to_string(id)];
auto pos = node->getPosition();
currNodeOutput = saveNode(node.get());
currNodeOutput["id"] = id;
currNodeOutput["pos"] = {
{ "x", pos.x },
{ "y", pos.y }
};
}
output["links"] = nlohmann::json::object();
for (auto &link : workspace.links) {
auto id = link.getId();
auto &currOutput = output["links"][std::to_string(id)];
@ -468,41 +569,55 @@ namespace hex::plugin::builtin {
currOutput["to"] = link.getToId();
}
return output.dump(4);
return output;
}
void ViewDataProcessor::loadNodes(prv::Provider *provider, const std::string &jsonData) {
if (!ImHexApi::Provider::isValid()) return;
std::unique_ptr<dp::Node> ViewDataProcessor::loadNode(const nlohmann::json &node) {
try {
auto &data = ProviderExtraData::get(provider).dataProcessor;
auto &nodeEntries = ContentRegistry::DataProcessorNode::getEntries();
using json = nlohmann::json;
json input = json::parse(jsonData);
u32 maxNodeId = 0;
u32 maxAttrId = 0;
u32 maxLinkId = 0;
data.nodes.clear();
data.endNodes.clear();
data.links.clear();
auto &nodeEntries = ContentRegistry::DataProcessorNode::getEntries();
for (auto &node : input["nodes"]) {
std::unique_ptr<dp::Node> newNode;
for (auto &entry : nodeEntries) {
if (entry.name == node["type"].get<std::string>())
newNode = entry.creatorFunction();
}
if (newNode == nullptr)
return nullptr;
if (node.contains("id"))
newNode->setId(node["id"]);
if (node.contains("name"))
newNode->setUnlocalizedTitle(node["name"]);
u32 attrIndex = 0;
for (auto &attr : newNode->getAttributes()) {
attr.setId(node["attrs"][attrIndex]);
attrIndex++;
}
if (!node["data"].is_null())
newNode->load(node["data"]);
return newNode;
} catch (nlohmann::json::exception &e) {
return nullptr;
}
}
void ViewDataProcessor::loadNodes(ViewDataProcessor::Workspace &workspace, const nlohmann::json &jsonData) {
workspace.nodes.clear();
workspace.endNodes.clear();
workspace.links.clear();
for (auto &node : jsonData["nodes"]) {
auto newNode = loadNode(node);
if (newNode == nullptr)
continue;
u32 nodeId = node["id"];
maxNodeId = std::max(nodeId, maxNodeId);
newNode->setId(nodeId);
newNode->setPosition(ImVec2(node["pos"]["x"], node["pos"]["y"]));
bool hasOutput = false;
bool hasInput = false;
@ -514,10 +629,7 @@ namespace hex::plugin::builtin {
if (attr.getIOType() == dp::Attribute::IOType::In)
hasInput = true;
u32 attrId = node["attrs"][attrIndex];
maxAttrId = std::max(attrId, maxAttrId);
attr.setId(attrId);
attr.setId(node["attrs"][attrIndex]);
attrIndex++;
}
@ -525,23 +637,23 @@ namespace hex::plugin::builtin {
newNode->load(node["data"]);
if (hasInput && !hasOutput)
data.endNodes.push_back(newNode.get());
workspace.endNodes.push_back(newNode.get());
data.nodes.push_back(std::move(newNode));
ImNodes::SetNodeGridSpacePos(nodeId, ImVec2(node["pos"]["x"], node["pos"]["y"]));
workspace.nodes.push_back(std::move(newNode));
}
for (auto &link : input["links"]) {
int maxLinkId = 0;
for (auto &link : jsonData["links"]) {
dp::Link newLink(link["from"], link["to"]);
u32 linkId = link["id"];
int linkId = link["id"];
maxLinkId = std::max(linkId, maxLinkId);
newLink.setID(linkId);
data.links.push_back(newLink);
workspace.links.push_back(newLink);
dp::Attribute *fromAttr = nullptr, *toAttr = nullptr;
for (auto &node : data.nodes) {
for (auto &node : workspace.nodes) {
for (auto &attribute : node->getAttributes()) {
if (attribute.getId() == newLink.getFromId())
fromAttr = &attribute;
@ -566,11 +678,21 @@ namespace hex::plugin::builtin {
toAttr->addConnectedAttribute(newLink.getId(), fromAttr);
}
int maxNodeId = 0;
int maxAttrId = 0;
for (auto &node : workspace.nodes) {
maxNodeId = std::max(maxNodeId, node->getId());
for (auto &attr : node->getAttributes()) {
maxAttrId = std::max(maxAttrId, attr.getId());
}
}
dp::Node::setIdCounter(maxNodeId + 1);
dp::Attribute::setIdCounter(maxAttrId + 1);
dp::Link::setIdCounter(maxLinkId + 1);
this->processNodes();
ViewDataProcessor::processNodes(workspace);
}
}

View File

@ -20,10 +20,12 @@ namespace hex::plugin::windows {
// Explicitly trigger a segfault by writing to an invalid memory location
// Used for debugging crashes
*reinterpret_cast<u8 *>(0x10) = 0x10;
std::unreachable();
} else if (ImGui::GetIO().KeyShift) {
// Explicitly trigger an abort by throwing an uncaught exception
// Used for debugging exception errors
throw std::runtime_error("Debug Error");
std::unreachable();
} else {
hex::openWebpage("https://imhex.werwolv.net/debug");
}