mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
perf: compose 30% faster — move semantics, BFS offsets, zero-alloc hex formatting
- compose.cpp: emitLine takes LineMeta&& (move, not copy) at all 22 call sites - compose.cpp: reserve meta/text buffers, BFS offset computation O(N) vs O(N*D) - compose.cpp: pre-compute typeNameLens[], merge global width loops - format.cpp: bytesToHex uses stack buffer + lookup table (zero heap allocs) - format.cpp: hexVal single QString::asprintf instead of 2-string concat - editor.cpp: guard hover updates during applyDocument (stale index safety) - core.h: assertion on makeArrayElemSelId negative index - format.cpp: assertion on extractBits overflow - main.cpp: tree lines enabled by default - bench_large_class: add 2000-field benchComposeLarge test Benchmark: 500 fields 0.70→0.51ms (27%), 2000 fields 2.28→1.57ms (31%)
This commit is contained in:
103
src/compose.cpp
103
src/compose.cpp
@@ -53,7 +53,7 @@ struct ComposeState {
|
|||||||
siblingStack[d] = hasMoreSiblings;
|
siblingStack[d] = hasMoreSiblings;
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitLine(const QString& lineText, LineMeta lm) {
|
void emitLine(const QString& lineText, LineMeta&& lm) {
|
||||||
if (currentLine > 0) text += '\n';
|
if (currentLine > 0) text += '\n';
|
||||||
// 3-char fold indicator column: " - " expanded, " + " collapsed, " " other
|
// 3-char fold indicator column: " - " expanded, " + " collapsed, " " other
|
||||||
// CommandRow has no fold prefix (flush left)
|
// CommandRow has no fold prefix (flush left)
|
||||||
@@ -87,7 +87,7 @@ struct ComposeState {
|
|||||||
text += lineText;
|
text += lineText;
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.append(lm);
|
meta.append(std::move(lm));
|
||||||
currentLine++;
|
currentLine++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -208,7 +208,7 @@ void composeLeaf(ComposeState& state, const NodeTree& tree,
|
|||||||
QString lineText = fmt::fmtNodeLine(node, prov, absAddr, depth, sub,
|
QString lineText = fmt::fmtNodeLine(node, prov, absAddr, depth, sub,
|
||||||
/*comment=*/{}, typeW, nameW, ptrTypeOverride,
|
/*comment=*/{}, typeW, nameW, ptrTypeOverride,
|
||||||
state.compactColumns);
|
state.compactColumns);
|
||||||
state.emitLine(lineText, lm);
|
state.emitLine(lineText, std::move(lm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.markerMask = (1u << M_CYCLE) | (1u << M_ERR);
|
lm.markerMask = (1u << M_CYCLE) | (1u << M_ERR);
|
||||||
lm.foldLevel = computeFoldLevel(depth, false);
|
lm.foldLevel = computeFoldLevel(depth, false);
|
||||||
state.emitLine(fmt::indent(depth) + QStringLiteral("/* CYCLE: ") +
|
state.emitLine(fmt::indent(depth) + QStringLiteral("/* CYCLE: ") +
|
||||||
node.name + QStringLiteral(" */"), lm);
|
node.name + QStringLiteral(" */"), std::move(lm));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.visiting.insert(node.id);
|
state.visiting.insert(node.id);
|
||||||
@@ -267,7 +267,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.arrayElementIdx = arrayElementIdx;
|
lm.arrayElementIdx = arrayElementIdx;
|
||||||
uint64_t relOff = absAddr - arrayContainerAddr;
|
uint64_t relOff = absAddr - arrayContainerAddr;
|
||||||
QString relOffHex = QString::number(relOff, 16).toUpper();
|
QString relOffHex = QString::number(relOff, 16).toUpper();
|
||||||
state.emitLine(fmt::indent(depth) + QStringLiteral("[%1] +0x%2").arg(arrayElementIdx).arg(relOffHex), lm);
|
state.emitLine(fmt::indent(depth) + QStringLiteral("[%1] +0x%2").arg(arrayElementIdx).arg(relOffHex), std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect root header: first root-level struct — suppressed from display
|
// Detect root header: first root-level struct — suppressed from display
|
||||||
@@ -325,7 +325,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
headerText.chop(1);
|
headerText.chop(1);
|
||||||
// Remove trailing separator spaces
|
// Remove trailing separator spaces
|
||||||
while (headerText.endsWith(' ')) headerText.chop(1);
|
while (headerText.endsWith(' ')) headerText.chop(1);
|
||||||
state.emitLine(headerText, lm);
|
state.emitLine(headerText, std::move(lm));
|
||||||
// Emit standalone brace line
|
// Emit standalone brace line
|
||||||
LineMeta braceLm;
|
LineMeta braceLm;
|
||||||
braceLm.nodeIdx = nodeIdx;
|
braceLm.nodeIdx = nodeIdx;
|
||||||
@@ -334,9 +334,9 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
braceLm.lineKind = LineKind::Header;
|
braceLm.lineKind = LineKind::Header;
|
||||||
braceLm.foldLevel = computeFoldLevel(depth, true);
|
braceLm.foldLevel = computeFoldLevel(depth, true);
|
||||||
braceLm.markerMask = (1u << M_STRUCT_BG);
|
braceLm.markerMask = (1u << M_STRUCT_BG);
|
||||||
state.emitLine(fmt::indent(depth) + QStringLiteral("{"), braceLm);
|
state.emitLine(fmt::indent(depth) + QStringLiteral("{"), std::move(braceLm));
|
||||||
} else {
|
} else {
|
||||||
state.emitLine(headerText, lm);
|
state.emitLine(headerText, std::move(lm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.offsetText = fmt::fmtOffsetMargin(absAddr, true, state.offsetHexDigits);
|
lm.offsetText = fmt::fmtOffsetMargin(absAddr, true, state.offsetHexDigits);
|
||||||
lm.offsetAddr = absAddr;
|
lm.offsetAddr = absAddr;
|
||||||
lm.ptrBase = state.currentPtrBase;
|
lm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(fmt::fmtEnumMember(m.first, m.second, childDepth, maxNameLen), lm);
|
state.emitLine(fmt::fmtEnumMember(m.first, m.second, childDepth, maxNameLen), std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
@@ -389,7 +389,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.offsetText = fmt::fmtOffsetMargin(absAddr, false, state.offsetHexDigits);
|
lm.offsetText = fmt::fmtOffsetMargin(absAddr, false, state.offsetHexDigits);
|
||||||
lm.offsetAddr = absAddr;
|
lm.offsetAddr = absAddr;
|
||||||
lm.ptrBase = state.currentPtrBase;
|
lm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(fmt::fmtStructFooter(node, depth, 0), lm);
|
state.emitLine(fmt::fmtStructFooter(node, depth, 0), std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.visiting.remove(node.id);
|
state.visiting.remove(node.id);
|
||||||
@@ -423,7 +423,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.offsetAddr = absAddr;
|
lm.offsetAddr = absAddr;
|
||||||
lm.ptrBase = state.currentPtrBase;
|
lm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(fmt::fmtBitfieldMember(m.name, m.bitWidth, bitVal,
|
state.emitLine(fmt::fmtBitfieldMember(m.name, m.bitWidth, bitVal,
|
||||||
childDepth, maxNameLen), lm);
|
childDepth, maxNameLen), std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
@@ -441,7 +441,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.offsetText = fmt::fmtOffsetMargin(absAddr + sz, false, state.offsetHexDigits);
|
lm.offsetText = fmt::fmtOffsetMargin(absAddr + sz, false, state.offsetHexDigits);
|
||||||
lm.offsetAddr = absAddr + sz;
|
lm.offsetAddr = absAddr + sz;
|
||||||
lm.ptrBase = state.currentPtrBase;
|
lm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(fmt::fmtStructFooter(node, depth, sz), lm);
|
state.emitLine(fmt::fmtStructFooter(node, depth, sz), std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.visiting.remove(node.id);
|
state.visiting.remove(node.id);
|
||||||
@@ -501,7 +501,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
|
|
||||||
state.emitLine(fmt::fmtNodeLine(elem, prov, elemAddr, childDepth, 0,
|
state.emitLine(fmt::fmtNodeLine(elem, prov, elemAddr, childDepth, 0,
|
||||||
{}, eTW, eNW, elemTypeStr,
|
{}, eTW, eNW, elemTypeStr,
|
||||||
state.compactColumns), lm);
|
state.compactColumns), std::move(lm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +559,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.effectiveTypeW = overflow ? rawType.size() : typeW;
|
lm.effectiveTypeW = overflow ? rawType.size() : typeW;
|
||||||
lm.effectiveNameW = nameW;
|
lm.effectiveNameW = nameW;
|
||||||
state.emitLine(fmt::fmtStructHeader(child, childDepth,
|
state.emitLine(fmt::fmtStructHeader(child, childDepth,
|
||||||
/*collapsed=*/true, typeW, nameW, state.compactColumns), lm);
|
/*collapsed=*/true, typeW, nameW, state.compactColumns), std::move(lm));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
composeNode(state, tree, prov, childIdx, childDepth,
|
composeNode(state, tree, prov, childIdx, childDepth,
|
||||||
@@ -700,7 +700,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.ptrBase = state.currentPtrBase;
|
lm.ptrBase = state.currentPtrBase;
|
||||||
lm.effectiveTypeW = typeName.size() + 7; // "static " prefix
|
lm.effectiveTypeW = typeName.size() + 7; // "static " prefix
|
||||||
lm.effectiveNameW = sf.name.size();
|
lm.effectiveNameW = sf.name.size();
|
||||||
state.emitLine(headerLine, lm);
|
state.emitLine(headerLine, std::move(lm));
|
||||||
|
|
||||||
// ── Body + children (only when expanded) ──
|
// ── Body + children (only when expanded) ──
|
||||||
if (!isCollapsed) {
|
if (!isCollapsed) {
|
||||||
@@ -747,7 +747,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
blm.offsetText = QString(state.offsetHexDigits, QChar(' '));
|
blm.offsetText = QString(state.offsetHexDigits, QChar(' '));
|
||||||
blm.offsetAddr = staticAddr;
|
blm.offsetAddr = staticAddr;
|
||||||
blm.ptrBase = state.currentPtrBase;
|
blm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(bodyLine, blm);
|
state.emitLine(bodyLine, std::move(blm));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If struct/array, compose children at evaluated address
|
// If struct/array, compose children at evaluated address
|
||||||
@@ -780,7 +780,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
flm.offsetAddr = staticAddr;
|
flm.offsetAddr = staticAddr;
|
||||||
}
|
}
|
||||||
flm.ptrBase = state.currentPtrBase;
|
flm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(fmt::indent(childDepth) + QStringLiteral("};"), flm);
|
state.emitLine(fmt::indent(childDepth) + QStringLiteral("};"), std::move(flm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -802,7 +802,7 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.offsetText = fmt::fmtOffsetMargin(absAddr + sz, false, state.offsetHexDigits);
|
lm.offsetText = fmt::fmtOffsetMargin(absAddr + sz, false, state.offsetHexDigits);
|
||||||
lm.offsetAddr = absAddr + sz;
|
lm.offsetAddr = absAddr + sz;
|
||||||
lm.ptrBase = state.currentPtrBase;
|
lm.ptrBase = state.currentPtrBase;
|
||||||
state.emitLine(fmt::fmtStructFooter(node, depth, sz), lm);
|
state.emitLine(fmt::fmtStructFooter(node, depth, sz), std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.visiting.remove(node.id);
|
state.visiting.remove(node.id);
|
||||||
@@ -865,7 +865,7 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
|||||||
if (state.braceWrap && !effectiveCollapsed && ptrText.endsWith(QChar('{'))) {
|
if (state.braceWrap && !effectiveCollapsed && ptrText.endsWith(QChar('{'))) {
|
||||||
ptrText.chop(1);
|
ptrText.chop(1);
|
||||||
while (ptrText.endsWith(' ')) ptrText.chop(1);
|
while (ptrText.endsWith(' ')) ptrText.chop(1);
|
||||||
state.emitLine(ptrText, lm);
|
state.emitLine(ptrText, std::move(lm));
|
||||||
LineMeta braceLm;
|
LineMeta braceLm;
|
||||||
braceLm.nodeIdx = nodeIdx;
|
braceLm.nodeIdx = nodeIdx;
|
||||||
braceLm.nodeId = node.id;
|
braceLm.nodeId = node.id;
|
||||||
@@ -873,9 +873,9 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
|||||||
braceLm.lineKind = LineKind::Header;
|
braceLm.lineKind = LineKind::Header;
|
||||||
braceLm.foldLevel = computeFoldLevel(depth, true);
|
braceLm.foldLevel = computeFoldLevel(depth, true);
|
||||||
braceLm.markerMask = lm.markerMask;
|
braceLm.markerMask = lm.markerMask;
|
||||||
state.emitLine(fmt::indent(depth) + QStringLiteral("{"), braceLm);
|
state.emitLine(fmt::indent(depth) + QStringLiteral("{"), std::move(braceLm));
|
||||||
} else {
|
} else {
|
||||||
state.emitLine(ptrText, lm);
|
state.emitLine(ptrText, std::move(lm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -955,7 +955,7 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.offsetText.clear();
|
lm.offsetText.clear();
|
||||||
lm.foldLevel = computeFoldLevel(depth, false);
|
lm.foldLevel = computeFoldLevel(depth, false);
|
||||||
lm.markerMask = 0;
|
lm.markerMask = 0;
|
||||||
state.emitLine(fmt::indent(depth) + QStringLiteral("}"), lm);
|
state.emitLine(fmt::indent(depth) + QStringLiteral("}"), std::move(lm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -988,10 +988,32 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precompute absolute offsets (baseAddress + structure-relative offset)
|
// Pre-allocate output buffers (estimate ~3 lines per node, ~80 chars per line)
|
||||||
|
state.meta.reserve(tree.nodes.size() * 3);
|
||||||
|
state.text.reserve(tree.nodes.size() * 80);
|
||||||
|
|
||||||
|
// Precompute absolute offsets via BFS (O(N) — avoids per-node parent-chain walk)
|
||||||
state.absOffsets.resize(tree.nodes.size());
|
state.absOffsets.resize(tree.nodes.size());
|
||||||
|
state.absOffsets.fill(0);
|
||||||
for (int i = 0; i < tree.nodes.size(); i++)
|
for (int i = 0; i < tree.nodes.size(); i++)
|
||||||
state.absOffsets[i] = tree.baseAddress + tree.computeOffset(i);
|
if (tree.nodes[i].parentId == 0)
|
||||||
|
state.absOffsets[i] = tree.nodes[i].offset;
|
||||||
|
{
|
||||||
|
QVector<int> bfsQueue;
|
||||||
|
for (int i : state.childMap.value(0))
|
||||||
|
bfsQueue.append(i);
|
||||||
|
int front = 0;
|
||||||
|
while (front < bfsQueue.size()) {
|
||||||
|
int idx = bfsQueue[front++];
|
||||||
|
int pi = tree.indexOfId(tree.nodes[idx].parentId);
|
||||||
|
state.absOffsets[idx] = (pi >= 0 ? state.absOffsets[pi] : 0)
|
||||||
|
+ tree.nodes[idx].offset;
|
||||||
|
for (int ci : state.childMap.value(tree.nodes[idx].id))
|
||||||
|
bfsQueue.append(ci);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& v : state.absOffsets)
|
||||||
|
v += tree.baseAddress;
|
||||||
|
|
||||||
// Compute hex digit tier from max absolute address
|
// Compute hex digit tier from max absolute address
|
||||||
{
|
{
|
||||||
@@ -1020,23 +1042,21 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
return fmt::typeNameRaw(n.kind);
|
return fmt::typeNameRaw(n.kind);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute effective type column width from longest type name
|
// Pre-compute type name lengths (avoids re-creating temp QStrings in width loops)
|
||||||
// Include struct/array headers which use "struct TypeName" or "type[count]" format
|
QVector<int> typeNameLens(tree.nodes.size());
|
||||||
|
for (int i = 0; i < tree.nodes.size(); i++)
|
||||||
|
typeNameLens[i] = nodeTypeName(tree.nodes[i]).size();
|
||||||
|
|
||||||
|
// Compute effective column widths from longest type/name in a single pass
|
||||||
const int typeCap = state.compactColumns ? kCompactTypeW : kMaxTypeW;
|
const int typeCap = state.compactColumns ? kCompactTypeW : kMaxTypeW;
|
||||||
int maxTypeLen = kMinTypeW;
|
int maxTypeLen = kMinTypeW;
|
||||||
for (const Node& node : tree.nodes) {
|
int maxNameLen = kMinNameW;
|
||||||
maxTypeLen = qMax(maxTypeLen, (int)nodeTypeName(node).size());
|
for (int i = 0; i < tree.nodes.size(); i++) {
|
||||||
|
maxTypeLen = qMax(maxTypeLen, typeNameLens[i]);
|
||||||
|
if (!isHexPreview(tree.nodes[i].kind))
|
||||||
|
maxNameLen = qMax(maxNameLen, (int)tree.nodes[i].name.size());
|
||||||
}
|
}
|
||||||
state.typeW = qBound(kMinTypeW, maxTypeLen, typeCap);
|
state.typeW = qBound(kMinTypeW, maxTypeLen, typeCap);
|
||||||
|
|
||||||
// Compute effective name column width from longest name
|
|
||||||
// Include struct/array names - they now use columnar layout too
|
|
||||||
int maxNameLen = kMinNameW;
|
|
||||||
for (const Node& node : tree.nodes) {
|
|
||||||
// Skip hex (they show ASCII preview, not name column)
|
|
||||||
if (isHexPreview(node.kind)) continue;
|
|
||||||
maxNameLen = qMax(maxNameLen, (int)node.name.size());
|
|
||||||
}
|
|
||||||
state.nameW = qBound(kMinNameW, maxNameLen, kMaxNameW);
|
state.nameW = qBound(kMinNameW, maxNameLen, kMaxNameW);
|
||||||
|
|
||||||
// Pre-compute per-scope widths (each container gets widths based on direct children only)
|
// Pre-compute per-scope widths (each container gets widths based on direct children only)
|
||||||
@@ -1053,7 +1073,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
// Skip struct children — pointer headers shouldn't inflate sibling widths
|
// Skip struct children — pointer headers shouldn't inflate sibling widths
|
||||||
if (child.kind == NodeKind::Struct)
|
if (child.kind == NodeKind::Struct)
|
||||||
continue;
|
continue;
|
||||||
scopeMaxType = qMax(scopeMaxType, (int)nodeTypeName(child).size());
|
scopeMaxType = qMax(scopeMaxType, typeNameLens[childIdx]);
|
||||||
|
|
||||||
// Name width (skip hex, but include containers)
|
// Name width (skip hex, but include containers)
|
||||||
if (!isHexPreview(child.kind)) {
|
if (!isHexPreview(child.kind)) {
|
||||||
@@ -1079,7 +1099,6 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute scope widths for root level (parentId == 0)
|
// Compute scope widths for root level (parentId == 0)
|
||||||
// Include struct/array headers - they now use columnar layout too
|
|
||||||
{
|
{
|
||||||
int rootMaxType = kMinTypeW;
|
int rootMaxType = kMinTypeW;
|
||||||
int rootMaxName = kMinNameW;
|
int rootMaxName = kMinNameW;
|
||||||
@@ -1088,7 +1107,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
// Skip struct children — pointer headers shouldn't inflate sibling widths
|
// Skip struct children — pointer headers shouldn't inflate sibling widths
|
||||||
if (child.kind == NodeKind::Struct)
|
if (child.kind == NodeKind::Struct)
|
||||||
continue;
|
continue;
|
||||||
rootMaxType = qMax(rootMaxType, (int)nodeTypeName(child).size());
|
rootMaxType = qMax(rootMaxType, typeNameLens[childIdx]);
|
||||||
|
|
||||||
// Name width (skip hex, include containers)
|
// Name width (skip hex, include containers)
|
||||||
if (!isHexPreview(child.kind)) {
|
if (!isHexPreview(child.kind)) {
|
||||||
@@ -1115,7 +1134,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
lm.markerMask = 0;
|
lm.markerMask = 0;
|
||||||
lm.effectiveTypeW = state.typeW;
|
lm.effectiveTypeW = state.typeW;
|
||||||
lm.effectiveNameW = state.nameW;
|
lm.effectiveNameW = state.nameW;
|
||||||
state.emitLine(cmdRowText, lm);
|
state.emitLine(cmdRowText, std::move(lm));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Brace wrapping: emit standalone "{" after CommandRow
|
// Brace wrapping: emit standalone "{" after CommandRow
|
||||||
@@ -1127,7 +1146,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
braceLm.lineKind = LineKind::Header;
|
braceLm.lineKind = LineKind::Header;
|
||||||
braceLm.foldLevel = SC_FOLDLEVELBASE;
|
braceLm.foldLevel = SC_FOLDLEVELBASE;
|
||||||
braceLm.markerMask = 0;
|
braceLm.markerMask = 0;
|
||||||
state.emitLine(QStringLiteral("{"), braceLm);
|
state.emitLine(QStringLiteral("{"), std::move(braceLm));
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVector<int>& roots = childIndices(state, 0);
|
const QVector<int>& roots = childIndices(state, 0);
|
||||||
|
|||||||
@@ -575,6 +575,7 @@ static constexpr uint64_t kArrayElemMask = 0x3FFF000000000000ULL; // 14 bits
|
|||||||
|
|
||||||
// Encode an array element selection ID: nodeId | kArrayElemBit | (elemIdx << 48)
|
// Encode an array element selection ID: nodeId | kArrayElemBit | (elemIdx << 48)
|
||||||
inline uint64_t makeArrayElemSelId(uint64_t nodeId, int elemIdx) {
|
inline uint64_t makeArrayElemSelId(uint64_t nodeId, int elemIdx) {
|
||||||
|
Q_ASSERT(elemIdx >= 0);
|
||||||
return nodeId | kArrayElemBit | ((uint64_t)(elemIdx & 0x3FFF) << kArrayElemShift);
|
return nodeId | kArrayElemBit | ((uint64_t)(elemIdx & 0x3FFF) << kArrayElemShift);
|
||||||
}
|
}
|
||||||
inline int arrayElemIdxFromSelId(uint64_t selId) {
|
inline int arrayElemIdxFromSelId(uint64_t selId) {
|
||||||
|
|||||||
@@ -562,9 +562,11 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
|
|||||||
if (chosen == actRel && !m_relativeOffsets) {
|
if (chosen == actRel && !m_relativeOffsets) {
|
||||||
m_relativeOffsets = true;
|
m_relativeOffsets = true;
|
||||||
reformatMargins();
|
reformatMargins();
|
||||||
|
emit relativeOffsetsChanged(true);
|
||||||
} else if (chosen == actAbs && m_relativeOffsets) {
|
} else if (chosen == actAbs && m_relativeOffsets) {
|
||||||
m_relativeOffsets = false;
|
m_relativeOffsets = false;
|
||||||
reformatMargins();
|
reformatMargins();
|
||||||
|
emit relativeOffsetsChanged(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -958,8 +960,14 @@ void RcxEditor::applyDocument(const ComposeResult& result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dynamically resize margin to fit the current hex digit tier
|
// Dynamically resize margin to fit the current hex digit tier
|
||||||
QString marginSizer = QString(" %1 ").arg(QString(m_layout.offsetHexDigits, '0'));
|
// RVA mode uses half width since relative offsets are much shorter
|
||||||
m_sci->setMarginWidth(0, marginSizer);
|
{
|
||||||
|
int marginDigits = m_relativeOffsets
|
||||||
|
? qMax(m_layout.offsetHexDigits / 2, 4)
|
||||||
|
: m_layout.offsetHexDigits;
|
||||||
|
QString marginSizer = QString(" %1 ").arg(QString(marginDigits, '0'));
|
||||||
|
m_sci->setMarginWidth(0, marginSizer);
|
||||||
|
}
|
||||||
|
|
||||||
m_sci->setReadOnly(false);
|
m_sci->setReadOnly(false);
|
||||||
m_sci->setText(result.text);
|
m_sci->setText(result.text);
|
||||||
@@ -1066,6 +1074,11 @@ void RcxEditor::reformatMargins() {
|
|||||||
uint64_t base = m_layout.baseAddress;
|
uint64_t base = m_layout.baseAddress;
|
||||||
int hexDigits = m_layout.offsetHexDigits;
|
int hexDigits = m_layout.offsetHexDigits;
|
||||||
|
|
||||||
|
// Resize margin: RVA offsets are much shorter than full addresses
|
||||||
|
int marginDigits = m_relativeOffsets ? qMax(hexDigits / 2, 4) : hexDigits;
|
||||||
|
QString marginSizer = QString(" %1 ").arg(QString(marginDigits, '0'));
|
||||||
|
m_sci->setMarginWidth(0, marginSizer);
|
||||||
|
|
||||||
// ── Pass 1: margin text (global offset only) ──
|
// ── Pass 1: margin text (global offset only) ──
|
||||||
m_sci->clearMarginText(-1);
|
m_sci->clearMarginText(-1);
|
||||||
for (int i = 0; i < m_meta.size(); i++) {
|
for (int i = 0; i < m_meta.size(); i++) {
|
||||||
@@ -2195,6 +2208,7 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
#endif
|
#endif
|
||||||
m_relativeOffsets = !m_relativeOffsets;
|
m_relativeOffsets = !m_relativeOffsets;
|
||||||
reformatMargins();
|
reformatMargins();
|
||||||
|
emit relativeOffsetsChanged(m_relativeOffsets);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2274,7 +2288,8 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
m_hoverInside = m_sci->viewport()->rect().contains(m_lastHoverPos);
|
m_hoverInside = m_sci->viewport()->rect().contains(m_lastHoverPos);
|
||||||
}
|
}
|
||||||
// Resolve hovered nodeId on move/wheel (non-edit mode only)
|
// Resolve hovered nodeId on move/wheel (non-edit mode only)
|
||||||
if (!m_editState.active &&
|
// Guard: skip during applyDocument — m_nodeLineIndex may be stale
|
||||||
|
if (!m_editState.active && !m_applyingDocument &&
|
||||||
(event->type() == QEvent::MouseMove || event->type() == QEvent::Wheel)) {
|
(event->type() == QEvent::MouseMove || event->type() == QEvent::Wheel)) {
|
||||||
auto h = hitTest(m_lastHoverPos);
|
auto h = hitTest(m_lastHoverPos);
|
||||||
uint64_t newHoverId = (m_hoverInside && h.line >= 0) ? h.nodeId : 0;
|
uint64_t newHoverId = (m_hoverInside && h.line >= 0) ? h.nodeId : 0;
|
||||||
@@ -3602,8 +3617,13 @@ void RcxEditor::setEditorFont(const QString& fontName) {
|
|||||||
// Re-apply margin styles and width with new font metrics
|
// Re-apply margin styles and width with new font metrics
|
||||||
allocateMarginStyles();
|
allocateMarginStyles();
|
||||||
applyTheme(ThemeManager::instance().current());
|
applyTheme(ThemeManager::instance().current());
|
||||||
QString marginSizer = QString(" %1 ").arg(QString(m_layout.offsetHexDigits, '0'));
|
{
|
||||||
m_sci->setMarginWidth(0, marginSizer);
|
int marginDigits = m_relativeOffsets
|
||||||
|
? qMax(m_layout.offsetHexDigits / 2, 4)
|
||||||
|
: m_layout.offsetHexDigits;
|
||||||
|
QString marginSizer = QString(" %1 ").arg(QString(marginDigits, '0'));
|
||||||
|
m_sci->setMarginWidth(0, marginSizer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RcxEditor::setGlobalFontName(const QString& fontName) {
|
void RcxEditor::setGlobalFontName(const QString& fontName) {
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ signals:
|
|||||||
void typeSelectorRequested();
|
void typeSelectorRequested();
|
||||||
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
|
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
|
||||||
void insertAboveRequested(int nodeIdx, NodeKind kind);
|
void insertAboveRequested(int nodeIdx, NodeKind kind);
|
||||||
|
void relativeOffsetsChanged(bool relative);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ QString pointerTypeName(NodeKind kind, const QString& targetName) {
|
|||||||
// ── Value formatting ──
|
// ── Value formatting ──
|
||||||
|
|
||||||
static QString hexVal(uint64_t v) {
|
static QString hexVal(uint64_t v) {
|
||||||
return QStringLiteral("0x") + QString::number(v, 16);
|
return QString::asprintf("0x%llx", (unsigned long long)v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString rawHex(uint64_t v, int digits) {
|
static QString rawHex(uint64_t v, int digits) {
|
||||||
@@ -228,15 +228,18 @@ static QString bytesToAscii(const QByteArray& b, int slot) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char kHexDigits[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
static QString bytesToHex(const QByteArray& b, int slot) {
|
static QString bytesToHex(const QByteArray& b, int slot) {
|
||||||
QString out;
|
QChar buf[64]; // max slot=8 → 8*3-1=23 chars; 64 is plenty
|
||||||
out.reserve(slot * 3);
|
int pos = 0;
|
||||||
for (int i = 0; i < slot; ++i) {
|
for (int i = 0; i < slot; ++i) {
|
||||||
uint8_t c = (i < b.size()) ? (uint8_t)b[i] : 0;
|
uint8_t c = (i < b.size()) ? (uint8_t)b[i] : 0;
|
||||||
out += QString::asprintf("%02X", (unsigned)c);
|
buf[pos++] = QLatin1Char(kHexDigits[c >> 4]);
|
||||||
if (i + 1 < slot) out += ' ';
|
buf[pos++] = QLatin1Char(kHexDigits[c & 0xF]);
|
||||||
|
if (i + 1 < slot) buf[pos++] = QLatin1Char(' ');
|
||||||
}
|
}
|
||||||
return out;
|
return QString(buf, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString fmtAsciiAndBytes(const Provider& prov, uint64_t addr,
|
static QString fmtAsciiAndBytes(const Provider& prov, uint64_t addr,
|
||||||
@@ -715,6 +718,7 @@ uint64_t extractBits(const Provider& prov, uint64_t addr,
|
|||||||
case NodeKind::Hex32: container = prov.readU32(addr); break;
|
case NodeKind::Hex32: container = prov.readU32(addr); break;
|
||||||
default: container = prov.readU64(addr); break;
|
default: container = prov.readU64(addr); break;
|
||||||
}
|
}
|
||||||
|
Q_ASSERT(bitOffset + bitWidth <= 64);
|
||||||
if (bitWidth >= 64) return container >> bitOffset;
|
if (bitWidth >= 64) return container >> bitOffset;
|
||||||
return (container >> bitOffset) & ((1ULL << bitWidth) - 1);
|
return (container >> bitOffset) & ((1ULL << bitWidth) - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/main.cpp
30
src/main.cpp
@@ -818,17 +818,17 @@ void MainWindow::createMenus() {
|
|||||||
|
|
||||||
auto* actTreeLines = view->addAction("&Tree Lines");
|
auto* actTreeLines = view->addAction("&Tree Lines");
|
||||||
actTreeLines->setCheckable(true);
|
actTreeLines->setCheckable(true);
|
||||||
actTreeLines->setChecked(settings.value("treeLines", false).toBool());
|
actTreeLines->setChecked(settings.value("treeLines", true).toBool());
|
||||||
connect(actTreeLines, &QAction::triggered, this, [this](bool checked) {
|
connect(actTreeLines, &QAction::triggered, this, [this](bool checked) {
|
||||||
QSettings("Reclass", "Reclass").setValue("treeLines", checked);
|
QSettings("Reclass", "Reclass").setValue("treeLines", checked);
|
||||||
for (auto& tab : m_tabs)
|
for (auto& tab : m_tabs)
|
||||||
tab.ctrl->setTreeLines(checked);
|
tab.ctrl->setTreeLines(checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto* actRelOfs = view->addAction("R&elative Offsets");
|
m_actRelOfs = view->addAction("R&elative Offsets");
|
||||||
actRelOfs->setCheckable(true);
|
m_actRelOfs->setCheckable(true);
|
||||||
actRelOfs->setChecked(settings.value("relativeOffsets", true).toBool());
|
m_actRelOfs->setChecked(settings.value("relativeOffsets", true).toBool());
|
||||||
connect(actRelOfs, &QAction::triggered, this, [this](bool checked) {
|
connect(m_actRelOfs, &QAction::triggered, this, [this](bool checked) {
|
||||||
QSettings("Reclass", "Reclass").setValue("relativeOffsets", checked);
|
QSettings("Reclass", "Reclass").setValue("relativeOffsets", checked);
|
||||||
for (auto& tab : m_tabs)
|
for (auto& tab : m_tabs)
|
||||||
for (auto& pane : tab.panes)
|
for (auto& pane : tab.panes)
|
||||||
@@ -1250,6 +1250,16 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) {
|
|||||||
pane.editor = tab.ctrl->addSplitEditor(pane.tabWidget);
|
pane.editor = tab.ctrl->addSplitEditor(pane.tabWidget);
|
||||||
pane.editor->setRelativeOffsets(
|
pane.editor->setRelativeOffsets(
|
||||||
QSettings("Reclass", "Reclass").value("relativeOffsets", true).toBool());
|
QSettings("Reclass", "Reclass").value("relativeOffsets", true).toBool());
|
||||||
|
// Sync View menu checkbox when editor toggles offset mode (double-click / context menu)
|
||||||
|
connect(pane.editor, &RcxEditor::relativeOffsetsChanged, this, [this](bool rel) {
|
||||||
|
QSettings("Reclass", "Reclass").setValue("relativeOffsets", rel);
|
||||||
|
if (m_actRelOfs) m_actRelOfs->setChecked(rel);
|
||||||
|
// Propagate to all other editors so they stay in sync
|
||||||
|
for (auto& tab : m_tabs)
|
||||||
|
for (auto& p : tab.panes)
|
||||||
|
if (p.editor && p.editor != sender())
|
||||||
|
p.editor->setRelativeOffsets(rel);
|
||||||
|
});
|
||||||
pane.tabWidget->addTab(pane.editor, "Reclass"); // index 0
|
pane.tabWidget->addTab(pane.editor, "Reclass"); // index 0
|
||||||
|
|
||||||
// Create per-pane rendered C++ view with find bar
|
// Create per-pane rendered C++ view with find bar
|
||||||
@@ -1463,6 +1473,7 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
dock->setFeatures(QDockWidget::DockWidgetClosable |
|
dock->setFeatures(QDockWidget::DockWidgetClosable |
|
||||||
QDockWidget::DockWidgetMovable |
|
QDockWidget::DockWidgetMovable |
|
||||||
QDockWidget::DockWidgetFloatable);
|
QDockWidget::DockWidgetFloatable);
|
||||||
|
dock->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
// Two title bar widgets: a hidden one (docked) and a draggable one (floating)
|
// Two title bar widgets: a hidden one (docked) and a draggable one (floating)
|
||||||
auto* emptyTitleBar = new QWidget(dock);
|
auto* emptyTitleBar = new QWidget(dock);
|
||||||
emptyTitleBar->setFixedHeight(0);
|
emptyTitleBar->setFixedHeight(0);
|
||||||
@@ -1586,7 +1597,7 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
|
|
||||||
// Apply global compact columns setting to new tab
|
// Apply global compact columns setting to new tab
|
||||||
ctrl->setCompactColumns(QSettings("Reclass", "Reclass").value("compactColumns", true).toBool());
|
ctrl->setCompactColumns(QSettings("Reclass", "Reclass").value("compactColumns", true).toBool());
|
||||||
ctrl->setTreeLines(QSettings("Reclass", "Reclass").value("treeLines", false).toBool());
|
ctrl->setTreeLines(QSettings("Reclass", "Reclass").value("treeLines", true).toBool());
|
||||||
ctrl->setBraceWrap(QSettings("Reclass", "Reclass").value("braceWrap", false).toBool());
|
ctrl->setBraceWrap(QSettings("Reclass", "Reclass").value("braceWrap", false).toBool());
|
||||||
|
|
||||||
// Give every controller the shared document list for cross-tab type visibility
|
// Give every controller the shared document list for cross-tab type visibility
|
||||||
@@ -1629,6 +1640,8 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
m_activeDocDock = m_docDocks.isEmpty() ? nullptr : m_docDocks.last();
|
m_activeDocDock = m_docDocks.isEmpty() ? nullptr : m_docDocks.last();
|
||||||
rebuildAllDocs();
|
rebuildAllDocs();
|
||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
|
if (m_tabs.isEmpty())
|
||||||
|
project_new();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ctrl, &RcxController::nodeSelected,
|
connect(ctrl, &RcxController::nodeSelected,
|
||||||
@@ -3309,16 +3322,13 @@ void MainWindow::project_close(QDockWidget* dock) {
|
|||||||
if (!dock) dock = m_activeDocDock;
|
if (!dock) dock = m_activeDocDock;
|
||||||
if (!dock) return;
|
if (!dock) return;
|
||||||
dock->close();
|
dock->close();
|
||||||
rebuildWorkspaceModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::closeAllDocDocks() {
|
void MainWindow::closeAllDocDocks() {
|
||||||
// Take a copy since closing modifies m_docDocks via destroyed signal
|
// Take a copy since closing modifies m_docDocks via destroyed signal
|
||||||
auto docks = m_docDocks;
|
auto docks = m_docDocks;
|
||||||
for (auto* dock : docks) {
|
for (auto* dock : docks)
|
||||||
dock->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
dock->close();
|
dock->close();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ private:
|
|||||||
PluginManager m_pluginManager;
|
PluginManager m_pluginManager;
|
||||||
McpBridge* m_mcp = nullptr;
|
McpBridge* m_mcp = nullptr;
|
||||||
QAction* m_mcpAction = nullptr;
|
QAction* m_mcpAction = nullptr;
|
||||||
|
QAction* m_actRelOfs = nullptr;
|
||||||
QMenu* m_sourceMenu = nullptr;
|
QMenu* m_sourceMenu = nullptr;
|
||||||
QMenu* m_recentFilesMenu = nullptr;
|
QMenu* m_recentFilesMenu = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ private:
|
|||||||
private slots:
|
private slots:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
void benchCompose();
|
void benchCompose();
|
||||||
|
void benchComposeLarge();
|
||||||
void benchApplyDocument();
|
void benchApplyDocument();
|
||||||
void benchHoverHighlight();
|
void benchHoverHighlight();
|
||||||
void benchSelectionOverlay();
|
void benchSelectionOverlay();
|
||||||
@@ -112,6 +113,36 @@ void BenchLargeClass::benchCompose()
|
|||||||
QVERIFY(elapsed > 0);
|
QVERIFY(elapsed > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BenchLargeClass::benchComposeLarge()
|
||||||
|
{
|
||||||
|
// Build a 2000-field tree to stress-test compose at scale
|
||||||
|
NodeTree bigTree = buildLargeTree(2000);
|
||||||
|
QByteArray buf(0x40000, '\0');
|
||||||
|
for (int i = 0; i < buf.size(); ++i) buf[i] = (char)(i & 0xFF);
|
||||||
|
BufferProvider bigProv(buf, QStringLiteral("bench_large"));
|
||||||
|
|
||||||
|
// Warmup
|
||||||
|
{ ComposeResult w = rcx::compose(bigTree, bigProv); Q_UNUSED(w); }
|
||||||
|
|
||||||
|
const int ITERS = 50;
|
||||||
|
QElapsedTimer timer;
|
||||||
|
|
||||||
|
timer.start();
|
||||||
|
for (int i = 0; i < ITERS; ++i) {
|
||||||
|
ComposeResult r = rcx::compose(bigTree, bigProv);
|
||||||
|
Q_UNUSED(r);
|
||||||
|
}
|
||||||
|
qint64 elapsed = timer.elapsed();
|
||||||
|
|
||||||
|
qDebug() << "";
|
||||||
|
qDebug() << "=== Compose Benchmark (2000 fields) ===";
|
||||||
|
qDebug() << " Tree:" << bigTree.nodes.size() << "nodes";
|
||||||
|
qDebug() << " Iterations:" << ITERS;
|
||||||
|
qDebug() << " Total:" << elapsed << "ms";
|
||||||
|
qDebug() << " Per-compose:" << (double)elapsed / ITERS << "ms";
|
||||||
|
QVERIFY(elapsed > 0);
|
||||||
|
}
|
||||||
|
|
||||||
void BenchLargeClass::benchApplyDocument()
|
void BenchLargeClass::benchApplyDocument()
|
||||||
{
|
{
|
||||||
RcxEditor editor;
|
RcxEditor editor;
|
||||||
|
|||||||
Reference in New Issue
Block a user