├── .clang-format ├── .github ├── FUNDING.yml └── workflows │ ├── documentation.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── art ├── eventbus_logo.afdesign └── export │ ├── bus_icon.png │ ├── bus_icon.svg │ ├── bus_icon_thumbnail.png │ ├── bus_icon_web.svg │ ├── full_logo.png │ ├── full_logo.svg │ └── logo_no_text.png ├── benchmark ├── CMakeLists.txt └── src │ ├── event_bus.cpp │ └── main.cpp ├── cmake ├── CPM.cmake ├── CompilerWarnings.cmake └── tools.cmake ├── demo ├── CMakeLists.txt └── main.cpp ├── documentation ├── CMakeLists.txt └── Doxyfile └── eventbus ├── CMakeLists.txt ├── include └── eventbus │ ├── detail │ ├── function_traits.hpp │ ├── storage_policy.hpp │ └── value_traits.hpp │ └── event_bus.hpp └── test ├── event_bus_tests.cpp └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | AccessModifierOffset: '-2' 4 | AlignTrailingComments: 'true' 5 | AllowAllParametersOfDeclarationOnNextLine: 'false' 6 | BreakBeforeBraces: Attach 7 | ColumnLimit: '100' 8 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 9 | IncludeBlocks: Regroup 10 | IndentPPDirectives: AfterHash 11 | IndentWidth: '4' 12 | NamespaceIndentation: All 13 | BreakBeforeBinaryOperators: None 14 | BreakBeforeTernaryOperators: 'true' 15 | AlwaysBreakTemplateDeclarations: 'Yes' 16 | ... 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Thanks for any donations! :) 2 | 3 | github: DeveloperPaul123 4 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | workflow_dispatch: 8 | 9 | env: 10 | CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules 11 | 12 | jobs: 13 | build: 14 | name: Build and publish documentation 15 | runs-on: windows-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: actions/cache@v3 20 | with: 21 | path: "**/cpm_modules" 22 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 23 | 24 | - name: Install dependencies 25 | run: | 26 | choco install doxygen.install -y 27 | choco install Graphviz -y 28 | 29 | - name: Build 30 | run: | 31 | cmake -S documentation -B build 32 | cmake --build build --target GenerateDocs 33 | 34 | - name: Publish 35 | uses: peaceiris/actions-gh-pages@v3 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | publish_dir: ./build/doxygen/html 39 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | branches: [ develop, main] 6 | pull_request: 7 | branches: [ develop, main ] 8 | 9 | env: 10 | CTEST_OUTPUT_ON_FAILURE: 1 11 | 12 | jobs: 13 | build-gcc: 14 | runs-on: ${{matrix.os}} 15 | name: ${{ matrix.os}} gcc${{ matrix.version }} C++${{ matrix.cpp }} 16 | strategy: 17 | fail-fast: false 18 | max-parallel: 4 19 | matrix: 20 | os: [ubuntu-20.04, ubuntu-22.04] 21 | cpp: [17] 22 | version: [9, 10, 11] 23 | include: 24 | - os: ubuntu-22.04 25 | version: 12 26 | - os: ubuntu-20.04 27 | version: 7 28 | - os: ubuntu-20.04 29 | version: 8 30 | env: 31 | CPP_STANDARD: ${{ matrix.cpp }} 32 | steps: 33 | - uses: actions/checkout@v3 34 | 35 | - uses: actions/cache@v3 36 | with: 37 | path: "**/cpm_modules" 38 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 39 | 40 | - name: set up GCC 41 | uses: egor-tensin/setup-gcc@v1 42 | with: 43 | version: ${{matrix.version}} 44 | platform: x64 45 | 46 | - name: Configure 47 | run: cmake -S . -B build 48 | 49 | - name: Build 50 | run: cmake --build build --config Debug -j4 51 | 52 | - name: Run tests 53 | run: | 54 | cd build 55 | ctest --build-config Debug 56 | build-clang: 57 | runs-on: ${{matrix.os}} 58 | name: ${{ matrix.os}} clang${{ matrix.version }} C++${{ matrix.cpp }} 59 | strategy: 60 | fail-fast: false 61 | max-parallel: 4 62 | matrix: 63 | os: [ubuntu-20.04, ubuntu-22.04] 64 | cpp: [17] 65 | version: [14, 15, 16, 17] 66 | include: 67 | - os: ubuntu-22.04 68 | version: 18 69 | env: 70 | CPP_STANDARD: ${{ matrix.cpp }} 71 | steps: 72 | - uses: actions/checkout@v3 73 | 74 | - uses: actions/cache@v3 75 | with: 76 | path: "**/cpm_modules" 77 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 78 | 79 | # clang needs GCC 80 | - name: set up GCC 81 | uses: egor-tensin/setup-gcc@v1 82 | with: 83 | version: 11 84 | platform: x64 85 | 86 | - name: Install libs 87 | run: | 88 | sudo apt-get update 89 | sudo apt-get install build-essential -y 90 | 91 | - name: Set up Clang 92 | run: | 93 | wget -O llvm.sh https://apt.llvm.org/llvm.sh 94 | chmod +x llvm.sh 95 | sudo ./llvm.sh ${{matrix.version}} 96 | 97 | - name: Configure 98 | run: cmake -S . -B build 99 | env: 100 | CC: clang-${{matrix.version}} 101 | CXX: clang++-${{matrix.version}} 102 | 103 | - name: Build 104 | run: cmake --build build --config Debug -j4 105 | 106 | - name: Run tests 107 | run: | 108 | cd build 109 | ctest --build-config Debug 110 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ develop, main] 6 | pull_request: 7 | branches: [ develop, main ] 8 | 9 | env: 10 | CTEST_OUTPUT_ON_FAILURE: 1 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | name: ${{ matrix.version }} C++${{ matrix.cpp }} 16 | strategy: 17 | fail-fast: false 18 | max-parallel: 2 19 | matrix: 20 | os: [windows-latest] 21 | cpp: [17] 22 | version: [msvc, clangcl] 23 | include: 24 | - version: msvc 25 | generator: '"Visual Studio 17 2022"' 26 | - version: clangcl 27 | generator: '"Visual Studio 17 2022" -T "clangcl"' 28 | env: 29 | CPP_STANDARD: ${{ matrix.cpp }} 30 | steps: 31 | - uses: actions/checkout@v1 32 | 33 | - name: Configure 34 | run: cmake -G ${{ matrix.generator }} -S . -B build 35 | 36 | - name: Build 37 | run: cmake --build build --config Debug -j4 38 | 39 | - name: Run tests 40 | run: | 41 | cd build 42 | ctest --build-config Debug 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build* 2 | .vscode/ 3 | /act* 4 | .vs/* 5 | out/ 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | project(eventbus) 4 | 5 | set(CXX_STANDARD 17) 6 | set(CXX_STANDARD_REQUIRED ON) 7 | 8 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 9 | include(CPM) 10 | 11 | option(EVENTBUS_BUILD_TESTS "Build unit tests." ON) 12 | option(EVENTBUS_BUILD_BENCHMARKS "Build benchmarks." OFF) 13 | option(EVENTBUS_BUILD_EXAMPLES "Build examples" OFF) 14 | 15 | # set up warnings interface project to re-use 16 | include(CompilerWarnings) 17 | add_library(project_warnings INTERFACE) 18 | set_project_warnings(project_warnings) 19 | 20 | # set up options interface project to re-use 21 | add_library(project_options INTERFACE) 22 | # this project requires C++17 23 | target_compile_features(project_options INTERFACE cxx_std_17) 24 | 25 | if(EVENTBUS_BUILD_TESTS) 26 | CPMAddPackage( 27 | NAME doctest 28 | GITHUB_REPOSITORY onqtam/doctest 29 | # bump CMake version to 3.5 30 | GIT_TAG 3a01ec37828affe4c9650004edb5b304fb9d5b75 31 | VERSION 2.4.11 32 | ) 33 | 34 | # this needs to be in the top level directory 35 | # before any calls to `add_subdirectory()` for 36 | # ctest to be set up properly 37 | enable_testing() 38 | endif() 39 | 40 | add_subdirectory(eventbus) 41 | 42 | if(EVENTBUS_BUILD_EXAMPLES) 43 | add_subdirectory(demo) 44 | endif() 45 | 46 | if(EVENTBUS_BUILD_BENCHMARKS) 47 | add_subdirectory(benchmark) 48 | endif() 49 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "base", 6 | "hidden": true, 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build/${presetName}", 9 | "installDir": "${sourceDir}/build/install/${presetName}", 10 | "cacheVariables": { 11 | "CMAKE_EXPORT_COMPILE_COMMANDS": "YES", 12 | "EVENTBUS_BUILD_TESTS": "ON", 13 | "EVENTBUS_BUILD_EXAMPLES": "ON", 14 | "EVENTBUS_BUILD_BENCHMARKS": "ON" 15 | } 16 | }, 17 | { 18 | "name": "macos-base", 19 | "hidden": true, 20 | "inherits": "base", 21 | "condition": { 22 | "type": "equals", 23 | "lhs": "${hostSystemName}", 24 | "rhs": "Darwin" 25 | } 26 | }, 27 | { 28 | "name": "clang-debug", 29 | "inherits": "macos-base", 30 | "displayName": "macOS Clang Debug", 31 | "cacheVariables": { 32 | "CMAKE_BUILD_TYPE": "Debug", 33 | "CMAKE_CXX_COMPILER": "clang++", 34 | "CMAKE_C_COMPILER": "clang" 35 | } 36 | }, 37 | { 38 | "name": "clang-release", 39 | "inherits": "macos-base", 40 | "displayName": "macOS Clang Release", 41 | "cacheVariables": { 42 | "CMAKE_BUILD_TYPE": "Release", 43 | "CMAKE_CXX_COMPILER": "clang++", 44 | "CMAKE_C_COMPILER": "clang" 45 | } 46 | }, 47 | { 48 | "name": "linux-base", 49 | "hidden": true, 50 | "inherits": "base", 51 | "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", 52 | "condition": { 53 | "type": "equals", 54 | "lhs": "${hostSystemName}", 55 | "rhs": "Linux" 56 | }, 57 | "vendor": { 58 | "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { 59 | "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" 60 | } 61 | } 62 | }, 63 | { 64 | "name": "windows-base", 65 | "description": "Target Windows with the Visual Studio development environment.", 66 | "hidden": true, 67 | "inherits": "base", 68 | "cacheVariables": { 69 | "CMAKE_CXX_COMPILER": "cl", 70 | "CMAKE_C_COMPILER": "cl" 71 | }, 72 | "condition": { 73 | "type": "equals", 74 | "lhs": "${hostSystemName}", 75 | "rhs": "Windows" 76 | }, 77 | "architecture": { 78 | "value": "x64", 79 | "strategy": "external" 80 | }, 81 | "toolset": { 82 | "value": "host=x64", 83 | "strategy": "external" 84 | } 85 | }, 86 | { 87 | "name": "gcc-base", 88 | "hidden": true, 89 | "inherits": "linux-base", 90 | "cacheVariables": { 91 | "CMAKE_CXX_COMPILER": "g++", 92 | "CMAKE_C_COMPILER": "gcc" 93 | } 94 | }, 95 | { 96 | "name": "gcc-debug", 97 | "inherits": "gcc-base", 98 | "displayName": "GCC Debug", 99 | "cacheVariables": { 100 | "CMAKE_BUILD_TYPE": "Debug" 101 | } 102 | }, 103 | { 104 | "name": "gcc-release", 105 | "inherits": "gcc-base", 106 | "displayName": "GCC Release", 107 | "cacheVariables": { 108 | "CMAKE_BUILD_TYPE": "Release" 109 | } 110 | }, 111 | { 112 | "name": "clang-base", 113 | "hidden": true, 114 | "inherits": "linux-base", 115 | "cacheVariables": { 116 | "CMAKE_CXX_COMPILER": "clang++", 117 | "CMAKE_C_COMPILER": "clang" 118 | } 119 | }, 120 | { 121 | "name": "clang-debug", 122 | "inherits": "clang-base", 123 | "displayName": "Clang Debug", 124 | "cacheVariables": { 125 | "CMAKE_BUILD_TYPE": "Debug" 126 | } 127 | }, 128 | { 129 | "name": "clang-release", 130 | "inherits": "clang-base", 131 | "displayName": "Clang Release", 132 | "cacheVariables": { 133 | "CMAKE_BUILD_TYPE": "Release" 134 | } 135 | }, 136 | { 137 | "name": "clang-release-with-debug-info", 138 | "inherits": "clang-base", 139 | "displayName": "Clang RelWithDebInfo", 140 | "cacheVariables": { 141 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 142 | } 143 | }, 144 | { 145 | "name": "x64-debug", 146 | "displayName": "x64 Debug", 147 | "description": "Target Windows (64-bit) with the Visual Studio development environment. (Debug)", 148 | "inherits": "windows-base", 149 | "architecture": { 150 | "value": "x64", 151 | "strategy": "external" 152 | }, 153 | "cacheVariables": { 154 | "CMAKE_BUILD_TYPE": "Debug" 155 | } 156 | }, 157 | { 158 | "name": "x64-debug-clang", 159 | "displayName": "x64 Debug Clang", 160 | "description": "Target Windows (64-bit) with Clang", 161 | "inherits": "windows-base", 162 | "cacheVariables": { 163 | "CMAKE_C_COMPILER": "clang", 164 | "CMAKE_CXX_COMPILER": "clang++", 165 | "CMAKE_BUILD_TYPE": "Debug" 166 | } 167 | }, 168 | { 169 | "name": "x64-release-clang", 170 | "displayName": "x64 Release Clang", 171 | "description": "Target Windows (64-bit) with Clang (Release)", 172 | "inherits": "windows-base", 173 | "cacheVariables": { 174 | "CMAKE_C_COMPILER": "clang", 175 | "CMAKE_CXX_COMPILER": "clang++", 176 | "CMAKE_BUILD_TYPE": "Release" 177 | } 178 | }, 179 | { 180 | "name": "x64-release", 181 | "displayName": "x64 Release", 182 | "description": "Target Windows (64-bit) with the Visual Studio development environment. (Release)", 183 | "inherits": "x64-debug", 184 | "cacheVariables": { 185 | "CMAKE_BUILD_TYPE": "Release" 186 | } 187 | }, 188 | { 189 | "name": "x64-release-with-debug", 190 | "displayName": "x64 Release w/Debug", 191 | "description": "Target Windows (64-bit) with the Visual Studio development environment. (RelWithDebInfo)", 192 | "inherits": "x64-debug", 193 | "cacheVariables": { 194 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 195 | } 196 | } 197 | ] 198 | } 199 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to eventbus 2 | 3 | :thumbsup: First, thanks for contributing! Your help is much appreciated :thumbsup: 4 | 5 | The following is a set of guidelines for contributing to the `eventbus` C++ library. These are *guidelines*, not rules. Always use sound judgement and when it doubt, ask! 6 | 7 | ## Table of Contents 8 | 9 | - [Contributing to eventbus](#contributing-to-eventbus) 10 | - [Table of Contents](#table-of-contents) 11 | - [Code of Conduct](#code-of-conduct) 12 | - [TL;DR](#tldr) 13 | - [Guidelines](#guidelines) 14 | - [How to Contribute](#how-to-contribute) 15 | - [Report a Bug](#report-a-bug) 16 | - [Suggest New Features](#suggest-new-features) 17 | - [Contribute Code](#contribute-code) 18 | 19 | ## Code of Conduct 20 | 21 | All who work on this project are expected to be respectful of all other people along with their ideas and suggestions. Abusive behavior, harassment of any kind, or any sort of vulgar comments will **not** be tolerated. Please always be kind and reasonable when dealing with others. All who contribute to this project are expected to adhere to this code of conduct. 22 | 23 | ## TL;DR 24 | 25 | If you have a question, I encourage you to join the [Discord](https://discord.gg/yNQ9dW4). Please do **not** file an issue to ask a question. 26 | 27 | ## Guidelines 28 | 29 | ### How to Contribute 30 | 31 | You don't have to write code to contribute to the project. There are many ways to help out! 32 | 33 | #### Report a Bug 34 | 35 | Before reporting a bug, please check that an issue isn't already open for the bug you found. Bugs are tracked as Github issues in this project. You need to fill out as much information as possible when filing a bug so that it can be hunted and squashed properly. Follow the Github template and fill out the information as requested. 36 | 37 | #### Suggest New Features 38 | 39 | Have a good idea on how to make the app better? Please share! Before filing an issue however, it would be best to first discuss the idea on [Discord](https://discord.gg/yNQ9dW4) before filing an issue on Github. 40 | 41 | #### Contribute Code 42 | 43 | Arguably the best way to contribute is to submit a pull request (PR). Before doing so, please keep in mind the following reminders: 44 | 45 | - **Respect the Style**: When making contributions please respect the existing coding style present in the existing code. PRs made to make sweeping changes to code style will not be accepted. 46 | - **Commits**: Please be verbose in your commit messages. Limit the first line to ~72 characters and use good, long descriptions. I prefer too much information as opposed to too little. 47 | - **Document you Code**: Large chunks of un-documented code will not be accepted. Please use comments liberarly where needed in your code and also provide proper documentation. 48 | 49 | The general flow for contributers should be something like: 50 | 51 | - Fork the project 52 | - Clone locally (if needed) 53 | - Create a new branch (`feature/my-new-awesome-feature`) 54 | - Create commits and push 55 | - File PR to `eventbus/develop` (PRs on `master` will *not* be accepted). 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | eventbus 4 | 5 |
6 |
7 | 8 | 9 | License Apache 2.0 10 | 11 | 12 | 13 | Say thanks 14 | 15 | 16 | 17 | Discord 18 | 19 | 20 | 21 | Windows 22 | 23 | 24 | Ubuntu 25 | 26 |

