#pragma once #include #include #include namespace secsgem::gem { // Replaces the single-slot `std::function on_change_` pattern with a primary // slot plus an append-only observer list, so multiple parties can watch one // state machine without fighting over a single handler. // // - `slot = fn;` / set_*_handler(fn) -> sets/replaces the PRIMARY handler. // Exactly the legacy single-slot semantics: callers that always owned the // slot (register_default_handlers, apps, tests) keep working unchanged, // including replacement. // - `slot.add(fn)` / add_*_handler(fn) -> appends an OBSERVER. Observers are // immune to later set_ calls — this is what lets the runtime keep an // atomic control-state mirror (and later: WatchHealth, the Subscribe // stream) while default_handlers still owns the primary slot. // // Invocation order: primary first, then observers in registration order. // Copyable (fire sites that snapshot the handler before invoking keep // working). Not thread-safe: register on the owning thread before the // io_context runs, fire on the io thread — same contract as before. template class HandlerSlot { public: using Fn = std::function; HandlerSlot& operator=(Fn f) { primary_ = std::move(f); return *this; } void add(Fn f) { observers_.push_back(std::move(f)); } void operator()(Args... args) const { if (primary_) primary_(args...); for (const auto& o : observers_) if (o) o(args...); } explicit operator bool() const { return static_cast(primary_) || !observers_.empty(); } private: Fn primary_; std::vector observers_; }; } // namespace secsgem::gem