Extract elapsedAtLeast to core/util, dedupe the throttle rationale, and correct the alloc-count/PLAN citations

This commit is contained in:
2026-08-03 14:58:33 -04:00
parent 170b2e4994
commit 32c7e959ae
7 changed files with 129 additions and 24 deletions
+5
View File
@@ -5,3 +5,8 @@ reasampler_test(file_bytes LINK file_bytes)
add_library(curve_law INTERFACE)
target_include_directories(curve_law INTERFACE ${REASAMPLER_SRC_DIR})
reasampler_test(curve_law LINK curve_law)
# The GetTickCount()-wraparound-safe throttle-gate test is header-only, hence INTERFACE.
add_library(elapsed_at_least INTERFACE)
target_include_directories(elapsed_at_least INTERFACE ${REASAMPLER_SRC_DIR})
reasampler_test(elapsed_at_least LINK elapsed_at_least)
+19
View File
@@ -0,0 +1,19 @@
#pragma once
// elapsed_at_least — the ONE "has at least this long passed" test behind every
// GetTickCount()-style throttle gate (panel_input's detect cadence, panel_drag's tooltip
// delay). Unsigned subtraction is the deliberate implementation, not an oversight: it is
// exactly correct across a GetTickCount() wraparound (~49.7 days uptime) because `now -
// since`, computed mod 2^32, IS the true forward millisecond distance even when `now`'s
// raw value is numerically less than `since`'s. A signed or `now < since` guard would
// instead wedge the gate shut for up to the wrap period.
namespace reasampler::util {
// True once at least `interval` ms separate `since` from `now` (both GetTickCount()-style
// millisecond counters). `since == 0, interval == 0` reads true, so a zero-initialized
// "last fired" tick opens the gate on its very first call.
inline bool elapsedAtLeast(unsigned int now, unsigned int since, unsigned int interval) {
return now - since >= interval;
}
} // namespace reasampler::util
+1 -1
View File
@@ -322,7 +322,7 @@ void maybeShowTooltip() {
const HoverKind k = g_panel.hovered.kind;
if (k != HoverKind::TopBarButton && k != HoverKind::BottomBarButton) return;
const unsigned int now = GetTickCount();
if (now - g_panel.hoverSinceTick >= kTooltipDelayMs) {
if (elapsedAtLeast(now, g_panel.hoverSinceTick, kTooltipDelayMs)) {
g_panel.tooltipShown = true;
invalidatePanel();
}
+17 -11
View File
@@ -136,8 +136,8 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
// One detection tick: REAPER exposes no "item/track added" callback, so this diffs live
// GUIDs against the baseline and auto-tags the new ones into the active mode. Called every
// timer tick regardless of panel open/close, but the enumeration itself is throttled to
// kDetectIntervalMs (see the gate below). READ-ONLY on the project; mutates only the
// timer tick regardless of panel open/close; the enumeration's throttle and its correctness
// argument are the gate immediately below. READ-ONLY on the project; mutates only the
// in-memory membership index — deliberately OUTSIDE any Undo block (auto-tag is a
// background metadata update, not a destructive edit; an Undo block here would flood
// REAPER's history with an entry per tick that sees new content).
@@ -147,12 +147,18 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
bool detectNewContent() {
if (!g_panel.session) return false;
// Throttles the enumeration (O(T+I) REAPER calls + allocations) to kDetectIntervalMs,
// independent of how often the caller ticks. A skipped tick leaves reloadPending/the
// baseline untouched, so the guards below still run before the NEXT diff whenever this
// gate next opens — only the diff's cadence changes, not its correctness.
// Throttles the enumeration (O(T+I) REAPER calls + allocations) to kDetectIntervalMs via
// elapsedAtLeast (core/util; wraparound-safe against GetTickCount()'s rollover, see its
// header). A skipped tick leaves reloadPending/the baseline untouched, so the guards below
// still run before the NEXT diff whenever this gate next opens — only the diff's cadence
// changes, not its correctness.
//
// KNOWN CONSEQUENCE, not new: a new item's mode resolves from
// preExistingTrackModes()/activeModeId() at ENUMERATION time (below), not creation time, so
// switching mode or moving the item before the next tick can land it in a different mode
// than a tighter cadence would — the window existed at ~33 ms; now widened ~16x.
const unsigned int now = GetTickCount();
if (now - g_panel.lastDetectTick < kDetectIntervalMs) return false;
if (!elapsedAtLeast(now, g_panel.lastDetectTick, kDetectIntervalMs)) return false;
g_panel.lastDetectTick = now;
ReaProject* proj = EnumProjects(-1, nullptr, 0);
@@ -556,10 +562,10 @@ void bankPanelNotifyProjectLoaded() {
}
void bankPanelRefresh() {
// New-content auto-tag detection is CALLED every tick regardless of panel open/close
// (tracks/items are created in the arrange view, not the panel), but the enumeration
// it drives is throttled — see kDetectIntervalMs. READ-ONLY on the project; only
// mutates the in-memory membership index.
// New-content auto-tag detection runs every tick regardless of panel open/close (tracks/
// items are created in the arrange view, not the panel); detectNewContent (panel_input.cpp,
// above) is the authoritative comment for its throttle, correctness argument, and
// read-only/mutation contract.
const bool tagged = panel::detectNewContent();
// Lane minting runs ONLY when detection just tagged new content — a track can only
+10 -4
View File
@@ -47,6 +47,7 @@
#include "core/ui/tab_strip.h"
#include "core/ui/theme.h"
#include "core/ui/tooltip.h"
#include "core/util/elapsed_at_least.h"
#include "core/version/app_version.h"
#include "core/view/guid_diff.h"
#include "core/view/lane_keys.h"
@@ -182,6 +183,9 @@ using version::channelCommandId;
using version::dockIdent;
using version::dockTitle;
// core/util
using util::elapsedAtLeast;
// core/wire
using wire::buildInstrumentDropPreset;
@@ -208,9 +212,8 @@ inline constexpr int kTooltipTextH = 14;
// New-content detection's enumeration cadence — matches the VST3 side's own
// kSyncTimerIntervalMs (editor_platform.cpp), this codebase's established interval for a
// background poll nothing visible depends on. Auto-tag is an invisible metadata update
// (see detectNewContent), so a 15x cadence cut (30/s -> 2/s) costs latency no one watches
// for, not correctness.
// background poll nothing visible depends on. The throttle's correctness argument lives at
// detectNewContent's own comment (panel_input.cpp) — the authoritative home; not restated here.
inline constexpr unsigned int kDetectIntervalMs = 500;
// Client area top to bottom: top toolbar | split body | bottom toolbar | footer.
@@ -362,7 +365,10 @@ struct PanelState {
// Throttles enumerateLiveGuids to kDetectIntervalMs regardless of how often
// bankPanelRefresh itself is called (the OnTimer poll, ~30/s, plus a handful of
// post-capture/ingest/bake call sites). 0 forces the very first tick through.
// post-capture/ingest/bake call sites); the gate's correctness argument lives at
// detectNewContent (panel_input.cpp), not restated here. 0 forces the very first tick
// through PROVIDED system uptime is already >= kDetectIntervalMs at that call — always
// true in practice, but an assumption elapsedAtLeast takes, not one it guarantees.
unsigned int lastDetectTick = 0;
};