From 32ea4d0524269bb0ee82eb2c7e725558121d8b30 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 13 Oct 2026 16:00:10 -0500 Subject: [PATCH] fix(mining): keep the thread benchmark running while the tab is hidden The thread benchmark state machine was advanced from the Mining tab's render path, so it only progressed while that tab was visible and the window was on screen. Hiding the tab or minimizing the window froze the benchmark mid-sweep. - The benchmark now advances from the main update loop, ungated by connectivity, so it keeps running when the tab is hidden or the window is minimized. - Its clock is driven from a steady wall-clock reading rather than the render delta, and each step is clamped like the other logic timers so a suspend/resume cannot leap a benchmark phase. Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 3 +++ src/ui/windows/mining_benchmark.cpp | 8 ++++++ src/ui/windows/mining_benchmark.h | 4 +++ src/ui/windows/mining_tab.cpp | 39 +++++++++++++++-------------- src/ui/windows/mining_tab.h | 6 +++++ 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 355a5de..8fbaf62 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1079,6 +1079,9 @@ void App::update() // Mine-when-idle check (cheap on most frames: it gates itself to 1 Hz) checkIdleMining(); + // Thread benchmark: ungated (xmrig->pool is daemon-independent) so it advances while minimized/tab hidden. + ui::TickMiningBenchmark(this, state_.pool_mining.hashrate_10s); + // P8: Dedup rebuildAddressList — only rebuild once per frame if (address_list_dirty_) { address_list_dirty_ = false; diff --git a/src/ui/windows/mining_benchmark.cpp b/src/ui/windows/mining_benchmark.cpp index 6bf6bbf..9eb3d54 100644 --- a/src/ui/windows/mining_benchmark.cpp +++ b/src/ui/windows/mining_benchmark.cpp @@ -6,6 +6,14 @@ namespace dragonx { namespace ui { +float AdvanceBenchmarkClock(double& lastSec, double nowSec, float maxStep) +{ + float dt = 0.0f; + if (lastSec >= 0.0) dt = static_cast(std::min(std::max(nowSec - lastSec, 0.0), maxStep)); + lastSec = nowSec; + return dt; +} + void ThreadBenchmark::reset() { phase = Phase::Idle; diff --git a/src/ui/windows/mining_benchmark.h b/src/ui/windows/mining_benchmark.h index 9ec3a19..dafb3b0 100644 --- a/src/ui/windows/mining_benchmark.h +++ b/src/ui/windows/mining_benchmark.h @@ -68,6 +68,10 @@ struct ThreadBenchmarkCancel { }; ThreadBenchmarkCancel PlanThreadBenchmarkCancel(const ThreadBenchmark& benchmark); +// Wall-clock step for the benchmark: seconds since the previous call, clamped to [0, maxStep]. The first call +// (lastSec < 0) returns 0. Updates lastSec. +float AdvanceBenchmarkClock(double& lastSec, double nowSec, float maxStep); + ThreadBenchmarkUpdate AdvanceThreadBenchmark(ThreadBenchmark& benchmark, float deltaSeconds, double poolHashrate10s); diff --git a/src/ui/windows/mining_tab.cpp b/src/ui/windows/mining_tab.cpp index 3b0855f..45a0838 100644 --- a/src/ui/windows/mining_tab.cpp +++ b/src/ui/windows/mining_tab.cpp @@ -29,6 +29,7 @@ #include "imgui.h" #include "imgui_internal.h" +#include #include #include #include @@ -67,6 +68,23 @@ void CancelMiningBenchmark(App* app, bool restoreMiner) { app->startPoolMining(plan.restartThreads); } +void TickMiningBenchmark(App* app, double hashrate10s) { + static double s_lastTickSec = -1.0; + if (!s_benchmark.active()) { s_lastTickSec = -1.0; return; } // next run starts its clock from 0 + const double nowSec = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); + // Capped like the logic timers (App::kMaxLogicDeltaSeconds): a suspend/resume must not leap a phase. + const float dt = AdvanceBenchmarkClock(s_lastTickSec, nowSec, App::kMaxLogicDeltaSeconds); + auto benchmarkUpdate = AdvanceThreadBenchmark(s_benchmark, dt, hashrate10s); + if (benchmarkUpdate.stopPoolMining) app->stopPoolMining(); + if (benchmarkUpdate.saveOptimalThreads) { + s_selected_threads = benchmarkUpdate.optimalThreads; + app->settings()->setPoolThreads(s_selected_threads); + app->settings()->save(); + } + if (benchmarkUpdate.startPoolMining) app->startPoolMining(benchmarkUpdate.startThreads); + if (benchmarkUpdate.inconclusive) Notifications::instance().warning(TR("grpc_benchmark_inconclusive")); +} + // Miner-update version check (one shot per session): fetches the latest DRG-XMRig release tag in // the background so the "Update" button can show it. Network call to the project Gitea, started // the first time the pool section is shown. @@ -249,25 +267,8 @@ static void RenderMiningTabContent(App* app) // Thread Benchmark state machine — runs pool mining at each candidate // thread count to find the optimal setting for this CPU. // ================================================================ - if (s_benchmark.active()) { - auto benchmarkUpdate = AdvanceThreadBenchmark( - s_benchmark, app->logicDeltaTime(), state.pool_mining.hashrate_10s); - if (benchmarkUpdate.stopPoolMining) { - app->stopPoolMining(); - } - if (benchmarkUpdate.saveOptimalThreads) { - s_selected_threads = benchmarkUpdate.optimalThreads; - app->settings()->setPoolThreads(s_selected_threads); - app->settings()->save(); - } - if (benchmarkUpdate.startPoolMining) { - app->startPoolMining(benchmarkUpdate.startThreads); - } - if (benchmarkUpdate.inconclusive) { - Notifications::instance().warning( - TR("grpc_benchmark_inconclusive")); - } - } + // The benchmark state machine is advanced from App::update() via TickMiningBenchmark() so it keeps running + // while this tab is hidden or the window is minimized (it used to freeze there). // ================================================================ // Proportional section budget — ensures all content fits without diff --git a/src/ui/windows/mining_tab.h b/src/ui/windows/mining_tab.h index 4a8fc43..3ffb000 100644 --- a/src/ui/windows/mining_tab.h +++ b/src/ui/windows/mining_tab.h @@ -28,5 +28,11 @@ bool IsMiningBenchmarkActive(); */ void CancelMiningBenchmark(App* app, bool restoreMiner = true); +/** + * @brief Advance the thread benchmark state machine. Call once per App::update() (ungated by connectivity). + * No-op when no benchmark is active. + */ +void TickMiningBenchmark(App* app, double hashrate10s); + } // namespace ui } // namespace dragonx