From 00d2245538f397adbdadc06c5802359cf1413e24 Mon Sep 17 00:00:00 2001 From: optizone <42874998+optizone@users.noreply.github.com> Date: Mon, 15 Feb 2021 09:46:57 +0300 Subject: [PATCH] refactor DisassemblyLeftPanel::paintEvent (#2559) --- src/common/BinaryTrees.h | 108 ++++++++++++++++ src/widgets/DisassemblyWidget.cpp | 197 +++++++++++++++++------------- src/widgets/DisassemblyWidget.h | 37 ++++++ 3 files changed, 255 insertions(+), 87 deletions(-) diff --git a/src/common/BinaryTrees.h b/src/common/BinaryTrees.h index d229736b..077aaab8 100644 --- a/src/common/BinaryTrees.h +++ b/src/common/BinaryTrees.h @@ -396,4 +396,112 @@ public: } }; + +/** + * @brief Structure for keeping track of minimum and maximum value set at each position. + * + * Supports range update and range query. + * + * Example: + * @code{.cpp} + * MinMaxAccumulateTree t(30); // operate within range [0; 30) + * t.updateRange(1, 5, 10); + * rangeMinMax(0, 20);// -> {10, 10} + * t.updateRange(4, 6, 15); + * t.updateRange(3, 10, 20); + * t.rangeMinMax(0, 20); // -> {10, 20} + * t.rangeMinMax(1, 3); // -> {10, 10} + * t.rangeMinMax(5, 8); // -> {15, 20} + * @endcode + */ +template +class MinMaxAccumulateTree : public LazySegmentTreeBase, std::pair, MinMaxAccumulateTree> +{ + // Could work with other types but that would require changing LIMITS + static_assert (std::is_integral::value, "Template argument IntegerType must be integer"); + using MinMax = std::pair; + using ValueType = MinMax; + using ThisType = MinMaxAccumulateTree; + using BaseType = LazySegmentTreeBase; + using NodeType = typename BaseType::NodeType; + using NodePosition = typename BaseType::NodePosition; + + static constexpr MinMax LIMITS() + { + return {std::numeric_limits::max(), + std::numeric_limits::min()}; + } + + static MinMax Combine(const MinMax &a, const MinMax &b) + { + return {std::min(a.first, b.first), std::max(a.second, b.second)}; + } + + void UpdateNode(NodePosition nodePos, ValueType value) + { + this->nodes[nodePos] = Combine(this->nodes[nodePos], value); + if (!this->isLeave(nodePos)) { + this->promise[nodePos] = Combine(this->promise[nodePos], value); + } + } + +public: + MinMaxAccumulateTree(size_t size, ValueType initialValue = LIMITS()) + : BaseType(size, initialValue, LIMITS()) + { + } + + void updateFromChildren(NodeType &parent, const NodeType &left, const NodeType &right) + { + parent = Combine(left, right); + } + + void pushDown(NodePosition parent) + { + size_t left = (parent << 1); + size_t right = (parent << 1) | 1; + this->UpdateNode(left, this->promise[parent]); + this->UpdateNode(right, this->promise[parent]); + this->promise[parent] = this->neutralPromiseElement; + } + + /** + * @brief Update min and max values in the range [\a left, \a right) with number \a value. + * @param left inclusive range left side + * @param right exclusive right side of range + * @param value number to be used for updating minimum and maximum + */ + void updateRange(size_t left, size_t right, IntegerType value) + { + left = this->leaveIndexToPosition(left); + right = this->leaveIndexToPosition(right); + this->pushDownFromRoot(left); + this->pushDownFromRoot(right - 1); + MinMax pairValue{value, value}; + for (size_t l = left, r = right; l < r; l >>= 1, r >>= 1) { + if (l & 1) { + UpdateNode(l, pairValue); + l += 1; + } + if (r & 1) { + r -= 1; + UpdateNode(r, pairValue); + } + } + this->updateUntilRoot(left); + this->updateUntilRoot(right - 1); + } + + /** + * @brief Calculate minimum and maximum value in the range [l, r) + * @param l inclusive left side of range + * @param r exclusive right side of range + * @return std::pair {min, max} + */ + MinMax rangeMinMax(size_t l, size_t r) + { + return this->rangeOperation(l, r, this->neutralPromiseElement); + } +}; + #endif // BINARY_TREES_H diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 32ef79dc..ca6139cf 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -4,6 +4,7 @@ #include "common/Helpers.h" #include "common/TempConfig.h" #include "common/SelectionHighlight.h" +#include "common/BinaryTrees.h" #include "core/MainWindow.h" #include @@ -18,7 +19,10 @@ #include #include -class DisassemblyTextBlockUserData : public QTextBlockUserData +#include +#include + +class DisassemblyTextBlockUserData: public QTextBlockUserData { public: DisassemblyLine line; @@ -833,24 +837,10 @@ void DisassemblyWidget::seekPrev() * Left panel *********************/ -struct Range -{ - Range(RVA v1, RVA v2) : from(v1), to(v2) - { - if (from > to) - std::swap(from, to); - } - RVA from; - RVA to; - - inline bool contains(const Range &other) const { return from <= other.from && to >= other.to; } - - inline bool contains(RVA point) const { return from <= point && to >= point; } -}; - DisassemblyLeftPanel::DisassemblyLeftPanel(DisassemblyWidget *disas) { this->disas = disas; + arrows.reserve((arrowsSize * 3) / 2); } void DisassemblyLeftPanel::wheelEvent(QWheelEvent *event) @@ -865,7 +855,6 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) { Q_UNUSED(event) - using namespace std; constexpr int penSizePix = 1; constexpr int distanceBetweenLines = 10; constexpr int arrowWidth = 5; @@ -883,99 +872,114 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) QList lines = disas->getLines(); - QMap linesPixPosition; - QMap> arrowInfo; /* offset -> (arrow, layer of arrow) */ - int nLines = 0; - for (const auto &line : lines) { - linesPixPosition[line.offset] = nLines * lineHeight + lineHeight / 2 + topOffset; - nLines++; - if (line.arrow != RVA_INVALID) { - arrowInfo.insert(line.offset, { line.arrow, -1 }); - } - } + using LineInfo = std::pair; + std::vector lineOffsets; + lineOffsets.reserve(lines.size()); - for (auto it = arrowInfo.begin(); it != arrowInfo.end(); it++) { - Range currRange = { it.key(), it.value().first }; - it.value().second = it.value().second == -1 ? 1 : it.value().second; - for (auto innerIt = arrowInfo.begin(); innerIt != arrowInfo.end(); innerIt++) { - if (innerIt == it) { - continue; - } - Range innerRange = { innerIt.key(), innerIt.value().first }; - if (currRange.contains(innerRange) || currRange.contains(innerRange.from)) { - it.value().second++; + for (int i = 0; i < lines.size(); i++) { + lineOffsets.emplace_back(lines[i].offset, i); + if (lines[i].arrow != RVA_INVALID) { + Arrow a { lines[i].offset, lines[i].arrow }; + bool contains = std::find_if(std::begin(arrows), std::end(arrows), [&](const Arrow& it) { + return it.min == a.min && it.max == a.max; + }) != std::end(arrows); + if (!contains) { + arrows.emplace_back(lines[i].offset, lines[i].arrow); } } } - // I'm sorry this loop below, but it is only way I see how to implement the feature - while (true) { - bool correction = false; - bool correction2 = false; - for (auto it = arrowInfo.begin(); it != arrowInfo.end(); it++) { - int minDistance = INT32_MAX; - Range currRange = { it.key(), it.value().first }; - for (auto innerIt = arrowInfo.begin(); innerIt != arrowInfo.end(); innerIt++) { - if (innerIt == it) { - continue; - } - Range innerRange = { innerIt.key(), innerIt.value().first }; - if (it.value().second == innerIt.value().second - && (currRange.contains(innerRange) || currRange.contains(innerRange.from))) { - it.value().second++; - correction = true; - } - int distance = it.value().second - innerIt.value().second; - if (distance > 0 && distance < minDistance) { - minDistance = distance; - } - } - if (minDistance > 1 && minDistance != INT32_MAX) { - correction2 = true; - it.value().second -= minDistance - 1; - } + auto offsetToLine = [&](RVA offset) -> int { + // binary search because linesPixPosition is sorted by offset + if (lineOffsets.empty()) { + return 0; } - if (!correction && !correction2) { - break; + if (offset < lineOffsets[0].first) { + return -2; + } + auto res = lower_bound(std::begin(lineOffsets), std::end(lineOffsets), offset, [](const LineInfo& it, RVA offset) { + return it.first < offset; + }); + if (res == std::end(lineOffsets)) { + return lines.size() + 2; + } + return res->second; + }; + + + RVA visibleTop = lineOffsets[0].first, visibleBottom = lineOffsets.back().first; + auto fitsInScreen = [&](const Arrow &a) { + return visibleBottom - visibleTop < a.length(); + }; + + std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow& l, const Arrow& r) { + int lScreen = fitsInScreen(l), rScreen = fitsInScreen(r); + if (lScreen != rScreen) { + return lScreen < rScreen; + } + return l.max != r.max ? l.max < r.max : l.min > r.min; + }); + + RVA max = 0; + RVA min = RVA_MAX; + for (auto& it : arrows) { + min = std::min(it.min, min); + max = std::max(it.max, max); + it.level = 0; + } + + uint32_t maxLevel = 0; + if (!arrows.empty()) { + MinMaxAccumulateTree maxLevelTree(max - min + 2); + for (Arrow &arrow : arrows) { + RVA top = arrow.min >= min ? arrow.min - min + 1: 0; + RVA bottom = std::min(arrow.max - min, max - min) + 2; + auto minMax = maxLevelTree.rangeMinMax(top, bottom); + if (minMax.first > 1) { + arrow.level = 1; + } else { + arrow.level = minMax.second + 1; + maxLevel = std::max(maxLevel, arrow.level); + } + maxLevelTree.updateRange(top, bottom, arrow.level); } } const RVA currOffset = disas->getSeekable()->getOffset(); - qreal pixelRatio = qhelpers::devicePixelRatio(p.device()); + const qreal pixelRatio = qhelpers::devicePixelRatio(p.device()); + const Arrow visibleRange { lines.first().offset, lines.last().offset }; // Draw the lines - for (const auto &l : lines) { - int lineOffset = - int((distanceBetweenLines * arrowInfo[l.offset].second + distanceBetweenLines) - * pixelRatio); - // Skip until we reach a line that jumps to a destination - if (l.arrow == RVA_INVALID) { + for (const auto& arrow : arrows) { + if (!visibleRange.intersects(arrow)) { continue; } - - bool jumpDown = l.arrow > l.offset; - p.setPen(jumpDown ? penDown : penUp); - if (l.offset == currOffset || l.arrow == currOffset) { + int lineOffset = int((distanceBetweenLines * arrow.level + distanceBetweenLines) * pixelRatio); + + p.setPen(arrow.up ? penUp : penDown); + if (arrow.min == currOffset || arrow.max == currOffset) { QPen pen = p.pen(); pen.setWidthF((penSizePix * 3) / 2.0); p.setPen(pen); } - bool endVisible = true; - int currentLineYPos = linesPixPosition[l.offset]; - int lineArrowY = linesPixPosition.value(l.arrow, -1); + auto lineToPixels = [&] (int i) { + int offset = int(arrow.up ? std::floor(pixelRatio) : -std::floor(pixelRatio)); + return i * lineHeight + lineHeight / 2 + topOffset + offset; + }; - if (lineArrowY == -1) { - lineArrowY = jumpDown ? geometry().bottom() : 0; - endVisible = false; - } + int lineStartNumber = offsetToLine(arrow.jmpFromOffset()); + int currentLineYPos = lineToPixels(lineStartNumber); + + int arrowLineNumber = offsetToLine(arrow.jmpToffset()); + int lineArrowY = lineToPixels(arrowLineNumber); // Draw the lines - p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos); - p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineArrowY); + p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos); // left + p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineArrowY); // horizontal - if (endVisible) { - p.drawLine(rightOffset - lineOffset, lineArrowY, rightOffset, lineArrowY); + p.drawLine(rightOffset - lineOffset, lineArrowY, rightOffset, lineArrowY); // right + { // triangle QPainterPath arrow; arrow.moveTo(rightOffset - arrowWidth, lineArrowY + arrowWidth); arrow.lineTo(rightOffset - arrowWidth, lineArrowY - arrowWidth); @@ -983,4 +987,23 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) p.fillPath(arrow, p.pen().brush()); } } + + if (maxLevel > maxLevelBeforeFlush) { + arrows.clear(); + } + + const size_t eraseN = arrows.size() > arrowsSize ? arrows.size() - arrowsSize: 0; + if (eraseN > 0) { + const bool scrolledDown = lastBeginOffset > lines.first().offset; + std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow& l, const Arrow& r) { + if (scrolledDown) { + return l.jmpFromOffset() < r.jmpFromOffset(); + } else { + return l.jmpFromOffset() > r.jmpFromOffset(); + } + }); + arrows.erase(std::end(arrows) - eraseN, std::end(arrows)); + } + + lastBeginOffset = lines.first().offset; } diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index eaffa5a1..7d294303 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -12,6 +12,9 @@ #include #include +#include + + class DisassemblyTextEdit; class DisassemblyScrollArea; class DisassemblyContextMenu; @@ -155,6 +158,40 @@ public: private: DisassemblyWidget *disas; + + struct Arrow { + Arrow(RVA v1, RVA v2) + : min(v1), max(v2), + level(0), up(false) + { + if (min > max) { + std::swap(min, max); + up = true; + } + } + + inline bool contains(RVA point) const + { return min <= point && max >= point; } + + inline bool intersects(const Arrow& other) const + { return std::max(min, other.min) <= std::min(max, other.max); } + + ut64 length() const { return max - min; } + + RVA jmpFromOffset() const { return up ? max : min; } + + RVA jmpToffset() const { return up ? min : max; } + + RVA min; + RVA max; + uint32_t level; + bool up; + }; + + const size_t arrowsSize = 128; + const uint32_t maxLevelBeforeFlush = 32; + RVA lastBeginOffset = 0; + std::vector arrows; }; #endif // DISASSEMBLYWIDGET_H