27 | 28 | ## Overview 29 | 30 | `eventbus` is a simple, header only C++17 event bus library that doesn't require you to inherit from any sort of `event` class. The library implements the "Mediator" pattern. This pattern is useful when you want components to communicate to each other without necessarily "knowing" about each other. Effectively, this is a thread safe event dispatcher with a list of callbacks. 31 | 32 | ## Features 33 | 34 | - **Does not require event object inheritance** A base `Event` class is not requied for use with `dp::event_bus`. Any class/struct can be used as an event object. 35 | - **Flexible Callback Types** `eventbus` supports a variety different types of callbacks including: 36 | - Lambdas 37 | - Class member functions 38 | - Free functions 39 | - **Flexible Callbacks** No parameter callbacks are also supported as well as taking the event type by value or by `const &`. 40 | - **RAII de-registrations** The handler registration objects automatically de-register the handler upon destruction. 41 | - **Thread safety** Multiple threads can fire events at once to the same `event_bus`. Handlers can also be registered from different threads. 42 | - **Note:** While the library can handle events fired from different threads note that the thread that fires the event is also the thread that the callback will run on. This library does not ensure that the callback is run on the thread it was registered on. This may or may not be the desired behavior especially in the context of something like thread pools. 43 | - **Multiple Storage Types** Choose to store event types in the bus either using `std::any` or `std::variant`. 44 | 45 | ## Usage 46 | 47 | The basic premise of the `event_bus` is that with it, you can: 48 | 49 | - Register handlers 50 | - Fire events that call the corresponding handlers 51 | 52 | ### Selecting Storage Type 53 | 54 | Selecting a storage type is as simple as instantiating the `event_bus`: 55 | 56 | ```cpp 57 | dp::event_bus any_bus; // uses std::any 58 | dp::event_bus variant_bus; // uses std::variant 59 | ``` 60 | 61 | Declaring the `event_bus` with no template arguments will default to using `std::any` as the storage type. If you want to use `std::variant` you must specify the event types you want to use. 62 | 63 | ### Define An Event Object 64 | 65 | The "event" object can be any class or structure you want. 66 | 67 | ### Registering Handlers 68 | 69 | #### Free function 70 | 71 | ````cpp 72 | void event_callback(event_type evt) 73 | { 74 | // event callback logic... 75 | } 76 | 77 | dp::event_bus evt_bus; 78 | const auto registration_handler = evt_bus.register_handler(&event_callback) 79 | ```` 80 | 81 | #### Lambda 82 | 83 | ````cpp 84 | dp::event_bus evt_bus; 85 | const auto registration_handler = evt_bus.register_handler([](const event_type& evt) 86 | { 87 | // logic code... 88 | }); 89 | ```` 90 | 91 | #### Class Member Function 92 | 93 | ````cpp 94 | class event_handler 95 | { 96 | public: 97 | void on_event(event_type type) 98 | { 99 | // handler logic... 100 | } 101 | }; 102 | 103 | // other code 104 | dp::event_bus evt_bus; 105 | event_handler handler; 106 | const auto registration_handler = evt_bus.register_handler(&handler, &event_handler::on_event); 107 | ```` 108 | 109 | **Note:** You can't mix a class instance of type `T` with the member function of another class (i.e. `&U::function_name`). 110 | 111 | #### Firing Events 112 | 113 | ````cpp 114 | event_type evt 115 | { 116 | // data and info.. 117 | }; 118 | dp::event_bus evt_bus; 119 | evt_bus.fire_event(evt); // all connect handler for the given event type will be fired. 120 | ```` 121 | 122 | A complete example can be seen in the [demo](https://github.com/DeveloperPaul123/eventbus/tree/develop/demo) project. 123 | 124 | ## Integration 125 | 126 | `eventbus` is a header only library. All the files you need are in the `eventbus/include` folder. To use the library just include `eventbus/event_bus.hpp`. 127 | 128 | ### CMake 129 | 130 | `eventbus` defines three CMake `INTERFACE` targets that can be used in your project: 131 | 132 | - `eventbus` 133 | - `eventbus::eventbus` 134 | - `dp::eventbus` 135 | 136 | ````cmake 137 | find_package(dp::eventbus REQUIRED) 138 | ```` 139 | 140 | Alternatively, you can use something like [CPM](https://github.com/TheLartians/CPM) which is based on CMake's `Fetch_Content` module. 141 | 142 | ````cmake 143 | CPMAddPackage( 144 | NAME eventbus 145 | GITHUB_REPOSITORY DeveloperPaul123/eventbus 146 | GIT_TAG #053902d63de5529ee65d965f8b1fb0851eceed24 change this to latest commit/release tag 147 | ) 148 | ```` 149 | 150 | ### vcpkg 151 | 152 | :construction: This library will be on `vcpkg` soon. :construction: 153 | 154 | ## Limitations 155 | 156 | In general, all callback functions **must** return `void`. Currently, `eventbus` only supports single argument functions as callbacks. 157 | 158 | The following use cases are not supported: 159 | 160 | - Registering a callback inside an event callback. 161 | - De-registering a callback inside an event callback. 162 | 163 | ## Contributing 164 | 165 | If you find an issue with this library please file an [issue](https://github.com/DeveloperPaul123/eventbus/issues). Pull requests are also welcome! Please see the [contribution guidelines](CONTRIBUTING.md) for more information. 166 | 167 | ## License 168 | 169 | The project is licensed under the Apache License Version 2.0. See [LICENSE](LICENSE) for more details. 170 | 171 | ## Author 172 | 173 | | [
@DeveloperPaul123](https://github.com/DeveloperPaul123) | 174 | |:----:| 175 | 176 | ## Contributors 177 | 178 | See [here](https://github.com/DeveloperPaul123/eventbus/graphs/contributors) for a list of contributors. 179 | -------------------------------------------------------------------------------- /art/eventbus_logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperPaul123/eventbus/be632ba8400e90316a098a18b60a8958353c2bb0/art/eventbus_logo.afdesign -------------------------------------------------------------------------------- /art/export/bus_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperPaul123/eventbus/be632ba8400e90316a098a18b60a8958353c2bb0/art/export/bus_icon.png -------------------------------------------------------------------------------- /art/export/bus_icon_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperPaul123/eventbus/be632ba8400e90316a098a18b60a8958353c2bb0/art/export/bus_icon_thumbnail.png -------------------------------------------------------------------------------- /art/export/bus_icon_web.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /art/export/full_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperPaul123/eventbus/be632ba8400e90316a098a18b60a8958353c2bb0/art/export/full_logo.png -------------------------------------------------------------------------------- /art/export/full_logo.svg: -------------------------------------------------------------------------------- 1 | eventbus -------------------------------------------------------------------------------- /art/export/logo_no_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperPaul123/eventbus/be632ba8400e90316a098a18b60a8958353c2bb0/art/export/logo_no_text.png -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | project(eventbus-benchmarks) 3 | 4 | CPMAddPackage( 5 | NAME nanobench 6 | GITHUB_REPOSITORY martinus/nanobench 7 | VERSION 4.3.11 8 | GIT_SHALLOW 9 | ) 10 | 11 | file(GLOB benchmark_sources CONFIGURE_DEPENDS src/*.cpp) 12 | add_executable(${PROJECT_NAME} ${benchmark_sources}) 13 | target_link_libraries( 14 | ${PROJECT_NAME} 15 | PUBLIC 16 | dp::eventbus 17 | doctest::doctest 18 | nanobench 19 | project_options 20 | project_warnings 21 | ) 22 | -------------------------------------------------------------------------------- /benchmark/src/event_bus.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE("event dispatch - std::any vs std::variant") { 10 | // This test compares the performance of event dispatching using std::any and std::variant 11 | std::vector args = {1'000, 10'000, 100'000}; 12 | 13 | using namespace std::chrono_literals; 14 | for (const auto& dispatch_count : args) { 15 | ankerl::nanobench::Bench bench; 16 | auto bench_title = 17 | std::string("event dispatch - " + std::to_string(dispatch_count) + " times"); 18 | bench.title(bench_title).relative(true).warmup(100).minEpochIterations(1000); 19 | bench.timeUnit(1us, "us"); 20 | 21 | struct event { 22 | int data_int; 23 | float data_float; 24 | double data_double; 25 | std::uint64_t data_int_large; 26 | }; 27 | 28 | struct event2 { 29 | int data_int; 30 | }; 31 | 32 | struct allocating_event { 33 | std::vector data; 34 | allocating_event() : data(8, std::byte{0}) {} // 8 bytes 35 | }; 36 | 37 | bench.run("std::any", [dispatch_count]() { 38 | dp::event_bus bus{}; 39 | 40 | auto registration1 = bus.register_handler([] {}); 41 | auto registration2 = bus.register_handler([] {}); 42 | ankerl::nanobench::doNotOptimizeAway(registration1); 43 | ankerl::nanobench::doNotOptimizeAway(registration2); 44 | 45 | for (std::size_t i = 0; i < dispatch_count; ++i) { 46 | bus.fire_event(event{}); 47 | bus.fire_event(event2{}); 48 | } 49 | }); 50 | 51 | bench.run("std::variant", [dispatch_count]() { 52 | dp::event_bus bus{}; 53 | 54 | auto registration1 = bus.register_handler([] {}); 55 | auto registration2 = bus.register_handler([] {}); 56 | ankerl::nanobench::doNotOptimizeAway(registration1); 57 | ankerl::nanobench::doNotOptimizeAway(registration2); 58 | 59 | for (std::size_t i = 0; i < dispatch_count; ++i) { 60 | bus.fire_event(event{}); 61 | bus.fire_event(event2{}); 62 | } 63 | }); 64 | } 65 | } 66 | 67 | TEST_CASE("event dispatch - std::any vs std::variant with allocating event") { 68 | // This test compares the performance of event dispatching using std::any and std::variant 69 | std::vector args = {1'000, 10'000}; 70 | 71 | using namespace std::chrono_literals; 72 | for (const auto& dispatch_count : args) { 73 | ankerl::nanobench::Bench bench; 74 | auto bench_title = 75 | std::string("event dispatch - " + std::to_string(dispatch_count) + " times"); 76 | bench.title(bench_title).relative(true).warmup(100).minEpochIterations(1000); 77 | bench.timeUnit(1us, "us"); 78 | 79 | struct allocating_event { 80 | std::vector data; 81 | allocating_event() : data(8, std::byte{0}) {} // 8 bytes 82 | }; 83 | 84 | bench.run("std::any", [dispatch_count]() { 85 | dp::event_bus bus{}; 86 | 87 | auto registration1 = bus.register_handler([] {}); 88 | ankerl::nanobench::doNotOptimizeAway(registration1); 89 | 90 | for (std::size_t i = 0; i < dispatch_count; ++i) { 91 | bus.fire_event(allocating_event{}); 92 | } 93 | }); 94 | 95 | bench.run("std::variant", [dispatch_count]() { 96 | dp::event_bus bus{}; 97 | 98 | auto registration1 = bus.register_handler([] {}); 99 | ankerl::nanobench::doNotOptimizeAway(registration1); 100 | 101 | for (std::size_t i = 0; i < dispatch_count; ++i) { 102 | bus.fire_event(allocating_event{}); 103 | } 104 | }); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /benchmark/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | set(CPM_DOWNLOAD_VERSION 0.40.2) 2 | 3 | if(CPM_SOURCE_CACHE) 4 | # Expand relative path. This is important if the provided path contains a tilde (~) 5 | get_filename_component(CPM_SOURCE_CACHE ${CPM_SOURCE_CACHE} ABSOLUTE) 6 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 7 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 8 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | else() 10 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 11 | endif() 12 | 13 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 14 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 15 | file(DOWNLOAD 16 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 17 | ${CPM_DOWNLOAD_LOCATION} 18 | ) 19 | endif() 20 | 21 | include(${CPM_DOWNLOAD_LOCATION}) -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # Originally from here: https://github.com/lefticus/cpp_starter_project 2 | 3 | function(set_project_warnings project_name) 4 | option(EVENTBUS_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" ON) 5 | 6 | set(MSVC_WARNINGS 7 | /W4 # Baseline reasonable warnings 8 | /w14242 # 'identfier': conversion from 'type1' to 'type1', possible loss 9 | # of data 10 | /w14254 # 'operator': conversion from 'type1:field_bits' to 11 | # 'type2:field_bits', possible loss of data 12 | /w14263 # 'function': member function does not override any base class 13 | # virtual member function 14 | /w14265 # 'classname': class has virtual functions, but destructor is not 15 | # virtual instances of this class may not be destructed correctly 16 | /w14287 # 'operator': unsigned/negative constant mismatch 17 | /we4289 # nonstandard extension used: 'variable': loop control variable 18 | # declared in the for-loop is used outside the for-loop scope 19 | /w14296 # 'operator': expression is always 'boolean_value' 20 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 21 | /w14545 # expression before comma evaluates to a function which is missing 22 | # an argument list 23 | /w14546 # function call before comma missing argument list 24 | /w14547 # 'operator': operator before comma has no effect; expected 25 | # operator with side-effect 26 | /w14549 # 'operator': operator before comma has no effect; did you intend 27 | # 'operator'? 28 | /w14555 # expression has no effect; expected expression with side- effect 29 | /w14619 # pragma warning: there is no warning number 'number' 30 | /w14640 # Enable warning on thread un-safe static member initialization 31 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may 32 | # cause unexpected runtime behavior. 33 | /w14905 # wide string literal cast to 'LPSTR' 34 | /w14906 # string literal cast to 'LPWSTR' 35 | /w14928 # illegal copy-initialization; more than one user-defined 36 | # conversion has been implicitly applied 37 | /permissive- # require ISO conformant code 38 | /wd4251 # 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' 39 | /experimental:external # experimental features 40 | /external:anglebrackets # includes that use <> are treated as "external" headers 41 | /external:W0 # set "external" headers warning level to nothing 42 | ) 43 | 44 | set(CLANG_WARNINGS 45 | -Wall 46 | -Wextra # reasonable and standard 47 | -Wshadow # warn the user if a variable declaration shadows one from a 48 | # parent context 49 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a 50 | # non-virtual destructor. This helps catch hard to 51 | # track down memory errors 52 | -Wold-style-cast # warn for c-style casts 53 | -Wcast-align # warn for potential performance problem casts 54 | -Wunused # warn on anything being unused 55 | -Woverloaded-virtual # warn if you overload (not override) a virtual 56 | # function 57 | -Wpedantic # warn if non-standard C++ is used 58 | -Wconversion # warn on type conversions that may lose data 59 | -Wsign-conversion # warn on sign conversions 60 | -Wnull-dereference # warn if a null dereference is detected 61 | -Wdouble-promotion # warn if float is implicit promoted to double 62 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 63 | ) 64 | 65 | if (EVENTBUS_WARNINGS_AS_ERRORS) 66 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 67 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 68 | endif() 69 | 70 | set(GCC_WARNINGS 71 | ${CLANG_WARNINGS} 72 | -Wmisleading-indentation # warn if identation implies blocks where blocks 73 | # do not exist 74 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 75 | -Wduplicated-branches # warn if if / else branches have duplicated code 76 | -Wlogical-op # warn about logical operations being used where bitwise were 77 | # probably wanted 78 | -Wuseless-cast # warn if you perform a cast to the same type 79 | ) 80 | 81 | message("Using compiler: ${CMAKE_CXX_COMPILER_ID}") 82 | # clang can be used with visual studio directly and uses the cl like interface. 83 | if(MSVC AND NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 84 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 85 | elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 86 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 87 | elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang") 88 | list(REMOVE_ITEM ${CLANG_WARNINGS} -Wduplicated-cond) 89 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) # AppleClang doesn't support -Wpedantic 90 | else() 91 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 92 | endif() 93 | 94 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 95 | 96 | endfunction() 97 | -------------------------------------------------------------------------------- /cmake/tools.cmake: -------------------------------------------------------------------------------- 1 | # this file contains a list of tools that can be activated and downloaded on-demand each tool is 2 | # enabled during configuration by passing an additional `-DUSE_=` argument to CMake 3 | 4 | # only activate tools for top level project 5 | if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 6 | return() 7 | endif() 8 | 9 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 10 | 11 | # enables sanitizers support using the the `USE_SANITIZER` flag available values are: Address, 12 | # Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined' 13 | if(USE_SANITIZER OR USE_STATIC_ANALYZER) 14 | CPMAddPackage("gh:StableCoder/cmake-scripts#1f822d1fc87c8d7720c074cde8a278b44963c354") 15 | 16 | if(USE_SANITIZER) 17 | include(${cmake-scripts_SOURCE_DIR}/sanitizers.cmake) 18 | endif() 19 | 20 | if(USE_STATIC_ANALYZER) 21 | if("clang-tidy" IN_LIST USE_STATIC_ANALYZER) 22 | set(CLANG_TIDY 23 | ON 24 | CACHE INTERNAL "" 25 | ) 26 | else() 27 | set(CLANG_TIDY 28 | OFF 29 | CACHE INTERNAL "" 30 | ) 31 | endif() 32 | if("iwyu" IN_LIST USE_STATIC_ANALYZER) 33 | set(IWYU 34 | ON 35 | CACHE INTERNAL "" 36 | ) 37 | else() 38 | set(IWYU 39 | OFF 40 | CACHE INTERNAL "" 41 | ) 42 | endif() 43 | if("cppcheck" IN_LIST USE_STATIC_ANALYZER) 44 | set(CPPCHECK 45 | ON 46 | CACHE INTERNAL "" 47 | ) 48 | else() 49 | set(CPPCHECK 50 | OFF 51 | CACHE INTERNAL "" 52 | ) 53 | endif() 54 | 55 | include(${cmake-scripts_SOURCE_DIR}/tools.cmake) 56 | 57 | clang_tidy(${CLANG_TIDY_ARGS}) 58 | include_what_you_use(${IWYU_ARGS}) 59 | cppcheck(${CPPCHECK_ARGS}) 60 | endif() 61 | endif() 62 | -------------------------------------------------------------------------------- /demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(demo) 4 | 5 | add_executable(${PROJECT_NAME} main.cpp) 6 | 7 | target_link_libraries(${PROJECT_NAME} 8 | PUBLIC 9 | eventbus::eventbus 10 | ) 11 | -------------------------------------------------------------------------------- /demo/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const char new_line = '\n'; 6 | 7 | struct first_event { 8 | std::string message; 9 | }; 10 | 11 | struct second_event { 12 | int id; 13 | std::string message; 14 | }; 15 | 16 | struct third_event { 17 | double value{234.00}; 18 | }; 19 | 20 | void free_function_event_handler(const first_event& evt) { 21 | std::cout << "event: " << evt.message << std::endl; 22 | } 23 | 24 | class first_event_callback_object { 25 | public: 26 | first_event_callback_object() = default; 27 | void on_event_fired(const first_event&) { event_count_++; } 28 | void on_third_event() {}; 29 | [[nodiscard]] int get_event_count() const { return event_count_; } 30 | 31 | private: 32 | int event_count_{0}; 33 | }; 34 | 35 | class third_event_callback_object { 36 | public: 37 | void on_third_event() {}; 38 | }; 39 | 40 | class internal_registration_class { 41 | dp::handler_registration reg; 42 | 43 | public: 44 | /// CTAD not allowed in non-static struct member so we have to include the empty brackets 45 | internal_registration_class(dp::event_bus<>& bus) 46 | : reg(std::move(bus.register_handler([](const first_event& evt) { 47 | std::cout << "test class: " << evt.message << "\n"; 48 | }))) {} 49 | }; 50 | 51 | int main() { 52 | using namespace dp; 53 | event_bus evt_bus; 54 | 55 | // register free function 56 | const auto reg = evt_bus.register_handler(&free_function_event_handler); 57 | const auto third_event_reg = evt_bus.register_handler([](const third_event& evt) { 58 | std::cout << "my third event handler: " << evt.value << new_line; 59 | }); 60 | 61 | const auto third_event_reg_2 = evt_bus.register_handler( 62 | []() { std::cout << "I just do stuff when a third_event is fired." << new_line; }); 63 | 64 | internal_registration_class test_object(evt_bus); 65 | 66 | first_event evt{"hello from first event"}; 67 | evt_bus.fire_event(evt); 68 | evt_bus.fire_event(third_event{13.0}); 69 | evt_bus.remove_handler(third_event_reg); 70 | evt_bus.fire_event(third_event{13.0}); 71 | 72 | const auto other_event_reg = evt_bus.register_handler([](const second_event& other_evt) { 73 | std::cout << "first other event handler says: " << other_evt.message << std::endl; 74 | }); 75 | const auto other_event_second_reg = evt_bus.register_handler([](const second_event& other_evt) { 76 | std::cout << "second other event handler says: " << other_evt.id << " " << other_evt.message 77 | << std::endl; 78 | }); 79 | const auto dmy_evt_first_reg = evt_bus.register_handler([](const first_event& dmy_evt) { 80 | std::cout << "third event handler says: " << dmy_evt.message << std::endl; 81 | }); 82 | 83 | first_event_callback_object callback_obj; 84 | const auto dmy_evt_pmr_reg = 85 | evt_bus.register_handler(&callback_obj, &first_event_callback_object::on_event_fired); 86 | const auto thrid_event_reg_pmr = evt_bus.register_handler( 87 | &callback_obj, &first_event_callback_object::on_third_event); 88 | 89 | // the following does not compile 90 | // third_event_callback_object teo; 91 | // const auto rg = evt_bus.register_handler(&teo, 92 | // &first_event_callback_object::on_third_event); 93 | 94 | second_event snd_evt{2, "hello there from second event"}; 95 | first_event first_evt{"another first event"}; 96 | 97 | evt_bus.fire_event(first_evt); 98 | 99 | std::cout << "Firing second event\n"; 100 | evt_bus.fire_event(snd_evt); 101 | evt_bus.remove_handler(other_event_reg); 102 | std::cout << "Firing second and third event\n"; 103 | evt_bus.fire_event(snd_evt); 104 | evt_bus.fire_event(third_event{}); 105 | 106 | std::cout << "callback count: " << callback_obj.get_event_count() << std::endl; 107 | std::cout << "handler count: " << evt_bus.handler_count() << "\n"; 108 | std::cout << "removing handlers..." 109 | << "\n"; 110 | 111 | // this is optional as the handler registrations are RAII objects 112 | evt_bus.remove_handler(reg); 113 | evt_bus.remove_handler(other_event_second_reg); 114 | evt_bus.remove_handler(third_event_reg_2); 115 | evt_bus.remove_handler(dmy_evt_first_reg); 116 | evt_bus.remove_handler(dmy_evt_pmr_reg); 117 | evt_bus.remove_handler(thrid_event_reg_pmr); 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /documentation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | project(eventbusdocs) 4 | 5 | # ---- Dependencies ---- 6 | 7 | include(../cmake/CPM.cmake) 8 | 9 | CPMAddPackage("gh:jothepro/doxygen-awesome-css#v1.6.1") 10 | CPMAddPackage(NAME eventbus SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../eventbus) 11 | find_package(Doxygen REQUIRED) 12 | 13 | # ---- Doxygen variables ---- 14 | 15 | # set Doxyfile variables 16 | set(DOXYGEN_PROJECT_NAME eventbus) 17 | set(DOXYGEN_PROJECT_VERSION ${eventbus_VERSION}) 18 | set(DOXYGEN_PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../") 19 | set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doxygen") 20 | 21 | configure_file(${CMAKE_CURRENT_LIST_DIR}/Doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 22 | 23 | add_custom_target( 24 | GenerateDocs 25 | ${CMAKE_COMMAND} -E make_directory "${DOXYGEN_OUTPUT_DIRECTORY}" 26 | COMMAND Doxygen::doxygen ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 27 | COMMAND echo "Docs written to: ${DOXYGEN_OUTPUT_DIRECTORY}" 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 29 | ) 30 | -------------------------------------------------------------------------------- /documentation/Doxyfile: -------------------------------------------------------------------------------- 1 | # Configuration for Doxygen for use with CMake 2 | # Only options that deviate from the default are included 3 | # To create a new Doxyfile containing all available options, call `doxygen -g` 4 | 5 | # Get Project name and version from CMake 6 | PROJECT_NAME = @DOXYGEN_PROJECT_NAME@ 7 | PROJECT_NUMBER = @DOXYGEN_PROJECT_VERSION@ 8 | 9 | # Add sources 10 | INPUT = @DOXYGEN_PROJECT_ROOT@/README.md @DOXYGEN_PROJECT_ROOT@/eventbus/include @DOXYGEN_PROJECT_ROOT@/documentation/pages 11 | EXCLUDE_PATTERNS = */test/* */detail/* 12 | EXTRACT_ALL = YES 13 | EXTRACT_PRIVATE = NO 14 | RECURSIVE = YES 15 | OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@ 16 | 17 | # Use the README as a main page 18 | USE_MDFILE_AS_MAINPAGE = @DOXYGEN_PROJECT_ROOT@/README.md 19 | 20 | # set relative include paths 21 | FULL_PATH_NAMES = YES 22 | STRIP_FROM_PATH = @DOXYGEN_PROJECT_ROOT@/eventbus/include @DOXYGEN_PROJECT_ROOT@ 23 | 24 | # We use m.css to generate the html documentation, so we only need XML output 25 | GENERATE_XML = NO 26 | GENERATE_HTML = YES 27 | GENERATE_LATEX = YES 28 | XML_PROGRAMLISTING = NO 29 | CREATE_SUBDIRS = YES 30 | 31 | HTML_EXTRA_STYLESHEET = @doxygen-awesome-css_SOURCE_DIR@/doxygen-awesome.css 32 | HTML_COLORSTYLE_HUE = 209 33 | HTML_COLORSTYLE_SAT = 255 34 | HTML_COLORSTYLE_GAMMA = 113 35 | GENERATE_TREEVIEW = YES 36 | HAVE_DOT = YES 37 | DOT_IMAGE_FORMAT = svg 38 | EXAMPLE_PATH = @DOXYGEN_PROJECT_ROOT@/demo/ -------------------------------------------------------------------------------- /eventbus/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(eventbus) 4 | 5 | set(project_headers 6 | include/eventbus/detail/function_traits.hpp 7 | include/eventbus/detail/storage_policy.hpp 8 | include/eventbus/detail/value_traits.hpp 9 | include/eventbus/event_bus.hpp 10 | ) 11 | 12 | # System threading library 13 | # Required for multithreading testing 14 | find_package(Threads REQUIRED) 15 | 16 | # create library and aliases 17 | add_library(${PROJECT_NAME} INTERFACE) 18 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 19 | add_library(dp::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 20 | 21 | target_include_directories(${PROJECT_NAME} INTERFACE include) 22 | 23 | target_link_libraries(${PROJECT_NAME} INTERFACE project_options Threads::Threads) 24 | 25 | if(EVENTBUS_BUILD_TESTS) 26 | file(GLOB_RECURSE project_test_sources CONFIGURE_DEPENDS test/*.cpp) 27 | set(project_test_name ${PROJECT_NAME}-tests) 28 | add_executable(${project_test_name} ${project_test_sources}) 29 | target_link_libraries(${project_test_name} 30 | PUBLIC 31 | doctest::doctest 32 | ${PROJECT_NAME} 33 | ) 34 | 35 | # Note: doctest and similar testing frameworks can automatically configure CMake tests. For other 36 | include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake) 37 | doctest_discover_tests(${project_test_name}) 38 | endif() 39 | -------------------------------------------------------------------------------- /eventbus/include/eventbus/detail/function_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * `member_function_traits originally from here (PMF_traits struct): 7 | * https://github.com/KonanM/CppProperties/blob/master/include/cppproperties/Signal.h 8 | * `function_traits` taken from answer here: 9 | * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda 10 | * Original code is here: https://github.com/kennytm/utils/blob/master/traits.hpp 11 | */ 12 | namespace dp { 13 | namespace detail { 14 | template 15 | struct member_function_traits { 16 | using member_type = void; 17 | using class_type = void; 18 | }; 19 | 20 | template 21 | struct member_function_traits { 22 | using member_type = U; 23 | using class_type = T; 24 | }; 25 | 26 | template 27 | struct memfn_type { 28 | typedef typename std::conditional< 29 | std::is_const::value, 30 | typename std::conditional::value, R (C::*)(A...) const volatile, 31 | R (C::*)(A...) const>::type, 32 | typename std::conditional::value, R (C::*)(A...) volatile, 33 | R (C::*)(A...)>::type>::type type; 34 | }; 35 | 36 | template 37 | struct function_traits : public function_traits {}; 38 | 39 | template 40 | struct function_traits { 41 | /** 42 | .. type:: type result_type 43 | The type returned by calling an instance of the function object type *F*. 44 | */ 45 | typedef ReturnType result_type; 46 | 47 | /** 48 | .. type:: type function_type 49 | The function type (``R(T...)``). 50 | */ 51 | typedef ReturnType function_type(Args...); 52 | 53 | /** 54 | .. type:: type member_function_type 55 | The member function type for an *OwnerType* (``R(OwnerType::*)(T...)``). 56 | */ 57 | template 58 | using member_function_type = typename memfn_type< 59 | typename std::remove_pointer::type>::type, 60 | ReturnType, Args...>::type; 61 | 62 | /** 63 | .. data:: static const size_t arity 64 | Number of arguments the function object will take. 65 | */ 66 | enum { arity = sizeof...(Args) }; 67 | 68 | /** 69 | .. type:: type arg::type 70 | The type of the *n*-th argument. 71 | */ 72 | template 73 | struct arg { 74 | typedef typename std::tuple_element>::type type; 75 | }; 76 | }; 77 | 78 | template 79 | struct function_traits 80 | : public function_traits {}; 81 | 82 | template 83 | struct function_traits 84 | : public function_traits { 85 | typedef ClassType& owner_type; 86 | }; 87 | 88 | template 89 | struct function_traits 90 | : public function_traits { 91 | typedef const ClassType& owner_type; 92 | }; 93 | 94 | template 95 | struct function_traits 96 | : public function_traits { 97 | typedef volatile ClassType& owner_type; 98 | }; 99 | 100 | template 101 | struct function_traits 102 | : public function_traits { 103 | typedef const volatile ClassType& owner_type; 104 | }; 105 | 106 | template 107 | struct function_traits> : public function_traits { 108 | }; 109 | 110 | template 111 | struct function_traits : public function_traits {}; 112 | template 113 | struct function_traits : public function_traits {}; 114 | template 115 | struct function_traits : public function_traits {}; 116 | template 117 | struct function_traits : public function_traits {}; 118 | template 119 | struct function_traits : public function_traits {}; 120 | template 121 | struct function_traits : public function_traits {}; 122 | template 123 | struct function_traits : public function_traits {}; 124 | template 125 | struct function_traits : public function_traits {}; 126 | } // namespace detail 127 | } // namespace dp 128 | -------------------------------------------------------------------------------- /eventbus/include/eventbus/detail/storage_policy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace dp::detail { 8 | struct any_event_bus_storage_policy { 9 | using event_type = std::any; 10 | using event_handler = std::function; 11 | }; 12 | 13 | template 14 | struct variant_event_bus_storage_policy { 15 | using event_type = std::variant...>; 16 | using event_handler = std::function; 17 | }; 18 | } // namespace dp::detail 19 | -------------------------------------------------------------------------------- /eventbus/include/eventbus/detail/value_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace dp::detail { 7 | template 8 | struct is_any : std::false_type {}; 9 | template <> 10 | struct is_any : std::true_type {}; 11 | 12 | } // namespace dp::detail 13 | -------------------------------------------------------------------------------- /eventbus/include/eventbus/event_bus.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "eventbus/detail/function_traits.hpp" 16 | #include "eventbus/detail/storage_policy.hpp" 17 | #include "eventbus/detail/value_traits.hpp" 18 | 19 | namespace { 20 | template 21 | auto access_event_value(Event&& value) -> std::reference_wrapper { 22 | if constexpr (dp::detail::is_any::value) { 23 | return std::any_cast>(value); 24 | } else { 25 | return std::get>(value); 26 | } 27 | } 28 | } // namespace 29 | namespace dp { 30 | struct default_event_bus_storage_policy : detail::any_event_bus_storage_policy {}; 31 | 32 | /** 33 | * @brief A central event handler class that connects event handlers with the events. 34 | */ 35 | template 36 | class event_bus { 37 | public: 38 | /** 39 | * @brief A registration handle for a particular handler of an event type. 40 | * @details This class is move constructible only. It also assumed that the lifespan of this 41 | * object will be as long or shorter than that of the event bus. This class is move 42 | * constructible for that reason, but there are still some cases where you can run into life 43 | * time issues. 44 | */ 45 | class handler_registration { 46 | const void* handle_{nullptr}; 47 | dp::event_bus* event_bus_{nullptr}; 48 | 49 | public: 50 | handler_registration(handler_registration&& other) noexcept 51 | : handle_(std::exchange(other.handle_, nullptr)), 52 | event_bus_(std::exchange(other.event_bus_, nullptr)) {} 53 | 54 | handler_registration& operator=(handler_registration&& other) noexcept { 55 | if (this == &other) { 56 | return *this; 57 | } 58 | 59 | handle_ = std::exchange(other.handle_, nullptr); 60 | event_bus_ = std::exchange(other.event_bus_, nullptr); 61 | return *this; 62 | } 63 | ~handler_registration() noexcept { unregister(); } 64 | 65 | handler_registration(const handler_registration& other) = delete; 66 | handler_registration& operator=(const handler_registration& other) = delete; 67 | /** 68 | * @brief Pointer to the underlying handle. 69 | */ 70 | [[nodiscard]] const void* handle() const noexcept { return handle_; } 71 | 72 | /** 73 | * @brief Unregister this handler from the event bus. 74 | */ 75 | void unregister() noexcept { 76 | if (event_bus_ && handle_) { 77 | event_bus_->remove_handler(*this); 78 | handle_ = nullptr; 79 | } 80 | } 81 | 82 | protected: 83 | handler_registration(const void* handle, dp::event_bus* bus) noexcept 84 | : handle_(handle), event_bus_(bus) {} 85 | friend class event_bus; 86 | }; 87 | 88 | /// @brief Public type aliases 89 | /// @{ 90 | using StoragePolicy = 91 | std::conditional_t>; 93 | using event_type = typename StoragePolicy::event_type; 94 | using event_handler = typename StoragePolicy::event_handler; 95 | using handler_registration = typename event_bus::handler_registration; 96 | /// @} 97 | 98 | event_bus() = default; 99 | 100 | /** 101 | * @brief Register an event handler for a given event type. 102 | * @tparam EventHandler The invocable event handler type. 103 | * @param handler A callable handler of the event type. This invocation is designed for 104 | * when `handler` takes the EventType as an argument. 105 | * @return A handler_registration instance for the given handler. 106 | */ 107 | template 108 | [[nodiscard]] auto register_handler(EventHandler&& handler) noexcept { 109 | using EventType = typename detail::function_traits::template arg<0>::type; 110 | 111 | static_assert(std::is_invocable_v, 112 | "EventHandler must be invocable with EventType as an argument."); 113 | 114 | return register_handler_impl(std::forward(handler)); 115 | } 116 | 117 | /** 118 | * @brief Register an event handler for a given event type. 119 | * @tparam EventType The event type 120 | * @tparam EventHandler The invocable event handler type. 121 | * @param handler A callable handler of the event type. This invocation is used for when 122 | * `handler` takes no parameters but wants to be fired when EventType is fired. 123 | * @return A handler_registration instance for the given handler. 124 | */ 125 | template 126 | [[nodiscard]] handler_registration register_handler(EventHandler&& handler) noexcept { 127 | static_assert(std::is_invocable_v, 128 | "EventHandler must be invocable with no arguments."); 129 | 130 | return register_handler_impl(std::forward(handler)); 131 | } 132 | 133 | /** 134 | * @brief Register an event handler for a given event type. 135 | * @tparam EventType The event type 136 | * @tparam ClassType Event handler class 137 | * @tparam MemberFunction Event handler member function 138 | * @param class_instance Instance of ClassType that will handle the event. 139 | * @param function Pointer to the MemberFunction of the ClassType. This invocation is 140 | * for when `function` takes the EventType as an argument. 141 | * @return A handler_registration instance for the given handler. 142 | */ 143 | template 144 | [[nodiscard]] handler_registration register_handler(ClassType* class_instance, 145 | MemberFunction&& function) noexcept { 146 | using EventType = 147 | typename detail::function_traits::template arg<0>::type; 148 | 149 | static_assert(std::is_invocable_v, 150 | "EventHandler must be a member function of ClassType and one " 151 | "EventType argument."); 152 | 153 | return register_handler_impl( 154 | [class_instance, func = std::forward(function)]( 155 | const EventType& event) { (class_instance->*func)(event); }); 156 | } 157 | 158 | /** 159 | * @brief Register an event handler for a given event type. 160 | * @tparam EventType The event type 161 | * @tparam ClassType Event handler class 162 | * @tparam MemberFunction Event handler member function 163 | * @param class_instance Instance of ClassType that will handle the event. 164 | * @param function Pointer to the MemberFunction of the ClassType. This invocation is 165 | * for when `function` takes no arguments but wants to be fired when EventType is fired. 166 | * @return A handler_registration instance for the given handler. 167 | */ 168 | template 169 | [[nodiscard]] handler_registration register_handler(ClassType* class_instance, 170 | MemberFunction&& function) noexcept { 171 | static_assert( 172 | std::is_invocable_v, 173 | "EventHandler must be a member function of ClassType and take no arguments."); 174 | 175 | return register_handler_impl( 176 | [class_instance, func = std::forward(function)]() { 177 | (class_instance->*func)(); 178 | }); 179 | } 180 | 181 | /** 182 | * @brief Fire an event to notify event handlers. 183 | * @tparam EventType The event type 184 | * @param evt The event to pass to all event handlers. 185 | */ 186 | template >> 187 | void fire_event(const EventType& evt) noexcept { 188 | safe_shared_registrations_access([this, &evt]() { 189 | // only call the functions we need to 190 | for (auto [begin_evt_id, end_evt_id] = 191 | handler_registrations_.equal_range(std::type_index(typeid(EventType))); 192 | begin_evt_id != end_evt_id; ++begin_evt_id) { 193 | // call all handlers by passing a std::reference_wrapper 194 | begin_evt_id->second(std::cref(evt)); 195 | } 196 | }); 197 | } 198 | 199 | /** 200 | * @brief Remove a given handler from the event bus. 201 | * @param registration The registration object returned by register_handler. 202 | * @return true is handler removal was successful, false otherwise. 203 | */ 204 | bool remove_handler(const handler_registration& registration) noexcept { 205 | if (!registration.handle()) { 206 | return false; 207 | } 208 | 209 | auto result = false; 210 | safe_unique_registrations_access([this, &result, ®istration]() { 211 | for (auto it = handler_registrations_.begin(); it != handler_registrations_.end(); 212 | ++it) { 213 | if (static_cast(&(it->second)) == registration.handle()) { 214 | handler_registrations_.erase(it); 215 | result = true; 216 | break; 217 | } 218 | } 219 | }); 220 | return result; 221 | } 222 | 223 | /** 224 | * @brief Remove all handlers from event bus. 225 | */ 226 | void remove_handlers() noexcept { 227 | safe_unique_registrations_access([this]() { handler_registrations_.clear(); }); 228 | } 229 | 230 | /** 231 | * @brief Get the number of handlers registered with the event bus. 232 | * @return The total number of handlers. 233 | */ 234 | [[nodiscard]] std::size_t handler_count() noexcept { 235 | std::size_t count{}; 236 | safe_shared_registrations_access( 237 | [this, &count]() { count = handler_registrations_.size(); }); 238 | return count; 239 | } 240 | 241 | private: 242 | using mutex_type = std::shared_mutex; 243 | mutable mutex_type registration_mutex_; 244 | std::unordered_multimap handler_registrations_; 245 | 246 | template 247 | void safe_shared_registrations_access(Callable&& callable) noexcept { 248 | try { 249 | std::scoped_lock lock{registration_mutex_}; 250 | callable(); 251 | } catch (std::system_error&) { 252 | } 253 | } 254 | 255 | template 256 | void safe_unique_registrations_access(Callable&& callable) noexcept { 257 | try { 258 | // if this fails, an exception may be thrown. 259 | std::scoped_lock lock{registration_mutex_}; 260 | callable(); 261 | } catch (std::system_error&) { 262 | // do nothing 263 | } 264 | } 265 | 266 | // Helper function which drastically cleans up the template parameterization 267 | // requirements of the users of this library. EventType is now deduced from the handler 268 | // function directly unless it takes no arguments. 269 | template 270 | [[nodiscard]] handler_registration register_handler_impl(EventHandler&& handler) noexcept { 271 | using traits = detail::function_traits; 272 | using RawParameterType = std::remove_cv_t>; 273 | 274 | const auto type_idx = std::type_index(typeid(RawParameterType)); 275 | const void* handle; 276 | 277 | // check if the function takes any arguments. 278 | if constexpr (traits::arity == 0) { 279 | // arity is 0, so we can safely call the function without any arguments. 280 | safe_unique_registrations_access([&]() { 281 | auto it = handler_registrations_.emplace( 282 | type_idx, 283 | [func = std::forward(handler)](event_type&&) { func(); }); 284 | 285 | handle = static_cast(&(it->second)); 286 | }); 287 | } else { 288 | // function takes at least one argument, so we need to wrap the event in a 289 | // std::reference_wrapper to avoid copying the event. 290 | safe_unique_registrations_access([&]() { 291 | auto it = handler_registrations_.emplace( 292 | type_idx, [func = std::forward(handler)](event_type&& value) { 293 | std::reference_wrapper local_event = 294 | ::access_event_value( 295 | std::forward(value)); 296 | 297 | // Check if the event type is an rvalue reference and handle accordingly 298 | if constexpr (std::is_rvalue_reference_v) { 299 | static_assert(std::is_copy_constructible_v, 300 | "Event type must be copy constructible."); 301 | func(RawParameterType(local_event.get())); 302 | } else { 303 | func(local_event); 304 | } 305 | }); 306 | 307 | handle = static_cast(&(it->second)); 308 | }); 309 | } 310 | return {handle, this}; 311 | } 312 | }; 313 | 314 | /// @brief CTAD guide for dp::event_bus 315 | template 316 | event_bus() -> event_bus<>; 317 | 318 | using handler_registration = event_bus<>::handler_registration; 319 | } // namespace dp 320 | -------------------------------------------------------------------------------- /eventbus/test/event_bus_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | struct test_event_type { 11 | int id{-1}; 12 | std::string event_message; 13 | double data_value{1.0}; 14 | }; 15 | 16 | inline std::ostream& operator<<(std::ostream& out, const test_event_type& evt) { 17 | out << "id: " << evt.id << " msg: " << evt.event_message << " data: " << evt.data_value; 18 | return out; 19 | } 20 | 21 | class event_handler_counter { 22 | std::atomic event_count_{0}; 23 | 24 | public: 25 | event_handler_counter() = default; 26 | [[nodiscard]] unsigned int get_count() const { return event_count_.load(); } 27 | void on_test_event() { ++event_count_; } 28 | }; 29 | 30 | void free_function_callback(const test_event_type& type_event) { 31 | std::cout << "Free function callback : " << type_event << "\n"; 32 | } 33 | 34 | TEST_CASE("lambda registration and de-registration") { 35 | dp::event_bus evt_bus; 36 | event_handler_counter counter; 37 | auto registration = 38 | evt_bus.register_handler(&counter, &event_handler_counter::on_test_event); 39 | 40 | test_event_type test_event{1, "event message", 32.56}; 41 | const auto lambda_one_reg = 42 | evt_bus.register_handler([]() { std::cout << "Lambda 1\n"; }); 43 | const auto lambda_two_reg = evt_bus.register_handler([&test_event](const test_event_type& evt) { 44 | CHECK_EQ(evt.id, test_event.id); 45 | CHECK_EQ(evt.event_message, test_event.event_message); 46 | CHECK_EQ(evt.data_value, test_event.data_value); 47 | }); 48 | 49 | const auto lambda_three_reg = 50 | evt_bus.register_handler([](test_event_type) { std::cout << "Lambda 3 take by copy.\n"; }); 51 | 52 | // should be 4 because we register a handler in the test fixture SetUp 53 | REQUIRE_EQ(evt_bus.handler_count(), 4); 54 | evt_bus.fire_event(test_event); 55 | CHECK_EQ(counter.get_count(), 1); 56 | evt_bus.fire_event(test_event); 57 | CHECK_EQ(counter.get_count(), 2); 58 | 59 | evt_bus.remove_handler(lambda_one_reg); 60 | 61 | evt_bus.fire_event(test_event); 62 | CHECK_EQ(counter.get_count(), 3); 63 | CHECK_EQ(evt_bus.handler_count(), 3); 64 | 65 | evt_bus.remove_handler(lambda_two_reg); 66 | 67 | evt_bus.fire_event(test_event); 68 | CHECK_EQ(counter.get_count(), 4); 69 | CHECK_EQ(evt_bus.handler_count(), 2); 70 | 71 | evt_bus.remove_handler(lambda_three_reg); 72 | 73 | evt_bus.fire_event(test_event); 74 | CHECK_EQ(counter.get_count(), 5); 75 | CHECK_EQ(evt_bus.handler_count(), 1); 76 | } 77 | 78 | TEST_CASE("deregister while dispatching") { 79 | dp::event_bus evt_bus; 80 | event_handler_counter counter; 81 | auto registration = 82 | evt_bus.register_handler(&counter, &event_handler_counter::on_test_event); 83 | 84 | struct deregister_while_dispatch_listener { 85 | // CTAD not allowed in non-static struct members so we have to include the empty brackets 86 | dp::event_bus<>* evt_bus{nullptr}; 87 | std::vector* registrations{nullptr}; 88 | void on_event(test_event_type) { 89 | if (evt_bus && registrations) { 90 | std::for_each(registrations->begin(), registrations->end(), 91 | [&](auto& reg) { evt_bus->remove_handler(reg); }); 92 | } 93 | } 94 | }; 95 | 96 | std::vector registrations; 97 | std::vector listeners; 98 | for (auto i = 0; i < 20; ++i) { 99 | deregister_while_dispatch_listener listener; 100 | auto reg = 101 | evt_bus.register_handler(&listener, &deregister_while_dispatch_listener::on_event); 102 | listeners.emplace_back(listener); 103 | registrations.emplace_back(std::move(reg)); 104 | } 105 | 106 | listeners[0].evt_bus = &evt_bus; 107 | listeners[0].registrations = ®istrations; 108 | 109 | for (auto i = 0; i < 40; ++i) { 110 | evt_bus.fire_event(test_event_type{3, "test event", 3.4}); 111 | // add 1 because of the test fixture. 112 | CHECK_EQ(evt_bus.handler_count(), listeners.size() + 1); 113 | } 114 | 115 | // remove all the registrations 116 | for (auto& reg : registrations) { 117 | CHECK(evt_bus.remove_handler(reg)); 118 | } 119 | 120 | CHECK_EQ(evt_bus.handler_count(), 1); 121 | } 122 | 123 | TEST_CASE("multi-threaded event dispatch") { 124 | class simple_listener { 125 | int index_; 126 | 127 | public: 128 | explicit simple_listener(int index) : index_(index) {} 129 | void on_event(const test_event_type& evt) const { 130 | std::cout << "simple event: " << index_ << " " << evt.event_message << "\n"; 131 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 132 | } 133 | }; 134 | 135 | dp::event_bus evt_bus; 136 | simple_listener listener_one(1); 137 | simple_listener listener_two(2); 138 | 139 | auto reg_one = evt_bus.register_handler(&listener_one, &simple_listener::on_event); 140 | auto reg_two = evt_bus.register_handler(&listener_two, &simple_listener::on_event); 141 | 142 | event_handler_counter event_counter; 143 | auto event_handler_reg = evt_bus.register_handler( 144 | &event_counter, &event_handler_counter::on_test_event); 145 | 146 | auto thread_one = std::thread([&evt_bus, &listener_one]() { 147 | for (auto i = 0; i < 5; ++i) { 148 | evt_bus.fire_event(test_event_type{3, "thread_one", 1.0}); 149 | } 150 | }); 151 | 152 | auto thread_two = std::thread([&evt_bus, &listener_two]() { 153 | for (auto i = 0; i < 5; ++i) { 154 | evt_bus.fire_event(test_event_type{3, "thread_two", 2.0}); 155 | } 156 | }); 157 | 158 | thread_one.join(); 159 | thread_two.join(); 160 | 161 | // include the event counter 162 | CHECK_EQ(evt_bus.handler_count(), 3); 163 | 164 | CHECK_EQ(event_counter.get_count(), 10); 165 | } 166 | 167 | TEST_CASE("auto de-register in destructor") { 168 | dp::event_bus evt_bus; 169 | event_handler_counter counter; 170 | { 171 | auto registration = evt_bus.register_handler( 172 | &counter, &event_handler_counter::on_test_event); 173 | } 174 | 175 | CHECK_EQ(evt_bus.handler_count(), 0); 176 | evt_bus.fire_event(test_event_type{}); 177 | evt_bus.fire_event(test_event_type{}); 178 | evt_bus.fire_event(test_event_type{}); 179 | CHECK_EQ(counter.get_count(), 0); 180 | } 181 | 182 | TEST_CASE("Ensure events are not unnecessarily copied") { 183 | dp::event_bus evt_bus; 184 | bool event_copied = false; 185 | 186 | struct event_checker { 187 | bool& event_copied; 188 | 189 | event_checker& operator=(const event_checker&) { 190 | event_copied = true; 191 | return *this; 192 | } 193 | event_checker(const event_checker& other) : event_copied{other.event_copied} { 194 | event_copied = true; 195 | } 196 | event_checker(bool& copied) : event_copied{copied} {} 197 | }; 198 | 199 | event_checker checker{event_copied}; 200 | 201 | auto registration1 = evt_bus.register_handler([](const event_checker& evt) {}); 202 | 203 | auto registration2 = evt_bus.register_handler([](const event_checker& evt) {}); 204 | 205 | // l-value reference 206 | evt_bus.fire_event(checker); 207 | // r-value reference 208 | evt_bus.fire_event(event_checker{event_copied}); 209 | CHECK_FALSE(event_copied); 210 | 211 | { 212 | // register a handler that expects a value type -- this will make a copy 213 | auto registration3 = evt_bus.register_handler([](event_checker evt) {}); 214 | 215 | evt_bus.fire_event(checker); 216 | CHECK(event_copied); 217 | 218 | // register a handler that expects an r-value reference -- this will make a copy 219 | auto registration4 = evt_bus.register_handler([](event_checker&& evt) {}); 220 | event_copied = false; 221 | 222 | evt_bus.fire_event(checker); 223 | CHECK(event_copied); 224 | } 225 | // deregister copying events 226 | 227 | event_copied = false; 228 | 229 | const event_checker const_checker{event_copied}; 230 | 231 | evt_bus.fire_event(const_checker); 232 | 233 | CHECK_FALSE(event_copied); 234 | } 235 | 236 | TEST_CASE("event_bus_variant: multi-threaded event dispatch") { 237 | class simple_listener { 238 | int index_; 239 | 240 | public: 241 | explicit simple_listener(int index) : index_(index) {} 242 | void on_event(const test_event_type& evt) const { 243 | std::cout << "simple event: " << index_ << " " << evt.event_message << "\n"; 244 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 245 | } 246 | }; 247 | 248 | dp::event_bus evt_bus{}; 249 | 250 | simple_listener listener_one(1); 251 | simple_listener listener_two(2); 252 | 253 | auto reg_one = evt_bus.register_handler(&listener_one, &simple_listener::on_event); 254 | auto reg_two = evt_bus.register_handler(&listener_two, &simple_listener::on_event); 255 | 256 | event_handler_counter event_counter; 257 | auto event_handler_reg = evt_bus.register_handler( 258 | &event_counter, &event_handler_counter::on_test_event); 259 | 260 | auto thread_one = std::thread([&evt_bus, &listener_one]() { 261 | for (auto i = 0; i < 5; ++i) { 262 | evt_bus.fire_event(test_event_type{3, "thread_one", 1.0}); 263 | } 264 | }); 265 | 266 | auto thread_two = std::thread([&evt_bus, &listener_two]() { 267 | for (auto i = 0; i < 5; ++i) { 268 | evt_bus.fire_event(test_event_type{3, "thread_two", 2.0}); 269 | } 270 | }); 271 | 272 | thread_one.join(); 273 | thread_two.join(); 274 | 275 | // include the event counter 276 | CHECK_EQ(evt_bus.handler_count(), 3); 277 | 278 | CHECK_EQ(event_counter.get_count(), 10); 279 | } 280 | 281 | TEST_CASE("event_bus_variant: basic multi-event support") { 282 | struct event1 { 283 | int id; 284 | std::string message; 285 | }; 286 | struct event2 { 287 | double value; 288 | }; 289 | struct event3 { 290 | char character; 291 | }; 292 | 293 | dp::event_bus evt_bus{}; 294 | event_handler_counter event_counter; 295 | auto event_handler_reg = 296 | evt_bus.register_handler(&event_counter, &event_handler_counter::on_test_event); 297 | auto event_handler_reg2 = 298 | evt_bus.register_handler(&event_counter, &event_handler_counter::on_test_event); 299 | auto event_handler_reg3 = 300 | evt_bus.register_handler(&event_counter, &event_handler_counter::on_test_event); 301 | 302 | struct conglomerate_handler { 303 | void ev1(const event1& evt) { e1 = evt; } 304 | void ev2(const event2& evt) { e2 = evt; } 305 | void ev3(const event3& evt) { e3 = evt; } 306 | 307 | std::optional e1; 308 | std::optional e2; 309 | std::optional e3; 310 | 311 | auto combine() -> std::string { 312 | REQUIRE(e1.has_value()); 313 | REQUIRE(e2.has_value()); 314 | REQUIRE(e3.has_value()); 315 | std::stringstream oss; 316 | oss << e1->id << " " << e1->message << " | " << e2->value << " | " << e3->character; 317 | return oss.str(); 318 | } 319 | }; 320 | 321 | conglomerate_handler handler; 322 | auto registration = evt_bus.register_handler(&handler, &conglomerate_handler::ev1); 323 | auto registration2 = evt_bus.register_handler(&handler, &conglomerate_handler::ev2); 324 | auto registration3 = evt_bus.register_handler(&handler, &conglomerate_handler::ev3); 325 | 326 | evt_bus.fire_event(event1{1, "Hello"}); 327 | evt_bus.fire_event(event2{3.14}); 328 | evt_bus.fire_event(event3{'A'}); 329 | 330 | CHECK_EQ(handler.combine(), "1 Hello | 3.14 | A"); 331 | CHECK_EQ(event_counter.get_count(), 3); 332 | } 333 | -------------------------------------------------------------------------------- /eventbus/test/main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include 3 | --------------------------------------------------------------------------------