refactor(audit): batch 3 — decompose App::update() + collapse acrylic fork
Three P1 structural refactors from the audit; no behavior change. 1. Extract the ~180-line daemon-stdout rescan/witness parser out of the ~790-line App::update() into a pure, static, unit-testable NetworkRefreshService::parseDaemonRescanOutput() returning a DaemonRescanScan result struct (sits next to the existing static parse* siblings). The scanning half moved verbatim; App::update() keeps the state-application half, now reading scan.* fields. Adds testParseDaemonRescanOutput() covering all six daemon signals + edge cases against fixture log snippets — previously untestable without a live daemon. 2. Extract the ~95-line global keyboard-shortcut block (Ctrl+, / theme cycle / F5 / low-spec / effects / gradient / wizard) out of App::update() into App::handleGlobalShortcuts(). 3. Collapse the ImGuiAcrylic frontend fork: the GLAD and DX11 namespaces were ~375 lines of semantically-identical code (the frontend makes zero direct GL/DX calls — all backend work delegates to AcrylicMaterial, which has a backend per API). Extended the single frontend's guard to #if defined(DRAGONX_HAS_GLAD) || defined(DRAGONX_USE_DX11) and deleted the DX11 duplicate, leaving the no-backend stub. Kills the Windows/DX11 drift hazard (acrylic frontend changes now made once). Verified: full-node + Lite build clean; ctest 1/1 (incl. the new parser tests); source-hygiene clean; and the collapsed acrylic path was compile-verified under DX11 via the mingw-w64 Windows cross-build (ObsidianDragon.exe links). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
224
src/app.cpp
224
src/app.cpp
@@ -854,195 +854,18 @@ void App::update()
|
|||||||
// be blocked for minutes on a getrescaninfo call (it waits on cs_main while the daemon
|
// be blocked for minutes on a getrescaninfo call (it waits on cs_main while the daemon
|
||||||
// holds it for a witness rebuild). On the worker, the parse queued behind that and the
|
// holds it for a witness rebuild). On the worker, the parse queued behind that and the
|
||||||
// rescan/witness progress froze. Inline, progress updates every frame regardless.
|
// rescan/witness progress froze. Inline, progress updates every frame regardless.
|
||||||
const std::string output = std::move(newOutput);
|
const auto scan = services::NetworkRefreshService::parseDaemonRescanOutput(std::move(newOutput));
|
||||||
bool foundRescan = false;
|
|
||||||
bool finished = false;
|
|
||||||
float rescanPct = 0.0f;
|
|
||||||
std::string lastStatus;
|
|
||||||
|
|
||||||
// Sapling witness rebuild progress. Two daemon signals:
|
|
||||||
// "Setting Initial Sapling Witness for tx <hash>, <i> of <N>" (the common one —
|
|
||||||
// one per wallet tx; <hash> is the stable per-tx key, <N> the total tx count)
|
|
||||||
// "Building Witnesses for block <h> <frac> complete, <n> remaining" (rarer)
|
|
||||||
bool foundWitness = false;
|
|
||||||
float witnessPct = -1.0f; // -1 = no fraction parsed this batch
|
|
||||||
int witnessRemaining = -1;
|
|
||||||
int witnessReadTotal = -1; // new daemon: the <total> in "<done> / <total>"
|
|
||||||
bool witnessRebuilt = false; // new daemon's completion line seen this batch
|
|
||||||
std::vector<std::string> witnessTxids; // distinct-tx keys seen this batch
|
|
||||||
int witnessTotalTxs = -1; // max N seen this batch
|
|
||||||
|
|
||||||
size_t pos = 0;
|
|
||||||
while (pos < output.size()) {
|
|
||||||
size_t eol = output.find('\n', pos);
|
|
||||||
if (eol == std::string::npos) eol = output.size();
|
|
||||||
std::string line = output.substr(pos, eol - pos);
|
|
||||||
pos = eol + 1;
|
|
||||||
|
|
||||||
if (line.find("Rescanning from height") != std::string::npos ||
|
|
||||||
line.find("Rescanning last") != std::string::npos) {
|
|
||||||
foundRescan = true;
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto stillIdx = line.find("Still rescanning");
|
|
||||||
if (stillIdx != std::string::npos) {
|
|
||||||
foundRescan = true;
|
|
||||||
auto progIdx = line.find("Progress=");
|
|
||||||
if (progIdx != std::string::npos) {
|
|
||||||
size_t numStart = progIdx + 9;
|
|
||||||
size_t numEnd = numStart;
|
|
||||||
while (numEnd < line.size() && (std::isdigit(line[numEnd]) || line[numEnd] == '.')) {
|
|
||||||
numEnd++;
|
|
||||||
}
|
|
||||||
if (numEnd > numStart) {
|
|
||||||
try { rescanPct = std::stof(line.substr(numStart, numEnd - numStart)) * 100.0f; } catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto rescIdx = line.find("Rescanning...");
|
|
||||||
if (rescIdx != std::string::npos) {
|
|
||||||
foundRescan = true;
|
|
||||||
auto pctIdx = line.find('%');
|
|
||||||
if (pctIdx != std::string::npos && pctIdx > 0) {
|
|
||||||
size_t numEnd = pctIdx;
|
|
||||||
size_t numStart = numEnd;
|
|
||||||
while (numStart > 0 && (std::isdigit(line[numStart - 1]) || line[numStart - 1] == '.')) {
|
|
||||||
numStart--;
|
|
||||||
}
|
|
||||||
if (numStart < numEnd) {
|
|
||||||
try { rescanPct = std::stof(line.substr(numStart, numEnd - numStart)); } catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.find("Done rescanning") != std::string::npos ||
|
|
||||||
line.find("Rescan complete") != std::string::npos) {
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This daemon prints no "Done rescanning" line; instead it logs the rescan
|
|
||||||
// benchmark timing exactly when the scan finishes, e.g.:
|
|
||||||
// "... rescan 16760577ms"
|
|
||||||
// Match the lowercase " rescan " bench category ending in "<digits>ms" (the
|
|
||||||
// progress lines are "Still rescanning"/"Rescanning...", which never end in ms).
|
|
||||||
if (line.find(" rescan ") != std::string::npos) {
|
|
||||||
std::string t = line;
|
|
||||||
while (!t.empty() && (t.back() == '\r' || t.back() == '\n' || t.back() == ' '))
|
|
||||||
t.pop_back();
|
|
||||||
if (t.size() >= 3 && t.compare(t.size() - 2, 2, "ms") == 0 &&
|
|
||||||
std::isdigit(static_cast<unsigned char>(t[t.size() - 3]))) {
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sapling note witness rebuild: "Building Witnesses for block <h> <frac>
|
|
||||||
// complete, <n> remaining". The float before "complete" is a 0..1 fraction.
|
|
||||||
auto witIdx = line.find("Building Witnesses for block");
|
|
||||||
if (witIdx != std::string::npos) {
|
|
||||||
foundWitness = true;
|
|
||||||
auto compIdx = line.find(" complete", witIdx);
|
|
||||||
if (compIdx != std::string::npos) {
|
|
||||||
size_t numEnd = compIdx, numStart = numEnd;
|
|
||||||
while (numStart > 0 && (std::isdigit((unsigned char)line[numStart - 1]) ||
|
|
||||||
line[numStart - 1] == '.')) numStart--;
|
|
||||||
if (numStart < numEnd) {
|
|
||||||
try { witnessPct = std::stof(line.substr(numStart, numEnd - numStart)) * 100.0f; } catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto remIdx = line.find(" remaining", witIdx);
|
|
||||||
if (remIdx != std::string::npos) {
|
|
||||||
size_t numEnd = remIdx, numStart = numEnd;
|
|
||||||
while (numStart > 0 && std::isdigit((unsigned char)line[numStart - 1])) numStart--;
|
|
||||||
if (numStart < numEnd) {
|
|
||||||
try { witnessRemaining = std::stoi(line.substr(numStart, numEnd - numStart)); } catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Newer multi-threaded daemon: "Reading blocks for witness rebuild: <done> / <total>".
|
|
||||||
// This replaced the per-block "Building Witnesses" line and is an exact done/total
|
|
||||||
// count, so capture BOTH (total drives the denominator directly — no peak anchoring).
|
|
||||||
auto rdIdx = line.find("Reading blocks for witness rebuild:");
|
|
||||||
if (rdIdx != std::string::npos) {
|
|
||||||
foundWitness = true;
|
|
||||||
auto slash = line.find('/', rdIdx);
|
|
||||||
if (slash != std::string::npos) {
|
|
||||||
size_t dEnd = slash;
|
|
||||||
while (dEnd > rdIdx && !std::isdigit((unsigned char)line[dEnd - 1])) dEnd--;
|
|
||||||
size_t dStart = dEnd;
|
|
||||||
while (dStart > rdIdx && std::isdigit((unsigned char)line[dStart - 1])) dStart--;
|
|
||||||
size_t tStart = slash + 1;
|
|
||||||
while (tStart < line.size() && !std::isdigit((unsigned char)line[tStart])) tStart++;
|
|
||||||
size_t tEnd = tStart;
|
|
||||||
while (tEnd < line.size() && std::isdigit((unsigned char)line[tEnd])) tEnd++;
|
|
||||||
if (dStart < dEnd && tStart < tEnd) {
|
|
||||||
try {
|
|
||||||
long done = std::stol(line.substr(dStart, dEnd - dStart));
|
|
||||||
long total = std::stol(line.substr(tStart, tEnd - tStart));
|
|
||||||
if (total > 0 && done >= 0 && done <= total) {
|
|
||||||
witnessRemaining = static_cast<int>(total - done);
|
|
||||||
witnessReadTotal = static_cast<int>(total);
|
|
||||||
}
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// New daemon's witness-rebuild completion line:
|
|
||||||
// "... rebuilt <n> note witness cache(s) to height <h> in <ms>ms using <t> thread(s)".
|
|
||||||
// The parallel rebuild logs no progress, so on completion snap the bar to 100%.
|
|
||||||
if (line.find("note witness cache") != std::string::npos &&
|
|
||||||
line.find("thread(s)") != std::string::npos) {
|
|
||||||
foundWitness = true;
|
|
||||||
witnessRebuilt = true;
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Primary signal: "Setting Initial Sapling Witness for tx <hash>, <i> of <N>".
|
|
||||||
// <i> is the tx's slot in an unordered map (bounces — useless as progress), so
|
|
||||||
// we key on <hash> (one per tx) and count distinct txs against <N>. Extract the
|
|
||||||
// hash between "for tx " and the following comma, and N after " of ".
|
|
||||||
{
|
|
||||||
auto setIdx = line.find("Setting Initial Sapling Witness for tx ");
|
|
||||||
if (setIdx != std::string::npos) {
|
|
||||||
foundWitness = true;
|
|
||||||
size_t hStart = setIdx + std::string("Setting Initial Sapling Witness for tx ").size();
|
|
||||||
size_t comma = line.find(',', hStart);
|
|
||||||
if (comma != std::string::npos && comma > hStart) {
|
|
||||||
witnessTxids.push_back(line.substr(hStart, comma - hStart));
|
|
||||||
auto ofIdx = line.find(" of ", comma);
|
|
||||||
if (ofIdx != std::string::npos) {
|
|
||||||
size_t nStart = ofIdx + 4, nEnd = nStart;
|
|
||||||
while (nEnd < line.size() && std::isdigit((unsigned char)line[nEnd])) nEnd++;
|
|
||||||
if (nEnd > nStart) {
|
|
||||||
try {
|
|
||||||
int n = std::stoi(line.substr(nStart, nEnd - nStart));
|
|
||||||
if (n > witnessTotalTxs) witnessTotalTxs = n;
|
|
||||||
} catch (...) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastStatus = line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply results directly — we are already on the main thread.
|
// Apply results directly — we are already on the main thread.
|
||||||
const std::string& status = lastStatus;
|
const std::string& status = scan.lastStatus;
|
||||||
if (finished) {
|
if (scan.finished) {
|
||||||
if (state_.sync.rescanning) {
|
if (state_.sync.rescanning) {
|
||||||
ui::Notifications::instance().success("Blockchain rescan complete");
|
ui::Notifications::instance().success("Blockchain rescan complete");
|
||||||
}
|
}
|
||||||
// Witness rebuild finishes with the rescan it's part of.
|
// Witness rebuild finishes with the rescan it's part of.
|
||||||
resetWitnessRescanProgress();
|
resetWitnessRescanProgress();
|
||||||
state_.sync.rescan_progress = 1.0f;
|
state_.sync.rescan_progress = 1.0f;
|
||||||
} else if (foundWitness) {
|
} else if (scan.foundWitness) {
|
||||||
// Witness rebuild is the tail phase of a rescan — keep rescanning set so
|
// Witness rebuild is the tail phase of a rescan — keep rescanning set so
|
||||||
// the broader gating holds, but surface the witness-specific progress.
|
// the broader gating holds, but surface the witness-specific progress.
|
||||||
state_.sync.rescanning = true;
|
state_.sync.rescanning = true;
|
||||||
@@ -1056,8 +879,8 @@ void App::update()
|
|||||||
// never drops back to the initial pass. Otherwise an interleaved initial
|
// never drops back to the initial pass. Otherwise an interleaved initial
|
||||||
// line would flip the phase back and reset the bar every batch (thrash).
|
// line would flip the phase back and reset the bar every batch (thrash).
|
||||||
int phase = state_.sync.witness_phase;
|
int phase = state_.sync.witness_phase;
|
||||||
if (witnessRemaining >= 0 || witnessRebuilt) phase = 2;
|
if (scan.witnessRemaining >= 0 || scan.witnessRebuilt) phase = 2;
|
||||||
else if (!witnessTxids.empty() && phase < 2) phase = 1;
|
else if (!scan.witnessTxids.empty() && phase < 2) phase = 1;
|
||||||
|
|
||||||
// Entering a higher sub-phase → reset its progress + accumulators so the
|
// Entering a higher sub-phase → reset its progress + accumulators so the
|
||||||
// bar restarts from 0 (the two phases have different scales). Upgrade-only,
|
// bar restarts from 0 (the two phases have different scales). Upgrade-only,
|
||||||
@@ -1072,24 +895,24 @@ void App::update()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (phase == 2) {
|
if (phase == 2) {
|
||||||
state_.sync.witness_remaining = witnessRemaining;
|
state_.sync.witness_remaining = scan.witnessRemaining;
|
||||||
float p = -1.0f;
|
float p = -1.0f;
|
||||||
if (witnessRebuilt) {
|
if (scan.witnessRebuilt) {
|
||||||
// Parallel rebuild finished (it logs no progress) — snap to 100%.
|
// Parallel rebuild finished (it logs no progress) — snap to 100%.
|
||||||
p = 1.0f;
|
p = 1.0f;
|
||||||
state_.sync.witness_remaining = 0;
|
state_.sync.witness_remaining = 0;
|
||||||
} else if (witnessReadTotal > 0) {
|
} else if (scan.witnessReadTotal > 0) {
|
||||||
// New daemon: exact done/total. Use the reported total as the
|
// New daemon: exact done/total. Use the reported total as the
|
||||||
// denominator directly (no peak anchoring) for a smooth 0..1 sweep.
|
// denominator directly (no peak anchoring) for a smooth 0..1 sweep.
|
||||||
int done = witnessReadTotal - (witnessRemaining >= 0 ? witnessRemaining : 0);
|
int done = scan.witnessReadTotal - (scan.witnessRemaining >= 0 ? scan.witnessRemaining : 0);
|
||||||
p = static_cast<float>(done) / static_cast<float>(witnessReadTotal);
|
p = static_cast<float>(done) / static_cast<float>(scan.witnessReadTotal);
|
||||||
} else if (witnessRemaining >= 0) {
|
} else if (scan.witnessRemaining >= 0) {
|
||||||
// Older daemon: bare "<n> remaining" with no total — anchor the % to
|
// Older daemon: bare "<n> remaining" with no total — anchor the % to
|
||||||
// the largest remaining seen (its first/largest value ≈ the full span).
|
// the largest remaining seen (its first/largest value ≈ the full span).
|
||||||
if (witnessRemaining > witness_rebuild_total_blocks_)
|
if (scan.witnessRemaining > witness_rebuild_total_blocks_)
|
||||||
witness_rebuild_total_blocks_ = witnessRemaining;
|
witness_rebuild_total_blocks_ = scan.witnessRemaining;
|
||||||
if (witness_rebuild_total_blocks_ > 0)
|
if (witness_rebuild_total_blocks_ > 0)
|
||||||
p = 1.0f - static_cast<float>(witnessRemaining) /
|
p = 1.0f - static_cast<float>(scan.witnessRemaining) /
|
||||||
static_cast<float>(witness_rebuild_total_blocks_);
|
static_cast<float>(witness_rebuild_total_blocks_);
|
||||||
}
|
}
|
||||||
if (p >= 0.0f) {
|
if (p >= 0.0f) {
|
||||||
@@ -1101,8 +924,8 @@ void App::update()
|
|||||||
// Initial pass: count DISTINCT witnessed txs / total. The set only
|
// Initial pass: count DISTINCT witnessed txs / total. The set only
|
||||||
// grows (dedups the daemon's double-prints), so it's monotonic; the
|
// grows (dedups the daemon's double-prints), so it's monotonic; the
|
||||||
// raw "<i>" can't be used — it's an unordered-map slot that bounces.
|
// raw "<i>" can't be used — it's an unordered-map slot that bounces.
|
||||||
if (witnessTotalTxs > witness_total_txs_) witness_total_txs_ = witnessTotalTxs;
|
if (scan.witnessTotalTxs > witness_total_txs_) witness_total_txs_ = scan.witnessTotalTxs;
|
||||||
for (const auto& txid : witnessTxids) witness_seen_txids_.insert(txid);
|
for (const auto& txid : scan.witnessTxids) witness_seen_txids_.insert(txid);
|
||||||
if (witness_total_txs_ > 0 && !witness_seen_txids_.empty()) {
|
if (witness_total_txs_ > 0 && !witness_seen_txids_.empty()) {
|
||||||
float p = static_cast<float>(witness_seen_txids_.size()) /
|
float p = static_cast<float>(witness_seen_txids_.size()) /
|
||||||
static_cast<float>(witness_total_txs_);
|
static_cast<float>(witness_total_txs_);
|
||||||
@@ -1114,14 +937,14 @@ void App::update()
|
|||||||
if (!status.empty()) {
|
if (!status.empty()) {
|
||||||
state_.sync.rescan_status = status;
|
state_.sync.rescan_status = status;
|
||||||
}
|
}
|
||||||
} else if (foundRescan) {
|
} else if (scan.foundRescan) {
|
||||||
state_.sync.rescanning = true;
|
state_.sync.rescanning = true;
|
||||||
// Reading "Still rescanning" straight from the daemon log is hard proof the
|
// Reading "Still rescanning" straight from the daemon log is hard proof the
|
||||||
// rescan is genuinely running — confirm it so the getrescaninfo poll's
|
// rescan is genuinely running — confirm it so the getrescaninfo poll's
|
||||||
// completion check can fire even if it never caught a warmup error.
|
// completion check can fire even if it never caught a warmup error.
|
||||||
rescan_confirmed_active_ = true;
|
rescan_confirmed_active_ = true;
|
||||||
if (rescanPct > 0.0f) {
|
if (scan.rescanPct > 0.0f) {
|
||||||
state_.sync.rescan_progress = rescanPct / 100.0f;
|
state_.sync.rescan_progress = scan.rescanPct / 100.0f;
|
||||||
}
|
}
|
||||||
if (!status.empty()) {
|
if (!status.empty()) {
|
||||||
state_.sync.rescan_status = status;
|
state_.sync.rescan_status = status;
|
||||||
@@ -1290,6 +1113,13 @@ void App::update()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleGlobalShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::handleGlobalShortcuts()
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
// Keyboard shortcut: Ctrl+, to open Settings page
|
// Keyboard shortcut: Ctrl+, to open Settings page
|
||||||
if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Comma)) {
|
if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Comma)) {
|
||||||
setCurrentPage(ui::NavPage::Settings);
|
setCurrentPage(ui::NavPage::Settings);
|
||||||
|
|||||||
@@ -443,6 +443,9 @@ private:
|
|||||||
friend class AppDaemonLifecycleRuntime;
|
friend class AppDaemonLifecycleRuntime;
|
||||||
friend class AppDaemonLifecycleTaskContext;
|
friend class AppDaemonLifecycleTaskContext;
|
||||||
|
|
||||||
|
// Global keyboard-shortcut handling, dispatched once per frame from update().
|
||||||
|
void handleGlobalShortcuts();
|
||||||
|
|
||||||
bool sendStopCommandSafely(rpc::RPCClient& client, const char* context);
|
bool sendStopCommandSafely(rpc::RPCClient& client, const char* context);
|
||||||
void maybeFinishTransactionSendProgress();
|
void maybeFinishTransactionSendProgress();
|
||||||
void upsertPendingSendTransaction(const std::string& opid,
|
void upsertPendingSendTransaction(const std::string& opid,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../util/logger.h"
|
#include "../util/logger.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -1336,6 +1337,190 @@ void NetworkRefreshService::resetJobs()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkRefreshService::DaemonRescanScan NetworkRefreshService::parseDaemonRescanOutput(const std::string& output)
|
||||||
|
{
|
||||||
|
DaemonRescanScan scan;
|
||||||
|
bool& foundRescan = scan.foundRescan;
|
||||||
|
bool& finished = scan.finished;
|
||||||
|
float& rescanPct = scan.rescanPct;
|
||||||
|
std::string& lastStatus = scan.lastStatus;
|
||||||
|
|
||||||
|
// Sapling witness rebuild progress. Two daemon signals:
|
||||||
|
// "Setting Initial Sapling Witness for tx <hash>, <i> of <N>" (the common one —
|
||||||
|
// one per wallet tx; <hash> is the stable per-tx key, <N> the total tx count)
|
||||||
|
// "Building Witnesses for block <h> <frac> complete, <n> remaining" (rarer)
|
||||||
|
bool& foundWitness = scan.foundWitness;
|
||||||
|
float& witnessPct = scan.witnessPct; // -1 = no fraction parsed this batch
|
||||||
|
int& witnessRemaining = scan.witnessRemaining;
|
||||||
|
int& witnessReadTotal = scan.witnessReadTotal; // new daemon: the <total> in "<done> / <total>"
|
||||||
|
bool& witnessRebuilt = scan.witnessRebuilt; // new daemon's completion line seen this batch
|
||||||
|
std::vector<std::string>& witnessTxids = scan.witnessTxids; // distinct-tx keys seen this batch
|
||||||
|
int& witnessTotalTxs = scan.witnessTotalTxs; // max N seen this batch
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < output.size()) {
|
||||||
|
size_t eol = output.find('\n', pos);
|
||||||
|
if (eol == std::string::npos) eol = output.size();
|
||||||
|
std::string line = output.substr(pos, eol - pos);
|
||||||
|
pos = eol + 1;
|
||||||
|
|
||||||
|
if (line.find("Rescanning from height") != std::string::npos ||
|
||||||
|
line.find("Rescanning last") != std::string::npos) {
|
||||||
|
foundRescan = true;
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stillIdx = line.find("Still rescanning");
|
||||||
|
if (stillIdx != std::string::npos) {
|
||||||
|
foundRescan = true;
|
||||||
|
auto progIdx = line.find("Progress=");
|
||||||
|
if (progIdx != std::string::npos) {
|
||||||
|
size_t numStart = progIdx + 9;
|
||||||
|
size_t numEnd = numStart;
|
||||||
|
while (numEnd < line.size() && (std::isdigit(line[numEnd]) || line[numEnd] == '.')) {
|
||||||
|
numEnd++;
|
||||||
|
}
|
||||||
|
if (numEnd > numStart) {
|
||||||
|
try { rescanPct = std::stof(line.substr(numStart, numEnd - numStart)) * 100.0f; } catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rescIdx = line.find("Rescanning...");
|
||||||
|
if (rescIdx != std::string::npos) {
|
||||||
|
foundRescan = true;
|
||||||
|
auto pctIdx = line.find('%');
|
||||||
|
if (pctIdx != std::string::npos && pctIdx > 0) {
|
||||||
|
size_t numEnd = pctIdx;
|
||||||
|
size_t numStart = numEnd;
|
||||||
|
while (numStart > 0 && (std::isdigit(line[numStart - 1]) || line[numStart - 1] == '.')) {
|
||||||
|
numStart--;
|
||||||
|
}
|
||||||
|
if (numStart < numEnd) {
|
||||||
|
try { rescanPct = std::stof(line.substr(numStart, numEnd - numStart)); } catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.find("Done rescanning") != std::string::npos ||
|
||||||
|
line.find("Rescan complete") != std::string::npos) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This daemon prints no "Done rescanning" line; instead it logs the rescan
|
||||||
|
// benchmark timing exactly when the scan finishes, e.g.:
|
||||||
|
// "... rescan 16760577ms"
|
||||||
|
// Match the lowercase " rescan " bench category ending in "<digits>ms" (the
|
||||||
|
// progress lines are "Still rescanning"/"Rescanning...", which never end in ms).
|
||||||
|
if (line.find(" rescan ") != std::string::npos) {
|
||||||
|
std::string t = line;
|
||||||
|
while (!t.empty() && (t.back() == '\r' || t.back() == '\n' || t.back() == ' '))
|
||||||
|
t.pop_back();
|
||||||
|
if (t.size() >= 3 && t.compare(t.size() - 2, 2, "ms") == 0 &&
|
||||||
|
std::isdigit(static_cast<unsigned char>(t[t.size() - 3]))) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sapling note witness rebuild: "Building Witnesses for block <h> <frac>
|
||||||
|
// complete, <n> remaining". The float before "complete" is a 0..1 fraction.
|
||||||
|
auto witIdx = line.find("Building Witnesses for block");
|
||||||
|
if (witIdx != std::string::npos) {
|
||||||
|
foundWitness = true;
|
||||||
|
auto compIdx = line.find(" complete", witIdx);
|
||||||
|
if (compIdx != std::string::npos) {
|
||||||
|
size_t numEnd = compIdx, numStart = numEnd;
|
||||||
|
while (numStart > 0 && (std::isdigit((unsigned char)line[numStart - 1]) ||
|
||||||
|
line[numStart - 1] == '.')) numStart--;
|
||||||
|
if (numStart < numEnd) {
|
||||||
|
try { witnessPct = std::stof(line.substr(numStart, numEnd - numStart)) * 100.0f; } catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto remIdx = line.find(" remaining", witIdx);
|
||||||
|
if (remIdx != std::string::npos) {
|
||||||
|
size_t numEnd = remIdx, numStart = numEnd;
|
||||||
|
while (numStart > 0 && std::isdigit((unsigned char)line[numStart - 1])) numStart--;
|
||||||
|
if (numStart < numEnd) {
|
||||||
|
try { witnessRemaining = std::stoi(line.substr(numStart, numEnd - numStart)); } catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newer multi-threaded daemon: "Reading blocks for witness rebuild: <done> / <total>".
|
||||||
|
// This replaced the per-block "Building Witnesses" line and is an exact done/total
|
||||||
|
// count, so capture BOTH (total drives the denominator directly — no peak anchoring).
|
||||||
|
auto rdIdx = line.find("Reading blocks for witness rebuild:");
|
||||||
|
if (rdIdx != std::string::npos) {
|
||||||
|
foundWitness = true;
|
||||||
|
auto slash = line.find('/', rdIdx);
|
||||||
|
if (slash != std::string::npos) {
|
||||||
|
size_t dEnd = slash;
|
||||||
|
while (dEnd > rdIdx && !std::isdigit((unsigned char)line[dEnd - 1])) dEnd--;
|
||||||
|
size_t dStart = dEnd;
|
||||||
|
while (dStart > rdIdx && std::isdigit((unsigned char)line[dStart - 1])) dStart--;
|
||||||
|
size_t tStart = slash + 1;
|
||||||
|
while (tStart < line.size() && !std::isdigit((unsigned char)line[tStart])) tStart++;
|
||||||
|
size_t tEnd = tStart;
|
||||||
|
while (tEnd < line.size() && std::isdigit((unsigned char)line[tEnd])) tEnd++;
|
||||||
|
if (dStart < dEnd && tStart < tEnd) {
|
||||||
|
try {
|
||||||
|
long done = std::stol(line.substr(dStart, dEnd - dStart));
|
||||||
|
long total = std::stol(line.substr(tStart, tEnd - tStart));
|
||||||
|
if (total > 0 && done >= 0 && done <= total) {
|
||||||
|
witnessRemaining = static_cast<int>(total - done);
|
||||||
|
witnessReadTotal = static_cast<int>(total);
|
||||||
|
}
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New daemon's witness-rebuild completion line:
|
||||||
|
// "... rebuilt <n> note witness cache(s) to height <h> in <ms>ms using <t> thread(s)".
|
||||||
|
// The parallel rebuild logs no progress, so on completion snap the bar to 100%.
|
||||||
|
if (line.find("note witness cache") != std::string::npos &&
|
||||||
|
line.find("thread(s)") != std::string::npos) {
|
||||||
|
foundWitness = true;
|
||||||
|
witnessRebuilt = true;
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary signal: "Setting Initial Sapling Witness for tx <hash>, <i> of <N>".
|
||||||
|
// <i> is the tx's slot in an unordered map (bounces — useless as progress), so
|
||||||
|
// we key on <hash> (one per tx) and count distinct txs against <N>. Extract the
|
||||||
|
// hash between "for tx " and the following comma, and N after " of ".
|
||||||
|
{
|
||||||
|
auto setIdx = line.find("Setting Initial Sapling Witness for tx ");
|
||||||
|
if (setIdx != std::string::npos) {
|
||||||
|
foundWitness = true;
|
||||||
|
size_t hStart = setIdx + std::string("Setting Initial Sapling Witness for tx ").size();
|
||||||
|
size_t comma = line.find(',', hStart);
|
||||||
|
if (comma != std::string::npos && comma > hStart) {
|
||||||
|
witnessTxids.push_back(line.substr(hStart, comma - hStart));
|
||||||
|
auto ofIdx = line.find(" of ", comma);
|
||||||
|
if (ofIdx != std::string::npos) {
|
||||||
|
size_t nStart = ofIdx + 4, nEnd = nStart;
|
||||||
|
while (nEnd < line.size() && std::isdigit((unsigned char)line[nEnd])) nEnd++;
|
||||||
|
if (nEnd > nStart) {
|
||||||
|
try {
|
||||||
|
int n = std::stoi(line.substr(nStart, nEnd - nStart));
|
||||||
|
if (n > witnessTotalTxs) witnessTotalTxs = n;
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStatus = line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkRefreshService::DispatchTicket NetworkRefreshService::beginDispatch(
|
NetworkRefreshService::DispatchTicket NetworkRefreshService::beginDispatch(
|
||||||
Job job, std::size_t queuedWork, std::size_t maxQueuedWork)
|
Job job, std::size_t queuedWork, std::size_t maxQueuedWork)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -293,6 +293,23 @@ public:
|
|||||||
static OperationStatusPollResult parseOperationStatusPoll(const nlohmann::json& result,
|
static OperationStatusPollResult parseOperationStatusPoll(const nlohmann::json& result,
|
||||||
const std::vector<std::string>& requestedOpids);
|
const std::vector<std::string>& requestedOpids);
|
||||||
|
|
||||||
|
struct DaemonRescanScan {
|
||||||
|
bool foundRescan = false;
|
||||||
|
bool finished = false;
|
||||||
|
float rescanPct = 0.0f;
|
||||||
|
std::string lastStatus;
|
||||||
|
bool foundWitness = false;
|
||||||
|
float witnessPct = -1.0f;
|
||||||
|
int witnessRemaining = -1;
|
||||||
|
int witnessReadTotal = -1;
|
||||||
|
bool witnessRebuilt = false;
|
||||||
|
std::vector<std::string> witnessTxids;
|
||||||
|
int witnessTotalTxs = -1;
|
||||||
|
};
|
||||||
|
// Pure line-by-line scan of newly-appended dragonxd stdout for rescan + Sapling
|
||||||
|
// witness-rebuild progress. No I/O, no state — unit-tested against fixture log snippets.
|
||||||
|
static DaemonRescanScan parseDaemonRescanOutput(const std::string& output);
|
||||||
|
|
||||||
static void applyConnectionInfoResult(WalletState& state, const ConnectionInfoResult& result);
|
static void applyConnectionInfoResult(WalletState& state, const ConnectionInfoResult& result);
|
||||||
static void applyWalletEncryptionResult(WalletState& state, const WalletEncryptionResult& result);
|
static void applyWalletEncryptionResult(WalletState& state, const WalletEncryptionResult& result);
|
||||||
static void applyConnectionInitResult(WalletState& state, const ConnectionInitResult& result);
|
static void applyConnectionInitResult(WalletState& state, const ConnectionInitResult& result);
|
||||||
|
|||||||
@@ -107,7 +107,10 @@ int MatchPreset(AcrylicQuality q, float blur)
|
|||||||
|
|
||||||
} } } } // namespace dragonx::ui::effects::ImGuiAcrylic
|
} } } } // namespace dragonx::ui::effects::ImGuiAcrylic
|
||||||
|
|
||||||
#ifdef DRAGONX_HAS_GLAD
|
// Single ImGuiAcrylic frontend for BOTH GL (GLAD) and DX11: the frontend makes no
|
||||||
|
// direct GL/DX calls — every backend op is delegated to AcrylicMaterial, which has a
|
||||||
|
// backend for each API — so it must not fork per-backend.
|
||||||
|
#if defined(DRAGONX_HAS_GLAD) || defined(DRAGONX_USE_DX11)
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
@@ -638,408 +641,6 @@ void ApplyBlurAmount(float blur)
|
|||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|
||||||
#endif // DRAGONX_HAS_GLAD
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Stub Implementations (No GLAD)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
#ifndef DRAGONX_HAS_GLAD
|
|
||||||
|
|
||||||
#ifdef DRAGONX_USE_DX11
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// DX11 Implementation — mirrors the GLAD version above, delegating to
|
|
||||||
// AcrylicMaterial which now has a full DX11 backend.
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
namespace dragonx {
|
|
||||||
namespace ui {
|
|
||||||
namespace effects {
|
|
||||||
|
|
||||||
namespace ImGuiAcrylic {
|
|
||||||
|
|
||||||
static bool s_initialized = false;
|
|
||||||
static int s_viewportWidth = 0;
|
|
||||||
static int s_viewportHeight = 0;
|
|
||||||
static bool s_backgroundCaptured = false;
|
|
||||||
|
|
||||||
struct AcrylicWindowState {
|
|
||||||
ImVec2 windowPos;
|
|
||||||
ImVec2 windowSize;
|
|
||||||
AcrylicParams params;
|
|
||||||
bool active;
|
|
||||||
};
|
|
||||||
static AcrylicWindowState s_currentWindow = {};
|
|
||||||
|
|
||||||
bool Init()
|
|
||||||
{
|
|
||||||
if (s_initialized) return true;
|
|
||||||
|
|
||||||
AcrylicMaterial& acrylic = getAcrylicMaterial();
|
|
||||||
if (!acrylic.init()) {
|
|
||||||
DEBUG_LOGF("ImGuiAcrylic DX11: Failed to initialize acrylic material\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_initialized = true;
|
|
||||||
DEBUG_LOGF("ImGuiAcrylic DX11: Initialized successfully\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shutdown()
|
|
||||||
{
|
|
||||||
if (!s_initialized) return;
|
|
||||||
getAcrylicMaterial().shutdown();
|
|
||||||
s_initialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsAvailable()
|
|
||||||
{
|
|
||||||
return s_initialized && getAcrylicMaterial().isInitialized();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BeginFrame(int width, int height)
|
|
||||||
{
|
|
||||||
if (!s_initialized) return;
|
|
||||||
|
|
||||||
if (width != s_viewportWidth || height != s_viewportHeight) {
|
|
||||||
s_viewportWidth = width;
|
|
||||||
s_viewportHeight = height;
|
|
||||||
getAcrylicMaterial().resize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture the first few frames to ensure a clean initial blur,
|
|
||||||
// then stop. Resize / theme change already sets dirtyFrames_ via markBackgroundDirty().
|
|
||||||
static int s_warmupFrames = 3;
|
|
||||||
if (s_warmupFrames > 0) {
|
|
||||||
getAcrylicMaterial().markBackgroundDirty();
|
|
||||||
--s_warmupFrames;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_backgroundCaptured = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CaptureBackground()
|
|
||||||
{
|
|
||||||
if (!s_initialized || s_backgroundCaptured) return;
|
|
||||||
getAcrylicMaterial().captureBackground();
|
|
||||||
s_backgroundCaptured = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InvalidateCapture()
|
|
||||||
{
|
|
||||||
if (!s_initialized) return;
|
|
||||||
getAcrylicMaterial().markBackgroundDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void BackgroundCaptureCallback(const ImDrawList*, const ImDrawCmd*)
|
|
||||||
{
|
|
||||||
if (!s_initialized) return;
|
|
||||||
getAcrylicMaterial().captureBackgroundDirect();
|
|
||||||
s_backgroundCaptured = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImDrawCallback GetBackgroundCaptureCallback()
|
|
||||||
{
|
|
||||||
return BackgroundCaptureCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawAcrylicRect(ImDrawList* drawList,
|
|
||||||
const ImVec2& pMin, const ImVec2& pMax,
|
|
||||||
const AcrylicParams& params,
|
|
||||||
float rounding)
|
|
||||||
{
|
|
||||||
if (!drawList) return;
|
|
||||||
|
|
||||||
if (!IsAvailable() || !IsEnabled() || !params.enabled) {
|
|
||||||
drawList->AddRectFilled(pMin, pMax,
|
|
||||||
ImGui::ColorConvertFloat4ToU32(params.fallbackColor), rounding);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAcrylicMaterial().drawRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawAcrylicRect(ImDrawList* drawList,
|
|
||||||
const ImVec2& pMin, const ImVec2& pMax,
|
|
||||||
float rounding)
|
|
||||||
{
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, AcrylicMaterial::getDarkPreset(), rounding);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BeginAcrylicWindow(const char* name, bool* p_open,
|
|
||||||
ImGuiWindowFlags flags,
|
|
||||||
const AcrylicParams& params)
|
|
||||||
{
|
|
||||||
s_currentWindow.params = params;
|
|
||||||
s_currentWindow.active = true;
|
|
||||||
|
|
||||||
if (!(flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
|
|
||||||
ImGui::SetNextWindowFocus();
|
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
|
|
||||||
|
|
||||||
bool result = ImGui::Begin(name, p_open, flags);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
s_currentWindow.windowPos = ImGui::GetWindowPos();
|
|
||||||
s_currentWindow.windowSize = ImGui::GetWindowSize();
|
|
||||||
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 pMin = s_currentWindow.windowPos;
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + s_currentWindow.windowSize.x,
|
|
||||||
pMin.y + s_currentWindow.windowSize.y);
|
|
||||||
|
|
||||||
float rounding = ImGui::GetStyle().WindowRounding;
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndAcrylicWindow()
|
|
||||||
{
|
|
||||||
ImGui::End();
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
s_currentWindow.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BeginAcrylicChild(const char* str_id,
|
|
||||||
const ImVec2& size,
|
|
||||||
const AcrylicParams& params,
|
|
||||||
ImGuiChildFlags child_flags,
|
|
||||||
ImGuiWindowFlags window_flags)
|
|
||||||
{
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
|
|
||||||
bool result = ImGui::BeginChild(str_id, size, child_flags, window_flags);
|
|
||||||
if (result) {
|
|
||||||
ImVec2 childPos = ImGui::GetWindowPos();
|
|
||||||
ImVec2 childSize = ImGui::GetWindowSize();
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 pMin = childPos;
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + childSize.x, pMin.y + childSize.y);
|
|
||||||
float rounding = ImGui::GetStyle().ChildRounding;
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndAcrylicChild()
|
|
||||||
{
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BeginAcrylicPopup(const char* str_id,
|
|
||||||
ImGuiWindowFlags flags,
|
|
||||||
const AcrylicParams& params)
|
|
||||||
{
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
|
|
||||||
bool result = ImGui::BeginPopup(str_id, flags);
|
|
||||||
if (result) {
|
|
||||||
ImVec2 popupPos = ImGui::GetWindowPos();
|
|
||||||
ImVec2 popupSize = ImGui::GetWindowSize();
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 pMin = popupPos;
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
|
|
||||||
float rounding = ImGui::GetStyle().PopupRounding;
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
} else {
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndAcrylicPopup()
|
|
||||||
{
|
|
||||||
ImGui::EndPopup();
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BeginAcrylicPopupModal(const char* name, bool* p_open,
|
|
||||||
ImGuiWindowFlags flags,
|
|
||||||
const AcrylicParams& params)
|
|
||||||
{
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, ImVec4(0, 0, 0, 0));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
|
|
||||||
bool result = ImGui::BeginPopupModal(name, p_open, flags);
|
|
||||||
ImGui::PopStyleColor(); // ModalWindowDimBg
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
DrawFrostedScrim(drawList);
|
|
||||||
ImVec2 popupPos = ImGui::GetWindowPos();
|
|
||||||
ImVec2 popupSize = ImGui::GetWindowSize();
|
|
||||||
ImVec2 pMin = popupPos;
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
|
|
||||||
float rounding = ImGui::GetStyle().PopupRounding;
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
} else {
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BeginAcrylicContextItem(const char* str_id,
|
|
||||||
ImGuiPopupFlags popup_flags,
|
|
||||||
const AcrylicParams& params)
|
|
||||||
{
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
|
|
||||||
bool result = ImGui::BeginPopupContextItem(str_id, popup_flags);
|
|
||||||
if (result) {
|
|
||||||
ImVec2 popupPos = ImGui::GetWindowPos();
|
|
||||||
ImVec2 popupSize = ImGui::GetWindowSize();
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 pMin = popupPos;
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
|
|
||||||
float rounding = ImGui::GetStyle().PopupRounding;
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
} else {
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BeginAcrylicContextWindow(const char* str_id,
|
|
||||||
ImGuiPopupFlags popup_flags,
|
|
||||||
const AcrylicParams& params)
|
|
||||||
{
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0));
|
|
||||||
bool result = ImGui::BeginPopupContextWindow(str_id, popup_flags);
|
|
||||||
if (result) {
|
|
||||||
ImVec2 popupPos = ImGui::GetWindowPos();
|
|
||||||
ImVec2 popupSize = ImGui::GetWindowSize();
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 pMin = popupPos;
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + popupSize.x, pMin.y + popupSize.y);
|
|
||||||
float rounding = ImGui::GetStyle().PopupRounding;
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, rounding);
|
|
||||||
} else {
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawSidebarBackground(float width, const AcrylicParams& params)
|
|
||||||
{
|
|
||||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
||||||
float menuBarHeight = ImGui::GetFrameHeight();
|
|
||||||
ImVec2 pMin = ImVec2(viewport->WorkPos.x, viewport->WorkPos.y + menuBarHeight);
|
|
||||||
ImVec2 pMax = ImVec2(pMin.x + width, viewport->WorkPos.y + viewport->WorkSize.y);
|
|
||||||
ImDrawList* drawList = ImGui::GetBackgroundDrawList();
|
|
||||||
DrawAcrylicRect(drawList, pMin, pMax, params, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetQuality(AcrylicQuality quality)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setQuality(quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
AcrylicQuality GetQuality()
|
|
||||||
{
|
|
||||||
if (s_initialized) return getAcrylicMaterial().getQuality();
|
|
||||||
return AcrylicQuality::Off;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetEnabled(bool enabled)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsEnabled()
|
|
||||||
{
|
|
||||||
if (s_initialized) return getAcrylicMaterial().isEnabled();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetBlurMultiplier(float multiplier)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setBlurMultiplier(multiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
float GetBlurMultiplier()
|
|
||||||
{
|
|
||||||
if (s_initialized) return getAcrylicMaterial().getBlurMultiplier();
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetReducedTransparency(bool reduced)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setReducedTransparency(reduced);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetReducedTransparency()
|
|
||||||
{
|
|
||||||
if (s_initialized) return getAcrylicMaterial().getReducedTransparency();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetUIOpacity(float opacity)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setUIOpacity(opacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
float GetUIOpacity()
|
|
||||||
{
|
|
||||||
if (s_initialized) return getAcrylicMaterial().getUIOpacity();
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetNoiseOpacity(float multiplier)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setNoiseOpacityMultiplier(multiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
float GetNoiseOpacity()
|
|
||||||
{
|
|
||||||
if (s_initialized) return getAcrylicMaterial().getNoiseOpacityMultiplier();
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AcrylicSettings& GetSettings()
|
|
||||||
{
|
|
||||||
return getAcrylicMaterial().getSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSettings(const AcrylicSettings& settings)
|
|
||||||
{
|
|
||||||
if (s_initialized) getAcrylicMaterial().setSettings(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetPreset(int preset)
|
|
||||||
{
|
|
||||||
if (preset < 0 || preset >= kPresetCount) preset = 3;
|
|
||||||
SetEnabled(preset != 0);
|
|
||||||
SetQuality(PresetQuality(preset));
|
|
||||||
SetBlurMultiplier(PresetBlur(preset));
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetPreset()
|
|
||||||
{
|
|
||||||
return MatchPreset(GetQuality(), GetBlurMultiplier());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyBlurAmount(float blur)
|
|
||||||
{
|
|
||||||
if (blur < 0.001f) {
|
|
||||||
SetEnabled(false);
|
|
||||||
SetQuality(AcrylicQuality::Off);
|
|
||||||
SetBlurMultiplier(0.0f);
|
|
||||||
} else {
|
|
||||||
SetEnabled(true);
|
|
||||||
SetQuality(AcrylicQuality::Low);
|
|
||||||
SetBlurMultiplier(blur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ImGuiAcrylic
|
|
||||||
|
|
||||||
} // namespace effects
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace dragonx
|
|
||||||
|
|
||||||
#else // neither GLAD nor DX11
|
#else // neither GLAD nor DX11
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
@@ -1173,6 +774,5 @@ void ApplyBlurAmount(float) {}
|
|||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|
||||||
#endif // DRAGONX_USE_DX11
|
|
||||||
|
|
||||||
#endif // !DRAGONX_HAS_GLAD
|
#endif // DRAGONX_HAS_GLAD || DRAGONX_USE_DX11
|
||||||
|
|||||||
@@ -1051,6 +1051,99 @@ void testNetworkRefreshSnapshotHelpers()
|
|||||||
EXPECT_EQ(snapshot.sendTxids.count("pending-send"), static_cast<size_t>(1));
|
EXPECT_EQ(snapshot.sendTxids.count("pending-send"), static_cast<size_t>(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testParseDaemonRescanOutput()
|
||||||
|
{
|
||||||
|
using dragonx::services::NetworkRefreshService;
|
||||||
|
|
||||||
|
// "Still rescanning ... Progress=<frac>" — fraction scaled to a percentage.
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
"2026-07-01 Still rescanning at height 500000. Progress=0.42 tip=600000\n");
|
||||||
|
EXPECT_TRUE(scan.foundRescan);
|
||||||
|
EXPECT_NEAR(scan.rescanPct, 42.0f, 0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Rescanning... <n>%" — percentage read directly (already 0..100).
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
"Rescanning... 37% done\n");
|
||||||
|
EXPECT_TRUE(scan.foundRescan);
|
||||||
|
EXPECT_NEAR(scan.rescanPct, 37.0f, 0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit completion line marks finished.
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput("Done rescanning\n");
|
||||||
|
EXPECT_TRUE(scan.finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The daemon that prints no "Done rescanning" logs a rescan benchmark ending in "<digits>ms".
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
" rescan 16760577ms\n");
|
||||||
|
EXPECT_TRUE(scan.finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Building Witnesses for block <h> <frac> complete, <n> remaining".
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
"Building Witnesses for block 123 0.5 complete, 40 remaining\n");
|
||||||
|
EXPECT_TRUE(scan.foundWitness);
|
||||||
|
EXPECT_NEAR(scan.witnessPct, 50.0f, 0.01f);
|
||||||
|
EXPECT_EQ(scan.witnessRemaining, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Reading blocks for witness rebuild: <done> / <total>" — captures total and remaining.
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
"Reading blocks for witness rebuild: 10 / 200\n");
|
||||||
|
EXPECT_TRUE(scan.foundWitness);
|
||||||
|
EXPECT_EQ(scan.witnessReadTotal, 200);
|
||||||
|
EXPECT_EQ(scan.witnessRemaining, 190); // total - done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel-rebuild completion line snaps witnessRebuilt=true.
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
"rebuilt 3 note witness cache(s) to height 99 in 12ms using 4 thread(s)\n");
|
||||||
|
EXPECT_TRUE(scan.foundWitness);
|
||||||
|
EXPECT_TRUE(scan.witnessRebuilt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Setting Initial Sapling Witness for tx <hash>, <i> of <N>" — distinct hashes, max N.
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
|
||||||
|
"Setting Initial Sapling Witness for tx abc123, 2 of 7\n"
|
||||||
|
"Setting Initial Sapling Witness for tx def456, 3 of 7\n"
|
||||||
|
"Setting Initial Sapling Witness for tx abc123, 5 of 7\n");
|
||||||
|
EXPECT_TRUE(scan.foundWitness);
|
||||||
|
// The parser collects every occurrence (dedup happens in the apply step's set), so the
|
||||||
|
// vector has all three keys but only two distinct values.
|
||||||
|
EXPECT_EQ(scan.witnessTxids.size(), static_cast<size_t>(3));
|
||||||
|
std::set<std::string> distinct(scan.witnessTxids.begin(), scan.witnessTxids.end());
|
||||||
|
EXPECT_EQ(distinct.size(), static_cast<size_t>(2));
|
||||||
|
EXPECT_EQ(distinct.count("abc123"), static_cast<size_t>(1));
|
||||||
|
EXPECT_EQ(distinct.count("def456"), static_cast<size_t>(1));
|
||||||
|
EXPECT_EQ(scan.witnessTotalTxs, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty input yields all defaults.
|
||||||
|
{
|
||||||
|
auto scan = NetworkRefreshService::parseDaemonRescanOutput("");
|
||||||
|
EXPECT_FALSE(scan.foundRescan);
|
||||||
|
EXPECT_FALSE(scan.finished);
|
||||||
|
EXPECT_NEAR(scan.rescanPct, 0.0f, 0.0001f);
|
||||||
|
EXPECT_TRUE(scan.lastStatus.empty());
|
||||||
|
EXPECT_FALSE(scan.foundWitness);
|
||||||
|
EXPECT_NEAR(scan.witnessPct, -1.0f, 0.0001f);
|
||||||
|
EXPECT_EQ(scan.witnessRemaining, -1);
|
||||||
|
EXPECT_EQ(scan.witnessReadTotal, -1);
|
||||||
|
EXPECT_FALSE(scan.witnessRebuilt);
|
||||||
|
EXPECT_TRUE(scan.witnessTxids.empty());
|
||||||
|
EXPECT_EQ(scan.witnessTotalTxs, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void testNetworkRefreshRpcCollectors()
|
void testNetworkRefreshRpcCollectors()
|
||||||
{
|
{
|
||||||
using Refresh = dragonx::services::NetworkRefreshService;
|
using Refresh = dragonx::services::NetworkRefreshService;
|
||||||
@@ -5381,6 +5474,7 @@ int main()
|
|||||||
testRefreshScheduler();
|
testRefreshScheduler();
|
||||||
testNetworkRefreshService();
|
testNetworkRefreshService();
|
||||||
testNetworkRefreshSnapshotHelpers();
|
testNetworkRefreshSnapshotHelpers();
|
||||||
|
testParseDaemonRescanOutput();
|
||||||
testNetworkRefreshRpcCollectors();
|
testNetworkRefreshRpcCollectors();
|
||||||
testNetworkRefreshResultModels();
|
testNetworkRefreshResultModels();
|
||||||
testOperationStatusPollParsing();
|
testOperationStatusPollParsing();
|
||||||
|
|||||||
Reference in New Issue
Block a user