├── .github └── workflows │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── cmake └── eventppConfig.cmake.in ├── doc ├── anydata.md ├── anyid.md ├── argumentadapter.md ├── benchmark.md ├── build_for_development.md ├── callbacklist.md ├── cn │ ├── callbacklist.md │ ├── eventdispatcher.md │ ├── eventqueue.md │ ├── introduction.md │ ├── mixins.md │ ├── policies.md │ ├── readme.md │ ├── tutorial_callbacklist.md │ ├── tutorial_eventdispatcher.md │ └── tutorial_eventqueue.md ├── conditionalfunctor.md ├── conditionalremover.md ├── counterremover.md ├── eventdispatcher.md ├── eventmaker.md ├── eventqueue.md ├── eventutil.md ├── faq.md ├── hetercallbacklist.md ├── hetereventdispatcher.md ├── hetereventqueue.md ├── heterogeneous.md ├── install.md ├── introduction.md ├── mixins.md ├── orderedqueuelist.md ├── policies.md ├── scopedremover.md ├── tip_sample_use_cases.md ├── tip_use_type_as_id.md ├── tutorial_callbacklist.md ├── tutorial_eventdispatcher.md └── tutorial_eventqueue.md ├── include └── eventpp │ ├── callbacklist.h │ ├── eventdispatcher.h │ ├── eventpolicies.h │ ├── eventqueue.h │ ├── hetercallbacklist.h │ ├── hetereventdispatcher.h │ ├── hetereventqueue.h │ ├── internal │ ├── eventpolicies_i.h │ ├── eventqueue_i.h │ ├── hetercallbacklist_i.h │ └── typeutil_i.h │ ├── mixins │ ├── mixinfilter.h │ └── mixinheterfilter.h │ └── utilities │ ├── anydata.h │ ├── anyid.h │ ├── argumentadapter.h │ ├── conditionalfunctor.h │ ├── conditionalremover.h │ ├── counterremover.h │ ├── eventmaker.h │ ├── eventutil.h │ ├── orderedqueuelist.h │ └── scopedremover.h ├── license ├── readme.md └── tests ├── CMakeLists.txt ├── benchmark ├── CMakeLists.txt ├── b1_callbacklist_invoking_vs_cpp.cpp ├── b2_map_vs_unordered_map.cpp ├── b3_b5_eventqueue.cpp ├── b6_callbacklist_add_remove_callbacks.cpp ├── b7_callbacklist_vs_function_list.cpp ├── b8_eventqueue_anydata.cpp ├── test.h └── testmain.cpp ├── build └── makefile ├── catch.hpp ├── cmake-pkg-demo ├── CMakeLists.txt ├── main.cpp └── readme.md ├── tutorial ├── CMakeLists.txt ├── tip_use_type_as_id.cpp ├── tutorial.h ├── tutorial_anydata.cpp ├── tutorial_argumentadapter.cpp ├── tutorial_callbacklist.cpp ├── tutorial_eventdispatcher.cpp ├── tutorial_eventqueue.cpp ├── tutorial_hetercallbacklist.cpp ├── tutorial_hetereventdispatcher.cpp └── tutorialmain.cpp └── unittest ├── CMakeLists.txt ├── test.h ├── test_anydata.cpp ├── test_anyid.cpp ├── test_argumentadapter.cpp ├── test_callbacklist_basic.cpp ├── test_callbacklist_ctors.cpp ├── test_callbacklist_multithread.cpp ├── test_callbacklist_util.h ├── test_conditionalfunctor.cpp ├── test_conditionalremover.cpp ├── test_counterremover.cpp ├── test_dispatcher_basic.cpp ├── test_dispatcher_ctors.cpp ├── test_dispatcher_multithread.cpp ├── test_eventmaker.cpp ├── test_eventutil.cpp ├── test_hetercallbacklist_basic.cpp ├── test_hetercallbacklist_ctors.cpp ├── test_heterdispatcher_basic.cpp ├── test_heterdispatcher_ctors.cpp ├── test_heterdispatcher_multithread.cpp ├── test_heterqueue_basic.cpp ├── test_no_extra_copy_move.cpp ├── test_queue_basic.cpp ├── test_queue_ctors.cpp ├── test_queue_multithread.cpp ├── test_queue_ordered_list.cpp ├── test_scopedremover.cpp └── testmain.cpp /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | Test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [windows-latest, ubuntu-latest, macos-latest] 11 | build-type: [Release] 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Configure 16 | run: | 17 | mkdir -p build 18 | cmake -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" -B build -S tests 19 | 20 | - name: Build Tests 21 | run: cmake --build build --config "${{ matrix.build-type }}" -j 22 | 23 | - name: Run unit tests 24 | run: cd build && ctest --build-config "${{ matrix.build-type }}" --progress --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/build/temp* 2 | tests/build/project* 3 | tests/coverage 4 | build 5 | .vscode 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(eventpp VERSION 0.1.3) 3 | 4 | add_library(eventpp INTERFACE) 5 | set(CMAKE_CXX_STANDARD 11) 6 | 7 | include(GNUInstallDirs) 8 | target_include_directories( 9 | eventpp INTERFACE 10 | $ 11 | $ 12 | ) 13 | 14 | add_library(eventpp::eventpp ALIAS eventpp) 15 | 16 | # Checks if eventpp is the main project or if it is 17 | # being built as a subproject (using add_subdirectory/FetchContent). 18 | set(MAIN_PROJECT OFF) 19 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 20 | set(MAIN_PROJECT ON) 21 | endif() 22 | 23 | # Installation 24 | # ------------ 25 | if (POLICY CMP0077) 26 | # Allow CMake 3.13+ to override options when using add_subdirectory/FetchContent. 27 | cmake_policy(SET CMP0077 NEW) 28 | endif (POLICY CMP0077) 29 | 30 | option(EVENTPP_INSTALL "Enable installation" ${MAIN_PROJECT}) 31 | 32 | if (EVENTPP_INSTALL) 33 | # Install the library 34 | install( 35 | TARGETS eventpp 36 | EXPORT eventppTargets 37 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 38 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 39 | ) 40 | 41 | # Install the headers 42 | install( 43 | DIRECTORY include/ 44 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 45 | ) 46 | 47 | # (Generate and) install the target import file 48 | install( 49 | EXPORT eventppTargets 50 | NAMESPACE eventpp:: 51 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/eventpp 52 | ) 53 | 54 | # Generate the package version file 55 | include(CMakePackageConfigHelpers) 56 | write_basic_package_version_file( 57 | ${CMAKE_CURRENT_BINARY_DIR}/eventppConfigVersion.cmake 58 | VERSION ${PROJECT_VERSION} 59 | COMPATIBILITY AnyNewerVersion 60 | ) 61 | 62 | # Generate the package configuration file, that allows other 63 | # CMake projects to find the library with find_package() 64 | configure_package_config_file( 65 | cmake/eventppConfig.cmake.in 66 | ${CMAKE_CURRENT_BINARY_DIR}/eventppConfig.cmake 67 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/eventpp 68 | ) 69 | 70 | # Install the package version and configuration files 71 | install( 72 | FILES 73 | ${CMAKE_CURRENT_BINARY_DIR}/eventppConfig.cmake 74 | ${CMAKE_CURRENT_BINARY_DIR}/eventppConfigVersion.cmake 75 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/eventpp 76 | ) 77 | 78 | # Install readme and license 79 | install( 80 | FILES 81 | readme.md 82 | license 83 | DESTINATION ${CMAKE_INSTALL_DATADIR}/eventpp 84 | ) 85 | endif() 86 | -------------------------------------------------------------------------------- /cmake/eventppConfig.cmake.in: -------------------------------------------------------------------------------- 1 | get_filename_component(eventpp_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | if (NOT TARGET eventpp::eventpp) 3 | include("${eventpp_CMAKE_DIR}/eventppTargets.cmake") 4 | endif () 5 | 6 | set(eventpp_LIBRARIES eventpp::eventpp) 7 | -------------------------------------------------------------------------------- /doc/build_for_development.md: -------------------------------------------------------------------------------- 1 | # Build eventpp for development 2 | 3 | The library itself is header only and doesn't need building. 4 | If you want to improve eventpp, you need to build the test code. 5 | 6 | There are three parts of code to test the library, 7 | 8 | - unittests: tests the library. They require C++17 since it uses generic lambda and `std::any` (the library itself only requires C++11). 9 | - tutorials: sample code to demonstrate how to use the library. They require C++11. If you want to have a quick study on how to use the library, you can look at the tutorials. 10 | - benchmarks: measure the library performance. 11 | 12 | All parts are in the `tests` folder. 13 | 14 | All three parts require CMake to build, and there is a makefile to ease the building. 15 | Go to folder `tests/build`, then run `make` with different target. 16 | - `make vc19` #generate solution files for Microsoft Visual Studio 2019, then open eventpptest.sln in folder project_vc19 17 | - `make vc17` #generate solution files for Microsoft Visual Studio 2017, then open eventpptest.sln in folder project_vc17 18 | - `make vc15` #generate solution files for Microsoft Visual Studio 2015, then open eventpptest.sln in folder project_vc15 19 | - `make mingw` #build using MinGW 20 | - `make linux` #build on Linux 21 | - `make mingw_coverage` #build using MinGW and generate code coverage report 22 | 23 | -------------------------------------------------------------------------------- /doc/cn/callbacklist.md: -------------------------------------------------------------------------------- 1 | # CallbackList 类参考手册 2 | 3 | ## 目录 4 | 5 | * [描述](#a2_1) 6 | * [API 参考](#a2_2) 7 | * [头文件](#a3_1) 8 | * [模板参数](#a3_2) 9 | * [公共类型](#a3_3) 10 | * [成员函数](#a3_4) 11 | * [嵌套回调函数安全性](#a2_3) 12 | * [时间复杂度](#a2_4) 13 | * [内部数据结构](#a2_5) 14 | 15 | 16 | 17 | ## 描述 18 | 19 | CallbackList 是 eventpp 中最为核心、基础的类。EventDispatcher、EventQueue 都是以 CallbackList 类为基础开发的。 20 | 21 | CallbackList 内部维护一个回调函数的列表。在 CallbackList 被调用时,其会逐个调用列表中的所有回调函数。可以将 CallbackList 看做 Qt 中的信号/槽系统,或某些 Windows API 中的回调函数指针(例如 `ReadFileEx` 中的 IpCompletionRoutine )。 22 | 回调函数可以是任何回调目标 —— 函数、函数指针、指向成员函数的指针、lambda 表达式、函数对象等。 23 | 24 | 25 | 26 | ## API 参考 27 | 28 | 29 | ### 头文件 30 | 31 | eventpp/callbacklist.h 32 | 33 | 34 | ### 模板参数 35 | 36 | ```c++ 37 | template < 38 | typename Prototype, 39 | typename Policies = DefaultPolicies 40 | > 41 | class CallbackList; 42 | ``` 43 | 44 | `Prototype`:回调函数原型。该参数应为 C++ 函数类型,如 `void(int, std::string, const MyClass *)`。 45 | `Policies`:用于配置和扩展回调函数列表的规则。默认值为 `DefaultPolicies` 。详见 [policy 文档](https://github.com/marsCatXdu/eventpp/blob/master/doc/policies.md) 46 | 47 | 48 | ### 公共类型 49 | 50 | `Handle`:由 `append`、`prepend`、`insert` 函数返回的句柄类型。句柄可用于插入或移除一个回调函数。可以通过将句柄转为 bool 类型来检查其是否为空:句柄为空时值为 `false` 。`Handle` 时可拷贝的。 51 | 52 | 53 | ### 成员函数 54 | 55 | #### 构造函数 56 | 57 | ```C++ 58 | CallbackList() noexcept; 59 | CallbackList(const CallbackList & other); 60 | CallbackList(CallbackList && other) noexcept; 61 | CallbackList & operator = (const CallbackList & other); 62 | CallbackList & operator = (CallbackList && other) noexcept; 63 | void swap(CallbackList & other) noexcept; 64 | ``` 65 | 66 | CallbackList 可以被拷贝、move、赋值、move 赋值和交换。 67 | 68 | #### empty 69 | 70 | ```c++ 71 | bool empty() const; 72 | ``` 73 | 74 | 当回调列表为空时返回 true 。 75 | 提示:在多线程程序中,该函数的返回值无法保证确定就是列表的真实状态。可能会出现刚刚返回 true 之后列表马上变为非空的情况,反之亦然。 76 | 77 | #### bool 转换运算符 78 | 79 | ```c++ 80 | operator bool() const; 81 | ``` 82 | 83 | 若回调列表非空则返回 true。 84 | 借助该运算符,能够实现在条件语句中使用 CallbackList 实例 85 | 86 | #### append 87 | 88 | ```c++ 89 | Handle append(const Callback & callback); 90 | ``` 91 | 92 | 向回调函数列表中添加回调函数。 93 | 新的回调函数会被加在回调函数列表的末尾。 94 | 该函数返回一个代表回调函数的句柄。该句柄能够用于移除该回调函数,或在该回调函数前插入其他的回调函数。 95 | 如果`append`在回调函数列表执行的过程中被其他的回调函数调用,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 96 | 该函数的时间复杂度为 O(1) 。 97 | 98 | #### prepend 99 | 100 | ```c++ 101 | Handle prepend(const Callback & callback); 102 | ``` 103 | 104 | 向回调函数列表中添加回调函数。 105 | 回调函数将被加在回调函数列表的最前端。 106 | 该函数会返回一个代表回调函数的句柄(handler)。该句柄可被用于移除该回调函数,也可用于在该回调函数前插入其他回调函数。 107 | 如果 `prepend` 在回调函数列表执行的过程中被其他回调函数调用,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 108 | 该函数的时间复杂度为 O(1) 。 109 | 110 | #### insert 111 | 112 | ```c++ 113 | Handle insert(const Callback & callback, const Handle & before); 114 | ``` 115 | 116 | 将 *callback* 插入到回调列表中 *before* 前面的一个位置处。若找不到 *before* ,则 *callback* 会被加到回调列表的末尾。 117 | 该函数返回一个代表回调函数的句柄。该句柄可被用于移除该回调函数,也可用于在该回调函数前插入其他回调函数。 118 | 如果 `insert` 是在回调函数列表执行的过程中被其他回调函数调用的,则新添加的回调函数一定不会在该回调函数列表执行的过程中被执行。 119 | 该函数的时间复杂度为 O(1) 。 120 | 121 | 提示:该函数的调用者必须提前确定 `before` 是由 `this` 所指向的 CallbackList 调用的。如果不能确定,可以用 `ownsHandle` 函数来检查 `before` 是否属于 `this` CallbackList 。`insert` 函数仅能在 `ownsHandle(before)` 返回 true 的时候被调用,否则可能引发未定义的行为并带来诡异的 bug 。 122 | 需要确保只在 `assert(ownsHandle(before))` 时 `insert` ,但出于性能方面的考量,发布的代码中没有包含相关的检查。 123 | 124 | #### remove 125 | 126 | ```c++ 127 | bool remove(const Handle & handle); 128 | ``` 129 | 130 | 从回调函数列表中移除 *handle* 指向的回调函数。 131 | 移除成功会返回 true ,找不到回调函数则会返回 false 。 132 | 该函数的时间复杂度为 O(1) 133 | 134 | 提示:`handle` 必须是由 `this` 指向的 CallbackList 所创建的。更多细节请查看 `insert` 中的“提示”部分 135 | 136 | #### ownsHandle 137 | 138 | ```c++ 139 | bool ownsHandle(const Handle & handle) const; 140 | ``` 141 | 142 | 当 `handle` 是由当前 CallbackList 创建时返回 true,否则返回 false 。 143 | 该函数时间复杂度为 O(N) 144 | 145 | #### forEach 146 | 147 | ```c++ 148 | template 149 | void forEach(Func && func) const; 150 | ``` 151 | 152 | 对所有的回调函数使用 `func` 。 153 | `func` 可以是下面两种原型的其中一种: 154 | 155 | ```C++ 156 | AnyReturnType func(const CallbackList::Handle &, const CallbackList::Callback &); 157 | AnyReturnType func(const CallbackList:Callback &); 158 | ``` 159 | 160 | 提示:`func` 可以安全地移除和添加新的回调函数 161 | 162 | #### forEachIf 163 | 164 | ```c++ 165 | template 166 | bool forEachIf(Func && func) const; 167 | ``` 168 | 169 | 对所有的回调函数使用 `func` 。 `func` 必须返回一个 bool 值,若返回值为 false ,则 forEachIf 将会立即停止循环。 170 | 当所有回调函数都被触发后,或未找到 `event` 时,该函数会返回 true 。当 `func` 返回 `false` 时返回 `false` 171 | 172 | #### 调用运算符 173 | 174 | ```c++ 175 | void operator() (Args ...args) const; 176 | ``` 177 | 178 | 触发回调函数列表中所有回调函数的运行。 179 | 回调函数会被用 `args` 参数作为参数调用。 180 | 回调函数会在 `operator()` 所在的线程中调用。 181 | 182 | 183 | ## 嵌套回调函数安全性 184 | 185 | 1. 若一个回调函数在自身运行时,向回调函数列表中添加了新的回调函数,则新的回调函数不会在本次回调函数列表执行的过程中被执行。该行为是由一个 64 位无符号类型的计数器所保证的。如果在一次触发的过程中使该计数器溢出到 0 则会破坏上述的行为。 186 | 2. 所有在回调函数列表运行过程中被移除的回调函数都不会被触发运行。 187 | 3. 上面这几点在多线程情况下未必成立。也就是说,如果一个线程正在执行回调函数列表,如果另一个线程的函数向这个列表中添加或移除了函数,则被添加或移除的函数仍有可能在此次回调函数列表执行的过程中被执行。 188 | 189 | 190 | ## 时间复杂度 191 | 192 | - `append`:O(1) 193 | - `prepend`:O(1) 194 | - `insert`:O(1) 195 | - `remove`:O(1) 196 | 197 | 198 | 199 | ## 内部数据结构 200 | 201 | CallbackList 使用双向链表管理回调函数。 202 | 每个节点都使用共享指针(shared pointer)连接。使用共享指针可以实现在迭代的过程中移除节点。 203 | 204 | -------------------------------------------------------------------------------- /doc/cn/introduction.md: -------------------------------------------------------------------------------- 1 | # eventpp 库介绍 2 | 3 | eventpp 有三个核心的类:CallbackList, EventDispatcher 和 EventQueue ,三者各有其特定的用途。 4 | 5 | ## CallbackList 类 6 | 7 | CallbackList 是 eventpp 中最基础的类,EventDispatcher 和 EventQueue 类的实现都建立在 CallbackList 之上。 8 | 9 | CallbackList 会维护一个回调函数列表。当一个 CallbackList 被调用时,该 CallbackList 会逐个调用其中的回调函数。CallbackList 可以类比为 Qt 中的信号槽系统,以及某些 Windows API 中的回调函数指针(比如 `ReadFileEx` 中的 IpCompletionRoutine ) 10 | 11 | 这里的“回调函数”可以是任何能够回调的目标——函数、函数指针、成员函数指针、lambda表达式、函数对象等。 12 | 13 | eventpp 中的 CallbackList 相当于是 Qt 这类事件系统中的 “信号”,但 eventpp 中并没有特定的相当于 “槽”(回调函数)的东西 —— 在 eventpp 中,任何可调用的目标都可以是槽(回调函数) 14 | 15 | 当应用场景中的事件种类很少时,直接用 CallbackList 即可满足需求:为每个事件创建一个 CallbackList,每个 CallbackList 都可以有不同的原型( prototype ),例如: 16 | 17 | ```c++ 18 | eventpp::CallbackList onStart; 19 | eventpp::CallbackList onStop; 20 | ``` 21 | 22 | 当程序中有成百上千个事件时( GUI 和游戏程序中经常会有这么多的事件),上面这种写法就难以应付了——为每个事件都单独创建 CallbackList 肯定不是什么好选择。针对存在大量事件的场景,eventpp 设计了 EventDispatcher 23 | 24 | ## EventDispatcher 类 25 | 26 | EventDispatcher 类似于 `std::map` ,能根据 EventType 寻找对应 CallbackList。 27 | 28 | EventDispatcher 维护一个 `` 映射表。在进行事件分发时, EventDispatcher 会根据事件类型(EventType)查找并调用对应的回调列表(CallbackList) 。该调用过程是同步的,监听器会在 `EventDispatcher::dispatch` 被调用时触发。 29 | 30 | EventDispatcher 适用于事件种类繁多或无法预先确定事件数量的场景。每个事件都可以根据事件类型进行分类,例如: 31 | 32 | ```c++ 33 | enum class MyEventType 34 | { 35 | redraw, 36 | mouseDown, 37 | mouseUp, 38 | // 后面也许还有 2000 个事件 39 | }; 40 | 41 | struct MyEvent 42 | { 43 | MyEventType type; 44 | // 此处定义所有事件可能会需要的数据 45 | }; 46 | 47 | struct MyEventPolicies 48 | { 49 | static MyEventType getEvent(const MyEvent &e) { 50 | return e.type; 51 | } 52 | }; 53 | 54 | eventpp::EventDispatcher dispatcher; 55 | dispatcher.dispatch(MyEvent { MyEventType::redraw }); 56 | ``` 57 | 58 | (提示:若想了解上面代码中的 `MyEventPolicies` ,请阅读https://github.com/wqking/eventpp/blob/master/doc/policies.md 文档。本篇文档中可以暂且只关注分发器: `eventpp::EventDispatcher dispatcher`) 59 | 60 | EventDispatcher 的缺点是,分发器中所有事件都必须有相同的回调原型(例如示例代码中的 `void(const MyEvnet &)`)。通常可以通过下面的方案来规避该缺点:将 Event 基类作为回调函数的参数,然后所有的事件都通过继承 Event 来传递他们自己的数据。在示例代码中,`MyEvent` 就是事件的基类,回调函数接收 `const MyEvent &` 。 61 | 62 | ## EventQueue 类 63 | 64 | EventQueue 包含了 EventDispatcher 的所有特性,并在此基础上添加了事件队列。注意:EventQueue 并不是 EventDispatcher 的子类,因此不要进行类型转换。 65 | 66 | EventQueue 是异步的。事件会在调用 `EventQueue::enqueue` 时被保存在队列中,并在 `EventQueue::process` 被调用时被处理。 67 | 68 | EventQueue 相当于 Qt 中的事件系统(QEvent)或 Windows API 中的消息处理( message processing )。 69 | 70 | ```c++ 71 | eventpp::EventQueue queue; 72 | 73 | // 将事件加入队列。第一个参数永远是事件类型 74 | // 监听器不会在进入队列期间被触发 75 | queue.enqueue(3, "Hello", true); 76 | queue.enqueue(5, "World", false); 77 | 78 | // 处理事件队列,运行队列中的所有事件 79 | queue.process(); 80 | ``` 81 | 82 | ## 线程安全 83 | 84 | 所有类都是线程安全的,可以在多个线程中同时调用所有的公共函数。如果出现问题,请到 eventpp 的项目仓库报告 bug。 85 | 本库能够确保每个单独函数调用的整体性,如 `EventDispatcher::appendListener`, `CallbackList::remove` ,但无法确保多个线程中的操作执行顺序。例如,若在某个线程分发一个事件的同一时刻,另一个线程移除了一个监听器(listener),则被移除的监听器仍有可能在其被移除之后触发。 86 | 87 | ## 异常安全 88 | 89 | 所有的类都不会抛出异常。但库代码所依赖的代码可能会在下列情况发生时抛出异常: 90 | 91 | 1. 内存耗尽,无法分配新的内存空间; 92 | 2. 监听器(回调函数)在复制、移动、对比或调用时抛出异常 93 | 94 | 几乎所有操作都有很强的异常安全性,这意味着下层数据抛出异常时能够保持其原始值。唯一的例外是 `EventQueue::process`:在抛出异常时,剩余的事件就不再会被分发了,而且队列会被清空。 95 | 96 | 97 | -------------------------------------------------------------------------------- /doc/cn/mixins.md: -------------------------------------------------------------------------------- 1 | # Mixins 2 | 3 | ## 目录 4 | 5 | - [介绍](#description) 6 | - [定义一个 mixin](#define-a-mixin) 7 | - [将 mixin 注入到 EventDispatcher](#inject-mixins-to-eventdispatcher) 8 | - [可选拦截点(Optional interceptor points)](#optional-interceptor-points) 9 | - [MixinFilter](#mixin-filter) 10 | - [公共类型](#public-types) 11 | - [函数](#functions) 12 | - [MixinFilter 示例代码](#sample-code-for-mixinfilter) 13 | 14 | 15 | 16 | ## 介绍 17 | 18 | Mixin 用于向 EventDispatcher / EventQueue 继承层次中注入代码,以扩展它们的功能。本文将以 EventDispatcher 为例进行介绍,EventQueue 的 mixin 相关用法与 EventDispatcher 完全一致。 19 | 20 | 原有的 EventDispatcher 继承层次如下: 21 | 22 | ``` 23 | EventDispatcher <- EventDispatcherBase 24 | ``` 25 | 26 | `EventDispatcher` 是一个空类,其继承来自 `EventDispatcherBase` 的所有函数和数据。 27 | 28 | 在注入两个 mixin 类 A 、 B 后,层次如下: 29 | 30 | ``` 31 | EventDispatcher <- MixinA <- MixinB <- EventDispatcherBase 32 | ``` 33 | 34 | mixin 能够使用所有 EventDispatcherBase 中的所有 public 和 protected 成员(类型、函数和数据)。mixin 中的所有公共成员对用户来说都是可见、可用的。 35 | 36 | 37 | 38 | ## 定义一个 mixin 39 | 40 | 一个 mixin 是有着一个模板参数的模板类。mixin 必须继承其模板参数。一个典型的 mixin 如下: 41 | 42 | ```cpp 43 | template 44 | class MyMixin : public Base 45 | { 46 | }; 47 | ``` 48 | 49 | 50 | 51 | ## 将 mixin 注入到 EventDispatcher 52 | 53 | 想要启用 mixin ,需要将 mixin 加入到策略类的 `Mixins` 类型中。例如,要启动 `MixinFilter` ,可以像下面这样来定义调度器 54 | 55 | ```cpp 56 | struct MyPolicies { 57 | using Mixins = eventpp::MixinList; 58 | }; 59 | eventpp::EventDispatcher dispatcher; 60 | ``` 61 | 62 | 若有多个 mixin ,像是 `using Mixins = eventpp::MixinList` ,则继承层次如下: 63 | 64 | ``` 65 | EventDispatcher <- MixinA <- MixinB <- MixinC <- EventDispatcherBase 66 | ``` 67 | 68 | 最前面的排在继承层次的最底部 69 | 70 | 71 | 72 | ## 可选拦截点( Optional interceptor points ) 73 | 74 | 一个 mixin 可以有一些具有特殊名称的函数,这些函数会被在特定的时候调用。这些特殊的函数必须是 public 的。当前只有一个特殊函数,如下: 75 | 76 | ```cpp 77 | template 78 | bool mixinBeforeDispatch(Args && ...args) const; 79 | ``` 80 | 81 | `mixinBeforeDispatch` 会在 EventDispatcher、EventQueue 开始调度事件之前被调用,其接收所有传给 EventDispatcher::dispatch 的参数,除了所有的参数都是以左值引用的方式传递的,无论它们在回调函数原型中是否被引用(无法修改 const 引用)。所以该函数能够修改实参,让后续的监听器都看到修改后的值。 82 | 因此该函数可以修改实参,所有的监听器看到的参数都是修改后的值。 83 | 该函数返回 `true` 时将继续调度,返回 `false` 时会停止继续调度。 84 | 对于多 mixin 的情况,该函数会被按照这些 mixin 出现在策略类的 MixinList 中的顺序来执行。 85 | 86 | 87 | 88 | ## MixinFilter 89 | 90 | MixinFilter 可以在调度开始前过滤或修改所有的事件。 91 | 92 | `MixinFilter::appendFilter(filter)` 向调度器中添加新的事件过滤器。`filter` 接收参数的类型是带有左值引用类型的回调函数原型。 93 | 94 | 所有的事件都会在执行任何监听器前触发事件过滤器的执行。 95 | 96 | 因为所有的实参都是以左值引用的形式传递的,因此事件过滤器也能修改这些实参,无论这些实参是否被在回调原型中引用(当然,const 引用是改不了的)。 97 | 98 | 下面的表格展示了事件过滤器如何接收实参 99 | 100 | | 回调原型的实参类型 | 过滤器收到的实参类型 | 过滤器是否能修改实参? | 备注 | 101 | | ------------------------ | -------------------------- | ---------------------- | ------------------------- | 102 | | int, const int | int &, int & | 是 | 抛弃 const 值的不变性 | 103 | | int &, std::string & | int &, std::string & | 是 | | 104 | | const int &, const int * | const int &, const int * & | 否 | 必须保持引用/指针的不变性 | 105 | 106 | 事件过滤器强大且实用,下面是一些用例示例 107 | 108 | 1. 捕捉并阻塞所有感兴趣的事件。例如,在一个 GUI 窗口系统中,所有的窗口都能接收到鼠标的点击事件。然而,当一个窗口正在被鼠标拖拽时,只有被拖拽的窗口才应该能接收到鼠标事件,即使鼠标正在其他窗口上移动也应如此。所以当开始拖动时,窗口可以添加一个过滤器,该过滤器重定向所有发到窗口的鼠标事件,阻止其他窗口的监听器获得鼠标事件,但扔放行鼠标事件外的所有其他事件。 109 | 2. 设置捕捉所有事件的监听器。例如,在一个电话本系统中,系统根据动作(action)来发送事件(删除添加电话号、查找电话号等)。如果想要在该系统中实现这样的一个模块:其只需要取电话号的区号,而不关心具体发生了的动作,该怎么做呢?一种方法是让该模块可以监听所有的事件(添加、删除、查找),但这种方法比较脆弱——若添加了一种新的动作事件,而忘记了为该模块定义相应的监听逻辑,就会导致出现未定义的行为。更好的方法是为该模块添加一个过滤器,并在过滤器中检查区号。 110 | 111 | 112 | 113 | ### 公共类型 114 | 115 | `FilterHandle`:appendFilter 返回的句柄类型。过滤器句柄可用于移除过滤器。可以通过将 FilterHandle 实例转换为 bool 类型来检查其是否为空,当值为 false 时,该句柄为空。`FilterHandle` 是可拷贝的。 116 | 117 | 118 | 119 | ### 函数 120 | 121 | ```cpp 122 | FilterHandle appendFilter(const Filter & filter); 123 | ``` 124 | 125 | 将 `filter` 添加进调度器。 126 | 127 | 该函数将返回一个可用于 `removeFilter` 的过滤器句柄 128 | 129 | ```cpp 130 | bool removeFilter(const FilterHandle & filterHandle); 131 | ``` 132 | 133 | 从调度其中移除一个过滤器。 134 | 135 | 当过滤器被成功移除时返回 true。 136 | 137 | 138 | 139 | ### MixinFilter 示例代码 140 | 141 | #### 代码 142 | 143 | ```cpp 144 | struct MyPolicies { 145 | using Mixins = eventpp::MixinList; 146 | }; 147 | eventpp::EventDispatcher dispatcher; 148 | 149 | dispatcher.appendListener(3, [](const int e, const int i, const std::string & s) { 150 | std::cout 151 | << "Got event 3, i was 1 but actual is " << i 152 | << " s was Hello but actual is " << s 153 | << std::endl 154 | ; 155 | }); 156 | dispatcher.appendListener(5, [](const int e, const int i, const std::string & s) { 157 | std::cout << "Shout not got event 5" << std::endl; 158 | }); 159 | 160 | // 添加三个事件过滤器. 161 | 162 | // 第一个过滤器将输入的实参改为其他值,然后后续的过滤器及监听器将看到修改后的值 163 | dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { 164 | std::cout << "Filter 1, e is " << e << " passed in i is " << i << " s is " << s << std::endl; 165 | i = 38; 166 | s = "Hi"; 167 | std::cout << "Filter 1, changed i is " << i << " s is " << s << std::endl; 168 | return true; 169 | }); 170 | 171 | // 第二个过滤器将所有 5 事件都过滤出来。因此监听事件 5 的过滤器都不会被触发。 172 | // 第三个过滤器也不会在事件 5 触发 173 | dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { 174 | std::cout << "Filter 2, e is " << e << " passed in i is " << i << " s is " << s << std::endl; 175 | if(e == 5) { 176 | return false; 177 | } 178 | return true; 179 | }); 180 | 181 | // 第三个过滤器只打印输入的实参 182 | dispatcher.appendFilter([](const int e, int & i, std::string & s) -> bool { 183 | std::cout << "Filter 3, e is " << e << " passed in i is " << i << " s is " << s << std::endl; 184 | return true; 185 | }); 186 | 187 | // 调度所有事件,第一个实参总是事件类型 188 | dispatcher.dispatch(3, 1, "Hello"); 189 | dispatcher.dispatch(5, 2, "World"); 190 | ``` 191 | 192 | 输出 193 | 194 | > Filter 1, e is 3 passed in i is 1 s is Hello 195 | > Filter 1, changed i is 38 s is Hi 196 | > Filter 2, e is 3 passed in i is 38 s is Hi 197 | > Filter 3, e is 3 passed in i is 38 s is Hi 198 | > Got event 3, i was 1 but actual is 38 s was Hello but actual is Hi 199 | > Filter 1, e is 5 passed in i is 2 s is World 200 | > Filter 1, changed i is 38 s is Hi 201 | > Filter 2, e is 5 passed in i is 38 s is Hi 202 | 203 | -------------------------------------------------------------------------------- /doc/cn/readme.md: -------------------------------------------------------------------------------- 1 | # eventpp -- C++ 事件派发和回调列表开源库 2 | 3 | 本文档还有待完善,以下是一些已经翻译的中文文档。 4 | 非常感谢 [marsCatXdu](https://github.com/marsCatXdu) 的中文翻译。 5 | 6 | 文档 7 | 8 | * 核心类和函数库 9 | * [概述](introduction.md) 10 | * [CallbackList 教程](tutorial_callbacklist.md) 11 | * [EventDispatcher 教程](tutorial_eventdispatcher.md) 12 | * [EventQueue 教程](tutorial_eventqueue.md) 13 | * [CallbackList 类参考手册](callbacklist.md) 14 | * [EventDispatcher 类参考手册](eventdispatcher.md) 15 | * [EventQueue 类参考手册](eventqueue.md) 16 | * [Policies -- 配置 eventpp](policies.md) 17 | * [Mixins -- 扩展 eventpp](mixins.md) 18 | 19 | -------------------------------------------------------------------------------- /doc/cn/tutorial_callbacklist.md: -------------------------------------------------------------------------------- 1 | # CallbackList 使用教程 2 | 3 | 注意:如果想尝试运行教程代码,建议使用 `tests/unittest` 目录下的代码。本文中的示例代码可能已经过期而无法编译。 4 | 5 | ### CallbackList 教程 1, 基础 6 | 7 | **代码** 8 | 9 | ```c++ 10 | // 命名空间是 eventpp 11 | // 首个参数是监听器的原型 12 | eventpp::CallbackList callbackList; 13 | 14 | // 添加一个回调函数,此处即 [](){} 。回调函数并非一定要是 lambda 表达式。 15 | // 函数、std::function 或其他任何满足监听器原型要求的函数对象都可以作为监听器 16 | callbackList.append([](){ 17 | std::cout << "Got callback 1." << std::endl; 18 | }); 19 | callbackList.append([](){ 20 | std::cout << "Got callback 2." << std::endl; 21 | }); 22 | 23 | // 启动回调列表 24 | callbackList(); 25 | ``` 26 | 27 | **输出** 28 | 29 | > Got callback 1. 30 | > Got callback 2. 31 | 32 | **解读** 33 | 34 | 首先,定义一个回调列表( callback list ) 35 | 36 | ```c++ 37 | eventpp::CallbackList callbackList; 38 | ``` 39 | 40 | CallbackList 需要至少一个模板参数,作为回调函数的“原型”( prototype )。 41 | “原型”指 C++ 函数类型,例如 `void (int)`, `void (const std::string &, const MyClass &, int, bool)` 42 | 43 | 然后,添加一个回调函数 44 | 45 | ```c++ 46 | callbackList.append([]() { 47 | std::cout << "Got callback 1." << std::endl; 48 | }); 49 | ``` 50 | 51 | `append` 函数接收一个回调函数作为参数。 52 | 回调函数可以使任何回调目标——函数、函数指针、指向成员函数的指针、lambda 表达式、函数对象等。该回调函数必须可以使用 `callbackList` 中声明的原型调用。 53 | 54 | 接下来启动回调列表 55 | 56 | ```c++ 57 | callbackList(); 58 | ``` 59 | 60 | 在回调列表启动执行的过程中,所有回调函数都会按照被加入列表时的顺序执行。 61 | 62 | ### CallbackList 教程 2, 带参数的回调函数 63 | 64 | **代码** 65 | 66 | ```c++ 67 | // 下面这个 CallbackList 的回调函数原型有两个参数 68 | eventpp::CallbackList callbackList; 69 | 70 | callbackList.append([](const std::string & s, const bool b) { 71 | std::cout< Got callback 1, s is Hello world b is true 86 | > Got callback 2, s is Hello world b is 1 87 | 88 | **解读** 89 | 90 | 本例中,回调函数列表的回调函数原型接收两个参数: `const std::string &` 和 `const bool`。 91 | 回调函数的原型并不需要和回调完全一致,只要两个函数中的参数能够兼容即可。正如上面例子中的第二个回调函数,其参数为 `[](std::string s, int b)`,其原型与回调列表中的并不相同。 92 | 93 | ### CallbackList 教程 3, 移除 94 | 95 | **代码** 96 | 97 | ```c++ 98 | using CL = eventpp::CallbackList; 99 | CL callbackList; 100 | 101 | CL::Handle handle2; 102 | 103 | // 加一些回调函数 104 | callbackList.append([]() { 105 | std::cout << "Got callback 1." << std::endl; 106 | }); 107 | handle2 = callbackList.append([]() { 108 | std::cout << "Got callback 2." << std::endl; 109 | }); 110 | callbackList.append([]() { 111 | std::cout << "Got callback 3." << std::endl; 112 | }); 113 | 114 | callbackList.remove(handler2); 115 | 116 | // 启动回调列表。“Got callback 2.” 并不会被触发 117 | callbackList(); 118 | ``` 119 | 120 | **输出** 121 | 122 | > Got callback 1. 123 | > Got callback 3. 124 | 125 | ### CallbackList 教程 4, for each 126 | 127 | **代码** 128 | 129 | ```c++ 130 | using CL = eventpp::CallbackList; 131 | CL callbackList; 132 | 133 | // 添加回调函数 134 | callbackList.append([]() { 135 | std::cout << "Got callback 1." << std::endl; 136 | }); 137 | callbackList.append([]() { 138 | std::cout << "Got callback 2." << std::endl; 139 | }); 140 | callbackList.append([]() { 141 | std::cout << "Got callback 3." << std::endl; 142 | }); 143 | 144 | // 下面调用 forEach 移除第二个回调函数 145 | // forEach 回调函数的原型是 146 | // void(const CallbackList::Handle & handle, const CallbackList::Callback & callback) 147 | int index = 0; 148 | callbackList.forEach([&callbackList, &index](const CL::Handle & handle, const CL::Callback & callback) { 149 | std::cout << "forEach(Handle, Callback), invoked " << index << std::endl; 150 | if(index == 1) { 151 | callbackList.remove(handle); 152 | std::cout << "forEach(Handle, Callback), removed second callback" << std::endl; 153 | } 154 | ++index; 155 | }); 156 | 157 | // forEach 回调函数原型也可以是 void(const CallbackList::Callback & callback) 158 | callbackList.forEach([&callbackList, &index](const CL::Callback & callback) { 159 | std::cout << "forEach(Callback), invoked" << std::endl; 160 | }); 161 | 162 | // 启动回调列表。“Got callback 2.” 并不会被触发 163 | callbackList(); 164 | ``` 165 | 166 | **输出** 167 | 168 | > forEach(Handle, Callback), invoked 0 169 | > forEach(Handle, Callback), invoked 1 170 | > forEach(Handle, Callback), removed second callback 171 | > forEach(Handle, Callback), invoked 2 172 | > forEach(Callback), invoked 173 | > forEach(Callback), invoked 174 | > Got callback 1. 175 | > Got callback 3. -------------------------------------------------------------------------------- /doc/cn/tutorial_eventdispatcher.md: -------------------------------------------------------------------------------- 1 | # EventDispatcher 使用教程 2 | 3 | 注意:如果想尝试运行教程代码,建议使用 `tests/unittest` 目录下的代码。本文中的示例代码可能已经过期而无法编译。 4 | 5 | ### 教程 1 基本用法 6 | 7 | **代码** 8 | 9 | ```C++ 10 | // 命名空间为 eventpp 11 | // 第一个模板参数 int 是事件类型。事件类型可以是其他数据类型的,如 std::string,int 等 12 | // 第二个参数是监听器的原型 13 | eventpp::EventDispatcher dispatcher; 14 | 15 | // 添加一个监听器。这里的 3 和 5 是传给 dispatcher 的,用于标记自身的事件类型 16 | // []() {} 是监听器。 17 | // 监听器并不必须是 lambda,可以使任何满足原型要求的可调用对象,如函数、std::function等 18 | dispatcher.appendListener(3, []() { 19 | std::cout << "Got event 3." << std::endl; 20 | }); 21 | dispatcher.appendListener(5, []() { 22 | std::cout << "Got event 5." << std::endl; 23 | }); 24 | dispatcher.appendListener(5, []() { 25 | std::cout << "Got another event 5." << std::endl; 26 | }); 27 | 28 | // 分发事件。第一个参数是事件类型。 29 | dispatcher.dispatch(3); 30 | dispatcher.dispatch(5); 31 | ``` 32 | 33 | **输出** 34 | 35 | > Got event 3. 36 | > Got event 5. 37 | > Got another event 5. 38 | 39 | **解读** 40 | 41 | 首先定义一个分发器 42 | 43 | ```c++ 44 | eventpp::EventDispatcher dispatcher; 45 | ``` 46 | 47 | EventDispatcher 类接收两个模板参数。第一个是*事件类型*,此处是 `int` 。第二个是监听器的*原型*。 48 | *事件类型* 必须能够用作 `std::map` 的 key。也就是说该类型必须支持 `operator <`。 49 | *原型* 是 C++ 函数类型,例如 `void (int)`, `void (const std::string &, const MyClass &, int, bool)` 50 | 51 | 然后添加一个监听器 52 | 53 | ```c++ 54 | dispatcher.appendListener(3, []() { 55 | std::cout << "Got event 3." << std::endl; 56 | }); 57 | ``` 58 | 59 | `appendListener` 函数接收两个参数。第一个是 *事件类型* 的 *事件* (译注:此处的“事件类型”指的是用于区分事件的数据类型,此处为 int 。“事件”则是具体的时间值,此处为整数 3 ),此处为 `int` 类型。第二个参数是*回调函数*。 60 | 回调函数可以是任何能够回调的目标——函数、函数指针、成员函数指针、lambda表达式、函数对象等。其必须能够被 `dispatcher` 中声明的 *原型* 调用。 61 | 在上面这段代码的下面,我们还为 事件5 添加了两个监听器。 62 | 63 | 接下来,使用下面的代码分发事件 64 | 65 | ```c++ 66 | dispatcher.dispatch(3); 67 | dispatcher.dispatch(5); 68 | ``` 69 | 70 | 这里分发了两个事件,分别是事件 3 和 5 。 71 | 在事件分发的过程中,所有对应事件的监听器都会按照它们被添加进 EventDispatcher 的顺序逐个执行。 72 | 73 | ### 教程 2 —— 带参数的监听器 74 | 75 | **代码** 76 | 77 | ```c++ 78 | // 定义有两个参数的监听器原型 79 | eventpp::EventDispatcher dispatcher; 80 | 81 | dispatcher.appendListener(3, [](const std::string & s, const bool b) { 82 | std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl; 83 | }); 84 | // 监听器的原型不需要和 dispatcher 完全一致,只要参数类型能够兼容即可 85 | dispatcher.appendListener(5, [](std::string s, int b) { 86 | std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl; 87 | }); 88 | dispatcher.appendListener(5, [](const std::string & s, const bool b) { 89 | std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl; 90 | }); 91 | 92 | // 分发事件。第一个参数是事件类型 93 | dispatcher.dispatch(3, "Hello", true); 94 | dispatcher.dispatch(5, "World", false); 95 | ``` 96 | 97 | **输出** 98 | 99 | > Got event 3, s is Hello b is true 100 | > Got event 5, s is World b is 0 101 | > Got another event 5, s is World b is false 102 | 103 | **解读** 104 | 105 | 此处的 dispatcher 回调函数原型接收两个参数:`const std::string &` 和 `const bool`。 106 | 监听器原型不需要和 dispatcher 完全一致,只要参数类型能够兼容即可。例如第二个监听器,`[](std::string s, int b)`,其原型和 dispatcher 并不相同 107 | 108 | ### 教程 3 —— 自定义事件结构 109 | 110 | **代码** 111 | 112 | ```c++ 113 | // 定义一个能够保存所有参数的 Event 114 | struct MyEvent { 115 | int type; 116 | std::string message; 117 | int param; 118 | }; 119 | 120 | // 定义一个能让 dispatcher 知道如何展开事件类型的 policy 121 | struct MyEventPolicies 122 | { 123 | static int getEvent(const MyEvent & e, bool /*b*/) { 124 | return e.type 125 | } 126 | }; 127 | 128 | // 将刚刚定义的 MyEventPolicies 用作 EventDispatcher 的第三个模板参数 129 | // 注意:第一个模板参数是事件类型的类型 int ,并非 MyEvent 130 | eventpp::EventDispatcher< 131 | int, 132 | void (const MyEvent &, bool), 133 | MyEventPolicies 134 | > dispatcher; 135 | 136 | // 添加一个监听器。注意,第一个参数是事件类型 int,并非 MyEvent 137 | dispatcher.appendListener(3, [](const MyEvent & e, bool b) { 138 | std::cout 139 | << std::boolalpha 140 | << "Got event 3" << std::endl 141 | << "Event::type is " << e.type << std::endl 142 | << "Event::message is " << e.message << std::endl 143 | << "Event::param is " << e.param << std::endl 144 | << "b is " << b << std::endl 145 | ; 146 | }); 147 | 148 | // 启动事件。第一个参数是 Event 149 | dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true); 150 | ``` 151 | 152 | **输出** 153 | 154 | > Got event 3 155 | > Event::type is 3 156 | > Event::message is Hello world 157 | > Event::param is 38 158 | > b is true 159 | 160 | **解读** 161 | 162 | 通常的方法是将 Event 类定义为基类,所有其他的事件都从 Event 派生,实际的事件类型则是 Event 的成员(就像 Qt 中的 QEvent ),通过 policy 来为 EventDispatcher 定义如何从 Event 类中获取真正需要的数据。 163 | -------------------------------------------------------------------------------- /doc/cn/tutorial_eventqueue.md: -------------------------------------------------------------------------------- 1 | # EventQueue 使用教程 2 | 3 | 注意:如果想尝试运行教程代码,建议使用 `tests/unittest` 目录下的代码。本文中的示例代码可能已经过期而无法编译。 4 | 5 | ### 教程 1 基本用法 6 | 7 | **代码** 8 | 9 | ```C++ 10 | eventpp::EventQueue &)> queue; 11 | 12 | queue.appendListener(3, [](const std::string & s, std::unique_ptr & n) { 13 | std::cout << "Got event 3, s is " << s << " n is " << *n << std::endl; 14 | }); 15 | 16 | // 监听器原型不需要和 dispatcher 完全一致,参数类型兼容即可 17 | queue.appendListener(5, [](std::string s, const std::unique_ptr & n) { 18 | std::cout << "Got event 5, s is " << s << " n is " << *n << std::endl; 19 | }); 20 | queue.appendListener(5, [](const std::string & s, std::unique_ptr & n) { 21 | std::cout << "Got another event 5, s is " << s << " n is " << *n << std::endl; 22 | }); 23 | 24 | // 将事件加入队列,首个参数是事件类型。监听器在入队列期间不会被触发 25 | queue.enqueue(3, "Hello", std::unique_ptr(new int(38))); 26 | queue.enqueue(5, "World", std::unique_ptr(new int(58))); 27 | 28 | // 处理事件队列,分发队列中的所有事件 29 | queue.process(); 30 | ``` 31 | 32 | **输出** 33 | 34 | > Got event 3, s is Hello n is 38 35 | > Got event 5, s is World n is 58 36 | > Got another event 5, s is World n is 58 37 | 38 | **解读** 39 | `EventDispatcher<>::dispatch()` 触发监听器的动作是同步的。但异步事件队列在某些场景下能发挥更大的作用(例如 Windows 消息队列、游戏中的消息队列等)。EventQueue 就是用于满足该类需求的事件队列。 40 | `EventQueue<>::enqueue()` 将事件加入队列,其参数和 `dispatch` 的参数完全相同。 41 | `EventQueue<>::process()` 用于分发队列中的事件。不调用 process ,事件就不会被分发。 42 | 事件队列的典型用例:在 GUI 应用中,每个组件都调用 `EventQueue<>::enqueue()` 来发布事件,然后主事件循环调用 `EventQueue<>()::process()` 来 dispatch 所有队列中的事件。 43 | `EventQueue` 支持将不可拷贝对象作为事件参数,例如上面例子中的 unique_ptr 44 | 45 | ### 教程 2 —— 多线程 46 | 47 | **代码** 48 | 49 | ```c++ 50 | using EQ = eventpp::EventQueue; 51 | EQ queue; 52 | 53 | constexpr int stopEvent = 1; 54 | constexpr int otherEvent = 2; 55 | 56 | // 启动一个新线程来处理事件队列。所有监听器都会在该线程中启动运行 57 | std::thread thread([stopEvent, otherEvent, &queue]() { 58 | volatile bool shouldStop = false; 59 | queue.appendListener(stopEvent, [&shouldStop](int) { 60 | shouldStop = true; 61 | }); 62 | queue.appendListener(otherEvent, [](const int index) { 63 | std::cout << "Got event, index is " << index << std::endl; 64 | }); 65 | 66 | while(! shouldStop) { 67 | queue.wait(); 68 | 69 | queue.process(); 70 | } 71 | }); 72 | 73 | // 将一个主线程的事件加入队列。在休眠 10 ms 时,该事件应该已经被另一个线程处理了 74 | queue.enqueue(otherEvent, 1); 75 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 76 | std::cout << "Should have triggered event with index = 1" << std::endl; 77 | 78 | queue.enqueue(otherEvent, 2); 79 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 80 | std::cout << "Should have triggered event with index = 2" << std::endl; 81 | 82 | { 83 | // EventQueue::DisableQueueNotify 是一个 RAII 类,能避免唤醒其他的等待线程。 84 | // 所以该代码块内不会触发任何事件。 85 | // 当需要一次性添加很多事件,希望在事件都添加完成后才唤醒等待线程时, 86 | // 就可以使用 DisableQueueNotify 87 | EQ::DisableQueueNotify disableNotify(&queue); 88 | 89 | queue.enqueue(otherEvent, 10); 90 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 91 | std::cout << "Should NOT trigger event with index = 10" << std::endl; 92 | 93 | queue.enqueue(otherEvent, 11); 94 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 95 | std::cout << "Should NOT trigger event with index = 11" << std::endl; 96 | } 97 | // DisableQueueNotify 对象在此处销毁,恢复唤醒其他的等待线程。因此事件都会在此处触发 98 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 99 | std::cout << "Should have triggered events with index = 10 and 11" << std::endl; 100 | 101 | queue.enqueue(stopEvent, 1); 102 | thread.join(); 103 | ``` 104 | 105 | **输出** 106 | 107 | > Got event, index is 1 108 | > Should have triggered event with index = 1 109 | > Got event, index is 2 110 | > Should have triggered event with index = 2 111 | > Should NOT trigger event with index = 10 112 | > Should NOT trigger event with index = 11 113 | > Got event, index is 10 114 | > Got event, index is 11 115 | > Should have triggered events with index = 10 and 11 116 | 117 | -------------------------------------------------------------------------------- /doc/conditionalfunctor.md: -------------------------------------------------------------------------------- 1 | # Conditional functor reference 2 | 3 | ## Description 4 | 5 | The header file `eventpp/utilities/conditionalfunctor.h` contains utilities that can check certain condition then decide whether to invoke the underlying function. 6 | 7 | For example, 8 | 9 | ```c++ 10 | eventpp::CallbackList callbackList; 11 | 12 | callbackList.append( 13 | eventpp::conditionalFunctor( 14 | [](const int value) { 15 | std::cout << "We should get value 1." << here; 16 | }, 17 | [](const int value) { 18 | return value == 1; 19 | } 20 | ) 21 | ); 22 | callbackList(2); // not trigger the callback 23 | callbackList(1); // trigger the callback 24 | ``` 25 | 26 | ## Header 27 | 28 | eventpp/utilities/conditionalfunctor.h 29 | 30 | ## API reference 31 | 32 | ```c++ 33 | template 34 | ConditionalFunctor conditionalFunctor(Func func, Condition condition); 35 | ``` 36 | 37 | Function `conditionalFunctor` receives a function `func`, and a conditional function `condition`, and return a functor object of `ConditionalFunctor`. `ConditionalFunctor` has a function invoking operator that forwards all arguments to `condition`, if `condition` returns true, `func` will be invoked with same arguments, if `condition` return false, `func` will not be invoked. The return value of `conditionalFunctor` can be passed to CallbackList, EventDispatcher, or EventQueue. 38 | 39 | Below is the example code to demonstrate how to use `conditionalFunctor`, it's grabbed from the unit tests. 40 | 41 | ```c++ 42 | 43 | eventpp::CallbackList callbackList; 44 | 45 | std::vector dataList(3); 46 | callbackList.append( 47 | eventpp::conditionalFunctor( 48 | [&dataList](const int index) { 49 | ++dataList[index]; 50 | }, 51 | [](const int index) { 52 | return index == 0; 53 | } 54 | ) 55 | ); 56 | callbackList.append( 57 | eventpp::conditionalFunctor( 58 | [&dataList](const int index) { 59 | ++dataList[index]; 60 | }, 61 | [](const int index) { 62 | return index == 1; 63 | } 64 | ) 65 | ); 66 | callbackList.append( 67 | eventpp::conditionalFunctor( 68 | [&dataList](const int index) { 69 | ++dataList[index]; 70 | }, 71 | [](const int index) { 72 | return index == 2; 73 | } 74 | ) 75 | ); 76 | 77 | REQUIRE(dataList == std::vector{ 0, 0, 0 }); 78 | 79 | callbackList(2); 80 | REQUIRE(dataList == std::vector{ 0, 0, 1 }); 81 | 82 | callbackList(0); 83 | REQUIRE(dataList == std::vector{ 1, 0, 1 }); 84 | 85 | callbackList(1); 86 | REQUIRE(dataList == std::vector{ 1, 1, 1 }); 87 | 88 | ``` 89 | -------------------------------------------------------------------------------- /doc/conditionalremover.md: -------------------------------------------------------------------------------- 1 | # Class ConditionalRemover reference 2 | 3 | 4 | ## Table Of Contents 5 | 6 | * [Description](#a2_1) 7 | * [API reference](#a2_2) 8 | * [Header](#a3_1) 9 | * [Template parameters](#a3_2) 10 | * [Member functions](#a3_3) 11 | * [Free functions](#a3_4) 12 | * [Sample code](#a3_5) 13 | 14 | 15 | 16 | ## Description 17 | 18 | ConditionalRemover is a utility class that automatically removes listeners after the listeners are triggered and certain condition is satisfied. 19 | ConditionalRemover is a pure functional class. After the member functions in ConditionalRemover are invoked, the ConditionalRemover object can be destroyed safely. 20 | 21 | 22 | ## API reference 23 | 24 | 25 | ### Header 26 | 27 | eventpp/utilities/conditionalremover.h 28 | 29 | 30 | ### Template parameters 31 | 32 | ```c++ 33 | template 34 | class ConditionalRemover; 35 | ``` 36 | 37 | `DispatcherType` can be CallbackList, EventDispatcher, EventQueue, HeterCallbackList, HeterEventDispatcher, or HeterEventQueue. 38 | 39 | 40 | ### Member functions 41 | 42 | ```c++ 43 | explicit ConditionalRemover(DispatcherType & dispatcher); 44 | ``` 45 | 46 | Constructs an instance of ConditionalRemover. 47 | 48 | **Member functions for EventDispatcher and EventQueue** 49 | ```c++ 50 | template 51 | typename DispatcherType::Handle appendListener( 52 | const typename DispatcherType::Event & event, 53 | const typename DispatcherType::Callback & listener, 54 | const Condition & condition 55 | ); 56 | 57 | template 58 | typename DispatcherType::Handle prependListener( 59 | const typename DispatcherType::Event & event, 60 | const typename DispatcherType::Callback & listener, 61 | const Condition & condition 62 | ); 63 | 64 | template 65 | typename DispatcherType::Handle insertListener( 66 | const typename DispatcherType::Event & event, 67 | const typename DispatcherType::Callback & listener, 68 | const typename DispatcherType::Handle & before, 69 | const Condition & condition 70 | ); 71 | ``` 72 | 73 | **Member functions for CallbackList** 74 | ```c++ 75 | template 76 | typename CallbackListType::Handle append( 77 | const typename CallbackListType::Callback & listener, 78 | const Condition & condition 79 | ); 80 | 81 | template 82 | typename CallbackListType::Handle prepend( 83 | const typename CallbackListType::Callback & listener, 84 | const Condition & condition 85 | ); 86 | 87 | template 88 | typename CallbackListType::Handle insert( 89 | const typename CallbackListType::Callback & listener, 90 | const typename CallbackListType::Handle & before, 91 | const Condition & condition 92 | ); 93 | ``` 94 | 95 | The member functions have the same names with the corresponding underlying class (CallbackList, EventDispatcher, or EventQueue), and also have the same parameters except there is one more parameter, `condition`. `condition` is a predicate function that returns a bool value. It's invoked after each trigger, if it returns true, the listener will be removed. 96 | `condition` can have two kinds of prototype, 97 | 98 | ``` 99 | bool condition() 100 | ``` 101 | This `condition` doesn't receive any arguments. 102 | 103 | ``` 104 | bool condition(Args ...args) 105 | ``` 106 | This `condition` receives the arguments that passed to the listener. 107 | 108 | 109 | ### Free functions 110 | 111 | ```c++ 112 | template 113 | ConditionalRemover conditionalRemover(DispatcherType & dispatcher); 114 | ``` 115 | 116 | Since ConditionalRemover takes one template parameter and it's verbose to instantiate its instance, the function `conditionalRemover` is used to construct an instance of ConditionalRemover via the deduced argument. 117 | 118 | 119 | ### Sample code 120 | 121 | ```c++ 122 | #include "eventpp/utilities/conditionalRemover.h" 123 | #include "eventpp/eventdispatcher.h" 124 | 125 | eventpp::EventDispatcher dispatcher; 126 | constexpr int event = 3; 127 | 128 | dispatcher.appendListener(event, []() { 129 | // listener A 130 | }); 131 | 132 | // Note the ConditionalRemover instance returned by conditionalRemover is invoked 133 | // prependListener and destroyed immediately. 134 | std::string removeWho; 135 | eventpp::conditionalRemover(dispatcher).prependListener(event, [&dataList]() { 136 | // listener B 137 | }, [&removeWho]() -> bool { 138 | return removeWho == "removeB"; 139 | }); 140 | auto handle = eventpp::conditionalRemover(dispatcher).appendListener(event, [&dataList]() { 141 | // listener C 142 | }, [&removeWho]() -> bool { 143 | return removeWho == "removeC"; 144 | }); 145 | eventpp::conditionalRemover(dispatcher).insertListener(event, [&dataList]() { 146 | // listener D 147 | }, handle, [&removeWho]() -> bool { 148 | return removeWho == "removeD"; 149 | }); 150 | 151 | dispatcher.dispatch(event); 152 | // No listeners were removed since no conditions were met. 153 | 154 | removeWho = "removeB"; 155 | dispatcher.dispatch(event); 156 | // All listeners were triggered. 157 | // Listener B was removed. 158 | 159 | removeWho = "removeC"; 160 | dispatcher.dispatch(event); 161 | // Listeners A, C, D were triggered. 162 | // Listener C was removed. 163 | 164 | removeWho = "removeD"; 165 | dispatcher.dispatch(event); 166 | // Listeners A, D were triggered. 167 | // Listener D was removed. 168 | 169 | dispatcher.dispatch(event); 170 | // Listeners A was triggered. 171 | 172 | ``` 173 | -------------------------------------------------------------------------------- /doc/counterremover.md: -------------------------------------------------------------------------------- 1 | # Class CounterRemover reference 2 | 3 | 4 | ## Table Of Contents 5 | 6 | * [Description](#a2_1) 7 | * [API reference](#a2_2) 8 | * [Header](#a3_1) 9 | * [Template parameters](#a3_2) 10 | * [Member functions](#a3_3) 11 | * [Free functions](#a3_4) 12 | * [Sample code](#a3_5) 13 | 14 | 15 | 16 | ## Description 17 | 18 | CounterRemover is a utility class that automatically removes listeners after the listeners are triggered for certain times. 19 | CounterRemover is a pure functional class. After the member functions in CounterRemover are invoked, the CounterRemover object can be destroyed safely. 20 | 21 | 22 | ## API reference 23 | 24 | 25 | ### Header 26 | 27 | eventpp/utilities/counterremover.h 28 | 29 | 30 | ### Template parameters 31 | 32 | ```c++ 33 | template 34 | class CounterRemover; 35 | ``` 36 | 37 | `DispatcherType` can be CallbackList, EventDispatcher, EventQueue, HeterCallbackList, HeterEventDispatcher, or HeterEventQueue. 38 | 39 | 40 | ### Member functions 41 | 42 | ```c++ 43 | explicit CounterRemover(DispatcherType & dispatcher); 44 | ``` 45 | 46 | Constructs an instance of CounterRemover. 47 | 48 | **Member functions for EventDispatcher and EventQueue** 49 | ```c++ 50 | typename DispatcherType::Handle appendListener( 51 | const typename DispatcherType::Event & event, 52 | const typename DispatcherType::Callback & listener, 53 | const int triggerCount = 1 54 | ); 55 | 56 | typename DispatcherType::Handle prependListener( 57 | const typename DispatcherType::Event & event, 58 | const typename DispatcherType::Callback & listener, 59 | const int triggerCount = 1 60 | ); 61 | 62 | typename DispatcherType::Handle insertListener( 63 | const typename DispatcherType::Event & event, 64 | const typename DispatcherType::Callback & listener, 65 | const typename DispatcherType::Handle & before, 66 | const int triggerCount = 1 67 | ); 68 | ``` 69 | 70 | **Member functions for CallbackList** 71 | ```c++ 72 | typename CallbackListType::Handle append( 73 | const typename CallbackListType::Callback & listener, 74 | const int triggerCount = 1 75 | ); 76 | 77 | typename CallbackListType::Handle prepend( 78 | const typename CallbackListType::Callback & listener, 79 | const int triggerCount = 1 80 | ); 81 | 82 | typename CallbackListType::Handle insert( 83 | const typename CallbackListType::Callback & listener, 84 | const typename CallbackListType::Handle & before, 85 | const int triggerCount = 1 86 | ); 87 | ``` 88 | 89 | The member functions have the same names with the corresponding underlying class (CallbackList, EventDispatcher, or EventQueue), and also have the same parameters except there is one more parameter, `triggerCount`. `triggerCount` is decreased by one on each trigger, and when `triggerCount` is zero or negative, the listener will be removed. 90 | The default value of `triggerCount` is 1, that means the listener is removed after the first trigger, which is one shot listener. 91 | 92 | 93 | ### Free functions 94 | 95 | ```c++ 96 | template 97 | CounterRemover counterRemover(DispatcherType & dispatcher); 98 | ``` 99 | 100 | Since CounterRemover takes one template parameter and it's verbose to instantiate its instance, the function `counterRemover` is used to construct an instance of CounterRemover via the deduced argument. 101 | 102 | 103 | ### Sample code 104 | 105 | ```c++ 106 | #include "eventpp/utilities/counterremover.h" 107 | #include "eventpp/eventdispatcher.h" 108 | 109 | eventpp::EventDispatcher dispatcher; 110 | constexpr int event = 3; 111 | 112 | dispatcher.appendListener(event, []() { 113 | // listener A 114 | }); 115 | 116 | // Note the CounterRemover instance returned by counterRemover is invoked 117 | // prependListener and destroyed immediately. 118 | eventpp::counterRemover(dispatcher).prependListener(event, []() { 119 | // listener B 120 | }); 121 | auto handle = eventpp::counterRemover(dispatcher).appendListener(event, []() { 122 | // listener C 123 | }, 2); 124 | eventpp::counterRemover(dispatcher).insertListener(event, []() { 125 | // listener D 126 | }, handle, 3); 127 | 128 | dispatcher.dispatch(event); 129 | // All listeners were triggered. 130 | // Listener B was removed. 131 | 132 | dispatcher.dispatch(event); 133 | // Listeners A, C, D were triggered. 134 | // Listener C was removed. 135 | 136 | dispatcher.dispatch(event); 137 | // Listeners A, D were triggered. 138 | // Listener D was removed. 139 | 140 | dispatcher.dispatch(event); 141 | // Listeners A was triggered. 142 | 143 | ``` 144 | -------------------------------------------------------------------------------- /doc/eventmaker.md: -------------------------------------------------------------------------------- 1 | # Event maker macros reference 2 | 3 | ## Overview 4 | 5 | `eventpp/utilities/eventmaker.h` contains several macros to generate event classes. 6 | Without the macros, we may need to repeat the same code structure on lots of event classes. 7 | For example, 8 | 9 | ```c++ 10 | // The base event 11 | class Event 12 | { 13 | }; 14 | 15 | class EventMouseDown : public Event 16 | { 17 | public: 18 | int getX() const; 19 | int getY() const; 20 | int getButton() const; 21 | 22 | private: 23 | int x; 24 | int y; 25 | int button; 26 | }; 27 | 28 | class EventKeyDown : public Event 29 | { 30 | public: 31 | int getKey() const; 32 | 33 | private: 34 | int key; 35 | }; 36 | 37 | ``` 38 | 39 | It's not rare that there are dozens of or even hundreds of event classes in a system, and we need to repeat the same code structure many times, which is boring and error prone. 40 | 41 | The macros in eventmaker.h can reduce the work significantly. 42 | 43 | ## Header 44 | 45 | eventpp/utilities/eventmaker.h 46 | 47 | ## Macro EVENTPP_MAKE_EVENT 48 | ```c++ 49 | #define EVENTPP_MAKE_EVENT(className, baseClassName, baseClassArgs, ...) 50 | ``` 51 | 52 | EVENTPP_MAKE_EVENT declares an event class. 53 | 54 | **className**: the event class name. 55 | **baseClassName**: the class name that the event inherits from. The macro requires a base class. If the class name contains comma (,), such as a template class, it should be enclosed within parenthesis. Examples, `Event`, `(AnotherEvent)`. 56 | **baseClassArgs**: the arguments passed to the base class. If the arguments contain comma (,), it should be enclosed within parenthesis. If there is no arguments for the base class, `()` can be used. 57 | **The variadic arguments (...)**: the tuples of member data type and getter/setter function name. A tuple is elements in a pair of parenthesis. A tuple can contain 2 or 3 elements, the first element is the data type, the second element is the getter function name, and the optional third element is the setter function name. Some tuples examples: `(int, getX)`, `(std::string, getText, setText)`. 58 | Note: the variadic arguments can't be empty. If the even class doesn't have data member, use `EVENTPP_MAKE_EMPTY_EVENT` instead. 59 | 60 | The macro always generates a default constructor and a constructor that takes all data members and initialize them. 61 | 62 | Code examples: 63 | ```c++ 64 | EVENTPP_MAKE_EVENT( 65 | EventDraw, Event, EventType::draw, 66 | (std::string, getText, setText), (int, getX), (double, getSize) 67 | ); 68 | ``` 69 | Generates class like (in pseudo code), 70 | ```c++ 71 | class EventDraw : public Event 72 | { 73 | public: 74 | EventDraw(const std::string & text, const int x, const double size) 75 | : Event(EventType::draw), text(text), x(x), size(size) 76 | { 77 | } 78 | 79 | const std::string & getText() const { 80 | return text; 81 | } 82 | 83 | void setText(const std::string & value) { 84 | text = value; 85 | } 86 | 87 | const int getX() const { 88 | return x; 89 | } 90 | 91 | const double getSize() const { 92 | return size; 93 | } 94 | 95 | private: 96 | std::string text; 97 | int x; 98 | double size; 99 | }; 100 | 101 | ``` 102 | 103 | ```c++ 104 | EVENTPP_MAKE_EVENT(EventDraw, (Event), EventType::draw, 105 | (std::string, getText, setText), (int, getX), (double, getSize) 106 | ); 107 | ``` 108 | Similar to above example, except the base class is `Event`. 109 | 110 | ```c++ 111 | EVENTPP_MAKE_EVENT(EventDraw, (Event), (EventType::draw, 5), 112 | (std::string, getText, setText), (int, getX), (double, getSize) 113 | ); 114 | ``` 115 | Similar to above example, except the base class is constructed with `(EventType::draw, 5)` instead of `(EventType::draw)`. 116 | 117 | 118 | ## Macro EVENTPP_MAKE_EMPTY_EVENT 119 | ```c++ 120 | #define EVENTPP_MAKE_EMPTY_EVENT(className, baseClassName, baseClassArgs) 121 | ``` 122 | 123 | Similar with `EVENTPP_MAKE_EVENT`, but EVENTPP_MAKE_EMPTY_EVENT declares event class which doesn't have any data member. 124 | 125 | ## Tip: add getter/setter prefix automatically 126 | 127 | If you don't want to specify "get" or "set" prefix explictly, or you want the field definition looks like a property rather than getter/setter function, you can define some auxiliary macros to achieve that. For example, 128 | 129 | ``` 130 | // auto add "get" prefix 131 | #define G(type, name) (type, get ## name) 132 | 133 | // auto add "get" and "set" prefix 134 | #define GS(type, name) (type, get ## name, set ## name) 135 | 136 | EVENTPP_MAKE_EVENT(EventDrawGS, Event, EventType::draw, 137 | GS(std::string, Text), 138 | G(int, X), 139 | G(double, Size) 140 | ); 141 | ``` 142 | 143 | Of course you need to choose better macro names than `G` and `GS`, or `#undef` them after using them. 144 | -------------------------------------------------------------------------------- /doc/eventutil.md: -------------------------------------------------------------------------------- 1 | # Utilities reference 2 | 3 | ## Header 4 | 5 | eventpp/utilities/eventutil.h 6 | 7 | ## API reference 8 | 9 | ```c++ 10 | template 11 | bool removeListener( 12 | DispatcherType & dispatcher, 13 | const typename DispatcherType::Event & event, 14 | const typename DispatcherType::Callback & listener 15 | ); 16 | ``` 17 | The function finds `listener` of `event` in `dispatcher`, if it finds one, removes the listener and returns true, otherwise returns false. 18 | Note: the function only removes the first found listener. To remove more than one listeners, repeat calling this function until it returns false. 19 | This function requires the listener be able to be compared with equal operator (==). 20 | 21 | ```c++ 22 | template 23 | bool removeListener( 24 | CallbackListType & callbackList, 25 | const typename CallbackListType::Callback & callback 26 | ); 27 | ``` 28 | The function finds `callback` in `callbackList`, if it finds one, removes the callback and returns true, otherwise returns false. 29 | Note: the function only removes the first found callback. To remove more than one callbacks, repeat calling this function until it returns false. 30 | This function requires the callback be able to be compared with equal operator (==). 31 | 32 | ```c++ 33 | template 34 | bool hasListener( 35 | DispatcherType & dispatcher, 36 | const typename DispatcherType::Event & event, 37 | const typename DispatcherType::Callback & listener 38 | ); 39 | ``` 40 | The function finds `listener` of `event` in `dispatcher`, returns true if it finds any one, otherwise returns false. 41 | This function requires the listener be able to be compared with equal operator (==). 42 | 43 | ```c++ 44 | template 45 | bool hasAnyListener( 46 | DispatcherType & dispatcher, 47 | const typename DispatcherType::Event & event 48 | ); 49 | ``` 50 | The function finds any listener of `event` in `dispatcher`, returns true if it finds any one, otherwise returns false. 51 | 52 | ```c++ 53 | template 54 | bool hasListener( 55 | CallbackListType & callbackList, 56 | const typename CallbackListType::Callback & callback 57 | ); 58 | ``` 59 | The function finds `callback` in `callbackList`, returns true if it finds one, otherwise returns false. 60 | This function requires the callback be able to be compared with equal operator (==). 61 | 62 | ```c++ 63 | template 64 | bool hasAnyListener( 65 | CallbackListType & callbackList 66 | ); 67 | ``` 68 | The function finds any callback in `callbackList`, returns true if it finds any one, otherwise returns false. 69 | -------------------------------------------------------------------------------- /doc/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | - [Frequently Asked Questions](#frequently-asked-questions) 4 | - [Why can't rvalue reference be used as callback prototype in EventDispatcher and CallbackList? Such as CallbackList\](#why-cant-rvalue-reference-be-used-as-callback-prototype-in-eventdispatcher-and-callbacklist-such-as-callbacklistvoid-int-) 5 | - [Can the callback prototype have return value? Such as CallbackList\?](#can-the-callback-prototype-have-return-value-such-as-callbackliststdstring-const-stdstring--int) 6 | - [Why can't callback prototype be function pointer such as CallbackList\?](#why-cant-callback-prototype-be-function-pointer-such-as-callbacklistvoid-) 7 | - [Why aren't there APIs to remove listeners directly from an EventDispatcher? Why do we have to remove by handle?](#why-arent-there-apis-to-remove-listeners-directly-from-an-eventdispatcher-why-do-we-have-to-remove-by-handle) 8 | - [Isn't CallbackList equivalent to std::vector? It's simple for me to use std::vector directly.](#isnt-callbacklist-equivalent-to-stdvector-its-simple-for-me-to-use-stdvector-directly) 9 | - [I want to inherit my class from EventDispatcher, but EventDispatcher's destructor is not virtual?](#i-want-to-inherit-my-class-from-eventdispatcher-but-eventdispatchers-destructor-is-not-virtual) 10 | - [How to automatically remove listeners when certain object is destroyed (aka auto disconnection)?](#how-to-automatically-remove-listeners-when-certain-object-is-destroyed-aka-auto-disconnection) 11 | - [How to integrate EventQueue with boost::asio::io\_service?](#how-to-integrate-eventqueue-with-boostasioio_service) 12 | 13 | ## Why can't rvalue reference be used as callback prototype in EventDispatcher and CallbackList? Such as CallbackList 14 | 15 | ```c++ 16 | eventpp::CallbackList callbackList; 17 | callbackList("Hello"); // compile error 18 | ``` 19 | 20 | The above code doesn't compile. This is intended design and not a bug. 21 | A rvalue reference `std::string &&` means the argument can be moved by the callback and become invalid (or empty). Keep in mind CallbackList invokes many callbacks one by one. So what happens if the first callback moves the argument and the other callbacks get empty value? In above code example, that means the first callback sees the value "Hello" and moves it, then the other callbacks will see empty string, not "Hello"! 22 | To avoid such potential bugs, rvalue reference is forbidden deliberately. 23 | 24 | ## Can the callback prototype have return value? Such as CallbackList? 25 | 26 | Yes you can, but both EventDispatcher and CallbackList just discard the return value. It's not efficient nor useful to return value from EventDispatcher and CallbackList. 27 | 28 | ## Why can't callback prototype be function pointer such as CallbackList? 29 | 30 | It's rather easy to support function pointer, but it's year 2018 at the time written, and there is proposal for C++20 standard already, so let's use modern C++ features. Stick with function type `void ()` instead of function pointer `void (*)()`. 31 | 32 | ## Why aren't there APIs to remove listeners directly from an EventDispatcher? Why do we have to remove by handle? 33 | 34 | Both `EventDispatcher::removeListener(const Event & event, const Handle handle)` and `CallbackList::remove(const Handle handle)` requires the handle of a listener is passed in. So why can't we pass the listener object directly? The reason is, it's not guaranteed that the underlying callback storage is comparable while removing a listener object requires the comparable ability. Indeed the default callback storage, `std::function` is not comparable. 35 | If we use some customized callback storage and we are sure it's comparable, there is free functions 'removeListener' in [utility APIs](eventutil.md). 36 | 37 | ## Isn't CallbackList equivalent to std::vector? It's simple for me to use std::vector directly. 38 | 39 | `CallbackList` works like a `std::vector`. But one common usage is to implement one-shot callback that a callback removes itself from the callback list when it's invoked. In such case a simple `std::vector` will bang and crash. 40 | With `CallbackList` a callback can be removed at any time, even when the callback list is under invoking. 41 | 42 | ## I want to inherit my class from EventDispatcher, but EventDispatcher's destructor is not virtual? 43 | 44 | It's intended not to use any virtual functions in eventpp to avoid bloating the code size. New class can still inherit from EventDispatcher, as long as the object is not deleted via a pointer to EventDispatcher, which will cause resource leak. If you need to delete object via pointer to base class, make your own base class that inherits from EventDispatcher, and make the base class destructor virtual. 45 | For example, 46 | 47 | ```c++ 48 | class MyEventDispatcher : public EventDispatcher 49 | { 50 | public: 51 | virtual ~MyEventDispatcher(); 52 | }; 53 | 54 | class MyClass : public MyEventDispatcher 55 | { 56 | } 57 | 58 | MyEventDispatcher * myObject = new MyClass(); 59 | delete myObject; 60 | ``` 61 | 62 | ## How to automatically remove listeners when certain object is destroyed (aka auto disconnection)? 63 | 64 | [Use utility class ScopedRemover](scopedremover.md) 65 | 66 | ## How to integrate EventQueue with boost::asio::io_service? 67 | 68 | A common use case is there are multiple threads that executing boost::asio::io_service::run(). To integrate EventQueue with boost asio, we need to replace `run()` with `poll()` to avoid blocking. So a typical thread will look like, 69 | 70 | ```c++ 71 | boost::asio::io_service ioService; 72 | eventpp::CallbackList mainLoopTasks; 73 | 74 | void threadMain() 75 | { 76 | while(! stopped) { 77 | ioService.poll(); 78 | mainLoopTasks(); 79 | sleepSomeTime(); 80 | } 81 | 82 | ioService.run(); 83 | mainLoopTasks(); 84 | } 85 | 86 | class MyEventQueue : public eventpp::EventQueue 87 | { 88 | public: 89 | MyEventQueue() 90 | { 91 | mainLoopTasks.append([this]() { 92 | process(); 93 | }); 94 | } 95 | }; 96 | ``` 97 | 98 | Note that after the while loop is finished, the ioService is still run and mainLoopTasks is still invoked, that's to clean up any remaining tasks. 99 | -------------------------------------------------------------------------------- /doc/hetercallbacklist.md: -------------------------------------------------------------------------------- 1 | # Class HeterCallbackList reference 2 | 3 | 4 | ## Table Of Contents 5 | 6 | * [Description](#a2_1) 7 | * [API reference](#a2_2) 8 | * [Header](#a3_1) 9 | * [Template parameters](#a3_2) 10 | * [Public types](#a3_3) 11 | * [Member functions](#a3_4) 12 | 13 | 14 | 15 | ## Description 16 | 17 | HeterCallbackList is the heterogeneous counterpart of CallbackList in eventpp. The other classes HeterEventDispatcher and HeterEventQueue are built on HeterCallbackList. 18 | 19 | HeterCallbackList holds a list of callbacks. The callbacks can have different prototypes. At the time of the call, HeterCallbackList invokes each callback which matches the invoking parameters. 20 | The *callback* can be any callback target -- functions, pointers to functions, , pointers to member functions, lambda expressions, and function objects. 21 | 22 | 23 | ## API reference 24 | 25 | 26 | ### Header 27 | 28 | eventpp/hetercallbacklist.h 29 | 30 | 31 | ### Template parameters 32 | 33 | ```c++ 34 | template < 35 | typename PrototypeList, 36 | typename Policies = DefaultPolicies 37 | > 38 | class HeterCallbackList; 39 | ``` 40 | `Prototype`: a list of function types in `eventpp::HeterTuple`, such as `eventpp::HeterTuple`. 41 | `Policies`: the policies to configure and extend the callback list. The default value is `DefaultPolicies`. See [document of policies](policies.md) for details. 42 | 43 | 44 | ### Public types 45 | 46 | `Handle`: the handle type returned by append, prepend and insert. A handle can be used to insert a callback or remove a callback. To check if a `Handle` is empty, convert it to boolean, *false* is empty. `Handle` is copyable. 47 | `Callback`: the callback storage type. 48 | 49 | 50 | ### Member functions 51 | 52 | #### constructors 53 | 54 | ```c++ 55 | HeterCallbackList() noexcept; 56 | HeterCallbackList(const HeterCallbackList & other); 57 | HeterCallbackList(HeterCallbackList && other) noexcept; 58 | HeterCallbackList & operator = (const HeterCallbackList & other); 59 | HeterCallbackList & operator = (HeterCallbackList && other) noexcept; 60 | ``` 61 | 62 | HeterCallbackList can be copied, moved, assigned, and move assigned. 63 | 64 | #### empty 65 | 66 | ```c++ 67 | bool empty() const; 68 | ``` 69 | Return true if the callback list is empty. 70 | Note: in multi threading, this function returning true doesn't guarantee that the list is empty. The list may immediately become non-empty after the function returns true. 71 | 72 | #### bool casting operator 73 | 74 | ```c++ 75 | operator bool() const; 76 | ``` 77 | Return true if the callback list is not empty. 78 | This operator allows a HeterCallbackList instance be used in condition statement. 79 | 80 | #### append 81 | 82 | ```c++ 83 | template 84 | Handle append(const C & callback); 85 | ``` 86 | Add the `callback` to the callback list. 87 | The callback is added to the end of the callback list. 88 | The callback type `C` must be specified in `PrototypeList`. 89 | Return a handle that represents the callback. The handle can be used to remove this callback or to insert additional callbacks before this callback. 90 | If `append` is called in another callback during the invoking of the callback list, the new callback is guaranteed not to be triggered during the same callback list invoking. 91 | The time complexity is O(1). 92 | 93 | #### prepend 94 | 95 | ```c++ 96 | template 97 | Handle prepend(const C & callback); 98 | ``` 99 | Add the *callback* to the callback list. 100 | The callback is added to the beginning of the callback list. 101 | The callback type `C` must be specified in `PrototypeList`. 102 | Return a handle that represents the callback. The handle can be used to remove this callback or to insert additional callbacks before this callback. 103 | If `prepend` is called in another callback during the invoking of the callback list, the new callback is guaranteed not to be triggered during the same callback list invoking. 104 | The time complexity is O(1). 105 | 106 | #### insert 107 | 108 | ```c++ 109 | template 110 | Handle insert(const C & callback, const Handle & before); 111 | ``` 112 | Insert the *callback* to the callback list before the callback handle *before*. If *before* is not found, *callback* is added at the end of the callback list. 113 | The callback type `C` must be specified in `PrototypeList`. 114 | Return a handle that represents the callback. The handle can be used to remove this callback or to insert additional callbacks before this callback. 115 | If `insert` is called in another callback during the invoking of the callback list, the new callback is guaranteed not to be triggered during the same callback list invoking. 116 | The time complexity is O(1). 117 | 118 | #### remove 119 | 120 | ```c++ 121 | bool remove(const Handle & handle); 122 | ``` 123 | Remove the callback *handle* from the callback list. 124 | Return true if the callback is removed successfully, false if the callback is not found. 125 | The time complexity is O(1). 126 | 127 | #### forEach 128 | 129 | ```c++ 130 | template 131 | void forEach(Func && func) const; 132 | ``` 133 | Apply `func` to all callbacks which has the `Prototype`. 134 | The `func` can be one of the two prototypes: 135 | ```c++ 136 | AnyReturnType func(const HeterCallbackList::Handle & handle, const std::function & callback); 137 | AnyReturnType func(const std::function & callback); 138 | ``` 139 | **Note**: the `func` can remove any callbacks, or add other callbacks, safely. 140 | 141 | #### forEachIf 142 | ```c++ 143 | template 144 | bool forEachIf(Func && func) const; 145 | ``` 146 | Apply `func` to all callbacks. `func` must return a boolean value, and if the return value is false, forEachIf stops the looping immediately. 147 | Return `true` if all callbacks are invoked, or `event` is not found, `false` if `func` returns `false`. 148 | 149 | #### invoking operator 150 | 151 | ```c++ 152 | template 153 | void operator() (Args && ...args) const; 154 | ``` 155 | Invoke each callbacks that can be called with `Args` in the callback list. 156 | The callbacks are called with arguments `args`. 157 | The callbacks are called in the thread same as the callee of `operator()`. 158 | -------------------------------------------------------------------------------- /doc/heterogeneous.md: -------------------------------------------------------------------------------- 1 | # Overview of heterogeneous classes 2 | 3 | ## Table Of Contents 4 | 5 | * [Description](#a2_1) 6 | * [Warning](#a2_2) 7 | * [Usage](#a2_3) 8 | * [Headers](#a3_1) 9 | * [Template parameters](#a3_2) 10 | * [Differences between heterogeneous classes vs homogeneous classes](#a2_4) 11 | 12 | 13 | 14 | ## Description 15 | 16 | 'CallbackList', 'EventDispatcher', and 'EventQueue' are homogeneous. All listeners must have the same prototype. For example, 17 | 18 | ```c++ 19 | eventpp::EventDispatcher dispatcher; 20 | dispatcher.appendListener(3, []() {}); // OK 21 | dispatcher.appendListener(3, [](std::string) {}); // wrong, can't listen for void(std::string) 22 | ``` 23 | 24 | There are heterogeneous counterparts, 'HeterCallbackList', 'HeterEventDispatcher', and 'HeterEventQueue'. These classes allow listeners having different prototypes. For example, 25 | 26 | ```c++ 27 | eventpp::HeterEventDispatcher > dispatcher; 28 | dispatcher.appendListener(3, []() {}); // OK 29 | dispatcher.appendListener(3, [](std::string) {}); // OK 30 | ``` 31 | 32 | 33 | ## Warning 34 | 35 | The heterogeneous classes are mostly for proof of concept purpose. Misusing them most likely means your application design has flaws. 36 | You should stick with the homogeneous classes, even though sometimes the heterogeneous classes look convenient (but with overhead). 37 | The heterogeneous classes may be not well maintained or supported in the future. You use them on your own risk. 38 | 39 | 40 | ## Usage 41 | 42 | 43 | ### Headers 44 | 45 | eventpp/hetercallbacklist.h 46 | eventpp/hetereventdispatcher.h 47 | eventpp/hetereventqueue.h 48 | 49 | 50 | ### Template parameters 51 | 52 | ```c++ 53 | template < 54 | typename PrototypeList, 55 | typename Policies = DefaultPolicies 56 | > 57 | class HeterCallbackList; 58 | 59 | template < 60 | typename Event, 61 | typename PrototypeList, 62 | typename Policies = DefaultPolicies 63 | > 64 | class HeterEventDispatcher; 65 | 66 | template < 67 | typename Event, 68 | typename PrototypeList, 69 | typename Policies = DefaultPolicies 70 | > 71 | class HeterEventQueue; 72 | ``` 73 | 74 | For comparison, below are the template parameters for the homogeneous counterparts 75 | 76 | ```c++ 77 | template < 78 | typename Prototype, 79 | typename Policies = DefaultPolicies 80 | > 81 | class CallbackList; 82 | 83 | template < 84 | typename Event, 85 | typename Prototype, 86 | typename Policies = DefaultPolicies 87 | > 88 | class EventDispatcher; 89 | 90 | template < 91 | typename Event, 92 | typename Prototype, 93 | typename Policies = DefaultPolicies 94 | > 95 | class EventQueue; 96 | ``` 97 | 98 | The only difference is the `Prototype` in homo-classes becomes `PrototypeList` in heter-classes. 99 | In the homo-classes, `Prototype` is a single function type such as `void ()`. 100 | In the heter-classes, `PrototypeList` is a list of function types in `eventpp::HeterTuple`, such as `eventpp::HeterTuple`. 101 | Note: Ideally it would be better to use `std::tuple` instead of `eventpp::HeterTuple`, but the problem is that the tuple is instantiated in HeterEventDispatcher which cause compile error that function type can't be instantiated. 102 | 103 | 104 | ## Differences between heterogeneous classes vs homogeneous classes 105 | 106 | 1. Heterogeneous classes has both overhead on performance and memory usage. Usually event system is the core component in an application, the performance is critical. 107 | 2. Heterogeneous classes can't have the same API interface as homogeneous classes, because some APIs are impossible or very difficult to implement in heterogeneous classes. 108 | 3. Heterogeneous classes doesn't support eventpp::ArgumentPassingAutoDetect. That means the event in the argument can't be detected automatically. 109 | -------------------------------------------------------------------------------- /doc/install.md: -------------------------------------------------------------------------------- 1 | # Install and use eventpp in your project 2 | 3 | - [Install and use eventpp in your project](#install-and-use-eventpp-in-your-project) 4 | - [Include the source code in your project directly](#include-the-source-code-in-your-project-directly) 5 | - [Use CMake FetchContent](#use-cmake-fetchcontent) 6 | - [Use Vcpkg package manager](#use-vcpkg-package-manager) 7 | - [Use Conan package manager](#use-conan-package-manager) 8 | - [Use Hunter package manager](#use-hunter-package-manager) 9 | - [Use Homebrew on macOS (or Linux)](#use-homebrew-on-macos-or-linux) 10 | - [Install using CMake locally and use it in CMake](#install-using-cmake-locally-and-use-it-in-cmake) 11 | 12 | `eventpp` package is available in C++ package managers Vcpkg, Conan, Hunter, and Homebrew. 13 | `eventpp` is header only and not requires building. There are various methods to use `eventpp`. 14 | Here lists all possible methods to use `eventpp`. 15 | 16 | ## Include the source code in your project directly 17 | 18 | eventpp is header only library. Just clone the source code, or use git submodule, then add the 'include' folder inside eventpp to your project include directory, then you can use the library. 19 | You don't need to link to any source code. 20 | 21 | ## Use CMake FetchContent 22 | 23 | Add below code to your CMake file 24 | 25 | ``` 26 | include(FetchContent) 27 | FetchContent_Declare( 28 | eventpp 29 | GIT_REPOSITORY https://github.com/wqking/eventpp 30 | GIT_TAG d2db8af2a46c79f8dc75759019fba487948e9442 # v0.1.3 31 | ) 32 | FetchContent_MakeAvailable(eventpp) 33 | ``` 34 | 35 | Then `eventpp` is available to your project. If `GIT_TAG` is omitted, the latest code on master branch will be used. 36 | 37 | ## Use Vcpkg package manager 38 | 39 | ``` 40 | vcpkg install eventpp 41 | ``` 42 | 43 | Then in your CMake project CMakeLists.txt file, put below code, remember to replace ${TARGET} with your target, 44 | 45 | ``` 46 | find_package(eventpp CONFIG REQUIRED) 47 | target_link_libraries(${TARGET} PRIVATE eventpp::eventpp) 48 | find_path(EVENTPP_INCLUDE_DIR eventpp/eventqueue.h) 49 | include_directories(${EVENTPP_INCLUDE_DIR}) 50 | ``` 51 | 52 | Then run cmake, note you need -DCMAKE_TOOLCHAIN_FILE to specify vcpkg, and replace -G with your generator 53 | ``` 54 | cmake .. -DCMAKE_TOOLCHAIN_FILE=VCPKGDIR/vcpkg/scripts/buildsystems/vcpkg.cmake -G"MinGW Makefiles" 55 | ``` 56 | 57 | Note with vcpkg, only the released version can be used, which may be behind the latest update. You can't use the minor update on the master branch. 58 | 59 | ## Use Conan package manager 60 | 61 | In your CMake project, add a conanlist.txt file that should contain, 62 | 63 | ``` 64 | [requires] 65 | eventpp/0.1.3 66 | 67 | [generators] 68 | CMakeDeps 69 | CMakeToolchain 70 | ``` 71 | 72 | Then in your CMakeLists, add, 73 | 74 | ``` 75 | find_package(eventpp CONFIG REQUIRED) 76 | target_link_libraries(${TARGET} PRIVATE eventpp::eventpp) 77 | ``` 78 | 79 | Then run `conan install . --output-folder=build --build=missing` in your project folder (assuming the default conan profile exists and is okay). 80 | Then cd into folder build, run `cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -G "Whatever generator you want" -DCMAKE_BUILD_TYPE=Release`, alternatively it can also use cmake presets instead of passing the toolchain file on cmake >=3.23 81 | 82 | There are [more information here](https://github.com/wqking/eventpp/issues/70). 83 | 84 | ## Use Hunter package manager 85 | 86 | You may follow the example code on [Hunter document](https://hunter.readthedocs.io/en/latest/packages/pkg/eventpp.html). Below is the code snippet from that document, 87 | 88 | ``` 89 | hunter_add_package(eventpp) 90 | find_package(eventpp CONFIG REQUIRED) 91 | 92 | add_executable(main main.cpp) 93 | target_link_libraries(main eventpp::eventpp) 94 | include_directories(${EVENTPP_INCLUDE_DIR}) 95 | ``` 96 | 97 | ## Use Homebrew on macOS (or Linux) 98 | 99 | ``` 100 | brew install eventpp 101 | ``` 102 | 103 | ## Install using CMake locally and use it in CMake 104 | 105 | Note: this is only an alternative, you should use the FetchContent method instead of this. 106 | If you are going to use eventpp in CMake managed project, you can install eventpp then use it in CMake. 107 | In eventpp root folder, run the commands, 108 | ``` 109 | mkdir build 110 | cd build 111 | cmake .. 112 | sudo make install 113 | ``` 114 | 115 | Then in the project CMakeLists.txt, 116 | ``` 117 | # the project target is mytest, just for example 118 | add_executable(mytest test.cpp) 119 | 120 | find_package(eventpp) 121 | if(eventpp_FOUND) 122 | target_link_libraries(mytest eventpp::eventpp) 123 | else(eventpp_FOUND) 124 | message(FATAL_ERROR "eventpp library is not found") 125 | endif(eventpp_FOUND) 126 | ``` 127 | 128 | Note: when using this method with MingW on Windows, by default CMake will install eventpp in non-writable system folder and get error. You should specify another folder to install. To do so, replace `cmake ..` with `cmake .. -DCMAKE_INSTALL_PREFIX="YOUR_NEW_LIB_FOLDER"`. 129 | -------------------------------------------------------------------------------- /doc/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction to eventpp library 2 | 3 | eventpp includes three core classes, CallbackList, EventDispatcher, and EventQueue. Each class has a different purpose and usage. 4 | 5 | ## Class CallbackList 6 | 7 | CallbackList is the fundamental class in eventpp. The other classes EventDispatcher and EventQueue are built on CallbackList. 8 | 9 | CallbackList holds a list of callbacks. When a CallbackList is being invoked, CallbackList simply invokes each callback one by one. Consider CallbackList as the signal/slot system in Qt, or the callback function pointer in some Windows APIs (such as lpCompletionRoutine in `ReadFileEx`). 10 | The *callback* can be any callback target -- functions, pointers to functions, , pointers to member functions, lambda expressions, and function objects. 11 | 12 | CallbackList equivalents the 'signal' in other event system such as Qt. There is no 'slot' (or callback) equivalence in eventpp. Any callable can be the slot (or to say, callback). 13 | 14 | CallbackList is ideal when there are very few kinds of events. Each event can have its own CallbackList, and each CallbackList can have a different prototype. For example, 15 | ```c++ 16 | eventpp::CallbackList onStart; 17 | eventpp::CallbackList onStop; 18 | ``` 19 | However, if there are many kinds of events, hundreds of to unlimited (this is quite common in GUI or game systems), it would be crazy to use CallbackList for each event. This is how EventDispatcher comes useful. 20 | 21 | ## Class EventDispatcher 22 | 23 | EventDispatcher is something like std::map. 24 | 25 | EventDispatcher holds a map of `` pairs. On dispatching, EventDispatcher finds the CallbackList of the event type, then invoke the callback list. The invocation is always synchronous. The listeners are triggered when `EventDispatcher::dispatch` is called. 26 | 27 | EventDispatcher is ideal when there are many kinds of events, or the number of events cannot be predetermined. Each event is distinguished by the event type. For example, 28 | ```c++ 29 | enum class MyEventType 30 | { 31 | redraw, 32 | mouseDown, 33 | mouseUp, 34 | //... maybe 200 other events here 35 | }; 36 | 37 | struct MyEvent { 38 | MyEventType type; 39 | // data that all events may need 40 | }; 41 | 42 | struct MyEventPolicies 43 | { 44 | static MyEventType getEvent(const MyEvent & e) { 45 | return e.type; 46 | } 47 | }; 48 | 49 | eventpp::EventDispatcher dispatcher; 50 | dispatcher.dispatch(MyEvent { MyEventType::redraw }); 51 | ``` 52 | (Note: if you are confused with MyEventPolicies in above sample, please read the [document of policies](policies.md), and just consider the dispatcher as `eventpp::EventDispatcher dispatcher` for now.) 53 | The disadvantage of EventDispatcher is that all events must have the same callback prototype (`void(const MyEvent &)` in the sample code). The common solution is that the callback takes a base class of Event and all events derive their own event data from Event. In the sample code, MyEvent is the base event class, the callback takes an argument of `const MyEvent &`. 54 | 55 | ## Class EventQueue 56 | 57 | EventQueue includes all features of EventDispatcher and adds event queue features. Note: EventQueue doesn't inherit from EventDispatcher, don't try to cast EventQueue to EventDispatcher. 58 | EventQueue is asynchronous. Events are cached in the queue when `EventQueue::enqueue` is called, and dispatched later when `EventQueue::process` is called. 59 | EventQueue is equivalent to the event system (QEvent) in Qt, or the message processing in Windows API. 60 | 61 | ```c++ 62 | eventpp::EventQueue queue; 63 | 64 | // Enqueue the events, the first argument is always the event type. 65 | // The listeners are not triggered during enqueue. 66 | queue.enqueue(3, "Hello", true); 67 | queue.enqueue(5, "World", false); 68 | 69 | // Process the event queue, dispatch all queued events. 70 | queue.process(); 71 | ``` 72 | 73 | ## Thread safety 74 | All classes are thread-safe. You can call all public functions from multiple threads at the same time. If it fails, please report a bug. 75 | The library guarantees the integration of each single function call, such as `EventDispatcher::appendListener`, `CallbackList::remove`, but it does not guarantee the order of operations in multiple threads. For example, if a thread is dispatching an event, another thread removes a listener at the same time, the removed listener may be still triggered after it's removed. 76 | 77 | ## Exception safety 78 | 79 | All classes don't throw any exceptions. 80 | Exceptions may be thrown by underlying code when, 81 | 1. Out of memory, new memory can't be allocated. 82 | 2. The listeners (callbacks) throw exceptions during copying, moving, comparing, or invoking. 83 | 84 | Almost all operations guarantee strong exception safety, which means the underlying data remains original value on exception is thrown. 85 | An except is `EventQueue::process`, on exception, the remaining events will not be dispatched, and the queue becomes empty. 86 | -------------------------------------------------------------------------------- /doc/orderedqueuelist.md: -------------------------------------------------------------------------------- 1 | # Class OrderedQueueList reference 2 | 3 | 4 | ## Table Of Contents 5 | 6 | * [Description](#a2_1) 7 | * [API reference](#a2_2) 8 | * [Header](#a3_1) 9 | * [Template parameters](#a3_2) 10 | * [Sample code](#a3_3) 11 | 12 | 13 | 14 | ## Description 15 | 16 | `OrderedQueueList` is a utility class that sorts the events in an EventQueue in certain order. 17 | With `OrderedQueueList`, we can dispatch events in certain order such as in priority order. 18 | This class is used with the `QueueList` policy. See [document of policies](policies.md) for details and how to implement new `QueueList`. 19 | 20 | Warning: `OrderedQueueList` is not efficient since it simply inherits from `std::list` and sorts the full list on each `splice`. 21 | To use it in performance critical applications, you should implement your own version with sophisticated algorithm. 22 | 23 | 24 | ## API reference 25 | 26 | 27 | ### Header 28 | 29 | eventpp/utilities/orderedqueuelist.h 30 | 31 | 32 | ### Template parameters 33 | 34 | ```c++ 35 | template 36 | class OrderedQueueList; 37 | ``` 38 | 39 | `Item` is used by the policies. 40 | `Compare` is the comparison function object. 41 | Its default implementation `OrderedQueueListCompare` looks like, 42 | 43 | ```c++ 44 | struct OrderedQueueListCompare 45 | { 46 | template 47 | bool operator() (const T & a, const T & b) const { 48 | return a.event < b.event; 49 | } 50 | }; 51 | ``` 52 | The typename T is actually the `EventQueue::QueuedEvent`. But since `OrderedQueueList` is used in the policies that are used to construct the EventQueue, it's not possible to specify the actual type in `OrderedQueueListCompare`, so here we use a template operator. 53 | 54 | `EventQueue::QueuedEvent` is declared as, 55 | 56 | ```c++ 57 | struct QueuedEvent 58 | { 59 | EventType event; 60 | std::tuple arguments; 61 | }; 62 | ``` 63 | `event` is the event sent to the queue. 64 | `arguments` are the arguments that passed to `EventQueue::enqueue`. 65 | 66 | 67 | ### Sample code 68 | 69 | ```c++ 70 | // First let's define the event struct. e is the event type, priority determines the priority. 71 | struct MyEvent 72 | { 73 | int e; 74 | int priority; 75 | }; 76 | 77 | // The comparison function object used by eventpp::OrderedQueueList. 78 | // The function compares the event by priority. 79 | struct MyCompare 80 | { 81 | template 82 | bool operator() (const T & a, const T & b) const { 83 | return std::get<0>(a.arguments).priority > std::get<0>(b.arguments).priority; 84 | } 85 | }; 86 | 87 | // Define the EventQueue policy 88 | struct MyPolicy 89 | { 90 | template 91 | using QueueList = eventpp::OrderedQueueList; 92 | 93 | static int getEvent(const MyEvent & event) { 94 | return event.e; 95 | } 96 | }; 97 | 98 | void main() 99 | { 100 | using EQ = eventpp::EventQueue; 101 | EQ queue; 102 | 103 | queue.appendListener(3, [](const MyEvent & event) { 104 | std::cout << "Get event " << event.e << "(should be 3)." << " priority: " << event.priority << std::endl; 105 | }); 106 | queue.appendListener(5, [](const MyEvent & event) { 107 | std::cout << "Get event " << event.e << "(should be 5)." << " priority: " << event.priority << std::endl; 108 | }); 109 | queue.appendListener(7, [](const MyEvent & event) { 110 | std::cout << "Get event " << event.e << "(should be 7)." << " priority: " << event.priority << std::endl; 111 | }); 112 | 113 | // Add an event, the first number 5 is the event type, the second number 100 is the priority. 114 | // After the queue processes, the events will be processed from higher priority to lower priority. 115 | queue.enqueue(MyEvent{ 5, 100 }); 116 | queue.enqueue(MyEvent{ 5, 200 }); 117 | queue.enqueue(MyEvent{ 7, 300 }); 118 | queue.enqueue(MyEvent{ 7, 400 }); 119 | queue.enqueue(MyEvent{ 3, 500 }); 120 | queue.enqueue(MyEvent{ 3, 600 }); 121 | 122 | queue.process(); 123 | } 124 | 125 | ``` 126 | -------------------------------------------------------------------------------- /doc/tip_sample_use_cases.md: -------------------------------------------------------------------------------- 1 | # Tip - samples for typical use cases 2 | 3 | - [Tip - samples for typical use cases](#tip---samples-for-typical-use-cases) 4 | - [Overview](#overview) 5 | - [Implement signals and slots](#implement-signals-and-slots) 6 | - [Implement event loop in GUI application, similar to Win32 message processing or Qt event loop](#implement-event-loop-in-gui-application-similar-to-win32-message-processing-or-qt-event-loop) 7 | - [Process multiple EventQueue instances in a single main loop](#process-multiple-eventqueue-instances-in-a-single-main-loop) 8 | 9 | ## Overview 10 | 11 | Here lists several samples of typical use cases when using `eventpp`. The purpose is to inspire you, not to restrict you to do so. 12 | All code are pseudo code, they can't be compiled. 13 | 14 | ## Implement signals and slots 15 | 16 | Signal/slot is widely used in Qt, and there are some C++ libraries implement signal/slot. 17 | `eventpp` does not expose an explicit signal/slot concept, although it has equivalent functionality covered by `CallbackList`. 18 | A `CallbackList` object is a signal. Appending a callback to it is connecting a slot to the signal, and the callback is the slot. 19 | 20 | Below is the simplified Qt example code got from [Qt document](https://doc.qt.io/qt-6/signalsandslots.html), 21 | 22 | ```c++ 23 | class Counter 24 | { 25 | public slots: 26 | void setValue(int value) { 27 | m_value = value; 28 | emit valueChanged(value); 29 | } 30 | 31 | signals: 32 | void valueChanged(int newValue); 33 | 34 | private: 35 | int m_value; 36 | }; 37 | 38 | Counter a, b; 39 | QObject::connect(&a, &Counter::valueChanged, &b, &Counter::setValue); 40 | a.setValue(12); // a.m_value == 12, b.m_value == 12 41 | b.setValue(48); // a.m_value == 12, b.m_value == 48 42 | ``` 43 | 44 | Below is the equivalence in `eventpp`. 45 | 46 | ```c++ 47 | class Counter 48 | { 49 | public: 50 | void setValue(int value) { 51 | m_value = value; 52 | // invoke the CallbackList 53 | valueChanged(value); 54 | } 55 | 56 | // The "signal" 57 | eventpp::CallbackList valueChanged; 58 | 59 | private: 60 | int m_value; 61 | }; 62 | 63 | Counter a, b; 64 | // connect the "signal and slot" 65 | a.valueChanged.append([&b](int value) { 66 | b.setValue(value); 67 | }); 68 | a.setValue(12); // a.m_value == 12, b.m_value == 12 69 | b.setValue(48); // a.m_value == 12, b.m_value == 48 70 | ``` 71 | 72 | ## Implement event loop in GUI application, similar to Win32 message processing or Qt event loop 73 | 74 | Both Win32 message processing (using GetMessage and DispatchMessage) and Qt event loop are not typical observer pattern. They do not use listeners, but instead send events to the target widget. This is done in the event loop. If you are making GUI system, you may want to mimic the event loop found in Windows API and Qt. Below is the pseduo code for implementing such an event loop using `eventpp`. 75 | 76 | ```c++ 77 | 78 | struct Event 79 | { 80 | Widget * widget; // This is similar to the `HWND hWnd` parameter in Win32 PostMessageA function. 81 | int eventType; // Similar to `UINT Msg` in Win32 PostMessageA. 82 | AnyOtherData; // Similar to `WPARAM wParam` and `LPARAM lParam` in Win32 PostMessageA. 83 | } 84 | 85 | using MainEventQueue = eventpp::EventQueue)> 86 | MainEventQueue mainEventQueue; 87 | 88 | Widget widgetA; 89 | 90 | // Put a message into the queue. Equivalent to PostMessageA(&widgetA, MOUSE_DOWN, x, y) 91 | mainEventQueue.enqueue(shared_pointer_of MouseEvent { widget = &widgetA, eventType = MOUSE_DOWN, AnyOtherData = x/y location, etc } ); 92 | 93 | // Dispatch immediately without using the queue. Equivalent to SendMessageA(&widgetA, MOUSE_DOWN, x, y) 94 | mainEventQueue.dispatch(shared_pointer_of MouseEvent { widget = &widgetA, eventType = MOUSE_DOWN, AnyOtherData = x/y location, etc } ); 95 | 96 | // The event loop 97 | for(;;) { 98 | MainEventQueue::QueuedEvent queuedEvent; 99 | if(mainEventQueue.takeEvent(&queuedEvent)) { 100 | Event & event = std::get<0>(queuedEvent.arguments); 101 | if(event.eventType == MOUSE_DOWN) { 102 | event.widget.onMouseDown(event); 103 | } 104 | else if(event.eventType == KEY_DOWN) { 105 | event.widget.onKeyDown(event); 106 | } 107 | else if ... 108 | } 109 | } 110 | ``` 111 | 112 | Below is the Windows equivalent message loop, it's simplified version from [Windows document](https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues), 113 | 114 | ```c++ 115 | while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0) 116 | { 117 | TranslateMessage(&msg); 118 | DispatchMessage(&msg); 119 | } 120 | ``` 121 | 122 | ## Process multiple EventQueue instances in a single main loop 123 | 124 | It's common to have a single main loop in a GUI or game application, and there are various EventQueue instances in the system. The function `EventQueue::process` needs to be called on each queue to drive the queue moving. Below is a tip to process all the EventQueue instances in a single place. 125 | 126 | ```c++ 127 | 128 | // Here mainLoopTasks is global for simplify, in real application it can be in some object and passed around 129 | using MainLoopCallbackList = eventpp::CallbackList; 130 | MainLoopCallbackList mainLoopTasks; 131 | 132 | void mainLoop() 133 | { 134 | for(;;) { 135 | // Do any stuff in the loop 136 | 137 | mainLoopTasks(); 138 | } 139 | } 140 | 141 | class MyEventQueue : public eventpp::EventQueue 142 | { 143 | public: 144 | MyEventQueue() { 145 | handle = mainLoopTasks.append([this]() { 146 | process(); 147 | }); 148 | } 149 | ~MyEventQueue() { 150 | mainLoopTasks.remove(handle); 151 | } 152 | 153 | private: 154 | MainLoopCallbackList::Handle handle; 155 | }; 156 | ``` 157 | 158 | The idea is, the main loop invoke a callback list in each loop, and each event queue registers its `process` to the callback list. 159 | 160 | -------------------------------------------------------------------------------- /doc/tutorial_callbacklist.md: -------------------------------------------------------------------------------- 1 | # Tutorials of CallbackList 2 | 3 | 4 | 5 | ## Tutorials 6 | 7 | Note if you are going to try the tutorial code, you'd better test the code under the tests/unittest. The sample code in the document may be out of date and not compilable. 8 | 9 | ### CallbackList tutorial 1, basic 10 | 11 | **Code** 12 | ```c++ 13 | // The namespace is eventpp 14 | // the first parameter is the prototype of the listener. 15 | eventpp::CallbackList callbackList; 16 | 17 | // Add a callback. 18 | // []() {} is the callback. 19 | // Lambda is not required, any function or std::function 20 | // or whatever function object with the required prototype is fine. 21 | callbackList.append([]() { 22 | std::cout << "Got callback 1." << std::endl; 23 | }); 24 | callbackList.append([]() { 25 | std::cout << "Got callback 2." << std::endl; 26 | }); 27 | 28 | // Invoke the callback list 29 | callbackList(); 30 | ``` 31 | 32 | **Output** 33 | > Got callback 1. 34 | > Got callback 2. 35 | 36 | **Remarks** 37 | First let's define a callback list. 38 | ```c++ 39 | eventpp::CallbackList callbackList; 40 | ``` 41 | class CallbackList takes at least one template arguments. It is the *prototype* of the callback. 42 | The *prototype* is C++ function type, such as `void (int)`, `void (const std::string &, const MyClass &, int, bool)`. 43 | 44 | Now let's add a callback. 45 | ```c++ 46 | callbackList.append([]() { 47 | std::cout << "Got callback 1." << std::endl; 48 | }); 49 | ``` 50 | Function `append` takes one arguments, the *callback*. 51 | The *callback* can be any callback target -- functions, pointers to functions, , pointers to member functions, lambda expressions, and function objects. It must be able to be called with the *prototype* declared in `callbackList`. 52 | In the tutorial, we also add another callback. 53 | 54 | Now let's invoke the callbackList. 55 | ```c++ 56 | callbackList(); 57 | ``` 58 | During the invoking, all callbacks will be invoked one by one in the order of they were added. 59 | 60 | ### CallbackList tutorial 2, callback with parameters 61 | 62 | **Code** 63 | ```c++ 64 | // The callback list prototype has two parameters. 65 | eventpp::CallbackList callbackList; 66 | 67 | callbackList.append([](const std::string & s, const bool b) { 68 | std::cout << std::boolalpha << "Got callback 1, s is " << s << " b is " << b << std::endl; 69 | }); 70 | // The callback prototype doesn't need to be exactly same as the callback list. 71 | // It would be fine as long as the arguments are compatible with the callbacklist. 72 | callbackList.append([](std::string s, int b) { 73 | std::cout << std::boolalpha << "Got callback 2, s is " << s << " b is " << b << std::endl; 74 | }); 75 | 76 | // Invoke the callback list 77 | callbackList("Hello world", true); 78 | ``` 79 | 80 | **Output** 81 | > Got callback 1, s is Hello world b is true 82 | > Got callback 2, s is Hello world b is 1 83 | 84 | **Remarks** 85 | Now the callback list prototype takes two parameters, `const std::string &` and `const bool`. 86 | The callback's prototype is not required to be same as the callback list, it's fine as long as the prototype is compatible with the callback list. See the second callback, `[](std::string s, int b)`, its prototype is not same as the callback list. 87 | 88 | ### CallbackList tutorial 3, remove 89 | 90 | **Code** 91 | ```c++ 92 | using CL = eventpp::CallbackList; 93 | CL callbackList; 94 | 95 | CL::Handle handle2; 96 | 97 | // Add some callbacks. 98 | callbackList.append([]() { 99 | std::cout << "Got callback 1." << std::endl; 100 | }); 101 | handle2 = callbackList.append([]() { 102 | std::cout << "Got callback 2." << std::endl; 103 | }); 104 | callbackList.append([]() { 105 | std::cout << "Got callback 3." << std::endl; 106 | }); 107 | 108 | callbackList.remove(handle2); 109 | 110 | // Invoke the callback list 111 | // The "Got callback 2" callback should not be triggered. 112 | callbackList(); 113 | ``` 114 | 115 | **Output** 116 | > Got callback 1. 117 | > Got callback 3. 118 | 119 | **Remarks** 120 | 121 | ### CallbackList tutorial 4, for each 122 | 123 | **Code** 124 | ```c++ 125 | using CL = eventpp::CallbackList; 126 | CL callbackList; 127 | 128 | // Add some callbacks. 129 | callbackList.append([]() { 130 | std::cout << "Got callback 1." << std::endl; 131 | }); 132 | callbackList.append([]() { 133 | std::cout << "Got callback 2." << std::endl; 134 | }); 135 | callbackList.append([]() { 136 | std::cout << "Got callback 3." << std::endl; 137 | }); 138 | 139 | // Now call forEach to remove the second callback 140 | // The forEach callback prototype is void(const CallbackList::Handle & handle, const CallbackList::Callback & callback) 141 | int index = 0; 142 | callbackList.forEach([&callbackList, &index](const CL::Handle & handle, const CL::Callback & callback) { 143 | std::cout << "forEach(Handle, Callback), invoked " << index << std::endl; 144 | if(index == 1) { 145 | callbackList.remove(handle); 146 | std::cout << "forEach(Handle, Callback), removed second callback" << std::endl; 147 | } 148 | ++index; 149 | }); 150 | 151 | // The forEach callback prototype can also be void(const CallbackList::Callback & callback) 152 | callbackList.forEach([&callbackList, &index](const CL::Callback & callback) { 153 | std::cout << "forEach(Callback), invoked" << std::endl; 154 | }); 155 | 156 | // Invoke the callback list 157 | // The "Got callback 2" callback should not be triggered. 158 | callbackList(); 159 | ``` 160 | 161 | **Output** 162 | 163 | > forEach(Handle, Callback), invoked 0 164 | > forEach(Handle, Callback), invoked 1 165 | > forEach(Handle, Callback), removed second callback 166 | > forEach(Handle, Callback), invoked 2 167 | > forEach(Callback), invoked 168 | > forEach(Callback), invoked 169 | > Got callback 1. 170 | > Got callback 3. 171 | 172 | **Remarks** 173 | -------------------------------------------------------------------------------- /doc/tutorial_eventdispatcher.md: -------------------------------------------------------------------------------- 1 | # Tutorials of EventDispatcher 2 | 3 | 4 | 5 | ## Tutorials 6 | 7 | Note if you are going to try the tutorial code, you'd better test the code under the tests/unittest. The sample code in the document may be out of date and not compilable. 8 | 9 | ### Tutorial 1 -- Basic usage 10 | 11 | **Code** 12 | ```c++ 13 | // The namespace is eventpp 14 | // The first template parameter int is the event type, 15 | // the event type can be any type such as std::string, int, etc. 16 | // The second is the prototype of the listener. 17 | eventpp::EventDispatcher dispatcher; 18 | 19 | // Add a listener. As the type of dispatcher, 20 | // here 3 and 5 is the event type, 21 | // []() {} is the listener. 22 | // Lambda is not required, any function or std::function 23 | // or whatever function object with the required prototype is fine. 24 | dispatcher.appendListener(3, []() { 25 | std::cout << "Got event 3." << std::endl; 26 | }); 27 | dispatcher.appendListener(5, []() { 28 | std::cout << "Got event 5." << std::endl; 29 | }); 30 | dispatcher.appendListener(5, []() { 31 | std::cout << "Got another event 5." << std::endl; 32 | }); 33 | 34 | // Dispatch the events, the first argument is always the event type. 35 | dispatcher.dispatch(3); 36 | dispatcher.dispatch(5); 37 | ``` 38 | 39 | **Output** 40 | > Got event 3. 41 | > Got event 5. 42 | > Got another event 5. 43 | 44 | **Remarks** 45 | First let's define a dispatcher. 46 | ```c++ 47 | eventpp::EventDispatcher dispatcher; 48 | ``` 49 | class EventDispatcher takes two template arguments. The first argument is the *event type*, here is `int`. The second is the *prototype* of the listener. 50 | The *event type* must be able to use as the key of `std::map`, that's to say, it must support `operator <`. 51 | The *prototype* is C++ function type, such as `void (int)`, `void (const std::string &, const MyClass &, int, bool)`. 52 | 53 | Now let's add a listener. 54 | ```c++ 55 | dispatcher.appendListener(3, []() { 56 | std::cout << "Got event 3." << std::endl; 57 | }); 58 | ``` 59 | Function `appendListener` takes at least two arguments. The first argument is the *event* of type *event type*, here is `int`. The second is the *callback*. 60 | The *callback* can be any callback target -- functions, pointers to functions, , pointers to member functions, lambda expressions, and function objects. It must be able to be called with the *prototype* declared in `dispatcher`. 61 | In the tutorial, we also add two listeners for event 5. 62 | 63 | Now let's dispatch some event. 64 | ```c++ 65 | dispatcher.dispatch(3); 66 | dispatcher.dispatch(5); 67 | ``` 68 | Here we dispatched two events, one is event 3, the other is event 5. 69 | During the dispatching, all listeners of that event will be invoked one by one in the order of they were added. 70 | 71 | ### Tutorial 2 -- Listener with parameters 72 | 73 | **Code** 74 | ```c++ 75 | // The listener has two parameters. 76 | eventpp::EventDispatcher dispatcher; 77 | 78 | dispatcher.appendListener(3, [](const std::string & s, const bool b) { 79 | std::cout << std::boolalpha << "Got event 3, s is " << s << " b is " << b << std::endl; 80 | }); 81 | // The listener prototype doesn't need to be exactly same as the dispatcher. 82 | // It would be find as long as the arguments is compatible with the dispatcher. 83 | dispatcher.appendListener(5, [](std::string s, int b) { 84 | std::cout << std::boolalpha << "Got event 5, s is " << s << " b is " << b << std::endl; 85 | }); 86 | dispatcher.appendListener(5, [](const std::string & s, const bool b) { 87 | std::cout << std::boolalpha << "Got another event 5, s is " << s << " b is " << b << std::endl; 88 | }); 89 | 90 | // Dispatch the events, the first argument is always the event type. 91 | dispatcher.dispatch(3, "Hello", true); 92 | dispatcher.dispatch(5, "World", false); 93 | ``` 94 | 95 | **Output** 96 | > Got event 3, s is Hello b is true 97 | > Got event 5, s is World b is 0 98 | > Got another event 5, s is World b is false 99 | 100 | **Remarks** 101 | Now the dispatcher callback prototype takes two parameters, `const std::string &` and `const bool`. 102 | The listener's prototype is not required to be same as the dispatcher, it's fine as long as the prototype is compatible with the dispatcher. See the second listener, `[](std::string s, int b)`, its prototype is not same as the dispatcher. 103 | 104 | ### Tutorial 3 -- Customized event struct 105 | 106 | **Code** 107 | ```c++ 108 | // Define an Event to hold all parameters. 109 | struct MyEvent { 110 | int type; 111 | std::string message; 112 | int param; 113 | }; 114 | 115 | // Define policies to let the dispatcher knows how to 116 | // extract the event type. 117 | struct MyEventPolicies 118 | { 119 | static int getEvent(const MyEvent & e, bool /*b*/) { 120 | return e.type; 121 | } 122 | }; 123 | 124 | // Pass MyEventPolicies as the third template argument of EventDispatcher. 125 | // Note: the first template argument is the event type type int, not MyEvent. 126 | eventpp::EventDispatcher< 127 | int, 128 | void (const MyEvent &, bool), 129 | MyEventPolicies 130 | > dispatcher; 131 | 132 | // Add a listener. 133 | // Note: the first argument is the event type of type int, not MyEvent. 134 | dispatcher.appendListener(3, [](const MyEvent & e, bool b) { 135 | std::cout 136 | << std::boolalpha 137 | << "Got event 3" << std::endl 138 | << "Event::type is " << e.type << std::endl 139 | << "Event::message is " << e.message << std::endl 140 | << "Event::param is " << e.param << std::endl 141 | << "b is " << b << std::endl 142 | ; 143 | }); 144 | 145 | // Dispatch the event. 146 | // The first argument is Event. 147 | dispatcher.dispatch(MyEvent { 3, "Hello world", 38 }, true); 148 | ``` 149 | 150 | **Output** 151 | 152 | > Got event 3 153 | > Event::type is 3 154 | > Event::message is Hello world 155 | > Event::param is 38 156 | > b is true 157 | 158 | **Remarks** 159 | A common situation is an Event class is defined as the base class, all other events derive from Event, and the actual event type is a data member of Event (think QEvent in Qt). To let EventDispatcher knows how to get the event type from class Event, policies (the third template parameter) is used. 160 | -------------------------------------------------------------------------------- /include/eventpp/eventpolicies.h: -------------------------------------------------------------------------------- 1 | // eventpp library 2 | // Copyright (C) 2018 Wang Qi (wqking) 3 | // Github: https://github.com/wqking/eventpp 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | #ifndef EVENTOPTIONS_H_730367862613 15 | #define EVENTOPTIONS_H_730367862613 16 | 17 | #include "internal/typeutil_i.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace eventpp { 26 | 27 | struct TagHomo {}; 28 | struct TagCallbackList : public TagHomo {}; 29 | struct TagEventDispatcher : public TagHomo {}; 30 | struct TagEventQueue : public TagHomo {}; 31 | 32 | struct TagHeter {}; 33 | struct TagHeterCallbackList : public TagHeter {}; 34 | struct TagHeterEventDispatcher : public TagHeter {}; 35 | struct TagHeterEventQueue : public TagHeter {}; 36 | 37 | struct SpinLock 38 | { 39 | public: 40 | void lock() { 41 | while(locked.test_and_set(std::memory_order_acquire)) { 42 | } 43 | } 44 | 45 | void unlock() { 46 | locked.clear(std::memory_order_release); 47 | } 48 | 49 | private: 50 | std::atomic_flag locked = ATOMIC_FLAG_INIT; 51 | }; 52 | 53 | template < 54 | typename Mutex_, 55 | template class Atomic_ = std::atomic, 56 | typename ConditionVariable_ = std::condition_variable 57 | > 58 | struct GeneralThreading 59 | { 60 | using Mutex = Mutex_; 61 | 62 | template 63 | using Atomic = Atomic_; 64 | 65 | using ConditionVariable = ConditionVariable_; 66 | }; 67 | 68 | struct MultipleThreading 69 | { 70 | using Mutex = std::mutex; 71 | 72 | template 73 | using Atomic = std::atomic; 74 | 75 | using ConditionVariable = std::condition_variable; 76 | }; 77 | 78 | struct SingleThreading 79 | { 80 | struct Mutex 81 | { 82 | void lock() {} 83 | void unlock() {} 84 | }; 85 | 86 | template 87 | struct Atomic 88 | { 89 | Atomic() noexcept = default; 90 | constexpr Atomic(T desired) noexcept 91 | : value(desired) 92 | { 93 | } 94 | 95 | void store(T desired, std::memory_order /*order*/ = std::memory_order_seq_cst) noexcept 96 | { 97 | value = desired; 98 | } 99 | 100 | T load(std::memory_order /*order*/ = std::memory_order_seq_cst) const noexcept 101 | { 102 | return value; 103 | } 104 | 105 | T exchange(T desired, std::memory_order /*order*/ = std::memory_order_seq_cst) noexcept 106 | { 107 | const T previous = value; 108 | value = desired; 109 | return previous; 110 | } 111 | 112 | T operator ++ () noexcept 113 | { 114 | return ++value; 115 | } 116 | 117 | T operator -- () noexcept 118 | { 119 | return --value; 120 | } 121 | 122 | T value; 123 | }; 124 | 125 | struct ConditionVariable 126 | { 127 | void notify_one() noexcept 128 | { 129 | } 130 | 131 | template 132 | void wait(std::unique_lock & /*lock*/, Predicate /*pred*/) 133 | { 134 | } 135 | 136 | template 137 | bool wait_for(std::unique_lock & /*lock*/, 138 | const std::chrono::duration & /*rel_time*/, 139 | Predicate /*pred*/ 140 | ) 141 | { 142 | return true; 143 | } 144 | }; 145 | }; 146 | 147 | struct ArgumentPassingAutoDetect 148 | { 149 | enum { 150 | canIncludeEventType = true, 151 | canExcludeEventType = true 152 | }; 153 | }; 154 | 155 | struct ArgumentPassingIncludeEvent 156 | { 157 | enum { 158 | canIncludeEventType = true, 159 | canExcludeEventType = false 160 | }; 161 | }; 162 | 163 | struct ArgumentPassingExcludeEvent 164 | { 165 | enum { 166 | canIncludeEventType = false, 167 | canExcludeEventType = true 168 | }; 169 | }; 170 | 171 | struct DefaultPolicies 172 | { 173 | }; 174 | 175 | template