From 4eae80295af561b1f7881eac459a4aa1a3b06ba3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 12 Aug 2025 12:53:06 -0500 Subject: [PATCH] xo-callback xo-alloc: + GC copy callbacks + unique_ptr cbset support --- xo-alloc/include/xo/alloc/GC.hpp | 28 ++ xo-alloc/src/alloc/CMakeLists.txt | 1 + xo-alloc/src/alloc/GC.cpp | 20 +- .../include/xo/callback/CallbackSet.hpp | 290 +---------------- .../include/xo/callback/CallbackSetImpl.hpp | 293 ++++++++++++++++++ .../include/xo/callback/UpCallbackSet.hpp | 34 ++ .../include/xo/callback/callback_concept.hpp | 24 ++ 7 files changed, 405 insertions(+), 285 deletions(-) create mode 100644 xo-callback/include/xo/callback/CallbackSetImpl.hpp create mode 100644 xo-callback/include/xo/callback/UpCallbackSet.hpp create mode 100644 xo-callback/include/xo/callback/callback_concept.hpp diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index a0bcef2b..64cf198d 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/include/xo/alloc/GC.hpp @@ -7,6 +7,7 @@ #include "ListAlloc.hpp" #include "GcStatistics.hpp" +#include "xo/callback/UpCallbackSet.hpp" #include "xo/indentlog/print/array.hpp" #include #include @@ -105,12 +106,31 @@ namespace xo { using MutationLog = std::vector; + /** @class GcCopyCallback + * @brief optional callback to observe individual copy operations during GC + * + * For viz + **/ + class GcCopyCallback { + public: + virtual void notify_gc_copy(std::size_t z, const void * src_addr, const void * dest_addr, + generation src_gen, generation dest_gen) = 0; + /** invoked when added to callback set (i.e. @ref GC::GcCopyCallbackSet) **/ + void notify_add_callback() {} + /** invoked when removed from callback set **/ + void notify_remove_callback() {} + }; + /** @class GC * @brief generational garbage collector * * Works with objects of type @ref xo::Object **/ class GC : public IAlloc { + public: + using CallbackId = xo::fn::CallbackId; + using GcCopyCallbackSet = xo::fn::UpCallbackSet; + public: /** create new GC instance with configuration @p config **/ explicit GC(const Config & config); @@ -160,6 +180,11 @@ namespace xo { * from @c *addr **/ void add_gc_root(Object ** addr); + /** may optionally use this to observe GC copy phase. + * Will be invoked once _per surviving object_, so not cheap. + * Intended for GC visualization. + **/ + CallbackId add_gc_copy_callback(up fn); /** request garbage collection. **/ void request_gc(generation g); /** disable garbage collection until matching call to @ref enable_gc. @@ -335,6 +360,9 @@ namespace xo { /** enabled when 0. disabled when <0 **/ int gc_enabled_ = 0; + + /** for (optional) viz: invoke when copying individual objects **/ + GcCopyCallbackSet gc_copy_cbset_; }; } /*namespace gc*/ diff --git a/xo-alloc/src/alloc/CMakeLists.txt b/xo-alloc/src/alloc/CMakeLists.txt index 589f3b90..07f87784 100644 --- a/xo-alloc/src/alloc/CMakeLists.txt +++ b/xo-alloc/src/alloc/CMakeLists.txt @@ -15,5 +15,6 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} reflect) +xo_dependency(${SELF_LIB} callback) #end CMakeLists.txt diff --git a/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index bcb8118b..d0aaa660 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/src/alloc/GC.cpp @@ -289,6 +289,12 @@ namespace xo { gc_root_v_.push_back(addr); } + auto + GC::add_gc_copy_callback(up fn) -> CallbackId + { + return gc_copy_cbset_.add_callback(std::move(fn)); + } + void GC::checkpoint() { @@ -322,16 +328,19 @@ namespace xo { { scope log(XO_DEBUG(config_.debug_flag_), xtag("z", z), xtag("+pad", IAlloc::alloc_padding(z))); - generation_result gr = this->fromspace_generation_of(src); + generation_result src_gr = this->fromspace_generation_of(src); std::byte * retval = nullptr; - switch (gr) { + switch (src_gr) { case generation_result::tenured: { log && log("tenured"); retval = this->tenured_to()->alloc(z); + + gc_copy_cbset_.invoke(&GcCopyCallback::notify_gc_copy, + z, src, retval, generation::tenured, generation::tenured); } break; case generation_result::nursery: @@ -347,11 +356,18 @@ namespace xo { assert(this->tospace_generation_of(retval) == generation_result::tenured); + gc_copy_cbset_.invoke(&GcCopyCallback::notify_gc_copy, + z, src, retval, generation::nursery, generation::tenured); + this->gc_statistics_.total_promoted_ += IAlloc::with_padding(z); + } else { log && log("nursery"); retval = this->nursery_to()->alloc(z); + + gc_copy_cbset_.invoke(&GcCopyCallback::notify_gc_copy, + z, src, retval, generation::nursery, generation::nursery); } } break; diff --git a/xo-callback/include/xo/callback/CallbackSet.hpp b/xo-callback/include/xo/callback/CallbackSet.hpp index cfc3047b..15e938be 100644 --- a/xo-callback/include/xo/callback/CallbackSet.hpp +++ b/xo-callback/include/xo/callback/CallbackSet.hpp @@ -1,290 +1,13 @@ +// TOOD: rename to RpCallbackSet + /* @file CallbackSet.hpp */ #pragma once -//#include "indentlog/scope.hpp" -//#include "indentlog/print/tag.hpp" -#include -#include -#include +#include "CallbackSetImpl.hpp" namespace xo { namespace fn { - /* identifies a particular callback in a CallbackSet (see below). - * an unique id is created: - * CallbackSetImpl cbset = ...; - * CallbackId cb_id = cbset.add_callback(..); - * - * can use id to remove callback later: - * cbset.remove_callback(cb_id); - * - * Tag so xo-callback can be header-only - */ - template - class CallbackIdImpl { - public: - CallbackIdImpl() = default; - explicit CallbackIdImpl(uint32_t id) : id_{id} {} - - /* generate a globally-unique id (not threadsafe) */ - static CallbackIdImpl generate() { - static CallbackIdImpl s_last_id; - - s_last_id = CallbackIdImpl(s_last_id.id() + 1); - - return s_last_id; - } /*generate*/ - - uint32_t id() const { return id_; } - - private: - uint32_t id_ = 0; - }; /*CallbackIdImpl*/ - - template - inline bool operator==(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() == rhs.id(); } - template - inline bool operator!=(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() != rhs.id(); } - - using CallbackId = CallbackIdImpl; - - /* queue add/remove callback instructions encountered during callback - * execution, to avoid invalidating vector iterator. - * - */ - template - struct ReentrantCbsetCmd { - enum CbsetCmdEnum { AddCallback, RemoveCallback }; - - ReentrantCbsetCmd() = default; - ReentrantCbsetCmd(CbsetCmdEnum cmd, CallbackId id, Fn const & fn) - : cmd_{cmd}, id_{id}, fn_{fn} {} - - static ReentrantCbsetCmd add(CallbackId id, Fn const & fn) { - return ReentrantCbsetCmd{AddCallback, id, fn}; - } /*add*/ - - static ReentrantCbsetCmd remove(CallbackId id) { - return ReentrantCbsetCmd{RemoveCallback, id, Fn()}; - } /*remove*/ - - bool is_add() const { return cmd_ == AddCallback; } - bool is_remove() const { return cmd_ == RemoveCallback; } - CallbackId id() const { return id_; } - Fn const & fn() const { return fn_; } - - private: - /* AddCallback: deferred CallbackSet::add_callback(.fn) - * RemoveCallback: deferred CallbackSet::remove_callback(.fn) - */ - CbsetCmdEnum cmd_ = AddCallback; - /* operate on callback with this id */ - CallbackId id_; - /* callback function to add/remove */ - Fn fn_; - }; /*ReentrantCbsetCmd*/ - - /* record for remembering a single callback. - * callbacks are given unique ids so they can be removed later - */ - template - struct CbRecd { - CbRecd(CallbackId id, Fn const & fn) : id_{id}, fn_{fn} {} - - CallbackId id_; - Fn fn_; - }; /*CbRecd*/ - - /* If Fnptr is a type such that this works: - * Fnptr fn = ...; - * using Fn = Fnptr::element_type; - * Fn * native_fn = fn.get(); - * (native_fn->*member_fn)(args ...); - * - * then - * CallbackSet cbset = ...; - * cbset.invoke(&Fn::member_fn, args...) - * - * calls - * (cb->*member_fn)(args...) - * - * for each callback cb in this set. - * - * In addition, calls hook methods: - * cb->notify_add_callback() - * cb->notify_remove_callback() - * when adding/removing callback. - * - * Require: - * - Fnptr::element_type - * - Fnptr::get() -> Fnptr::element_type const * - * - can invoke (Fnptr->*member_fn)(...) - * - * implementation is reentrant: running callbacks can safely make - * add/remove calls on the cbset that invoked them. - * - * not threadsafe. - */ - template - class CallbackSetImpl { - public: - using callback_type = typename Fn::element_type; - //using scope = xo::scope; - - public: - CallbackSetImpl() = default; - - /* support for range iterators */ - typename std::vector>::const_iterator begin() const { return cb_v_.begin(); } - typename std::vector>::const_iterator end() const { return cb_v_.end(); } - - /* invoke callbacks registered with this callback set */ - template - void invoke(void (callback_type::* member_fn)(Sn... args), Tn&&... args) { - this->cb_running_ = true; - - try { - for(CbRecd const & cb_recd : this->cb_v_) { - callback_type * native_cb = cb_recd.fn_.get(); - - /* clang11 doesn't like (with cb=cb_recd.fn_) - * cb->*member_fn - * when cb-> is overloaded - */ - (native_cb->*member_fn)(args...); - } - - this->make_deferred_changes(); - } catch(...) { - this->make_deferred_changes(); - throw; - } - } /*operator()*/ - - /* call fn(cb) for each callback present in this set */ - void visit_callbacks(std::function fn) const { - CallbackSetImpl * self = const_cast(this); - - self->cb_running_ = true; - - try { - for(Fn const & cb : this->cb_v_) - fn(cb); - - this->make_deferred_changes(); - } catch(...) { - this->make_deferred_changes(); - throw; - } - } /*visit_callbacks*/ - - /* add callback target_fn to this callback set. - * reentrant - */ - CallbackId add_callback(Fn const & target_fn) { - CallbackId id = CallbackId::generate(); - - if(this->cb_running_) { - /* defer until callback execution completes */ - this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::add(id, target_fn)); - } else { -#ifdef NOT_USING - constexpr bool c_debug_enabled_flag = false; - scope lscope(reflect::type_name(), - "::add_callback", c_debug_enabled_flag); - - if (c_debug_enabled_flag) { - lscope.log("before appending .cb_v[]", - xo::xtag("target_fn", (void*)target_fn.get()), - xo::xtag("target_fn.refcnt", - target_fn->reference_counter())); - } -#endif - - this->cb_v_.push_back(CbRecd(id, target_fn)); - -#ifdef NOT_USING - if (c_debug_enabled_flag) { - lscope.log("after appending .cb_v[]", - xo::xtag("target_fn", (void *)target_fn.get()), - xo::xtag("target_fn.refcnt", - target_fn->reference_counter())); - } -#endif - } - - return id; - } /*add_callback*/ - - void remove_callback(CallbackId id) { - if(this->cb_running_) { - /* defer until callback execution completes */ - this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(id)); - } else { - this->remove_callback_impl(id); - } - - } /*remove_callback*/ - -#ifdef NOT_USING - /* remove callback target_fn from this callback set. - * noop if callback is not present - */ - void remove_callback(Fn const & target_fn) { - if(this->cb_running_) { - /* defer until callback execution completes */ - this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(target_fn)); - } else { - this->remove_callback_impl(target_fn); - } - } /*remove_callback*/ -#endif - - private: - /* apply deferred changes to .cb_v[] */ - void make_deferred_changes() { - this->cb_running_ = false; - - std::vector> cmd_v; - std::swap(cmd_v, this->reentrant_cmd_v_); - - for(ReentrantCbsetCmd const & cmd : cmd_v) { - if(cmd.is_add()) { - this->cb_v_.push_back(CbRecd(cmd.id(), cmd.fn())); - - cmd.fn()->notify_add_callback(); - } else if(cmd.is_remove()) { - this->remove_callback_impl(cmd.id()); - } - } - } /*make_deferred_changes*/ - - void remove_callback_impl(CallbackId target_id) { - for (auto ix = this->cb_v_.begin(); ix != this->cb_v_.end(); ++ix) { - if (ix->id_ == target_id) { - Fn target_fn = ix->fn_; - - this->cb_v_.erase(ix); - - target_fn->notify_remove_callback(); - break; - } - } - } /*remove_callback_impl*/ - - private: - bool cb_running_ = false; - /* collection of callback functions */ - std::vector> cb_v_; - /* when a callback registered with *this, while running, - * attempts to add/remove a callback to/from this set - * (including removing itself), - * must defer until all callbacks have executed. - * remember deferred instructions here. - */ - std::vector> reentrant_cmd_v_; - }; /*CallbackSetImpl*/ - template using RpCallbackSet = CallbackSetImpl>; @@ -300,18 +23,19 @@ namespace xo { template void operator()(Tn&&... args) { this->invoke(this->privileged_member_fn_, args...); - } /*operator()*/ + } private: - /* implements NotifyCallbackSet's operator()(...) */ + /** implements NotifyCallbackSet's operator()(...) **/ MemberFn privileged_member_fn_; - }; /*NotifyCallbackSet*/ + }; template inline NotifyCallbackSet make_notify_cbset(Sret (NativeFn::* member_fn)(Sn...)) { return NotifyCallbackSet(member_fn); } /*make_notify_cbset*/ + } /*namespace fn*/ } /*namespace xo*/ diff --git a/xo-callback/include/xo/callback/CallbackSetImpl.hpp b/xo-callback/include/xo/callback/CallbackSetImpl.hpp new file mode 100644 index 00000000..40ef31ae --- /dev/null +++ b/xo-callback/include/xo/callback/CallbackSetImpl.hpp @@ -0,0 +1,293 @@ +/* file CallbackSetImpl.hpp */ + +#pragma once + +//#include "indentlog/scope.hpp" +//#include "indentlog/print/tag.hpp" +#include +#include +#include + +namespace xo { + namespace fn { + /* identifies a particular callback in a CallbackSet (see below). + * an unique id is created: + * CallbackSetImpl cbset = ...; + * CallbackId cb_id = cbset.add_callback(..); + * + * can use id to remove callback later: + * cbset.remove_callback(cb_id); + * + * Tag so xo-callback can be header-only + */ + template + class CallbackIdImpl { + public: + CallbackIdImpl() = default; + explicit CallbackIdImpl(uint32_t id) : id_{id} {} + + /* generate a globally-unique id (not threadsafe) */ + static CallbackIdImpl generate() { + static CallbackIdImpl s_last_id; + + s_last_id = CallbackIdImpl(s_last_id.id() + 1); + + return s_last_id; + } /*generate*/ + + uint32_t id() const { return id_; } + + private: + uint32_t id_ = 0; + }; /*CallbackIdImpl*/ + + template + inline bool operator==(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() == rhs.id(); } + template + inline bool operator!=(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() != rhs.id(); } + + using CallbackId = CallbackIdImpl; + + /* queue add/remove callback instructions encountered during callback + * execution, to avoid invalidating vector iterator. + * + */ + template + struct ReentrantCbsetCmd { + enum CbsetCmdEnum { AddCallback, RemoveCallback }; + + ReentrantCbsetCmd() = default; + ReentrantCbsetCmd(CbsetCmdEnum cmd, CallbackId id, Fn fn) + : cmd_{cmd}, id_{id}, fn_{std::move(fn)} {} + + static ReentrantCbsetCmd add(CallbackId id, Fn fn) { + return ReentrantCbsetCmd{AddCallback, id, std::move(fn)}; + } /*add*/ + + static ReentrantCbsetCmd remove(CallbackId id) { + return ReentrantCbsetCmd{RemoveCallback, id, Fn()}; + } /*remove*/ + + bool is_add() const { return cmd_ == AddCallback; } + bool is_remove() const { return cmd_ == RemoveCallback; } + CallbackId id() const { return id_; } + Fn const & fn() const { return fn_; } + + Fn & fn() { return fn_; } + + private: + /* AddCallback: deferred CallbackSet::add_callback(.fn) + * RemoveCallback: deferred CallbackSet::remove_callback(.fn) + */ + CbsetCmdEnum cmd_ = AddCallback; + /* operate on callback with this id */ + CallbackId id_; + /* callback function to add/remove */ + Fn fn_; + }; /*ReentrantCbsetCmd*/ + + /* record for remembering a single callback. + * callbacks are given unique ids so they can be removed later + */ + template + struct CbRecd { + CbRecd(CallbackId id, Fn fn) : id_{id}, fn_{std::move(fn)} {} + + CallbackId id_; + Fn fn_; + }; /*CbRecd*/ + + /* If Fnptr is a type such that this works: + * Fnptr fn = ...; + * using Fn = Fnptr::element_type; + * Fn * native_fn = fn.get(); + * (native_fn->*member_fn)(args ...); + * + * then + * CallbackSet cbset = ...; + * cbset.invoke(&Fn::member_fn, args...) + * + * calls + * (cb->*member_fn)(args...) + * + * for each callback cb in this set. + * + * In addition, calls hook methods: + * cb->notify_add_callback() + * cb->notify_remove_callback() + * when adding/removing callback. + * + * Require: + * - Fnptr::element_type + * - Fnptr::get() -> Fnptr::element_type const * + * - can invoke (Fnptr->*member_fn)(...) + * + * implementation is reentrant: running callbacks can safely make + * add/remove calls on the cbset that invoked them. + * + * not threadsafe. + */ + template + class CallbackSetImpl { + public: + using callback_type = typename Fn::element_type; + //using scope = xo::scope; + + public: + CallbackSetImpl() = default; + + /* support for range iterators */ + typename std::vector>::const_iterator begin() const { return cb_v_.begin(); } + typename std::vector>::const_iterator end() const { return cb_v_.end(); } + + /* invoke callbacks registered with this callback set */ + template + void invoke(void (callback_type::* member_fn)(Sn... args), Tn&&... args) { + this->cb_running_ = true; + + try { + for(CbRecd const & cb_recd : this->cb_v_) { + callback_type * native_cb = cb_recd.fn_.get(); + + /* clang11 doesn't like (with cb=cb_recd.fn_) + * cb->*member_fn + * when cb-> is overloaded + */ + (native_cb->*member_fn)(args...); + } + + this->make_deferred_changes(); + } catch(...) { + this->make_deferred_changes(); + throw; + } + } /*operator()*/ + + /* call fn(cb) for each callback present in this set */ + void visit_callbacks(std::function fn) const { + CallbackSetImpl * self = const_cast(this); + + self->cb_running_ = true; + + try { + for(Fn const & cb : this->cb_v_) + fn(cb); + + this->make_deferred_changes(); + } catch(...) { + this->make_deferred_changes(); + throw; + } + } /*visit_callbacks*/ + + /* add callback target_fn to this callback set. + * reentrant + */ + CallbackId add_callback(Fn target_fn) { + CallbackId id = CallbackId::generate(); + + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.emplace_back(ReentrantCbsetCmd::add(id, std::move(target_fn))); + } else { +#ifdef NOT_USING + constexpr bool c_debug_enabled_flag = false; + scope lscope(reflect::type_name(), + "::add_callback", c_debug_enabled_flag); + + if (c_debug_enabled_flag) { + lscope.log("before appending .cb_v[]", + xo::xtag("target_fn", (void*)target_fn.get()), + xo::xtag("target_fn.refcnt", + target_fn->reference_counter())); + } +#endif + + this->cb_v_.push_back(CbRecd(id, std::move(target_fn))); + +#ifdef NOT_USING + if (c_debug_enabled_flag) { + lscope.log("after appending .cb_v[]", + xo::xtag("target_fn", (void *)target_fn.get()), + xo::xtag("target_fn.refcnt", + target_fn->reference_counter())); + } +#endif + } + + return id; + } /*add_callback*/ + + void remove_callback(CallbackId id) { + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(id)); + } else { + this->remove_callback_impl(id); + } + + } /*remove_callback*/ + +#ifdef NOT_USING + /* remove callback target_fn from this callback set. + * noop if callback is not present + */ + void remove_callback(Fn const & target_fn) { + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(target_fn)); + } else { + this->remove_callback_impl(target_fn); + } + } /*remove_callback*/ +#endif + + private: + /* apply deferred changes to .cb_v[] */ + void make_deferred_changes() { + this->cb_running_ = false; + + std::vector> cmd_v; + std::swap(cmd_v, this->reentrant_cmd_v_); + + for(ReentrantCbsetCmd & cmd : cmd_v) { + if(cmd.is_add()) { + this->cb_v_.push_back(CbRecd(cmd.id(), std::move(cmd.fn()))); + + cmd.fn()->notify_add_callback(); + } else if(cmd.is_remove()) { + this->remove_callback_impl(cmd.id()); + } + } + } /*make_deferred_changes*/ + + void remove_callback_impl(CallbackId target_id) { + for (auto ix = this->cb_v_.begin(); ix != this->cb_v_.end(); ++ix) { + if (ix->id_ == target_id) { + Fn target_fn = std::move(ix->fn_); + + this->cb_v_.erase(ix); + + target_fn->notify_remove_callback(); + break; + } + } + } /*remove_callback_impl*/ + + private: + bool cb_running_ = false; + /* collection of callback functions */ + std::vector> cb_v_; + /* when a callback registered with *this, while running, + * attempts to add/remove a callback to/from this set + * (including removing itself), + * must defer until all callbacks have executed. + * remember deferred instructions here. + */ + std::vector> reentrant_cmd_v_; + }; /*CallbackSetImpl*/ + + } /*namespace fn*/ +} /*namespace xo*/ + +/* CallbackSetImpl.hpp */ diff --git a/xo-callback/include/xo/callback/UpCallbackSet.hpp b/xo-callback/include/xo/callback/UpCallbackSet.hpp new file mode 100644 index 00000000..a8a39128 --- /dev/null +++ b/xo-callback/include/xo/callback/UpCallbackSet.hpp @@ -0,0 +1,34 @@ +/* @file UpCallbackSet.hpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "CallbackSetImpl.hpp" +#include "callback_concept.hpp" +#include + +namespace xo { + namespace fn { + template + using UpCallbackSet = CallbackSetImpl>; + + template + requires(callback_concept) + class UpNotifyCallbackSet : public UpCallbackSet { + public: + explicit UpNotifyCallbackSet(MemberFn fn) + : privileged_member_fn_{fn} {} + + template + void operator()(Tn&&... args) { + this->invoke(this->privileged_member_fn_, args...); + } + + private: + /** implements UpNotifyCallbackSet's call operator **/ + MemberFn privileged_member_fn_; + }; + } /*namespace fn*/ +} /*namespace xo*/ + +/* end UpCallbackSet.hpp */ diff --git a/xo-callback/include/xo/callback/callback_concept.hpp b/xo-callback/include/xo/callback/callback_concept.hpp new file mode 100644 index 00000000..ceba53ea --- /dev/null +++ b/xo-callback/include/xo/callback/callback_concept.hpp @@ -0,0 +1,24 @@ +/* @file callback_concept.hpp + * + * author: Roland Conybeare, Aug 2025 + */ + +namespace xo { + namespace fn { + /** @concept callback_concept + * @brief Concept for functors that can be used with @ref CallbackSetImpl + * + **/ + template + concept callback_concept = requires(Callback cb) + { + // no constraints on how callback is invoked. + + { cb.notify_add_callback() }; + { cb.notify_remove_callback() }; + }; + + } +} + +/* callback_concept.hpp */