From e423bf650239834d54eed686a5a1ed41251bb0be Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Sun, 9 Aug 2026 13:16:05 -0400 Subject: [PATCH] Buffer undo trace to a ring, add dump action, stop per-tick loadSaveProj spam Trace lines fill a 4096-line ring instead of hitting the console per line; DUMP_UNDO_TRACE drains it in one ShowConsoleMsg call. Overflow is marked, not silent. --- src/app/main.cpp | 17 +++++++++--- src/shell/undo/undo_trace.cpp | 50 +++++++++++++++++++++++++++++++---- src/shell/undo/undo_trace.h | 20 +++++++++++--- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index 3c6de88..75928cc 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -108,6 +108,9 @@ static void RunShowVersion(int) { // the console window). ShowConsoleMsg(("ReaSampler " + reasampler::version::appVersion() + "\n").c_str()); } +// TEMPORARY: drains the undo-trace ring buffer to the console in one call. See +// undo_trace.h; revert together with the rest of the undo-span tracing pass. +static void RunDumpUndoTrace(int) { reasampler::undoTraceDump(); } // ONE row per bindable action this TU owns: FOREVER-STABLE id suffix, Actions-list // phrase, handler, per-row arg. Registration, hookcommand dispatch, and the unload @@ -170,6 +173,9 @@ static std::vector buildMainActionTable() { rows.push_back({"IMPORT_BANK_PACKAGE", "import bank package (.rsbank)", &RunImportBankPackage}); rows.push_back({"SHOW_VERSION", "show version", &RunShowVersion}); + // TEMPORARY: see undo_trace.h; revert together with the rest of the undo-span + // tracing pass. + rows.push_back({"DUMP_UNDO_TRACE", "DIAG: dump undo trace", &RunDumpUndoTrace}); return rows; } @@ -185,10 +191,15 @@ static void OnTimer() if (capture::g_rtCapture) capture::DriveRealtimeCapture(g_session); // TEMPORARY: captured in a local (rather than calling GetCurrentProjectInLoadSave() - // twice) so the trace can report the value on every tick, standoff or not, with no - // change to the call below. See undo_trace.h. + // twice) so the trace can report it without a second call, with no change to the + // call below. See undo_trace.h. Traced only on CHANGE — every tick reads this + // regardless of state, and the transition is the whole point of the line. ReaProject* const loadSaveProj = GetCurrentProjectInLoadSave(); - REASAMPLER_TRACE("[Timer] loadSaveProj=%p", (void*)loadSaveProj); + static ReaProject* s_tracedLoadSaveProj = nullptr; + if (loadSaveProj != s_tracedLoadSaveProj) { + REASAMPLER_TRACE("[Timer] loadSaveProj=%p", (void*)loadSaveProj); + s_tracedLoadSaveProj = loadSaveProj; + } // A project load or save is IN FLIGHT, so this tick is running inside its own message // pump — a restore brings plugins online and a plugin load pumps. Everything below diff --git a/src/shell/undo/undo_trace.cpp b/src/shell/undo/undo_trace.cpp index 59b2156..6f430b6 100644 --- a/src/shell/undo/undo_trace.cpp +++ b/src/shell/undo/undo_trace.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_ShowConsoleMsg @@ -19,7 +20,21 @@ namespace reasampler { namespace { -unsigned long long g_seq = 0; +// A few thousand lines comfortably outlasts a full mode switch, its background park +// drain, and a mid-drain undo — the exact span the dump has to still hold whole when +// fired well after the fact. Static storage: one allocation for the process, none per +// line. +constexpr std::size_t kCapacity = 4096; +constexpr std::size_t kLineChars = 288; + +struct Line { + char text[kLineChars]; +}; + +Line g_lines[kCapacity]; +std::size_t g_next = 0; // next write slot, wraps +unsigned long long g_totalWritten = 0; // lines written since the last dump/clear +unsigned long long g_seq = 0; double startMs() { static const double t0 = std::chrono::duration( @@ -38,15 +53,40 @@ double nowMs() { } // namespace void undoTrace(const char* fmt, ...) { - char body[256]; + char body[224]; va_list args; va_start(args, fmt); std::vsnprintf(body, sizeof(body), fmt, args); va_end(args); - char line[320]; - std::snprintf(line, sizeof(line), "!SHOW:#%llu %.0fms %s\n", ++g_seq, nowMs(), body); - ShowConsoleMsg(line); + Line& line = g_lines[g_next]; + std::snprintf(line.text, sizeof(line.text), "#%llu %.0fms %s\n", ++g_seq, nowMs(), body); + g_next = (g_next + 1) % kCapacity; + ++g_totalWritten; +} + +void undoTraceDump() { + const bool overflowed = g_totalWritten > kCapacity; + const std::size_t count = + overflowed ? kCapacity : static_cast(g_totalWritten); + const std::size_t oldest = overflowed ? g_next : 0; + + std::string out; + out.reserve(count * 96 + 128); + if (overflowed) { + char marker[128]; + std::snprintf(marker, sizeof(marker), + "[UT OVERFLOW: %llu lines dropped, buffer holds %zu]\n", + g_totalWritten - kCapacity, kCapacity); + out += marker; + } + for (std::size_t i = 0; i < count; ++i) out += g_lines[(oldest + i) % kCapacity].text; + if (out.empty()) out = "[UT] buffer empty\n"; + + ShowConsoleMsg(out.c_str()); + + g_next = 0; + g_totalWritten = 0; } } // namespace reasampler diff --git a/src/shell/undo/undo_trace.h b/src/shell/undo/undo_trace.h index 844db35..faca82e 100644 --- a/src/shell/undo/undo_trace.h +++ b/src/shell/undo/undo_trace.h @@ -3,19 +3,31 @@ // Ctrl-Z-mid-drain). NOT for shipping. ONE switch below gates every trace line in the // tree; flip REASAMPLER_UNDO_TRACE to 0, or delete this file pair plus the call sites // that include it, to revert. +// +// Lines land in an in-memory ring buffer, never straight to the console — a tick-rate +// trace point that called ShowConsoleMsg directly scrolled the console faster than +// Daniel could copy the interesting span. "DIAG: dump undo trace" +// (shell/actions/action_registry, DUMP_UNDO_TRACE) is the one bindable action that +// drains the buffer to the console. #define REASAMPLER_UNDO_TRACE 1 #if REASAMPLER_UNDO_TRACE namespace reasampler { -// Emits one line via ShowConsoleMsg: "UT# ms \n", "!SHOW:"-prefixed so -// a trace point on a background tick never forces the console open. printf-style; +// Appends one line to the ring buffer: "# ms \n". printf-style; // sequence number and a monotonic millisecond timestamp are stamped here so lines sort -// and date themselves even pasted back out of order. Keep each `fmt` short — this has to -// read at a glance in REAPER's console. +// and date themselves however the buffer is later drained. No ShowConsoleMsg call here +// — that is what kept this off the reentrancy risk a tick-rate console write posed. +// Keep each `fmt` short — this has to read at a glance once dumped. void undoTrace(const char* fmt, ...); +// Dumps the whole buffer to the console in ONE ShowConsoleMsg call, oldest line +// first, then clears it. If the buffer wrapped since the last dump, the dump is +// prefixed with a marker naming how many lines were lost rather than dropping them +// silently. +void undoTraceDump(); + } // namespace reasampler #define REASAMPLER_TRACE(...) ::reasampler::undoTrace(__VA_ARGS__)