Say when a paused dashboard was frozen, so it cannot be mistaken for a hung one

This commit is contained in:
pgdalmeida 2026-09-21 21:53:04 +02:00
parent e2bdc8debf
commit 575e3c590b
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
2 changed files with 39 additions and 2 deletions

View File

@ -3,6 +3,7 @@
#include "fgc/ui/IUserInterface.h" #include "fgc/ui/IUserInterface.h"
#include <atomic> #include <atomic>
#include <chrono>
#include <deque> #include <deque>
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -46,7 +47,15 @@ private:
std::atomic<bool> running_{false}; std::atomic<bool> running_{false};
// When set, the 100ms auto-redraw is suspended so the screen holds still and // When set, the 100ms auto-redraw is suspended so the screen holds still and
// the operator can select/copy text (terminals drop a selection on repaint). // the operator can select/copy text (terminals drop a selection on repaint).
// Display freeze (the `p` key), for selecting text out of the pane. While
// set, refreshLoop() stops posting redraws, so the frame on screen ages.
// paused_at_ is when it was frozen, so the badge can say how stale it is.
std::atomic<bool> paused_{false}; std::atomic<bool> paused_{false};
// Wall-clock time of the freeze, "HH:MM:SS", shown in the badge. Captured
// once: the badge must stay truthful WITHOUT a repaint, because repainting
// would pull the live snapshot and unfreeze what the pause is holding still.
std::string paused_at_hms_;
static std::string nowHms();
// True while the gimbal overlay is open, so the refresh loop can periodically // True while the gimbal overlay is open, so the refresh loop can periodically
// re-pull a firmware dump (keeps the tracking counters ticking without 'd'). // re-pull a firmware dump (keeps the tracking counters ticking without 'd').
std::atomic<bool> gimbal_overlay_open_{false}; std::atomic<bool> gimbal_overlay_open_{false};

View File

@ -1,5 +1,7 @@
#include "fgc/ui/TuiUi.h" #include "fgc/ui/TuiUi.h"
#include <ctime>
#include "fgc/ChannelTopics.h" #include "fgc/ChannelTopics.h"
#include "fgc/DumpParser.h" #include "fgc/DumpParser.h"
@ -1247,13 +1249,30 @@ void TuiUi::pushLog(LogLevel level, const std::string& line) {
} }
} }
// Wall-clock time the frame was frozen at, "HH:MM:SS". Captured once, because
// the badge must not need a repaint to stay truthful - repainting would unfreeze
// the very data the pause is holding still.
std::string TuiUi::nowHms() {
const auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm tm{};
localtime_r(&t, &tm);
char buf[16];
std::strftime(buf, sizeof(buf), "%H:%M:%S", &tm);
return buf;
}
void TuiUi::refreshLoop() { void TuiUi::refreshLoop() {
using namespace std::chrono_literals; using namespace std::chrono_literals;
auto last_dump = std::chrono::steady_clock::now(); auto last_dump = std::chrono::steady_clock::now();
while (running_) { while (running_) {
std::this_thread::sleep_for(100ms); std::this_thread::sleep_for(100ms);
if (!running_) break; if (!running_) break;
if (paused_) continue; // frozen for text selection; don't repaint // Frozen for text selection: do NOT repaint. Re-rendering would pull the
// live snapshot and the numbers would keep moving under the cursor,
// which is precisely what the freeze exists to prevent. The badge
// carries the wall-clock time of the freeze instead, so the frame says
// how old it is without having to be redrawn to say it.
if (paused_) continue;
// While the gimbal overlay is open, re-pull a firmware dump every ~3s so // While the gimbal overlay is open, re-pull a firmware dump every ~3s so
// the encoder-tracking counters stay current without pressing 'd'. // the encoder-tracking counters stay current without pressing 'd'.
if (gimbal_overlay_open_.load() && sink_ && if (gimbal_overlay_open_.load() && sink_ &&
@ -1332,7 +1351,15 @@ void TuiUi::uiLoop() {
text(" " + s.header.tower + " ") | bold, text(" " + s.header.tower + " ") | bold,
text("build " + s.header.build) | dim, text("build " + s.header.build) | dim,
filler(), filler(),
(paused_ ? text(" PAUSED ") | color(Color::Black) | bgcolor(Color::Yellow) | bold // A frozen frame has to say that it is frozen, and for how long.
// Without the age it is indistinguishable from a hung program: the
// numbers simply stop, which during a multi-minute home reads as a
// crash. Costing 15 minutes of "is it stuck?" is how this got added.
// Says WHEN it froze, not just that it is frozen. Without the time a
// held frame is indistinguishable from a hung program - the numbers
// simply stop - which during a multi-minute home reads as a crash.
(paused_ ? text(" PAUSED @" + paused_at_hms_ + " ")
| color(Color::Black) | bgcolor(Color::Yellow) | bold
: text("")), : text("")),
text(" " + mode + " ") | (s.header.live ? color(Color::Green) : color(Color::Yellow)) | bold, text(" " + mode + " ") | (s.header.live ? color(Color::Green) : color(Color::Yellow)) | bold,
text(" up " + std::to_string(s.header.uptime_ms / 1000) + "s ") | dim, text(" up " + std::to_string(s.header.uptime_ms / 1000) + "s ") | dim,
@ -1649,6 +1676,7 @@ void TuiUi::uiLoop() {
} }
if (c == "p") { // freeze/unfreeze the live refresh so text can be selected if (c == "p") { // freeze/unfreeze the live refresh so text can be selected
paused_ = !paused_; paused_ = !paused_;
if (paused_) paused_at_hms_ = nowHms();
return true; // this keypress redraws once so the indicator updates return true; // this keypress redraws once so the indicator updates
} }
if (overlay == Overlay::Help) { // vim-style section nav while help is open if (overlay == Overlay::Help) { // vim-style section nav while help is open