1
0
mirror of synced 2024-12-04 20:17:57 +01:00
ImHex/plugins/builtin/source/ui/pattern_drawer.cpp

1319 lines
51 KiB
C++
Raw Normal View History

#include <ui/pattern_drawer.hpp>
#include <pl/patterns/pattern_array_dynamic.hpp>
#include <pl/patterns/pattern_array_static.hpp>
#include <pl/patterns/pattern_bitfield.hpp>
#include <pl/patterns/pattern_boolean.hpp>
#include <pl/patterns/pattern_character.hpp>
#include <pl/patterns/pattern_enum.hpp>
#include <pl/patterns/pattern_float.hpp>
#include <pl/patterns/pattern_pointer.hpp>
#include <pl/patterns/pattern_signed.hpp>
#include <pl/patterns/pattern_string.hpp>
#include <pl/patterns/pattern_struct.hpp>
#include <pl/patterns/pattern_union.hpp>
#include <pl/patterns/pattern_unsigned.hpp>
#include <pl/patterns/pattern_wide_character.hpp>
#include <pl/patterns/pattern_wide_string.hpp>
#include <string>
#include <hex/api/imhex_api.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/api/localization_manager.hpp>
#include <hex/helpers/utils.hpp>
2022-12-16 11:20:39 +01:00
#include <content/helpers/math_evaluator.hpp>
#include <imgui.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <fonts/codicons_font.h>
namespace hex::plugin::builtin::ui {
namespace {
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
std::mutex s_resetDrawMutex;
constexpr auto DisplayEndDefault = 50U;
using namespace ::std::literals::string_literals;
bool isPatternSelected(u64 address, u64 size) {
auto currSelection = ImHexApi::HexEditor::getSelection();
if (!currSelection.has_value())
return false;
return Region{ address, size }.overlaps(*currSelection);
}
template<typename T>
auto highlightWhenSelected(u64 address, u64 size, const T &callback) {
constexpr bool HasReturn = !requires(T t) { { t() } -> std::same_as<void>; };
auto selected = isPatternSelected(address, size);
if (selected)
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_PatternSelected));
if constexpr (HasReturn) {
auto result = callback();
if (selected)
ImGui::PopStyleColor();
return result;
} else {
callback();
if (selected)
ImGui::PopStyleColor();
}
}
template<typename T>
auto highlightWhenSelected(const pl::ptrn::Pattern& pattern, const T &callback) {
return highlightWhenSelected(pattern.getOffset(), pattern.getSize(), callback);
}
void drawTypenameColumn(const pl::ptrn::Pattern& pattern, const std::string& pattern_name) {
ImGuiExt::TextFormattedColored(ImColor(0xFFD69C56), pattern_name);
ImGui::SameLine();
ImGui::TextUnformatted(pattern.getTypeName().c_str());
ImGui::TableNextColumn();
}
void drawColorColumn(const pl::ptrn::Pattern& pattern) {
if (pattern.getVisibility() == pl::ptrn::Visibility::Visible)
ImGui::ColorButton("color", ImColor(pattern.getColor()), ImGuiColorEditFlags_NoTooltip, ImVec2(ImGui::GetColumnWidth(), ImGui::GetTextLineHeight()));
ImGui::TableNextColumn();
}
2023-11-10 20:47:08 +01:00
void drawOffsetColumnForBitfieldMember(const pl::ptrn::PatternBitfieldMember &pattern) {
if (pattern.isPatternLocal()) {
ImGuiExt::TextFormatted("[{}]", "hex.builtin.pattern_drawer.local"_lang);
ImGui::TableNextColumn();
ImGuiExt::TextFormatted("[{}]", "hex.builtin.pattern_drawer.local"_lang);
ImGui::TableNextColumn();
} else {
ImGuiExt::TextFormatted("0x{0:08X}, bit {1}", pattern.getOffset(), pattern.getBitOffsetForDisplay());
ImGui::TableNextColumn();
ImGuiExt::TextFormatted("0x{0:08X}, bit {1}", pattern.getOffset() + pattern.getSize(), pattern.getBitOffsetForDisplay() + pattern.getBitSize() - (pattern.getSize() == 0 ? 0 : 1));
ImGui::TableNextColumn();
}
}
void drawOffsetColumn(const pl::ptrn::Pattern& pattern) {
auto *bitfieldMember = dynamic_cast<pl::ptrn::PatternBitfieldMember const*>(&pattern);
if (bitfieldMember != nullptr && bitfieldMember->getParentBitfield() != nullptr) {
drawOffsetColumnForBitfieldMember(*bitfieldMember);
return;
}
if (pattern.isPatternLocal()) {
ImGuiExt::TextFormatted("[{}]", "hex.builtin.pattern_drawer.local"_lang);
} else {
ImGuiExt::TextFormatted("0x{0:08X}", pattern.getOffset());
}
ImGui::TableNextColumn();
if (pattern.isPatternLocal()) {
ImGuiExt::TextFormatted("[{}]", "hex.builtin.pattern_drawer.local"_lang);
} else {
ImGuiExt::TextFormatted("0x{0:08X}", pattern.getOffset() + pattern.getSize() - (pattern.getSize() == 0 ? 0 : 1));
}
ImGui::TableNextColumn();
}
2023-11-10 20:47:08 +01:00
void drawSizeColumnForBitfieldMember(const pl::ptrn::PatternBitfieldMember &pattern) {
if (pattern.getBitSize() == 1)
ImGuiExt::TextFormatted("1 bit");
else
ImGuiExt::TextFormatted("{0} bits", pattern.getBitSize());
}
void drawSizeColumn(const pl::ptrn::Pattern& pattern) {
if (auto *bitfieldMember = dynamic_cast<pl::ptrn::PatternBitfieldMember const*>(&pattern); bitfieldMember != nullptr && bitfieldMember->getParentBitfield() != nullptr)
drawSizeColumnForBitfieldMember(*bitfieldMember);
else
ImGuiExt::TextFormatted("0x{0:04X}", pattern.getSize());
ImGui::TableNextColumn();
}
void drawCommentTooltip(const pl::ptrn::Pattern &pattern) {
if (auto comment = pattern.getComment(); !comment.empty()) {
ImGuiExt::InfoTooltip(comment.c_str());
}
}
}
2023-06-04 16:13:46 +02:00
std::optional<PatternDrawer::Filter> PatternDrawer::parseRValueFilter(const std::string &filter) const {
Filter result;
if (filter.empty()) {
return result;
}
result.path.emplace_back();
for (size_t i = 0; i < filter.size(); i += 1) {
char c = filter[i];
if (i < filter.size() - 1 && c == '=' && filter[i + 1] == '=') {
try {
pl::core::Lexer lexer;
auto source = filter.substr(i + 2);
auto tokens = lexer.lex(filter.substr(i + 2), filter.substr(i + 2));
if (!tokens.has_value() || tokens->size() != 2)
return std::nullopt;
auto literal = std::get_if<pl::core::Token::Literal>(&tokens->front().value);
if (literal == nullptr)
return std::nullopt;
result.value = *literal;
2023-11-10 20:47:08 +01:00
} catch (pl::core::err::LexerError &) {
return std::nullopt;
2023-06-04 16:13:46 +02:00
}
break;
} else if (c == '.')
result.path.emplace_back();
else if (c == '[') {
result.path.emplace_back();
result.path.back() += c;
} else if (c == ' ') {
2023-11-10 20:47:08 +01:00
// Skip whitespace
} else {
result.path.back() += c;
}
2023-06-04 16:13:46 +02:00
}
return result;
}
bool PatternDrawer::isEditingPattern(const pl::ptrn::Pattern& pattern) const {
return this->m_editingPattern == &pattern && this->m_editingPatternOffset == pattern.getOffset();
}
void PatternDrawer::resetEditing() {
this->m_editingPattern = nullptr;
this->m_editingPatternOffset = 0x00;
}
2023-11-10 20:47:08 +01:00
bool PatternDrawer::matchesFilter(const std::vector<std::string> &filterPath, const std::vector<std::string> &patternPath, bool fullMatch) const {
if (fullMatch) {
if (patternPath.size() != filterPath.size())
return false;
}
if (patternPath.size() <= filterPath.size()) {
for (ssize_t i = patternPath.size() - 1; i >= 0; i--) {
2023-06-04 16:13:46 +02:00
const auto &filter = filterPath[i];
if (patternPath[i] != filter && !filter.empty() && filter != "*") {
2023-06-04 16:13:46 +02:00
return false;
}
}
}
return true;
}
void PatternDrawer::drawFavoriteColumn(const pl::ptrn::Pattern& pattern) {
if (this->m_rowColoring)
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, pattern.getColor() & 0x4FFFFFFF);
if (!this->m_showFavoriteStars) {
ImGui::TableNextColumn();
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
if (this->m_favorites.contains(this->m_currPatternPath)) {
if (ImGuiExt::DimmedIconButton(ICON_VS_STAR_DELETE, ImGui::GetStyleColorVec4(ImGuiCol_PlotHistogram))) {
this->m_favorites.erase(this->m_currPatternPath);
}
}
else {
if (ImGuiExt::DimmedIconButton(ICON_VS_STAR_ADD, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled))) {
this->m_favorites.insert({ this->m_currPatternPath, pattern.clone() });
}
}
ImGui::PopStyleVar();
ImGui::TableNextColumn();
}
void PatternDrawer::drawVisualizer(const std::map<std::string, ContentRegistry::PatternLanguage::impl::Visualizer> &visualizers, const std::vector<pl::core::Token::Literal> &arguments, pl::ptrn::Pattern &pattern, pl::ptrn::IIterable &iterable, bool reset) {
auto visualizerName = arguments.front().toString(true);
if (auto entry = visualizers.find(visualizerName); entry != visualizers.end()) {
const auto &[name, visualizer] = *entry;
auto paramCount = arguments.size() - 1;
auto [minParams, maxParams] = visualizer.parameterCount;
if (paramCount >= minParams && paramCount <= maxParams) {
try {
visualizer.callback(pattern, iterable, reset, { arguments.begin() + 1, arguments.end() });
} catch (std::exception &e) {
this->m_lastVisualizerError = e.what();
}
} else {
ImGui::TextUnformatted("hex.builtin.pattern_drawer.visualizer.invalid_parameter_count"_lang);
}
} else {
ImGui::TextUnformatted("hex.builtin.pattern_drawer.visualizer.unknown"_lang);
}
if (!this->m_lastVisualizerError.empty())
ImGui::TextUnformatted(this->m_lastVisualizerError.c_str());
}
2023-01-12 11:18:36 +01:00
void PatternDrawer::drawValueColumn(pl::ptrn::Pattern& pattern) {
std::string value;
try {
value = pattern.getFormattedValue();
} catch (const std::exception &e) {
value = e.what();
}
const auto width = ImGui::GetColumnWidth();
if (const auto &visualizeArgs = pattern.getAttributeArguments("hex::visualize"); !visualizeArgs.empty()) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5F));
bool shouldReset = false;
if (ImGui::Button(hex::format(" {} {}", ICON_VS_EYE_WATCH, value).c_str(), ImVec2(width, ImGui::GetTextLineHeight()))) {
auto previousPattern = this->m_currVisualizedPattern;
this->m_currVisualizedPattern = &pattern;
this->m_lastVisualizerError.clear();
if (this->m_currVisualizedPattern != previousPattern)
shouldReset = true;
ImGui::OpenPopup("Visualizer");
}
ImGui::PopStyleVar(2);
ImGui::SameLine();
if (ImGui::BeginPopup("Visualizer")) {
if (this->m_currVisualizedPattern == &pattern) {
drawVisualizer(ContentRegistry::PatternLanguage::impl::getVisualizers(), visualizeArgs, pattern, dynamic_cast<pl::ptrn::IIterable&>(pattern), !this->m_visualizedPatterns.contains(&pattern) || shouldReset);
this->m_visualizedPatterns.insert(&pattern);
}
ImGui::EndPopup();
}
} else if (const auto &inlineVisualizeArgs = pattern.getAttributeArguments("hex::inline_visualize"); !inlineVisualizeArgs.empty()) {
drawVisualizer(ContentRegistry::PatternLanguage::impl::getInlineVisualizers(), inlineVisualizeArgs, pattern, dynamic_cast<pl::ptrn::IIterable&>(pattern), true);
} else {
ImGuiExt::TextFormatted("{}", value);
}
if (ImGui::CalcTextSize(value.c_str()).x > width) {
ImGuiExt::InfoTooltip(value.c_str());
}
}
2023-07-23 09:14:00 +02:00
std::string PatternDrawer::getDisplayName(const pl::ptrn::Pattern& pattern) const {
if (this->m_showSpecName && pattern.hasAttribute("hex::spec_name"))
return pattern.getAttributeArguments("hex::spec_name")[0].toString(true);
else
return pattern.getDisplayName();
}
bool PatternDrawer::createTreeNode(const pl::ptrn::Pattern& pattern, bool leaf) {
drawFavoriteColumn(pattern);
if (pattern.isSealed() || leaf) {
ImGui::Indent();
2023-07-23 09:14:00 +02:00
highlightWhenSelected(pattern, [&]{ ImGui::TextUnformatted(this->getDisplayName(pattern).c_str()); });
ImGui::Unindent();
return false;
}
return highlightWhenSelected(pattern, [&]{
switch (this->m_treeStyle) {
using enum TreeStyle;
default:
case Default:
return ImGui::TreeNodeEx(this->getDisplayName(pattern).c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
case AutoExpanded:
return ImGui::TreeNodeEx(this->getDisplayName(pattern).c_str(), ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen);
case Flattened:
return ImGui::TreeNodeEx(this->getDisplayName(pattern).c_str(), ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen);
}
});
}
void PatternDrawer::makeSelectable(const pl::ptrn::Pattern &pattern) {
ImGui::PushID(static_cast<int>(pattern.getOffset()));
ImGui::PushID(pattern.getVariableName().c_str());
2023-11-15 20:22:56 +01:00
if (ImGui::Selectable("##PatternLine", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
this->m_selectionCallback(Region { pattern.getOffset(), pattern.getSize() });
if (this->m_editingPattern != &pattern) {
this->resetEditing();
}
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
this->m_editingPattern = &pattern;
this->m_editingPatternOffset = pattern.getOffset();
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.modify_data.name");
}
ImGui::SameLine(0, 0);
ImGui::PopID();
ImGui::PopID();
}
2023-11-10 20:47:08 +01:00
void PatternDrawer::createDefaultEntry(const pl::ptrn::Pattern &pattern) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
createTreeNode(pattern, true);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
drawColorColumn(pattern);
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
ImGuiExt::TextFormattedColored(ImColor(0xFF9BC64D), "{}", pattern.getFormattedName().empty() ? pattern.getTypeName() : pattern.getFormattedName());
ImGui::TableNextColumn();
}
2023-11-10 20:47:08 +01:00
void PatternDrawer::closeTreeNode(bool inlined) const {
if (!inlined && this->m_treeStyle != TreeStyle::Flattened)
ImGui::TreePop();
}
void PatternDrawer::visit(pl::ptrn::PatternArrayDynamic& pattern) {
drawArray(pattern, pattern, pattern.isInlined());
}
void PatternDrawer::visit(pl::ptrn::PatternArrayStatic& pattern) {
drawArray(pattern, pattern, pattern.isInlined());
}
void PatternDrawer::visit(pl::ptrn::PatternBitfieldField& pattern) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
2023-06-06 11:29:58 +02:00
createTreeNode(pattern, true);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
2023-06-06 11:29:58 +02:00
ImGui::TableNextColumn();
drawColorColumn(pattern);
drawOffsetColumnForBitfieldMember(pattern);
drawSizeColumnForBitfieldMember(pattern);
ImGui::TableNextColumn();
ImGuiExt::TextFormattedColored(ImColor(0xFF9BC64D), "bits");
ImGui::TableNextColumn();
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = pattern.getValue();
auto valueString = pattern.toString();
if (pattern.getBitSize() == 1) {
bool boolValue = value.toBoolean();
if (ImGui::Checkbox("##boolean", &boolValue)) {
pattern.setValue(boolValue);
}
} else if (std::holds_alternative<i128>(value)) {
if (ImGui::InputText("##Value", valueString, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
MathEvaluator<i128> mathEvaluator;
if (auto result = mathEvaluator.evaluate(valueString); result.has_value())
pattern.setValue(result.value());
this->resetEditing();
}
} else if (std::holds_alternative<u128>(value)) {
if (ImGui::InputText("##Value", valueString, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
MathEvaluator<u128> mathEvaluator;
if (auto result = mathEvaluator.evaluate(valueString); result.has_value())
pattern.setValue(result.value());
this->resetEditing();
}
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternBitfieldArray& pattern) {
drawArray(pattern, pattern, pattern.isInlined());
}
void PatternDrawer::visit(pl::ptrn::PatternBitfield& pattern) {
bool open = true;
if (!pattern.isInlined() && this->m_treeStyle != TreeStyle::Flattened) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
open = createTreeNode(pattern);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
if (pattern.isSealed())
drawColorColumn(pattern);
else
ImGui::TableNextColumn();
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
drawTypenameColumn(pattern, "bitfield");
drawValueColumn(pattern);
}
if (!open) {
return;
}
int id = 1;
pattern.forEachEntry(0, pattern.getEntryCount(), [&] (u64, auto *field) {
ImGui::PushID(id);
this->draw(*field);
ImGui::PopID();
id += 1;
});
closeTreeNode(pattern.isInlined());
}
void PatternDrawer::visit(pl::ptrn::PatternBoolean& pattern) {
createDefaultEntry(pattern);
2022-12-16 11:20:39 +01:00
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
2022-12-16 11:20:39 +01:00
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
2022-12-16 11:20:39 +01:00
bool value = pattern.getValue().toBoolean();
if (ImGui::Checkbox("##boolean", &value)) {
pattern.setValue(value);
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternCharacter& pattern) {
createDefaultEntry(pattern);
2022-12-16 11:20:39 +01:00
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = hex::encodeByteString(pattern.getBytes());
if (ImGui::InputText("##Character", value.data(), value.size() + 1, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
if (!value.empty()) {
auto result = hex::decodeByteString(value);
if (!result.empty())
pattern.setValue(char(result[0]));
this->resetEditing();
2022-12-16 11:20:39 +01:00
}
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternEnum& pattern) {
ImGui::TableNextRow();
2023-06-06 11:29:58 +02:00
ImGui::TableNextColumn();
createTreeNode(pattern, true);
2023-06-06 11:29:58 +02:00
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
drawColorColumn(pattern);
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
drawTypenameColumn(pattern, "enum");
2022-12-16 11:20:39 +01:00
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##Enum", pattern.getFormattedValue().c_str())) {
auto currValue = pattern.getValue().toUnsigned();
for (auto &value : pattern.getEnumValues()) {
auto min = value.min.toUnsigned();
auto max = value.max.toUnsigned();
bool isSelected = min <= currValue && max >= currValue;
if (ImGui::Selectable(fmt::format("{}::{} (0x{:0{}X})", pattern.getTypeName(), value.name, min, pattern.getSize() * 2).c_str(), isSelected)) {
pattern.setValue(value.min);
this->resetEditing();
2022-12-16 11:20:39 +01:00
}
if (isSelected)
ImGui::SetItemDefaultFocus();
2022-12-16 11:20:39 +01:00
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternFloat& pattern) {
createDefaultEntry(pattern);
2022-12-16 11:20:39 +01:00
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
2022-12-16 11:20:39 +01:00
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = pattern.toString();
if (ImGui::InputText("##Value", value, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
MathEvaluator<long double> mathEvaluator;
2022-12-16 11:20:39 +01:00
if (auto result = mathEvaluator.evaluate(value); result.has_value())
pattern.setValue(double(result.value()));
this->resetEditing();
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternPadding& pattern) {
// Do nothing
hex::unused(pattern);
}
void PatternDrawer::visit(pl::ptrn::PatternPointer& pattern) {
bool open = true;
if (!pattern.isInlined() && this->m_treeStyle != TreeStyle::Flattened) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
open = createTreeNode(pattern);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
drawColorColumn(pattern);
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
ImGuiExt::TextFormattedColored(ImColor(0xFF9BC64D), "{}", pattern.getFormattedName());
ImGui::TableNextColumn();
drawValueColumn(pattern);
}
if (open) {
pattern.getPointedAtPattern()->accept(*this);
closeTreeNode(pattern.isInlined());
}
}
void PatternDrawer::visit(pl::ptrn::PatternSigned& pattern) {
createDefaultEntry(pattern);
2022-12-16 11:20:39 +01:00
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
2022-12-16 11:20:39 +01:00
auto value = pattern.getFormattedValue();
if (ImGui::InputText("##Value", value, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
MathEvaluator<i128> mathEvaluator;
2022-12-16 11:20:39 +01:00
if (auto result = mathEvaluator.evaluate(value); result.has_value())
pattern.setValue(result.value());
this->resetEditing();
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternString& pattern) {
2022-12-16 11:20:39 +01:00
if (pattern.getSize() > 0) {
createDefaultEntry(pattern);
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = pattern.toString();
if (ImGui::InputText("##Value", value.data(), value.size() + 1, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
pattern.setValue(value);
this->resetEditing();
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
2022-12-16 11:20:39 +01:00
}
void PatternDrawer::visit(pl::ptrn::PatternStruct& pattern) {
bool open = true;
if (!pattern.isInlined() && this->m_treeStyle != TreeStyle::Flattened) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
open = createTreeNode(pattern);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
if (pattern.isSealed())
drawColorColumn(pattern);
else
ImGui::TableNextColumn();
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
drawTypenameColumn(pattern, "struct");
if (this->isEditingPattern(pattern) && !pattern.getWriteFormatterFunction().empty()) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = pattern.toString();
if (ImGui::InputText("##Value", value, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
pattern.setValue(value);
this->resetEditing();
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
} else {
drawValueColumn(pattern);
}
}
if (!open) {
return;
}
int id = 1;
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, auto *member){
ImGui::PushID(id);
this->draw(*member);
ImGui::PopID();
id += 1;
});
closeTreeNode(pattern.isInlined());
}
void PatternDrawer::visit(pl::ptrn::PatternUnion& pattern) {
bool open = true;
if (!pattern.isInlined() && this->m_treeStyle != TreeStyle::Flattened) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
open = createTreeNode(pattern);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
if (pattern.isSealed())
drawColorColumn(pattern);
else
ImGui::TableNextColumn();
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
drawTypenameColumn(pattern, "union");
if (this->isEditingPattern(pattern) && !pattern.getWriteFormatterFunction().empty()) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = pattern.toString();
if (ImGui::InputText("##Value", value, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
pattern.setValue(value);
this->resetEditing();
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
} else {
drawValueColumn(pattern);
}
}
if (!open) {
return;
}
int id = 1;
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, auto *member) {
ImGui::PushID(id);
this->draw(*member);
ImGui::PopID();
id += 1;
});
closeTreeNode(pattern.isInlined());
}
void PatternDrawer::visit(pl::ptrn::PatternUnsigned& pattern) {
createDefaultEntry(pattern);
2022-12-16 11:20:39 +01:00
if (!this->isEditingPattern(pattern)) {
drawValueColumn(pattern);
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
auto value = pattern.toString();
if (ImGui::InputText("##Value", value, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
MathEvaluator<u128> mathEvaluator;
2022-12-16 11:20:39 +01:00
if (auto result = mathEvaluator.evaluate(value); result.has_value())
pattern.setValue(result.value());
this->resetEditing();
2022-12-16 11:20:39 +01:00
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
}
void PatternDrawer::visit(pl::ptrn::PatternWideCharacter& pattern) {
createDefaultEntry(pattern);
drawValueColumn(pattern);
}
void PatternDrawer::visit(pl::ptrn::PatternWideString& pattern) {
2022-12-16 11:20:39 +01:00
if (pattern.getSize() > 0) {
createDefaultEntry(pattern);
drawValueColumn(pattern);
2022-12-16 11:20:39 +01:00
}
}
void PatternDrawer::draw(pl::ptrn::Pattern& pattern) {
2023-01-27 10:45:07 +01:00
if (pattern.getVisibility() == pl::ptrn::Visibility::Hidden)
return;
this->m_currPatternPath.push_back(pattern.getVariableName());
2023-06-04 16:13:46 +02:00
ON_SCOPE_EXIT { this->m_currPatternPath.pop_back(); };
if (matchesFilter(this->m_filter.path, this->m_currPatternPath, false)) {
if (this->m_filter.value.has_value()) {
auto patternValue = pattern.getValue();
if (patternValue == this->m_filter.value)
pattern.accept(*this);
else if (!matchesFilter(this->m_filter.path, this->m_currPatternPath, true))
pattern.accept(*this);
else if (patternValue.isPattern() && this->m_filter.value->isString()) {
if (patternValue.toString(true) == this->m_filter.value->toString(false))
pattern.accept(*this);
}
} else {
pattern.accept(*this);
}
}
}
void PatternDrawer::drawArray(pl::ptrn::Pattern& pattern, pl::ptrn::IIterable &iterable, bool isInlined) {
if (iterable.getEntryCount() == 0)
return;
bool open = true;
if (!isInlined && this->m_treeStyle != TreeStyle::Flattened) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
open = createTreeNode(pattern);
ImGui::SameLine(0, 0);
makeSelectable(pattern);
drawCommentTooltip(pattern);
ImGui::TableNextColumn();
if (pattern.isSealed())
drawColorColumn(pattern);
else
ImGui::TableNextColumn();
drawOffsetColumn(pattern);
drawSizeColumn(pattern);
ImGuiExt::TextFormattedColored(ImColor(0xFF9BC64D), "{0}", pattern.getTypeName());
ImGui::SameLine(0, 0);
ImGui::TextUnformatted("[");
ImGui::SameLine(0, 0);
ImGuiExt::TextFormattedColored(ImColor(0xFF00FF00), "{0}", iterable.getEntryCount());
ImGui::SameLine(0, 0);
ImGui::TextUnformatted("]");
ImGui::TableNextColumn();
drawValueColumn(pattern);
}
if (!open) {
return;
}
u64 chunkCount = 0;
for (u64 i = 0; i < iterable.getEntryCount(); i += ChunkSize) {
chunkCount++;
auto &displayEnd = this->getDisplayEnd(pattern);
if (chunkCount > displayEnd) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::Selectable(hex::format("... ({})", "hex.builtin.pattern_drawer.double_click"_lang).c_str(), false, ImGuiSelectableFlags_SpanAllColumns);
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
displayEnd += DisplayEndStep;
break;
}
auto endIndex = std::min<u64>(iterable.getEntryCount(), i + ChunkSize);
bool chunkOpen = true;
if (iterable.getEntryCount() > ChunkSize) {
auto startOffset = iterable.getEntry(i)->getOffset();
auto endOffset = iterable.getEntry(endIndex - 1)->getOffset();
auto endSize = iterable.getEntry(endIndex - 1)->getSize();
size_t chunkSize = (endOffset - startOffset) + endSize;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
chunkOpen = highlightWhenSelected(startOffset, ((endOffset + endSize) - startOffset) - 1, [&]{
return ImGui::TreeNodeEx(hex::format("{0}[{1} ... {2}]", this->m_treeStyle == TreeStyle::Flattened ? this->getDisplayName(pattern).c_str() : "", i, endIndex - 1).c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
});
ImGui::TableNextColumn();
ImGui::TableNextColumn();
drawOffsetColumn(pattern);
ImGuiExt::TextFormatted("0x{0:04X}", chunkSize);
ImGui::TableNextColumn();
ImGuiExt::TextFormattedColored(ImColor(0xFF9BC64D), "{0}", pattern.getTypeName());
ImGui::SameLine(0, 0);
ImGui::TextUnformatted("[");
ImGui::SameLine(0, 0);
ImGuiExt::TextFormattedColored(ImColor(0xFF00FF00), "{0}", endIndex - i);
ImGui::SameLine(0, 0);
ImGui::TextUnformatted("]");
ImGui::TableNextColumn();
ImGuiExt::TextFormatted("[ ... ]");
}
if (!chunkOpen) {
continue;
}
int id = 1;
iterable.forEachEntry(i, endIndex, [&](u64, auto *entry){
ImGui::PushID(id);
this->draw(*entry);
ImGui::PopID();
id += 1;
});
if (iterable.getEntryCount() > ChunkSize)
ImGui::TreePop();
}
closeTreeNode(isInlined);
}
u64& PatternDrawer::getDisplayEnd(const pl::ptrn::Pattern& pattern) {
auto it = this->m_displayEnd.find(&pattern);
if (it != this->m_displayEnd.end()) {
return it->second;
}
auto [value, success] = this->m_displayEnd.emplace(&pattern, DisplayEndDefault);
return value->second;
}
2023-07-23 09:14:00 +02:00
bool PatternDrawer::sortPatterns(const ImGuiTableSortSpecs* sortSpecs, const pl::ptrn::Pattern * left, const pl::ptrn::Pattern * right) const {
if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("name")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
2023-07-23 09:14:00 +02:00
return this->getDisplayName(*left) < this->getDisplayName(*right);
2023-03-16 13:35:09 +01:00
else
2023-07-23 09:14:00 +02:00
return this->getDisplayName(*left) > this->getDisplayName(*right);
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("start")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
return left->getOffsetForSorting() < right->getOffsetForSorting();
2023-03-16 13:35:09 +01:00
else
return left->getOffsetForSorting() > right->getOffsetForSorting();
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("end")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
return left->getOffsetForSorting() + left->getSize() < right->getOffsetForSorting() + right->getSize();
else
return left->getOffsetForSorting() + left->getSize() > right->getOffsetForSorting() + right->getSize();
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("size")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
return left->getSizeForSorting() < right->getSizeForSorting();
2023-03-16 13:35:09 +01:00
else
return left->getSizeForSorting() > right->getSizeForSorting();
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("value")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
2023-05-24 21:04:59 +02:00
return left->getValue().toString(true) < right->getValue().toString(true);
2023-03-16 13:35:09 +01:00
else
2023-05-24 21:04:59 +02:00
return left->getValue().toString(true) > right->getValue().toString(true);
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("type")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
return left->getTypeName() < right->getTypeName();
2023-03-16 13:35:09 +01:00
else
return left->getTypeName() > right->getTypeName();
} else if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("color")) {
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending)
return left->getColor() < right->getColor();
2023-03-16 13:35:09 +01:00
else
return left->getColor() > right->getColor();
}
return false;
}
2023-11-10 20:47:08 +01:00
bool PatternDrawer::beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<pl::ptrn::Pattern*> &sortedPatterns, float height) const {
if (!ImGui::BeginTable("##Patterntable", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, height))) {
return false;
}
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("##favorite", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_IndentDisable, ImGui::GetTextLineHeight(), ImGui::GetID("favorite"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.var_name"_lang, ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_IndentEnable, 0, ImGui::GetID("name"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.color"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("color"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.start"_lang, ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort, 0, ImGui::GetID("start"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.end"_lang, ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort, 0, ImGui::GetID("end"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.size"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("size"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.type"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("type"));
ImGui::TableSetupColumn("hex.builtin.pattern_drawer.value"_lang, ImGuiTableColumnFlags_PreferSortAscending, 0, ImGui::GetID("value"));
auto sortSpecs = ImGui::TableGetSortSpecs();
if (patterns.empty()) {
sortedPatterns.clear();
return true;
}
if (!sortSpecs->SpecsDirty && !sortedPatterns.empty()) {
return true;
}
sortedPatterns.clear();
std::transform(patterns.begin(), patterns.end(), std::back_inserter(sortedPatterns), [](const std::shared_ptr<pl::ptrn::Pattern> &pattern) {
return pattern.get();
});
2023-11-10 20:47:08 +01:00
std::sort(sortedPatterns.begin(), sortedPatterns.end(), [this, &sortSpecs](const pl::ptrn::Pattern *left, const pl::ptrn::Pattern *right) -> bool {
return this->sortPatterns(sortSpecs, left, right);
});
for (auto &pattern : sortedPatterns)
pattern->sort([this, &sortSpecs](const pl::ptrn::Pattern *left, const pl::ptrn::Pattern *right){
return this->sortPatterns(sortSpecs, left, right);
});
sortSpecs->SpecsDirty = false;
return true;
}
void PatternDrawer::traversePatternTree(pl::ptrn::Pattern &pattern, std::vector<std::string> &patternPath, const std::function<void(pl::ptrn::Pattern&)> &callback) {
patternPath.push_back(pattern.getVariableName());
ON_SCOPE_EXIT { patternPath.pop_back(); };
callback(pattern);
if (auto iterable = dynamic_cast<pl::ptrn::IIterable*>(&pattern); iterable != nullptr) {
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, pl::ptrn::Pattern *entry) {
traversePatternTree(*entry, patternPath, callback);
});
}
}
2023-11-10 20:47:08 +01:00
void PatternDrawer::draw(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, const pl::PatternLanguage *runtime, float height) {
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
std::scoped_lock lock(s_resetDrawMutex);
const auto treeStyleButton = [this](auto icon, TreeStyle style, const char *tooltip) {
bool pushed = false;
if (this->m_treeStyle == style) {
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
pushed = true;
}
if (ImGuiExt::DimmedIconButton(icon, ImGui::GetStyleColorVec4(ImGuiCol_Text)))
this->m_treeStyle = style;
if (pushed)
ImGui::PopStyleColor();
ImGuiExt::InfoTooltip(tooltip);
};
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGui::IsAnyItemHovered()) {
this->resetEditing();
}
2023-07-23 09:14:00 +02:00
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeightWithSpacing() * 9.5);
if (ImGuiExt::InputTextIcon("##Search", ICON_VS_FILTER, this->m_filterText)) {
2023-11-10 20:47:08 +01:00
this->m_filter = parseRValueFilter(this->m_filterText).value_or(Filter{ });
2023-06-04 16:13:46 +02:00
}
ImGui::PopItemWidth();
ImGui::SameLine();
ImGuiExt::DimmedIconToggle(ICON_VS_BOOK, &this->m_showSpecName);
ImGuiExt::InfoTooltip("hex.builtin.pattern_drawer.spec_name"_lang);
2023-07-23 09:14:00 +02:00
ImGui::SameLine();
treeStyleButton(ICON_VS_SYMBOL_KEYWORD, TreeStyle::Default, "hex.builtin.pattern_drawer.tree_style.tree"_lang);
ImGui::SameLine(0, 0);
treeStyleButton(ICON_VS_LIST_TREE, TreeStyle::AutoExpanded, "hex.builtin.pattern_drawer.tree_style.auto_expanded"_lang);
ImGui::SameLine(0, 0);
treeStyleButton(ICON_VS_LIST_FLAT, TreeStyle::Flattened, "hex.builtin.pattern_drawer.tree_style.flattened"_lang);
ImGui::SameLine(0, 15_scaled);
const auto startPos = ImGui::GetCursorPos();
ImGui::BeginDisabled(runtime == nullptr);
if (ImGuiExt::DimmedIconButton(ICON_VS_EXPORT, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
ImGui::OpenPopup("ExportPatterns");
}
ImGui::EndDisabled();
ImGuiExt::InfoTooltip("hex.builtin.pattern_drawer.export"_lang);
ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImVec2(startPos.x, ImGui::GetCursorPosY()));
if (ImGui::BeginPopup("ExportPatterns")) {
for (const auto &formatter : this->m_formatters) {
const auto name = [&]{
auto name = formatter->getName();
std::transform(name.begin(), name.end(), name.begin(), [](char c){ return char(std::toupper(c)); });
return name;
}();
const auto &extension = formatter->getFileExtension();
if (ImGui::MenuItem(name.c_str())) {
fs::openFileBrowser(fs::DialogMode::Save, { { name.c_str(), extension.c_str() } }, [&](const std::fs::path &path) {
auto result = formatter->format(*runtime);
wolv::io::File output(path, wolv::io::File::Mode::Create);
output.writeVector(result);
});
}
}
ImGui::EndPopup();
}
if (!this->m_favoritesUpdated) {
this->m_favoritesUpdated = true;
if (!patterns.empty() && !this->m_favoritesUpdateTask.isRunning()) {
this->m_favoritesUpdateTask = TaskManager::createTask("hex.builtin.pattern_drawer.updating"_lang, TaskManager::NoProgress, [this, patterns](auto &task) {
size_t updatedFavorites = 0;
for (auto &pattern : patterns) {
std::vector<std::string> patternPath;
2023-11-10 20:47:08 +01:00
traversePatternTree(*pattern, patternPath, [&, this](const pl::ptrn::Pattern &pattern) {
if (pattern.hasAttribute("hex::favorite"))
this->m_favorites.insert({ patternPath, pattern.clone() });
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
if (const auto &args = pattern.getAttributeArguments("hex::group"); !args.empty()) {
auto groupName = args.front().toString();
if (!this->m_groups.contains(groupName))
this->m_groups.insert({groupName, std::vector<std::unique_ptr<pl::ptrn::Pattern>>()});
this->m_groups[groupName].push_back(pattern.clone());
}
});
if (updatedFavorites == this->m_favorites.size())
task.interrupt();
task.update();
patternPath.clear();
2023-11-10 20:47:08 +01:00
traversePatternTree(*pattern, patternPath, [&, this](const pl::ptrn::Pattern &pattern) {
for (auto &[path, favoritePattern] : this->m_favorites) {
if (updatedFavorites == this->m_favorites.size())
task.interrupt();
task.update();
if (this->matchesFilter(patternPath, path, true)) {
favoritePattern = pattern.clone();
updatedFavorites += 1;
break;
}
}
});
}
std::erase_if(this->m_favorites, [](const auto &entry) {
const auto &[path, favoritePattern] = entry;
return favoritePattern == nullptr;
});
});
}
}
if (beginPatternTable(patterns, this->m_sortedPatterns, height)) {
ImGui::TableHeadersRow();
this->m_showFavoriteStars = false;
if (!this->m_favoritesUpdateTask.isRunning()) {
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
int id = 1;
bool doTableNextRow = false;
if (!this->m_favorites.empty() && !patterns.empty()) {
ImGui::TableNextColumn();
ImGui::TableNextColumn();
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
ImGui::PushID(id);
if (ImGui::TreeNodeEx("hex.builtin.pattern_drawer.favorites"_lang, ImGuiTreeNodeFlags_SpanFullWidth)) {
for (auto &[path, pattern] : this->m_favorites) {
if (pattern == nullptr)
continue;
ImGui::PushID(pattern->getDisplayName().c_str());
this->draw(*pattern);
ImGui::PopID();
}
ImGui::TreePop();
}
ImGui::PopID();
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
id += 1;
doTableNextRow = true;
}
if (!this->m_groups.empty() && !patterns.empty()) {
for (auto &[groupName, groupPatterns]: this->m_groups) {
2023-11-10 20:47:08 +01:00
if (doTableNextRow) {
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
ImGui::TableNextRow();
}
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::PushID(id);
if (ImGui::TreeNodeEx(groupName.c_str(), ImGuiTreeNodeFlags_SpanFullWidth)) {
for (auto &groupPattern: groupPatterns) {
if (groupPattern == nullptr)
continue;
ImGui::PushID(id);
this->draw(*groupPattern);
ImGui::PopID();
id += 1;
}
ImGui::TreePop();
}
ImGui::PopID();
id += 1;
doTableNextRow = true;
}
}
this->m_showFavoriteStars = true;
for (auto &pattern : this->m_sortedPatterns) {
ImGui::PushID(id);
this->draw(*pattern);
ImGui::PopID();
id += 1;
}
}
ImGui::EndTable();
}
if (this->m_favoritesUpdateTask.isRunning()) {
ImGuiExt::TextOverlay("hex.builtin.pattern_drawer.updating"_lang, ImGui::GetWindowPos() + ImGui::GetWindowSize() / 2);
}
}
void PatternDrawer::reset() {
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
std::scoped_lock lock(s_resetDrawMutex);
this->resetEditing();
this->m_displayEnd.clear();
this->m_visualizedPatterns.clear();
this->m_currVisualizedPattern = nullptr;
this->m_sortedPatterns.clear();
this->m_lastVisualizerError.clear();
2023-06-04 16:13:46 +02:00
this->m_currPatternPath.clear();
this->m_favoritesUpdateTask.interrupt();
for (auto &[path, pattern] : this->m_favorites)
pattern = nullptr;
feat: Added hex::group attribute and various fixes (#1302) As discussed (many times) on Discord, does the same as the new favorite tag, but instead allows you to add multiple groups. Initially, this would cause some insane issues with draw/reset (apparantly) fighting eachother in the pattern drawer. After a lot of trial and error, I decided to rewrite the flow that is responsible for calling reset. Now evaluating patterns is the one to decide when the reset happens, not the core "game"-loop. To make sure that draw and reset can never happen at the same time, the mutex originally used for the favorites has been repurposed. Due to the restructuring, the mutex in the favorite-task is no longer needed, as that will only ever kick-off after reset is called and if there are actually patterns, which can never line up to be accessed on different threads at the same time. Last but not least, I noticed that hard crashes could result in your config file getting overridden. I added a check to prevent that. Last I issue I can see is that if you use an excessive amount of favorites/groups, a crash can still happen, but it only happens when you close the program (occasionally, but unpredictable). Before, this would happen if you ran the evaluation a second time. I boiled the cause of the crash down to these lines of code in evaluator.cpp > patternDestroyed: ```cpp if (pattern->isPatternLocal()) { if (auto it = this->m_patternLocalStorage.find(pattern->getHeapAddress()); it != this->m_patternLocalStorage.end()) { auto &[key, data] = *it; data.referenceCount--; if (data.referenceCount == 0) this->m_patternLocalStorage.erase(it); } else if (!this->m_evaluated) { err::E0001.throwError(fmt::format("Double free of variable named '{}'.", pattern->getVariableName())); } } ``` Specifically, trying to access the `*it` is the reason for the crash (this was also the cause of the crashes before my fixes, but then during evaluation). I'm suspecting the root cause is somewhere in the `.clone` methods of the patterns. I'd say that for now a crash when closing the program is more acceptable than during evaluation (which can even happen if you use favorites).
2023-09-16 13:09:59 +02:00
for (auto &[groupName, patterns]: this->m_groups)
for (auto &pattern: patterns)
pattern = nullptr;
this->m_groups.clear();
this->m_favoritesUpdated = false;
}
}