From 487f516a3fd513e92fa75a338e13994626d7090f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:32:34 -0400 Subject: [PATCH 01/20] initial implementation --- CMakeLists.txt | 48 +++++ cmake/callbackConfig.cmake.in | 6 + include/xo/callback/CallbackSet.hpp | 312 ++++++++++++++++++++++++++++ src/callback/CMakeLists.txt | 14 ++ src/callback/CallbackSet.cpp | 22 ++ 5 files changed, 402 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/callbackConfig.cmake.in create mode 100644 include/xo/callback/CallbackSet.hpp create mode 100644 src/callback/CMakeLists.txt create mode 100644 src/callback/CallbackSet.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ada7743d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +# callback/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(callback VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +# PROJECT_CXX_FLAGS: bespoke for this project - usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/callback) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support to customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/callbackConfig.cmake.in b/cmake/callbackConfig.cmake.in new file mode 100644 index 00000000..f7176f38 --- /dev/null +++ b/cmake/callbackConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(refcnt) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp new file mode 100644 index 00000000..2abb2c04 --- /dev/null +++ b/include/xo/callback/CallbackSet.hpp @@ -0,0 +1,312 @@ +/* @file CallbackSet.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +//#include "indentlog/scope.hpp" +//#include "indentlog/print/tag.hpp" +#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); + */ + class CallbackId { + public: + CallbackId() = default; + explicit CallbackId(uint32_t id) : id_{id} {} + + /* generate a globally-unique id (not threadsafe) */ + static CallbackId generate(); + + uint32_t id() const { return id_; } + + private: + uint32_t id_ = 0; + }; /*CallbackId*/ + + inline bool operator==(CallbackId lhs, CallbackId rhs) { return lhs.id() == rhs.id(); } + inline bool operator!=(CallbackId lhs, CallbackId rhs) { return lhs.id() != rhs.id(); } + + /* 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; + CallbackId id_; + 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::destination_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: + * - 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 + * 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*/ + +#ifdef NOT_USING + void remove_callback_impl(Fn const & target_fn) { + auto ix = std::find(this->cb_v_.begin(), this->cb_v_.end(), target_fn); + + if(ix != this->cb_v_.end()) + this->cb_v_.erase(ix); + + target_fn->notify_remove_callback(); + } /*remove_callback_impl*/ +#endif + + 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>; + + /* like RpCallbackSet, + * but also provides overload(s) for operator()(..) + */ + template + class NotifyCallbackSet : public RpCallbackSet { + public: + NotifyCallbackSet(MemberFn fn) + : privileged_member_fn_{fn} {} + + template + void operator()(Tn&&... args) { + this->invoke(this->privileged_member_fn_, args...); + } /*operator()*/ + + private: + /* implements 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*/ + +/* end CallbackSet.hpp */ diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt new file mode 100644 index 00000000..58dc3de5 --- /dev/null +++ b/src/callback/CMakeLists.txt @@ -0,0 +1,14 @@ +# callback/CMakeLists.txt + +set(SELF_LIB callback) +set(SELF_SRCS CallbackSet.cpp) + +# reminder: can't be header-only library, because depends on non-header-only refcnt +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies: + +xo_dependency(${SELF_LIB} refcnt) + +# end CMakeLists.txt diff --git a/src/callback/CallbackSet.cpp b/src/callback/CallbackSet.cpp new file mode 100644 index 00000000..881f57f8 --- /dev/null +++ b/src/callback/CallbackSet.cpp @@ -0,0 +1,22 @@ +/* file CallbackSet.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "CallbackSet.hpp" + +namespace xo { + namespace fn { + CallbackId + CallbackId::generate() + { + static CallbackId s_last_id; + + s_last_id = CallbackId(s_last_id.id() + 1); + + return s_last_id; + } /*generate*/ + } /*namespace fn*/ +} /*namespace xo*/ + +/* end CallbackSet.cpp */ From e4f54cb2f75a973df28f9a22864e76a2c1fbfe8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:33:30 -0400 Subject: [PATCH 02/20] + .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9e716afc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# lsp keeps state here +.cache +# typical build directories +build +ccov From 9644d6b966f6640823cd34f28bf9ca7385ecaedb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:44:14 -0400 Subject: [PATCH 03/20] + README.md --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..fcfa8e8a --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# callback-set with reentrant invocation + +Reentrant: +1. A callback can modify parent callback-set (for example to remove itself), + even while being invoked. +2. Any such re-entrant operations are deferred until callback invocation completes. + +# build + install +``` +$ cd xo-callback +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-callback +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. +``` +$ cd xo-callback +$ ln -s build/compile_commands.json +``` From 437f943e37160972cae569d1d0b5e5b6b830728b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:59:54 -0400 Subject: [PATCH 04/20] cosmetic: comment adjustments --- include/xo/callback/CallbackSet.hpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 2abb2c04..05f6c5fa 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -82,7 +82,7 @@ namespace xo { /* If Fnptr is a type such that this works: * Fnptr fn = ...; - * using Fn = Fnptr::destination_type; + * using Fn = Fnptr::element_type; * Fn * native_fn = fn.get(); * (native_fn->*member_fn)(args ...); * @@ -101,6 +101,8 @@ namespace xo { * 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 @@ -130,7 +132,7 @@ namespace xo { for(CbRecd const & cb_recd : this->cb_v_) { callback_type * native_cb = cb_recd.fn_.get(); - /* clang11 doesn't like + /* clang11 doesn't like (with cb=cb_recd.fn_) * cb->*member_fn * when cb-> is overloaded */ @@ -255,17 +257,6 @@ namespace xo { } } /*remove_callback_impl*/ -#ifdef NOT_USING - void remove_callback_impl(Fn const & target_fn) { - auto ix = std::find(this->cb_v_.begin(), this->cb_v_.end(), target_fn); - - if(ix != this->cb_v_.end()) - this->cb_v_.erase(ix); - - target_fn->notify_remove_callback(); - } /*remove_callback_impl*/ -#endif - private: bool cb_running_ = false; /* collection of callback functions */ @@ -297,7 +288,7 @@ namespace xo { } /*operator()*/ private: - /* implements operator()(...) */ + /* implements NotifyCallbackSet's operator()(...) */ MemberFn privileged_member_fn_; }; /*NotifyCallbackSet*/ From d4c486abf13bd5d1fcc3b22c8bab9113241ce3a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:00:15 -0400 Subject: [PATCH 05/20] cosmetic: comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ada7743d..1735ddc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(xo_macros/xo_cxx) include(xo_macros/code-coverage) # ---------------------------------------------------------------- -# unit test setup +# unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) enable_testing() # activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) From 75ee64909e9aa20e66cf4924c77faf368536151c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:00:28 -0400 Subject: [PATCH 06/20] github: workflow --- .github/workflows/main.yml | 100 +++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..0971d8de --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,100 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Configure self (callback) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (callback) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Test self (callback) + working-directory: ${{github.workspace}}/build_callback + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 5b8f2b6d7458e5e8e7614ce0167cff331f0867d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:07:04 -0400 Subject: [PATCH 07/20] cosmetic: comments --- include/xo/callback/CallbackSet.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 05f6c5fa..c15aeb34 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -65,7 +65,9 @@ namespace xo { * 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*/ From 30a524b22cf63dfd25381986fb45231ed70d0a04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:51:31 -0400 Subject: [PATCH 08/20] build: install include tree --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1735ddc8..5b0079d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,4 +45,9 @@ add_subdirectory(src/callback) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + # end CMakeLists.txt From add10041086a807c9c984f3e0506b2aaaf20584f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:21:24 -0400 Subject: [PATCH 09/20] doc: README additions --- CMakeLists.txt | 2 +- README.md | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0079d4..d67984af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) # ---------------------------------------------------------------- -# c++ settings +# common c++ settings # PROJECT_CXX_FLAGS: bespoke for this project - usually empty set(PROJECT_CXX_FLAGS "") diff --git a/README.md b/README.md index fcfa8e8a..53cc5849 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,14 @@ Reentrant: even while being invoked. 2. Any such re-entrant operations are deferred until callback invocation completes. -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) + +### build + install + ``` $ cd xo-callback $ mkdir build @@ -20,7 +27,8 @@ $ make install ``` (also see .github/workflows/main.yml) -# build for unit test coverage +### build for unit test coverage + ``` $ cd xo-callback $ mkdir build-ccov @@ -32,7 +40,7 @@ $ cmake \ -DCMAKE_BUILD_TYPE=Debug .. ``` -# LSP (language server) support +### LSP (language server) support LSP looks for compile commands in the root of the source tree; cmake creates them in the root of its build directory. From 86b9a40ae7740f6f7e6aea2917138a405deea1d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:22:06 -0400 Subject: [PATCH 10/20] header-only implementation --- include/xo/callback/CallbackSet.hpp | 27 ++++++++++++++++++++------- src/callback/CallbackSet.cpp | 9 --------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index c15aeb34..04c83800 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -17,23 +17,36 @@ namespace xo { * * can use id to remove callback later: * cbset.remove_callback(cb_id); + * + * Tag so xo-callback can be header-only */ - class CallbackId { + template + class CallbackIdImpl { public: - CallbackId() = default; - explicit CallbackId(uint32_t id) : id_{id} {} + CallbackIdImpl() = default; + explicit CallbackIdImpl(uint32_t id) : id_{id} {} /* generate a globally-unique id (not threadsafe) */ - static CallbackId generate(); + 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; - }; /*CallbackId*/ + }; /*CallbackIdImpl*/ - inline bool operator==(CallbackId lhs, CallbackId rhs) { return lhs.id() == rhs.id(); } - inline bool operator!=(CallbackId lhs, CallbackId rhs) { return lhs.id() != rhs.id(); } + 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. diff --git a/src/callback/CallbackSet.cpp b/src/callback/CallbackSet.cpp index 881f57f8..1a889f3a 100644 --- a/src/callback/CallbackSet.cpp +++ b/src/callback/CallbackSet.cpp @@ -7,15 +7,6 @@ namespace xo { namespace fn { - CallbackId - CallbackId::generate() - { - static CallbackId s_last_id; - - s_last_id = CallbackId(s_last_id.id() + 1); - - return s_last_id; - } /*generate*/ } /*namespace fn*/ } /*namespace xo*/ From 4b65f3bb9577ca6949face5bfcc1895bda2d51e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:26:46 -0400 Subject: [PATCH 11/20] callbackset: drop Refcounted header --- include/xo/callback/CallbackSet.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 04c83800..9bba6514 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -2,11 +2,11 @@ #pragma once -#include "xo/refcnt/Refcounted.hpp" //#include "indentlog/scope.hpp" //#include "indentlog/print/tag.hpp" #include #include +#include namespace xo { namespace fn { From 8e1f89440edcc8fa2853be4de451bb4eb973f77e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:35:13 -0400 Subject: [PATCH 12/20] build: make callback header-only --- src/callback/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt index 58dc3de5..114a89e2 100644 --- a/src/callback/CMakeLists.txt +++ b/src/callback/CMakeLists.txt @@ -1,14 +1,14 @@ # callback/CMakeLists.txt set(SELF_LIB callback) -set(SELF_SRCS CallbackSet.cpp) +#set(SELF_SRCS CallbackSet.cpp) -# reminder: can't be header-only library, because depends on non-header-only refcnt -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # external dependencies: -xo_dependency(${SELF_LIB} refcnt) +#xo_dependency(${SELF_LIB} refcnt) # end CMakeLists.txt From 8d0a327aadc6003508ee9582b34ab15f2f90160e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:36:11 -0400 Subject: [PATCH 13/20] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9e716afc..e9746d99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# symlink to path/to/build/compile_commands.json should be manual +compile_commands.json # lsp keeps state here .cache # typical build directories From c0c9cf279eb51299997dcee22f0904defd54354f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:03:47 -0400 Subject: [PATCH 14/20] github: nit: name for workflow yaml --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0971d8de..38eee8cf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,4 @@ -# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml -name: CMake on a single platform +name: build xo-callback + xo dependencies on: push: From dd01874b2ea023202f3084f92c31199e7fa03e2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:04:09 -0400 Subject: [PATCH 15/20] build: support symlink-enabled variation --- CMakeLists.txt | 5 ----- src/callback/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d67984af..52296781 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,4 @@ add_subdirectory(src/callback) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt index 114a89e2..cfeaf08c 100644 --- a/src/callback/CMakeLists.txt +++ b/src/callback/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB callback) #set(SELF_SRCS CallbackSet.cpp) xo_add_headeronly_library(${SELF_LIB}) -xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # external dependencies: From 9e69bd6ad71606059cb537b3ef47f680866961e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:34:30 -0400 Subject: [PATCH 16/20] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index e9746d99..132925c0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ compile_commands.json # lsp keeps state here .cache # typical build directories -build -ccov +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index 52296781..c6e1c8bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(callback VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 791583399dc274ba958047ed2c4c4cf7f0b0f8ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 17/20] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 704f415239d8cb479ac523624ca6b291081e8d42 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:13:41 -0500 Subject: [PATCH 18/20] xo-callback: update to latest xo-cmake macros --- CMakeLists.txt | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6e1c8bc..17d73eb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(callback VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -# ---------------------------------------------------------------- -# unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) - -enable_testing() -# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) -add_code_coverage() -# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. -# we're not interested in code coverage for these sources. -# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; -# rather, want coverage on the code that the unit tests exercise. -# -# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # common c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources @@ -40,7 +24,7 @@ add_subdirectory(src/callback) #add_subdirectory(utest) # ---------------------------------------------------------------- -# provide find_package() support to customers +# provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From fda578b55493f8b5d1ba786171d9596f62db56c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:14:34 -0500 Subject: [PATCH 19/20] xo-callback: update to latest cmake bootstrap --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 4e1849c726ce3e6addbeb1cc8b3b67051eebf833 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:24:07 -0500 Subject: [PATCH 20/20] xo-callback: bugfix: track ns change xo::ref::rp -> xo::rp --- include/xo/callback/CallbackSet.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 9bba6514..cfc3047b 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -286,7 +286,7 @@ namespace xo { }; /*CallbackSetImpl*/ template - using RpCallbackSet = CallbackSetImpl>; + using RpCallbackSet = CallbackSetImpl>; /* like RpCallbackSet, * but also provides overload(s) for operator()(..)