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