// See undo_block.h. Compiled into the reaper_reasampler module; includes // reaper_plugin_functions.h without REAPERAPI_IMPLEMENT (main.cpp owns that). #include "shell/undo/undo_block.h" #include #include "shell/undo/undo_trace.h" // TEMPORARY — see undo_trace.h; revert together #include "shell/view/fx_reclaim.h" // fxReclaimEndUndoSpan — the span this must not nest over #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_Undo_BeginBlock2 #define REAPERAPI_WANT_Undo_EndBlock2 #define REAPERAPI_WANT_ValidatePtr2 #include "reaper_plugin_functions.h" namespace reasampler { namespace { // Extension-owned nesting depth only — REAPER's own counter is not readable, and the // reclaim span is deliberately NOT counted here (it is the thing this stands off). int g_depth = 0; } // namespace bool undoBlockOpen() { return g_depth > 0; } UndoBlock::UndoBlock(ReaProject* proj) : project_(proj) { // ONLY the outermost block ends the span, which mechanizes the whole nesting rule. // At depth 0 the span is the only block that can be open, so ending it mints the // switch's point whole, at depth 0, before this block exists. At depth > 0 the // enclosing UndoBlock already did it, and a close from here would land at depth // 2->1 — minting nothing and hijacking the enclosing block's label instead. That is // the same criterion applyMode's reapply is gated on. // // The whole body is one try: fxReclaimEndUndoSpan's closeSpan copies a std::string // and can throw. A throwing constructor runs no destructor, so an uncompensated // ++g_depth would stick forever — undoBlockOpen() reads true for the rest of the // session, fxReclaimTick refuses every tick, and no later UndoBlock is ever // outermost. Roll the depth back on any throw so the type degrades to "this one // block failed to open," not "every block after it is now poisoned." try { if (g_depth++ == 0) fxReclaimEndUndoSpan("UndoBlock ctor"); Undo_BeginBlock2(proj); } catch (...) { --g_depth; throw; } REASAMPLER_TRACE("[UndoBlock] CTOR proj=%s depth=%d", proj ? "real" : "active", g_depth); } UndoBlock::~UndoBlock() { // Depth drops AFTER the SDK call, not before: the close that reaches depth 0 mints a // point, and anything re-entered through that must still read a block as open rather // than let the drain open a span underneath it. // // Guarded the same way fx_reclaim.cpp's closeSpan guards its own Undo_EndBlock2, but // only for a REAL pinned pointer: `project_ == nullptr` means "the active project", // the same convention Undo_BeginBlock2 was handed, and ValidatePtr2 does not accept // nullptr as a valid object of any type — guarding it unconditionally would silently // stop every `UndoBlock undo(nullptr)` site (most of them) from ever closing. A real // pointer, by contrast, can go stale: several call sites allocate freely between open // and close, and the project it named may have closed underneath by now. if (!project_ || ValidatePtr2(nullptr, project_, "ReaProject*")) Undo_EndBlock2(project_, label_.c_str(), mask_); --g_depth; REASAMPLER_TRACE("[UndoBlock] DTOR label=\"%s\" mask=%d depth=%d", label_.empty() ? "(discard)" : label_.c_str(), mask_, g_depth); } void UndoBlock::recordPoint(std::string label, int mask) { label_ = std::move(label); mask_ = mask; } } // namespace reasampler