perf(mining): one save, at most one restart per thread-count gesture

Every thread-count change on the Mining tab saved settings.json and,
with a miner running, stopped and restarted it: xmrig re-inits its
RandomX dataset and reconnects to the pool, seconds of lost hashing
each time. A drag across the grid did this once per tile crossed (up to
~24), with a "Starting pool miner" toast each; every -/+ click or Enter
did it again.

The tab now hands each pick to the App, which puts the newest in
settings at once and applies it when the input settles: on drag
release, or 0.5 s after the last click, even if the Mining tab was left
in the meantime. The header, RAM estimate and tile fill still follow
the drag live.

- Pool: the restart is decided on the mining-control thread, after every
  earlier start or stop has run. It never starts a miner the user
  stopped, is a no-op when xmrig already runs at that count, and is no
  longer silently skipped when the pick lands in another restart's stop
  window (which left xmrig on the old count).
- Solo: one setgenerate per gesture. A pick made while one is in flight
  now waits for it instead of being dropped (another stale count).
- Clicking the tile that is already selected does nothing; it used to
  save settings and restart the miner.
- With idle thread scaling on, the grid no longer snaps back to the old
  count while a pick is on its way to the miner. A pool auto-balance
  restart keeps a count still queued.
- Idle thread scaling changes the pool count through the same retarget.
  Its correction after a start compares with the count last requested,
  not the xmrig API's lagging report, so it comes about 1 s in instead
  of after the first API report (3-5 s).
- A retarget whose pool config no longer validates shows the same error
  and stops the miner, as the old stop-then-start did.
- While a benchmark runs a pick is saved, but the miner change is
  dropped: the benchmark owns the count until it ends.

A crash within 0.5 s of a pick, or mid-drag, loses that pick; every
clean exit saves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 21:09:48 -05:00
co-authored by Claude Opus 5.5
parent c9cb50f4dd
commit 6c02e67c6d
9 changed files with 296 additions and 36 deletions
+13 -22
View File
@@ -143,14 +143,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
auto applyThreads = [&](int tc) {
tc = std::clamp(tc, 1, max_threads);
if (tc == s_selected_threads) return;
s_selected_threads = tc;
app->settings()->setPoolThreads(tc);
app->settings()->save();
if (mining.generate) app->startMining(tc);
if (s_pool_mode && state.pool_mining.xmrig_running) {
app->stopPoolMining();
app->startPoolMining(tc);
}
s_selected_threads = tc; // shown at once; the App applies it ~0.5 s after the last change
app->selectMiningThreads(tc);
};
ImFont* stepFont = Type().iconSmall();
// Match the -/+ button height to the InputInt's actual frame height (GetFrameHeight ==
@@ -707,11 +701,14 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
// Drag-to-select logic (disabled during benchmark; ignored while a dropdown popup is open)
if (!benchActive && !anyPopupOpen && ImGui::IsMouseClicked(0) && hovered_thread > 0) {
// Begin drag
// Begin drag. A click on the tile that is already selected changes nothing (it used to save
// settings and restart the miner).
s_drag_active = true;
s_drag_anchor_thread = hovered_thread;
s_selected_threads = hovered_thread;
threads_changed = true;
if (hovered_thread != s_selected_threads) {
s_selected_threads = hovered_thread;
threads_changed = true;
}
}
if (s_drag_active) {
if (ImGui::IsMouseDown(0)) {
@@ -783,17 +780,11 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
dl->AddText(capFont, capFont->LegacySize, txtPos, txtCol, buf);
}
if (threads_changed) {
app->settings()->setPoolThreads(s_selected_threads);
app->settings()->save();
if (mining.generate) {
app->startMining(s_selected_threads);
}
if (s_pool_mode && state.pool_mining.xmrig_running) {
app->stopPoolMining();
app->startPoolMining(s_selected_threads);
}
}
// Report every change (the header, RAM estimate and tile fill follow s_selected_threads live), but
// let the App apply it: it holds the commit while the mouse is down, so a drag across 16 tiles is
// ONE settings save and at most ONE miner change, on release.
if (threads_changed) app->selectMiningThreads(s_selected_threads);
app->setMiningThreadDragActive(s_drag_active);
curY += gridH + secGap;
}
+8 -5
View File
@@ -140,16 +140,19 @@ static void RenderMiningTabContent(App* app)
// Sync thread grid with actual count when idle thread scaling adjusts threads
// Skip during benchmark — the benchmark controls thread counts directly
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active && !IsMiningBenchmarkActive()) {
// ...nor while a thread-count pick is still on its way to the miner (the grid would snap back to the old count).
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active &&
!app->miningThreadCommitPending() && !IsMiningBenchmarkActive()) {
if (s_pool_mode && state.pool_mining.xmrig_running) {
// Use the requested thread count (available immediately) rather
// than threads_active from the xmrig API which lags during restarts.
int reqThreads = app->getXmrigRequestedThreads();
// Use the requested thread count (available immediately) rather than threads_active from the
// xmrig API, which lags during restarts; the App's target also covers a restart still queued.
const int queued = app->poolThreadsTarget();
int reqThreads = queued > 0 ? queued : app->getXmrigRequestedThreads();
if (reqThreads > 0)
s_selected_threads = ClampMiningThreads(reqThreads, max_threads);
else if (state.pool_mining.threads_active > 0)
s_selected_threads = ClampMiningThreads(state.pool_mining.threads_active, max_threads);
} else if (mining.generate && mining.genproclimit > 0) {
} else if (mining.generate && mining.genproclimit > 0 && !app->isMiningToggleInProgress()) {
s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
}
}