├── .clang-format ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FAQ.md ├── THANKS.md └── res │ ├── EventBus-concept.draw.io.xml │ └── EventBus-concept.png ├── lib ├── CMakeLists.txt ├── cmake │ ├── Config.cmake.in │ ├── EventBus_CPack.cmake │ └── InstallHelp.cmake └── src │ └── dexode │ ├── EventBus.cpp │ ├── EventBus.hpp │ └── eventbus │ ├── Bus.hpp │ ├── Listener.hpp │ ├── internal │ ├── ListenerAttorney.hpp │ ├── event_id.hpp │ └── listener_traits.hpp │ ├── perk │ ├── PassPerk.cpp │ ├── PassPerk.hpp │ ├── Perk.cpp │ ├── Perk.hpp │ ├── PerkEventBus.cpp │ ├── PerkEventBus.hpp │ ├── TagPerk.cpp │ ├── TagPerk.hpp │ ├── WaitPerk.cpp │ └── WaitPerk.hpp │ ├── permission │ └── PostponeBus.hpp │ └── stream │ ├── EventStream.hpp │ └── ProtectedEventStream.hpp ├── performance ├── .gitignore ├── CCNotificationCenterPerformance.patch ├── CMakeLists.txt ├── README.md ├── cocos2d-x-compare │ ├── CCNotificationCenterPerformance.cpp │ └── Cocos2dxCompare.cmake └── src │ ├── AsyncEventBusPerformance.cpp │ ├── EventBusPerformance.cpp │ └── PocoNotificationCenterPerformance.cpp ├── test ├── CMakeLists.txt ├── integration │ ├── CMakeLists.txt │ └── src │ │ ├── dexode │ │ └── eventbus │ │ │ └── test │ │ │ └── SuiteWait.cpp │ │ └── main.cpp └── unit │ ├── CMakeLists.txt │ └── src │ ├── dexode │ └── eventbus │ │ └── test │ │ ├── SuiteConcurrentEventBus.cpp │ │ ├── SuiteEventBus.cpp │ │ ├── SuiteEventID.cpp │ │ ├── SuiteListener.cpp │ │ └── event.hpp │ └── main.cpp └── use_case ├── CMakeLists.txt ├── README.md ├── basic ├── CMakeLists.txt ├── README.md └── src │ └── main.cpp └── tagged_events ├── CMakeLists.txt ├── README.md └── src ├── Character.cpp ├── Character.hpp ├── EventBus.hpp ├── Gui.cpp ├── Gui.hpp ├── Team.cpp ├── Team.hpp ├── event.hpp └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | # Checkout config tool: https://zed0.co.uk/clang-format-configurator/ 2 | # Or http://cf.monofraps.net/ 3 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 4 | # https://github.com/01org/parameter-framework/blob/master/.clang-format 5 | 6 | # Tested on: clang-format version 8.0.0 7 | # Version 1.2 8 | 9 | 10 | # Common settings 11 | BasedOnStyle: WebKit 12 | TabWidth: 4 13 | IndentWidth: 4 14 | UseTab: ForContinuationAndIndentation 15 | ColumnLimit: 100 16 | 17 | # Other languages JavaScript, Proto 18 | 19 | --- 20 | Language: Cpp 21 | 22 | # http://releases.llvm.org/6.0.1/tools/clang/docs/ClangFormatStyleOptions.html#disabling-formatting-on-a-piece-of-code 23 | # int formatted_code; 24 | # // clang-format off 25 | # void unformatted_code ; 26 | # // clang-format on 27 | # void formatted_code_again; 28 | 29 | DisableFormat: false 30 | Standard: Auto 31 | 32 | AccessModifierOffset: -4 33 | AlignAfterOpenBracket: true 34 | AlignConsecutiveAssignments: false 35 | AlignConsecutiveDeclarations: false 36 | AlignEscapedNewlinesLeft: false 37 | AlignEscapedNewlines: Right 38 | AlignOperands: true 39 | AlignTrailingComments: false 40 | AllowAllParametersOfDeclarationOnNextLine: true 41 | AllowShortBlocksOnASingleLine: false 42 | AllowShortCaseLabelsOnASingleLine: false 43 | AllowShortFunctionsOnASingleLine: Empty 44 | AllowShortIfStatementsOnASingleLine: false 45 | AllowShortLoopsOnASingleLine: false 46 | AlwaysBreakAfterDefinitionReturnType: false 47 | AlwaysBreakAfterReturnType: None 48 | AlwaysBreakBeforeMultilineStrings: false 49 | AlwaysBreakTemplateDeclarations: true 50 | BinPackArguments: false 51 | BinPackParameters: false 52 | 53 | # Configure each individual brace in BraceWrapping 54 | BreakBeforeBraces: Custom 55 | # Control of individual brace wrapping cases 56 | BraceWrapping: { 57 | AfterClass: 'true' 58 | AfterControlStatement: 'true' 59 | AfterEnum: 'true' 60 | AfterFunction: 'true' 61 | AfterNamespace: 'true' 62 | AfterStruct: 'true' 63 | AfterUnion: 'true' 64 | BeforeCatch: 'true' 65 | BeforeElse: 'true' 66 | IndentBraces: 'false' 67 | AfterExternBlock: 'true' 68 | SplitEmptyFunction: 'false' 69 | SplitEmptyRecord: 'false' 70 | SplitEmptyNamespace: 'true' 71 | } 72 | 73 | BreakAfterJavaFieldAnnotations: true 74 | BreakBeforeInheritanceComma: false 75 | BreakBeforeBinaryOperators: None 76 | BreakBeforeTernaryOperators: true 77 | BreakConstructorInitializersBeforeComma: true 78 | BreakStringLiterals: true 79 | 80 | CommentPragmas: '^ IWYU pragma:' 81 | CompactNamespaces: false 82 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 83 | ConstructorInitializerIndentWidth: 4 84 | ContinuationIndentWidth: 4 85 | Cpp11BracedListStyle: true 86 | SpaceBeforeCpp11BracedList: false 87 | DerivePointerAlignment: false 88 | ExperimentalAutoDetectBinPacking: false 89 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 90 | IncludeCategories: 91 | - Regex: '^<.*\..+' 92 | Priority: 2 93 | - Regex: '^<.*' 94 | Priority: 1 95 | - Regex: '.*' 96 | Priority: 3 97 | IndentCaseLabels: false 98 | FixNamespaceComments: true 99 | IndentWrappedFunctionNames: false 100 | KeepEmptyLinesAtTheStartOfBlocks: true 101 | MacroBlockBegin: '' 102 | MacroBlockEnd: '' 103 | JavaScriptQuotes: Double 104 | MaxEmptyLinesToKeep: 1 105 | NamespaceIndentation: None 106 | ObjCBlockIndentWidth: 4 107 | ObjCSpaceAfterProperty: true 108 | ObjCSpaceBeforeProtocolList: true 109 | 110 | PenaltyExcessCharacter: 1000000 111 | PenaltyReturnTypeOnItsOwnLine: 1000000 112 | PenaltyBreakAssignment: 2 113 | PenaltyBreakBeforeFirstCallParameter: 19 114 | PenaltyBreakComment: 300 115 | PenaltyBreakFirstLessLess: 120 116 | PenaltyBreakString: 1000 117 | PenaltyBreakTemplateDeclaration: 10 118 | 119 | PointerAlignment: Left 120 | SpaceAfterCStyleCast: false 121 | SpaceBeforeAssignmentOperators: true 122 | SpaceBeforeParens: Never 123 | SpaceInEmptyParentheses: false 124 | SpacesBeforeTrailingComments: 1 125 | SpacesInAngles: false 126 | SpacesInContainerLiterals: true 127 | SpacesInCStyleCastParentheses: false 128 | SpacesInParentheses: false 129 | SpacesInSquareBrackets: false 130 | SpaceAfterTemplateKeyword: true 131 | SpaceBeforeInheritanceColon: true 132 | 133 | SortUsingDeclarations: true 134 | SortIncludes: true 135 | 136 | # Comments are for developers, they should arrange them but now we allow for ReflowComments 137 | # It should add at least 1 space after // 138 | ReflowComments: true 139 | 140 | IncludeBlocks: Regroup 141 | IndentPPDirectives: AfterHash 142 | 143 | #AllowShortLambdasOnASingleLine: All 144 | --- 145 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Sample code or PR with unit test :) (preferable) 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Build:** 17 | - compiler: [e.g. clang, gcc] 18 | - Version [e.g. 6.0.0] 19 | - Link type [static, shared, buildin] 20 | - Any specific flags ? 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | build/ 31 | cmake-build*/ 32 | .idea/ 33 | 34 | Release/ 35 | Debug/ 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "performance/benchmark"] 2 | path = performance/benchmark 3 | url = https://github.com/google/benchmark.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: false 3 | 4 | matrix: 5 | exclude: # On OSX g++ is a symlink to clang++ by default 6 | - os: osx 7 | compiler: gcc 8 | 9 | include: 10 | # Bionic Ubuntu 18.04 11 | - os: linux 12 | name: "Bionic - gcc 7.4.0" 13 | dist: bionic # We can't install packages on Bionic, not sure why. Travis ignores addons... 14 | compiler: gcc 15 | # Bionic Ubuntu 18.04 16 | - os: linux 17 | name: "Bionic - clang 7" 18 | dist: bionic # We can't install packages on Bionic, not sure why. Travis ignores addons... 19 | compiler: clang 20 | 21 | # clang-6 22 | - os: linux 23 | name: "Xenial - clang 6" 24 | dist: xenial 25 | env: [USE_CC='/usr/bin/clang-6.0', USE_CXX='/usr/bin/clang++-6.0'] 26 | addons: 27 | apt: 28 | packages: ['clang-6.0', 'cmake', 'libstdc++-9-dev', 'libstdc++6'] 29 | sources: ['ubuntu-toolchain-r-test', 'ubuntu-toolchain-r-test'] 30 | # clang-8 31 | - os: linux 32 | name: "Xenial - clang 8" 33 | dist: xenial 34 | env: [USE_CC='/usr/bin/clang-8', USE_CXX='/usr/bin/clang++-8'] 35 | addons: 36 | apt: 37 | packages: ['clang-8', 'cmake', 'libstdc++-9-dev', 'libstdc++6'] 38 | sources: ['llvm-toolchain-xenial-8', 'ubuntu-toolchain-r-test'] 39 | # clang-9 40 | - os: linux 41 | name: "Xenial - clang 9" 42 | dist: xenial 43 | env: [USE_CC='/usr/bin/clang-9', USE_CXX='/usr/bin/clang++-9'] 44 | addons: 45 | apt: 46 | sources: 47 | - ubuntu-toolchain-r-test 48 | - sourceline: 'deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-9 main' 49 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 50 | packages: ['clang-9', 'cmake', 'libstdc++-9-dev', 'libstdc++6'] 51 | 52 | # gcc-8 53 | - os: linux 54 | name: "Xenial - gcc 8" 55 | dist: xenial 56 | env: [USE_CC='/usr/bin/gcc-8', USE_CXX='/usr/bin/g++-8'] 57 | addons: 58 | apt: 59 | packages: ['gcc-8', 'g++-8', 'cmake'] 60 | sources: ['ubuntu-toolchain-r-test'] 61 | # gcc-9 62 | - os: linux 63 | name: "Xenial - gcc 9" 64 | dist: xenial 65 | env: [USE_CC='/usr/bin/gcc-9', USE_CXX='/usr/bin/g++-9'] 66 | addons: 67 | apt: 68 | packages: ['gcc-9', 'g++-9', 'cmake'] 69 | sources: ['ubuntu-toolchain-r-test'] 70 | 71 | - os: osx 72 | osx_image: xcode10.3 73 | compiler: clang 74 | - os: osx 75 | osx_image: xcode11.3 76 | compiler: clang 77 | 78 | 79 | before_install: 80 | # Override compilers set by Travis 81 | - if [ ! -z "${USE_CC}" ]; then echo "Override CC=${USE_CC}"; eval "CC=${USE_CC}"; fi 82 | - if [ ! -z "${USE_CXX}" ]; then echo "Override CXX=${USE_CXX}"; eval "CXX=${USE_CXX}"; fi 83 | - echo ${CC} && ${CC} --version 84 | - echo ${CXX} && ${CXX} --version 85 | # Install catch 2 dependency 86 | - wget https://github.com/catchorg/Catch2/archive/v2.11.1.zip 87 | - unzip v2.11.1.zip && cd Catch2-2.11.1 && mkdir -p build/ && cd build/ 88 | - cmake -DCMAKE_BUILD_TYPE=Release .. -DCMAKE_CXX_STANDARD=14 -DCMAKE_INSTALL_PREFIX=~/.local/ && cmake --build . --target install 89 | - cd ../.. 90 | 91 | script: 92 | #Build & Install library 93 | - (mkdir -p lib/build-debug/ && cd lib/build-debug && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . --target install) 94 | - (mkdir -p lib/build-release/ && cd lib/build-release && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . --target install) 95 | # Run tests 96 | - (mkdir -p test/build-debug/ && cd test/build-debug && cmake ${EXTRA_CMAKE_FLAGS} -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . && ctest -V .) 97 | - (mkdir -p test/build-release/ && cd test/build-release && cmake ${EXTRA_CMAKE_FLAGS} -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . && ctest -V .) 98 | 99 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 2 | 3 | # Layout of project is inspired by: https://youtu.be/6sWec7b0JIc?t=20m50s 4 | # This top level CMakeLists should be used for development 5 | 6 | project(EventBusDev LANGUAGES CXX) 7 | 8 | option(ENABLE_TEST "Enable test" ON) 9 | option(ENABLE_PERFORMANCE "Enable performance subproject" OFF) 10 | option(ENABLE_LIBCXX "Enable build with libc++" OFF) 11 | 12 | set(CMAKE_CXX_STANDARD 17) 13 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 14 | set(CMAKE_CXX_EXTENSIONS OFF) 15 | 16 | if(ENABLE_LIBCXX) 17 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 19 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi") 20 | else() 21 | message(FATAL_ERROR "C++ compiler should be set to clang") 22 | endif() 23 | endif() 24 | 25 | # Build Types 26 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} 27 | CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel tsan asan lsan msan ubsan" 28 | FORCE) 29 | 30 | # ThreadSanitizer 31 | set(CMAKE_C_FLAGS_TSAN 32 | "-fsanitize=thread -g -O1" 33 | CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds." 34 | FORCE) 35 | set(CMAKE_CXX_FLAGS_TSAN 36 | "-fsanitize=thread -g -O1" 37 | CACHE STRING "Flags used by the C++ compiler during ThreadSanitizer builds." 38 | FORCE) 39 | 40 | # AddressSanitize 41 | set(CMAKE_C_FLAGS_ASAN 42 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" 43 | CACHE STRING "Flags used by the C compiler during AddressSanitizer builds." 44 | FORCE) 45 | set(CMAKE_CXX_FLAGS_ASAN 46 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" 47 | CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds." 48 | FORCE) 49 | 50 | # LeakSanitizer 51 | set(CMAKE_C_FLAGS_LSAN 52 | "-fsanitize=leak -fno-omit-frame-pointer -g -O1" 53 | CACHE STRING "Flags used by the C compiler during LeakSanitizer builds." 54 | FORCE) 55 | set(CMAKE_CXX_FLAGS_LSAN 56 | "-fsanitize=leak -fno-omit-frame-pointer -g -O1" 57 | CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds." 58 | FORCE) 59 | 60 | # Remember to switch lib: https://stackoverflow.com/questions/20617788/using-memory-sanitizer-with-libstdc/20784130#20784130 61 | # use ENABLE_LIBCXX 62 | # MemorySanitizer 63 | set(CMAKE_C_FLAGS_MSAN 64 | "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2" 65 | CACHE STRING "Flags used by the C compiler during MemorySanitizer builds." 66 | FORCE) 67 | set(CMAKE_CXX_FLAGS_MSAN 68 | "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2" 69 | CACHE STRING "Flags used by the C++ compiler during MemorySanitizer builds." 70 | FORCE) 71 | 72 | # UndefinedBehaviour 73 | set(CMAKE_C_FLAGS_UBSAN 74 | "-fsanitize=undefined" 75 | CACHE STRING "Flags used by the C compiler during UndefinedBehaviourSanitizer builds." 76 | FORCE) 77 | set(CMAKE_CXX_FLAGS_UBSAN 78 | "-fsanitize=undefined" 79 | CACHE STRING "Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds." 80 | FORCE) 81 | 82 | add_subdirectory(lib) 83 | add_subdirectory(use_case) 84 | 85 | if(ENABLE_TEST) 86 | enable_testing() 87 | add_subdirectory(test/) 88 | endif() 89 | 90 | if(ENABLE_PERFORMANCE) 91 | add_subdirectory(performance/) 92 | endif() 93 | 94 | if(NOT MSVC) 95 | target_compile_options(EventBus PRIVATE 96 | -Wall -pedantic 97 | -Wnon-virtual-dtor 98 | -Werror 99 | -Wno-error=deprecated-declarations 100 | ) 101 | endif() -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EventBus [![GitHub version](https://badge.fury.io/gh/gelldur%2FEventBus.svg)](https://badge.fury.io/gh/gelldur%2FEventBus) 2 |   [![Build status for Travis](https://travis-ci.org/gelldur/EventBus.svg?branch=master)](https://travis-ci.org/gelldur/EventBus) 3 |   [![Build status for Appveyor](https://ci.appveyor.com/api/projects/status/github/gelldur/EventBus)](https://ci.appveyor.com/project/gelldur/EventBus) 4 | 5 | Simple and very fast event bus. 6 | The EventBus library is a convenient realization of the observer pattern. 7 | It works perfectly to supplement the implementation of MVC logic (model-view-controller) in event-driven UIs 8 | 9 | General concept 10 | ![EventBus Diagram](docs/res/EventBus-concept.png) 11 | 12 | 13 | EventBus was created because I want something easy to use and faster than [CCNotificationCenter](https://github.com/cocos2d/cocos2d-x/blob/v2/cocos2dx/support/CCNotificationCenter.h) 14 | from [cocos2d-x](https://github.com/cocos2d/cocos2d-x) library. Of course C++11 support was mandatory at that moment. 15 | 16 | 17 | EventBus main goals: 18 | - Fast 19 | - Easy to use 20 | - Strongly typed 21 | - Free 22 | - tiny (~37 KB) 23 | - Decouples notification senders and receivers 24 | - on every platform you need (cross-platform) 25 | 26 | # Brief @ presentation 27 | Presentation [google docs](https://docs.google.com/presentation/d/1apAlKcVWo9FcqkPqL8108a1Fy9LGmhgLT56hSVpoI3w/edit?usp=sharing) 28 | 29 | # Sample / use cases 30 | You can checkout [use_case/](use_case/) 31 | If you want to play with sample online checkout this link: [wandbox.org](https://wandbox.org/permlink/VWo2acOX6hxUfV1Q) 32 | 33 | # Usage 34 | 0. Store bus 35 | 36 | ```cpp 37 | // store it in controller / singleton / std::shared_ptr whatever you want 38 | auto bus = std::make_shared(); 39 | ``` 40 | 41 | 1. Define events 42 | 43 | ```cpp 44 | namespace event // optional namespace 45 | { 46 | struct Gold 47 | { 48 | int goldReceived = 0; 49 | }; 50 | 51 | struct OK {}; // Simple event when user press "OK" button 52 | } 53 | ``` 54 | 55 | 2. Subscribe 56 | 57 | ```cpp 58 | // ... 59 | dexode::EventBus::Listener listener{bus}; 60 | listener.listen([](const event::Gold& event) // listen with lambda 61 | { 62 | std::cout << "I received gold: " << event.goldReceived << " 💰" << std::endl; 63 | }); 64 | 65 | HudLayer* hudLayer; 66 | // Hud layer will receive info about gold 67 | hudLayer->listener.listen(std::bind(&HudLayer::onGoldReceived, hudLayer, std::placeholders::_1)); 68 | ``` 69 | 70 | 3. Spread the news 71 | 72 | ```cpp 73 | //Inform listeners about event 74 | bus->postpone(event::Gold{12}); // 1 way 75 | bus->postpone({12}); // 2 way 76 | 77 | event::Gold myGold{12}; 78 | bus->postpone(myGold); // 3 way 79 | ``` 80 | 81 | 4. Process the events 82 | 83 | ```cpp 84 | bus->process(); 85 | ``` 86 | 87 | Checkout [tests](test/) or [use cases](use_case/) for more examples. Or create issue what isn't clear :) 88 | 89 | # Add to your project 90 | EventBus can be added as `ADD_SUBDIRECTORY` to your cmake file. 91 | Then simply link it via `TARGET_LINK_LIBRARIES` 92 | Example: 93 | ``` 94 | # No tests/benchmarks target won't be added. Root CMakeLists is for development. 95 | ADD_SUBDIRECTORY(path/to/EventBus/lib) 96 | ADD_EXECUTABLE(MyExecutable 97 | main.cpp 98 | ) 99 | 100 | TARGET_LINK_LIBRARIES(MyExecutable PUBLIC Dexode::EventBus) 101 | ``` 102 | 103 | Also if you want you can install library and add it at any way you want. 104 | Eg. 105 | ```commandline 106 | mkdir -p lib/build/ 107 | cd lib/build 108 | cmake -DCMAKE_BUILD_TYPE=Relase -DCMAKE_INSTALL_PREFIX=~/.local/ .. 109 | 110 | cmake --build . --target install 111 | # OR 112 | make && make install 113 | ``` 114 | 115 | Now in `Release/install` library is placed. 116 | 117 | Or, you can install the library through your package manager (dpkg, rpm, etc). 118 | E.g. 119 | ```commandline 120 | mkdir -p lib/build/ 121 | cd lib/build 122 | 123 | # For most RH-based Distributions 124 | cmake -DCMAKE_BUILD_TYPE=Relase -DCPACK_GENERATOR="RPM" .. 125 | 126 | # For most Debian-based Distributions 127 | cmake -DCMAKE_BUILD_TYPE=Relase -DCPACK_GENERATOR="DEB" .. 128 | 129 | # Or for both of them 130 | cmake -DCMAKE_BUILD_TYPE=Relase -DCPACK_GENERATOR="RPM;DEB" .. 131 | 132 | cmake --build . --target package 133 | # Or 134 | make package 135 | 136 | # For most Debian-based systems 137 | sudo dpkg -i EventBus*.deb 138 | 139 | # For most RH-based systems 140 | sudo rpm -i EventBus*.rpm 141 | # OR 142 | sudo yum install EventBus*.rpm 143 | ``` 144 | 145 | # Performance (could be outdated) 146 | I have prepared some performance results. You can read about them [here](performance/README.md) 147 | Small example: 148 | 149 | ``` 150 | check10NotificationsFor1kListeners 263 ns 263 ns 2668786 sum=-1.76281G 151 | check10NotificationsFor1kListeners_CCNotificationCenter 11172 ns 11171 ns 62865 sum=54.023M 152 | 153 | checkNotifyFor10kListenersWhenNoOneListens 18 ns 18 ns 38976599 sum=0 154 | checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter 127388 ns 127378 ns 5460 sum=0 155 | ``` 156 | 157 | # Issues ? [![GitHub issues](https://img.shields.io/github/issues/gelldur/EventBus.svg)](https://github.com/gelldur/EventBus/issues) 158 | Please report here issue / question / whatever, there is chance 99% I will answer ;) 159 | 160 | If you have any questions or want to chat use gitter. 161 | 162 | [![Join the chat at https://gitter.im/EventBusCpp/Lobby](https://badges.gitter.im/EventBusCpp/Lobby.svg)](https://gitter.im/EventBusCpp/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 163 | 164 | # Tesing and metrics 165 | // TODO 166 | 167 | 168 | [![LoC](https://tokei.rs/b1/github/gelldur/EventBus)](https://github.com/gelldur/EventBus) 169 | [![Coverage Status](https://coveralls.io/repos/github/gelldur/EventBus/badge.svg?branch=master)](https://coveralls.io/github/gelldur/EventBus?branch=master) 170 | 171 | # Thanks to [![GitHub contributors](https://img.shields.io/github/contributors/gelldur/EventBus.svg)](https://github.com/gelldur/EventBus/graphs/contributors) [![Open Source Helpers](https://www.codetriage.com/gelldur/eventbus/badges/users.svg)](https://www.codetriage.com/gelldur/eventbus) 172 | - [staakk](https://github.com/stanislawkabacinski) for fixing windows ;) [53d5026](https://github.com/gelldur/EventBus/commit/53d5026cad24810e82cd8d4a43d58cbfe329c502) 173 | - [kuhar](https://github.com/kuhar) for his advice and suggestions for EventBus 174 | - [swietlana](https://github.com/swietlana) for English correction and support ;) 175 | - [ruslo](https://github.com/ruslo) for this great example: https://github.com/forexample/package-example 176 | 177 | ## For modern cmake refer 178 | - https://github.com/forexample/package-example 179 | - https://www.youtube.com/watch?v=6sWec7b0JIc 180 | 181 | # License 182 | EventBus source code can be used according to the **Apache License, Version 2.0**. 183 | For more information see [LICENSE](LICENSE) file 184 | 185 | If you don't like to read to much here is sumup about license [Apache 2.0](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)#summary) 186 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at drozddawid.uam@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How Can I Contribute? 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | Just do it we will work out something ;) 6 | Just try to keep good quality of your work. 7 | 8 | ## Styleguides 9 | 10 | ### Common Convencions 11 | 12 | * Use tabs instead spaces (tab = 4 spaces) 13 | 14 | ### Git Commit Messages 15 | 16 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 17 | 18 | $ git commit -m "A brief summary of the commit 19 | > 20 | > A paragraph describing what changed and its impact." 21 | 22 | * Use the present tense ("Add feature" not "Added feature") 23 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 24 | * Limit the first line to 72 characters or less 25 | * Reference issues and pull requests liberally after the first line 26 | * When only changing documentation, include `[ci skip]` in the commit message 27 | * Consider starting the commit message with an applicable emoji: 28 | * :art: `:art:` when improving the format/structure of the code 29 | * :racehorse: `:racehorse:` when improving performance 30 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks 31 | * :memo: `:memo:` when writing docs 32 | * :penguin: `:penguin:` when fixing something on Linux 33 | * :apple: `:apple:` when fixing something on macOS 34 | * :checkered_flag: `:checkered_flag:` when fixing something on Windows 35 | * :bug: `:bug:` when fixing a bug 36 | * :fire: `:fire:` when removing code or files 37 | * :green_heart: `:green_heart:` when fixing the CI build 38 | * :white_check_mark: `:white_check_mark:` when adding tests 39 | * :lock: `:lock:` when dealing with security 40 | * :arrow_up: `:arrow_up:` when upgrading dependencies 41 | * :arrow_down: `:arrow_down:` when downgrading dependencies 42 | * :shirt: `:shirt:` when removing linter warnings 43 | 44 | ### C++ Styleguide 45 | 46 | Style is definied in [clang-format file](/.clang-format), so please just run `clang-format in root directory :) Style now should be fine. 47 | 48 | In other cases please follow style form older peaces of code :) 49 | 50 | 51 | Thanks 52 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | -------------------------------------------------------------------------------- /docs/THANKS.md: -------------------------------------------------------------------------------- 1 | # Big thanks to: 2 | 3 | - Nice article about template arguments deduction: 4 | 5 | ["Function templates - deduce template arguments or pass explicitly?"](https://foonathan.net/2016/11/template-argument-deduction/) 6 | - TBC ... 7 | -------------------------------------------------------------------------------- /docs/res/EventBus-concept.draw.io.xml: -------------------------------------------------------------------------------- 1 | 7Vnbbts4EP0aA92HBLpYivNoO06yWBdrNED7TEmUxJYStRTlS79+hyKpi+3YTmKnAVonSMjD4W3mzAxJD9xptn7gqEg/swjTgWNF64F7N3Dgc3sD/ySyUYhtea5CEk4ijbXAE/mJjaBGKxLhsicoGKOCFH0wZHmOQ9HDEOds1ReLGe3PWqAE7wBPIaK76DcSiVShI89q8UdMktTMbFu6JUNGWANliiK26kDubOBOOWNClbL1FFOpPaMX1e/+mdZmYRzn4pQOq/BxWI0fvH++L4No8beYJl+vrrR5SrExG8YR7F9XGRcpS1iO6KxFJ5xVeYTlqBbUWpk5YwWANoDfsRAbbUxUCQZQKjKqW0vB2Y9GnS4gESrTekTZvLsxvdeSVTzEB3bjaIIgnmBxQE5TUG61M4FW2wNmGRZ8AwIcUyTIsk8FpBmVNHKt0qGg9f4CG4x+SxsMP5QN9KqXiFZ6poHjU1j/pOAYiomolaKgmIFSugbz/6uYabgqa5WPQQAUuG4bzSizJeh0UpVmNFivGrA/CcC9qbco0ifAKiUCPxWotswKQnHf2DGhdMoo43VfN45jJwwbEnRaIj/wPf+Q/ZeYC7w+aDHd6vqe6qJDv+PrQLhq46htgmPaiaFG7uxGdi9hZNvbZ+Q5KQXOMX+bkSFlFLJYcBbisjxu6ACFP5KaGv9WgpIc7ydAhPAo3ksAPxzhID4PAeyR1SOA6/1qAgz/EOA9CeANPxoB/I+Ral+fXb0Ts6vzobKr937ZdVEFlMBB5kyOh9eyOWIwqnGlt2TeyMOjaLjP70ZO4PoXyry3J7rd6FJu57i/wu96B1pZWSAhMM9rxLGGb3JEcz897olnd8W665hztOkIFIzkouyMvJBAywnf7cdib+uyuCXuWofEoaDmbynRbOT1LDEq7YSJB3ld3/HNFckokqmtDgmGRZIlYUpoNEcbVkmTlAKyoalNUsbJT5BHbVxGXGj2OH5P4kn21GNyLGPOwnDE3oI+o3VPcI5KYVbDKEVFSdrYkQFbSD5hQrBMC8kt3KOMUKnpr5hHKEdnSr83fRvaNyfGAc+5UByw7R0LD5yJnCkDZxcqrtc83DK5UHEYUZJI96U4llWpChIiOtawkIFhUkIwJnkyr2Xuhi3yRe9QQgz6xrR+jklJFOG8DjgCCRQ0ZNIuBQvwJvALippa197AgwVNoW63dfiV4lxMWQ6RHZHaZhiYsMKl2HMB8+TP3uNX/dG06ODq8xK6HHax4yRqbm2nccbInT937OOMyuFg19xk8HEuFx1SBKdk5x70WoWik+u7ojvsOpLDzVkgWyfykfM6QCUJr+v09AWHYoooVRGmfva0rWtHlqX+7JHsriKMNHegUto86Ayt3gStXZLcj+XPq0hyhuDhb13eGqN3iWC959ndJNIz396G+06RQVVeQRllkgo1UrBSFCzHn+qUdDMBT7q5++tth8w+x1oiNAcXqxPzQrAlnGp3o14G8as+MG3zVD1/O+q/oaTV8hRKORNhairP8e8MbNp+CrBvnR02OcM9bHIvxqbLvAU4+9jE8gVFG8xniOefQpkiQFQdbTTF1F95SpSPg39YdRqrmnRm3hes3Rj1zqy6yE33OVZJCkEGwnBpOIVWv0e0iikpHgdHvsV4QR48hWP+eTgG1fbrQHWrar9VdWf/Aw== -------------------------------------------------------------------------------- /docs/res/EventBus-concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gelldur/EventBus/8e29fb4c53cfcc1d58918dfbe5ea6812c34f6df4/docs/res/EventBus-concept.png -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 2 | 3 | # Use ';' to specify multiple e.g. ZIP;TGZ;DEB 4 | set(CPACK_GENERATOR "" CACHE STRING "Set packages CPack should build e.g. ZIP;TGZ;DEB") 5 | 6 | # BUILD_SHARED_LIBS can controll build type! 7 | project(EventBus 8 | VERSION 3.0.4 9 | LANGUAGES CXX 10 | ) 11 | 12 | # Dependencies 13 | find_package(Threads) 14 | 15 | # Introduce variables: 16 | # * CMAKE_INSTALL_LIBDIR 17 | # * CMAKE_INSTALL_BINDIR 18 | # * CMAKE_INSTALL_INCLUDEDIR 19 | include(GNUInstallDirs) 20 | include(cmake/InstallHelp.cmake) 21 | 22 | # Layout. This works for all platforms: 23 | # * /lib*/cmake/ 24 | # * /lib*/ 25 | # * /include/ 26 | set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") 27 | 28 | set(EventBus_PUBLIC_HEADERS 29 | src/dexode/EventBus.hpp 30 | src/dexode/eventbus/Bus.hpp 31 | src/dexode/eventbus/internal/event_id.hpp 32 | src/dexode/eventbus/internal/listener_traits.hpp 33 | src/dexode/eventbus/internal/ListenerAttorney.hpp 34 | src/dexode/eventbus/Listener.hpp 35 | src/dexode/eventbus/perk/PassPerk.hpp 36 | src/dexode/eventbus/perk/Perk.hpp 37 | src/dexode/eventbus/perk/PerkEventBus.hpp 38 | src/dexode/eventbus/perk/TagPerk.hpp 39 | src/dexode/eventbus/perk/WaitPerk.hpp 40 | src/dexode/eventbus/permission/PostponeBus.hpp 41 | src/dexode/eventbus/stream/EventStream.hpp 42 | src/dexode/eventbus/stream/ProtectedEventStream.hpp 43 | ) 44 | 45 | # Library definition 46 | add_library(EventBus 47 | ${EventBus_PUBLIC_HEADERS} 48 | src/dexode/EventBus.cpp 49 | src/dexode/eventbus/perk/PassPerk.cpp 50 | src/dexode/eventbus/perk/Perk.cpp 51 | src/dexode/eventbus/perk/PerkEventBus.cpp 52 | src/dexode/eventbus/perk/TagPerk.cpp 53 | src/dexode/eventbus/perk/WaitPerk.cpp 54 | ) 55 | add_library(Dexode::EventBus ALIAS EventBus) 56 | 57 | # Why C++ 17 needed: 58 | # - std::shared_mutex used 59 | # - nested namespaces e.g. my::name::space 60 | 61 | set_target_properties(EventBus PROPERTIES 62 | CXX_STANDARD 17 63 | CXX_STANDARD_REQUIRED YES 64 | CXX_EXTENSIONS NO # -std=c++17 rather than -std=gnu++17 65 | ) 66 | 67 | target_compile_features(EventBus PUBLIC cxx_std_17) 68 | 69 | target_include_directories(EventBus PUBLIC 70 | $ 71 | $ 72 | ) 73 | target_link_libraries(EventBus ${CMAKE_THREAD_LIBS_INIT}) 74 | 75 | # Add definitions for targets 76 | # Values: 77 | # * Debug: -DEVENTBUS_DEBUG=1 78 | # * Release: -DEVENTBUS_DEBUG=0 79 | # * other: -DEVENTBUS_DEBUG=0 80 | target_compile_definitions(EventBus PUBLIC "EVENTBUS_DEBUG=$") 81 | 82 | 83 | # Configuration 84 | set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") 85 | set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") 86 | set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") 87 | set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") 88 | set(namespace "Dexode::") 89 | 90 | # Targets: 91 | # * /lib/libEventBus.a 92 | # * header location after install: /include/eventbus/EventBus.h 93 | # * headers can be included by C++ code `#include ` 94 | install(TARGETS EventBus 95 | EXPORT "${TARGETS_EXPORT_NAME}" 96 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 97 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 98 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 99 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 100 | ) 101 | 102 | # Include module with fuction 'write_basic_package_version_file' 103 | include(CMakePackageConfigHelpers) 104 | 105 | # Configure 'ConfigVersion.cmake' 106 | # Use: 107 | # * PROJECT_VERSION 108 | write_basic_package_version_file( 109 | "${version_config}" COMPATIBILITY SameMajorVersion 110 | ) 111 | 112 | # Configure 'Config.cmake' 113 | # Use variables: 114 | # * TARGETS_EXPORT_NAME 115 | # * PROJECT_NAME 116 | configure_package_config_file( 117 | "cmake/Config.cmake.in" 118 | "${project_config}" 119 | INSTALL_DESTINATION "${config_install_dir}" 120 | ) 121 | 122 | # Config 123 | # * /lib/cmake/EventBusventBusConfig.cmake 124 | # * /lib/cmake/EventBus/EventBusConfigVersion.cmake 125 | install( 126 | FILES "${project_config}" "${version_config}" 127 | DESTINATION "${config_install_dir}" 128 | # We don't want someone by accident modify his installed files 129 | PERMISSIONS OWNER_EXECUTE OWNER_READ 130 | ) 131 | 132 | # Config 133 | # * /lib/cmake/EventBus/EventBusTargets.cmake 134 | install(EXPORT "${TARGETS_EXPORT_NAME}" 135 | DESTINATION "${config_install_dir}" 136 | NAMESPACE "${namespace}" 137 | # We don't want someone by accident modify his installed files 138 | PERMISSIONS OWNER_EXECUTE OWNER_READ 139 | ) 140 | 141 | # Export headers (Install public headers) 142 | install_public_headers_with_directory(EventBus_PUBLIC_HEADERS "src/") 143 | 144 | # Cpack configuration 145 | if(NOT CPACK_GENERATOR STREQUAL "") 146 | include(cmake/EventBus_CPack.cmake) 147 | enable_cpack(${CPACK_GENERATOR}) 148 | endif() 149 | 150 | # If the compiler is MSVC, install the PDB file and add the debug suffix 151 | if(MSVC) 152 | set_target_properties(${PROJECT_NAME} PROPERTIES 153 | DEBUG_POSTFIX "d" 154 | PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 155 | COMPILE_PDB_NAME_DEBUG "${PROJECT_NAME}d" 156 | COMPILE_PDB_NAME_RELEASE ${PROJECT_NAME} 157 | COMPILE_PDB_NAME_MINSIZEREL ${PROJECT_NAME} 158 | COMPILE_PDB_NAME_RELWITHDEBINFO ${PROJECT_NAME}) 159 | 160 | get_target_property(PROJECT_PDB_NAME_DEBUG ${PROJECT_NAME} COMPILE_PDB_NAME_DEBUG) 161 | get_target_property(PROJECT_PDB_NAME_RELEASE ${PROJECT_NAME} COMPILE_PDB_NAME_RELEASE) 162 | get_target_property(PROJECT_PDB_DIRECTORY ${PROJECT_NAME} PDB_OUTPUT_DIRECTORY) 163 | set(PROJECT_PDB_NAME "$<$:${PROJECT_PDB_NAME_DEBUG}>$<$>:${PROJECT_PDB_NAME_RELEASE}>.pdb") 164 | install(FILES "${PROJECT_PDB_DIRECTORY}/${PROJECT_PDB_NAME}" DESTINATION lib OPTIONAL) 165 | install(FILES "${PROJECT_PDB_DIRECTORY}/\${CMAKE_INSTALL_CONFIG_NAME}/${PROJECT_PDB_NAME}" DESTINATION lib OPTIONAL) 166 | endif() -------------------------------------------------------------------------------- /lib/cmake/Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /lib/cmake/EventBus_CPack.cmake: -------------------------------------------------------------------------------- 1 | # CPack Configuration 2 | function(enable_cpack generator) 3 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 4 | set(CPACK_GENERATOR ${generator}) 5 | set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) 6 | set(CPACK_PACKAGE_RELEASE 1) 7 | set(CPACK_PACKAGE_CONTACT "gelldur") 8 | set(CPACK_PACKAGE_VENDOR "gelldur") 9 | set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) 10 | set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}") 11 | set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION 12 | "${CMAKE_INSTALL_PREFIX}" 13 | "${CMAKE_INSTALL_PREFIX}/include" 14 | "${CMAKE_INSTALL_PREFIX}/lib64" 15 | "${CMAKE_INSTALL_PREFIX}/lib" 16 | ) 17 | include(CPack) 18 | endfunction() 19 | -------------------------------------------------------------------------------- /lib/cmake/InstallHelp.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Installs files with preserving paths. 3 | # 4 | # Example usage: 5 | # install_public_headers_with_directory(MyHeadersList "src/") 6 | # 7 | macro(install_public_headers_with_directory HEADER_LIST IGNORE_PREFIX) 8 | foreach(HEADER ${${HEADER_LIST}}) 9 | get_filename_component(DIR ${HEADER} DIRECTORY) 10 | string(REPLACE ${IGNORE_PREFIX} "" DIR ${DIR}) 11 | install( 12 | FILES ${HEADER} 13 | DESTINATION include/${DIR} 14 | # We don't want someone by accident modify his installed files 15 | PERMISSIONS OWNER_EXECUTE OWNER_READ 16 | ) 17 | endforeach(HEADER) 18 | 19 | endmacro(install_public_headers_with_directory) 20 | -------------------------------------------------------------------------------- /lib/src/dexode/EventBus.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 26.11.2019. 3 | // 4 | #include "EventBus.hpp" 5 | 6 | #include 7 | 8 | namespace dexode 9 | { 10 | 11 | std::size_t EventBus::processLimit(const std::size_t limit) 12 | { 13 | std::size_t processCount{0}; 14 | std::lock_guard writeGuardProcess{_mutexProcess}; // Only one process at the time 15 | 16 | std::vector> eventStreams; 17 | { 18 | std::lock_guard writeGuard{_mutexStreams}; 19 | std::swap(eventStreams, _eventStreams); // move data FROM member 20 | } 21 | 22 | // Now if any setStream would be called it doesn't conflict without our process call 23 | for(auto& eventStream : eventStreams) 24 | { 25 | const auto runProcessCount = eventStream->process(limit); 26 | processCount += runProcessCount; 27 | if(processCount >= limit) 28 | { 29 | break; 30 | } 31 | } 32 | 33 | { 34 | std::lock_guard writeGuard{_mutexStreams}; 35 | if(!_eventStreams.empty()) 36 | { 37 | // If anything was added then we need to add those elements 38 | std::move(_eventStreams.begin(), _eventStreams.end(), std::back_inserter(eventStreams)); 39 | } 40 | std::swap(eventStreams, _eventStreams); // move data TO member 41 | 42 | // Check do we need remove something 43 | if(_eventStreams.size() != _eventToStream.size()) 44 | { 45 | auto removeFrom = std::remove_if( 46 | _eventStreams.begin(), _eventStreams.end(), [this](const auto& eventStream) { 47 | for(const auto& element : _eventToStream) 48 | { 49 | // Don't remove if we point to the same place (is it UB ?) 50 | if(element.second == eventStream.get()) 51 | { 52 | return false; 53 | } 54 | } 55 | return true; 56 | }); 57 | assert(removeFrom != _eventStreams.end()); 58 | _eventStreams.erase(removeFrom, _eventStreams.end()); 59 | } 60 | } 61 | 62 | return processCount; 63 | } 64 | 65 | eventbus::stream::EventStream* EventBus::findStream( 66 | const eventbus::internal::event_id_t eventID) const 67 | { 68 | std::shared_lock readGuard{_mutexStreams}; 69 | return findStreamUnsafe(eventID); 70 | } 71 | 72 | void EventBus::unlistenAll(const std::uint32_t listenerID) 73 | { 74 | std::shared_lock readGuard{_mutexStreams}; 75 | for(const auto& eventStream : _eventToStream) 76 | { 77 | eventStream.second->removeListener(listenerID); 78 | } 79 | } 80 | 81 | eventbus::stream::EventStream* EventBus::findStreamUnsafe( 82 | const eventbus::internal::event_id_t eventID) const 83 | { 84 | auto lookup = _eventToStream.find(eventID); 85 | return lookup != _eventToStream.end() ? lookup->second : nullptr; 86 | } 87 | 88 | eventbus::stream::EventStream* EventBus::obtainStream( 89 | const eventbus::internal::event_id_t eventID, 90 | eventbus::CreateStreamCallback createStreamCallback) 91 | { 92 | std::lock_guard writeGuard{_mutexStreams}; 93 | auto* found = findStreamUnsafe(eventID); 94 | if(found != nullptr) 95 | { 96 | return found; 97 | } 98 | else 99 | { 100 | auto stream = createStreamCallback(); 101 | _eventStreams.push_back(std::move(stream)); 102 | _eventToStream[eventID] = _eventStreams.back().get(); 103 | return _eventStreams.back().get(); 104 | } 105 | } 106 | 107 | eventbus::stream::EventStream* EventBus::streamForEvent( 108 | eventbus::internal::event_id_t eventID) const 109 | { 110 | std::lock_guard writeGuard{_mutexStreams}; 111 | auto* found = findStreamUnsafe(eventID); 112 | if(found != nullptr) 113 | { 114 | return found; 115 | } 116 | return nullptr; 117 | } 118 | 119 | bool EventBus::postponeEvent(eventbus::PostponeHelper& postponeCall) 120 | { 121 | auto* eventStream = obtainStream(postponeCall.eventID, postponeCall.createStreamCallback); 122 | eventStream->postpone(std::move(postponeCall.event)); 123 | return true; 124 | } 125 | 126 | eventbus::stream::EventStream* EventBus::listen(const std::uint32_t, 127 | const eventbus::internal::event_id_t eventID, 128 | eventbus::CreateStreamCallback createStreamCallback) 129 | { 130 | auto* eventStream = obtainStream(eventID, createStreamCallback); 131 | return eventStream; 132 | } 133 | 134 | void EventBus::unlisten(const std::uint32_t listenerID, 135 | const eventbus::internal::event_id_t eventID) 136 | { 137 | eventbus::stream::EventStream* eventStream = findStream(eventID); 138 | if(eventStream != nullptr) 139 | { 140 | eventStream->removeListener(listenerID); 141 | } 142 | } 143 | 144 | } // namespace dexode 145 | -------------------------------------------------------------------------------- /lib/src/dexode/EventBus.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 26.11.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "dexode/eventbus/Bus.hpp" 14 | 15 | namespace dexode 16 | { 17 | 18 | class EventBus : public dexode::eventbus::Bus 19 | { 20 | template 21 | friend class dexode::eventbus::internal::ListenerAttorney; 22 | 23 | public: 24 | std::size_t process() override 25 | { 26 | return processLimit(std::numeric_limits::max()); 27 | } 28 | 29 | std::size_t processLimit(std::size_t limit); 30 | 31 | protected: 32 | eventbus::stream::EventStream* streamForEvent( 33 | eventbus::internal::event_id_t eventID) const override; 34 | 35 | eventbus::stream::EventStream* obtainStream( 36 | eventbus::internal::event_id_t eventID, 37 | eventbus::CreateStreamCallback createStreamCallback); 38 | 39 | bool postponeEvent(eventbus::PostponeHelper& postponeCall) override; 40 | eventbus::stream::EventStream* findStream(eventbus::internal::event_id_t eventID) const; 41 | 42 | void unlistenAll(std::uint32_t listenerID) override; 43 | eventbus::stream::EventStream* listen( 44 | std::uint32_t listenerID, 45 | eventbus::internal::event_id_t eventID, 46 | eventbus::CreateStreamCallback createStreamCallback) override; 47 | void unlisten(std::uint32_t listenerID, eventbus::internal::event_id_t eventID) override; 48 | 49 | private: 50 | mutable std::shared_mutex _mutexStreams; 51 | std::shared_mutex _mutexProcess; 52 | std::vector> _eventStreams; 53 | std::map _eventToStream; 54 | 55 | eventbus::stream::EventStream* findStreamUnsafe(eventbus::internal::event_id_t eventID) const; 56 | }; 57 | 58 | } // namespace dexode 59 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/Bus.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 26.11.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dexode/eventbus/Listener.hpp" 11 | #include "dexode/eventbus/internal/ListenerAttorney.hpp" 12 | #include "dexode/eventbus/internal/event_id.hpp" 13 | #include "dexode/eventbus/stream/ProtectedEventStream.hpp" 14 | 15 | namespace dexode::eventbus 16 | { 17 | 18 | class Bus; 19 | 20 | template 21 | using DefaultEventStream = eventbus::stream::ProtectedEventStream; 22 | using CreateStreamCallback = std::unique_ptr (*const)(); 23 | using PostponeCallback = bool (*const)(Bus& bus, std::any event); 24 | 25 | template 26 | bool postpone(Bus& bus, std::any event); 27 | 28 | template 29 | std::unique_ptr createDefaultEventStream() 30 | { 31 | return std::make_unique>(); 32 | } 33 | 34 | class PostponeHelper 35 | { 36 | public: 37 | internal::event_id_t eventID = nullptr; 38 | std::any event; 39 | 40 | PostponeCallback postponeCallback = nullptr; // function pointer 41 | CreateStreamCallback createStreamCallback = nullptr; // function pointer 42 | 43 | PostponeHelper(const internal::event_id_t eventId, 44 | std::any&& event, 45 | PostponeCallback postponeCallback, 46 | CreateStreamCallback createStreamCallback) 47 | : eventID(eventId) 48 | , event(std::forward(event)) 49 | , postponeCallback(postponeCallback) 50 | , createStreamCallback(createStreamCallback) 51 | {} 52 | 53 | template 54 | static PostponeHelper create(std::any&& event) 55 | { 56 | return PostponeHelper{internal::event_id(), 57 | std::forward(event), 58 | postpone, 59 | createDefaultEventStream}; 60 | } 61 | 62 | ~PostponeHelper() = default; 63 | }; 64 | 65 | class Bus 66 | { 67 | template 68 | friend class dexode::eventbus::internal::ListenerAttorney; 69 | 70 | public: 71 | using Listener = eventbus::Listener; 72 | 73 | Bus() = default; 74 | virtual ~Bus() = default; 75 | 76 | virtual std::size_t process() = 0; 77 | 78 | template 79 | bool postpone(Event event) 80 | { 81 | static_assert(internal::validateEvent(), "Invalid event"); 82 | auto postponeCall = PostponeHelper::create(std::move(event)); 83 | return postponeEvent(postponeCall); 84 | } 85 | 86 | protected: 87 | virtual bool postponeEvent(PostponeHelper& postponeCall) = 0; 88 | virtual eventbus::stream::EventStream* listen(std::uint32_t listenerID, 89 | internal::event_id_t eventID, 90 | CreateStreamCallback createStreamCallback) = 0; 91 | 92 | virtual void unlistenAll(std::uint32_t listenerID) = 0; 93 | virtual void unlisten(std::uint32_t listenerID, internal::event_id_t eventID) = 0; 94 | 95 | virtual eventbus::stream::EventStream* streamForEvent( 96 | eventbus::internal::event_id_t eventID) const = 0; 97 | 98 | private: 99 | std::atomic _lastID{0}; 100 | 101 | std::uint32_t newListenerID() 102 | { 103 | return ++_lastID; // used for generate unique listeners ID's 104 | } 105 | 106 | template 107 | void listen(const std::uint32_t listenerID, std::function&& callback) 108 | { 109 | static_assert(internal::validateEvent(), "Invalid event"); 110 | assert(callback && "callback should be valid"); // Check for valid object 111 | 112 | constexpr auto eventID = internal::event_id(); 113 | 114 | auto* eventStream = listen(listenerID, eventID, createDefaultEventStream); 115 | if(eventStream != nullptr) // maybe someone don't want add listener 116 | { 117 | eventStream->addListener(listenerID, 118 | std::forward>(callback)); 119 | } 120 | } 121 | }; 122 | 123 | template 124 | bool postpone(Bus& bus, std::any event) 125 | { 126 | return bus.postpone(std::move(std::any_cast(event))); 127 | } 128 | 129 | } // namespace dexode::eventbus 130 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/Listener.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "dexode/eventbus/internal/ListenerAttorney.hpp" 9 | #include "dexode/eventbus/internal/event_id.hpp" 10 | #include "dexode/eventbus/internal/listener_traits.hpp" 11 | 12 | namespace dexode::eventbus 13 | { 14 | 15 | template 16 | class Listener 17 | { 18 | public: 19 | explicit Listener() = default; // Dummy listener 20 | 21 | explicit Listener(std::shared_ptr bus) 22 | : _id{internal::ListenerAttorney::newListenerID(*bus)} 23 | , _bus{std::move(bus)} 24 | {} 25 | 26 | static Listener createNotOwning(Bus& bus) 27 | { 28 | // This isn't safe but nice for playing around 29 | return Listener{std::shared_ptr{&bus, [](Bus*) {}}}; 30 | } 31 | 32 | Listener(const Listener& other) = delete; 33 | // To see why move is disabled search for tag: FORBID_MOVE_LISTENER in tests 34 | // Long story short, what if we capture 'this' in lambda during registering listener in ctor 35 | Listener(Listener&& other) = delete; 36 | 37 | ~Listener() 38 | { 39 | if(_bus != nullptr) // could be moved 40 | { 41 | unlistenAll(); 42 | } 43 | } 44 | 45 | Listener& operator=(const Listener& other) = delete; 46 | // To see why move is disabled search for tag: FORBID_MOVE_LISTENER in tests 47 | Listener& operator=(Listener&& other) = delete; 48 | 49 | template 50 | constexpr void listen(std::function&& callback) 51 | { 52 | static_assert(internal::validateEvent(), "Invalid event"); 53 | listenToCallback(std::forward>(callback)); 54 | } 55 | 56 | template > 57 | constexpr void listen(EventCallback&& callback) 58 | { 59 | static_assert(std::is_const_v>, "Event should be const"); 60 | static_assert(std::is_reference_v, "Event should be const & (reference)"); 61 | using PureEvent = std::remove_const_t>; 62 | static_assert(internal::validateEvent(), "Invalid event"); 63 | 64 | listenToCallback(std::forward(callback)); 65 | } 66 | 67 | template 68 | void listenToCallback(std::function&& callback) 69 | { 70 | static_assert(internal::validateEvent(), "Invalid event"); 71 | if(_bus == nullptr) 72 | { 73 | throw std::runtime_error{"bus is null"}; 74 | } 75 | 76 | internal::ListenerAttorney::template listen( 77 | *_bus, _id, std::forward>(callback)); 78 | } 79 | 80 | template 81 | void listenToCallback(const std::function& callback) 82 | { 83 | static_assert(internal::validateEvent(), "Invalid event"); 84 | if(_bus == nullptr) 85 | { 86 | throw std::runtime_error{"bus is null"}; 87 | } 88 | internal::ListenerAttorney::template listen( 89 | *_bus, _id, std::function{callback}); 90 | } 91 | 92 | void unlistenAll() 93 | { 94 | if(_bus == nullptr) 95 | { 96 | throw std::runtime_error{"bus is null"}; 97 | } 98 | internal::ListenerAttorney::unlistenAll(*_bus, _id); 99 | } 100 | 101 | template 102 | void unlisten() 103 | { 104 | static_assert(internal::validateEvent(), "Invalid event"); 105 | if(_bus == nullptr) 106 | { 107 | throw std::runtime_error{"bus is null"}; 108 | } 109 | internal::ListenerAttorney::unlisten(*_bus, _id, internal::event_id()); 110 | } 111 | 112 | // We want more explicit move so user knows what is going on 113 | void transfer(Listener&& from) 114 | { 115 | if(this == &from) 116 | { 117 | throw std::runtime_error("Self transfer not allowed"); 118 | } 119 | 120 | if(_bus != nullptr) 121 | { 122 | unlistenAll(); // remove previous 123 | } 124 | // we don't have to reset listener ID as bus is moved and we won't call unlistenAll 125 | _id = from._id; 126 | _bus = std::move(from._bus); 127 | } 128 | 129 | const std::shared_ptr& getBus() const 130 | { 131 | return _bus; 132 | } 133 | 134 | template 135 | [[nodiscard]] bool isListening() const 136 | { 137 | static_assert(internal::validateEvent(), "Invalid event"); 138 | if(_bus == nullptr) 139 | { 140 | throw std::runtime_error{"bus is null"}; 141 | } 142 | return internal::ListenerAttorney::isListening(*_bus 143 | , _id 144 | , internal::event_id()); 145 | } 146 | 147 | private: 148 | std::uint32_t _id = 0; 149 | std::shared_ptr _bus = nullptr; 150 | }; 151 | 152 | } // namespace dexode::eventbus 153 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/internal/ListenerAttorney.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 30.10.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "dexode/eventbus/internal/event_id.hpp" 9 | #include "dexode/eventbus/stream/EventStream.hpp" 10 | 11 | namespace dexode::eventbus 12 | { 13 | template 14 | class Listener; 15 | 16 | } // namespace dexode::eventbus 17 | 18 | namespace dexode::eventbus::internal 19 | { 20 | 21 | template 22 | class ListenerAttorney 23 | { 24 | template 25 | friend class dexode::eventbus::Listener; 26 | 27 | private: 28 | static constexpr std::uint32_t newListenerID(EventBus_t& bus) 29 | { 30 | return bus.newListenerID(); 31 | } 32 | 33 | template 34 | static constexpr void listen(EventBus_t& bus, 35 | const std::uint32_t listenerID, 36 | std::function&& callback) 37 | { 38 | bus.template listen(listenerID, 39 | std::forward>(callback)); 40 | } 41 | 42 | static constexpr void unlistenAll(EventBus_t& bus, const std::uint32_t listenerID) 43 | { 44 | bus.unlistenAll(listenerID); 45 | } 46 | 47 | static constexpr void unlisten(EventBus_t& bus, 48 | const std::uint32_t listenerID, 49 | const event_id_t eventID) 50 | { 51 | bus.unlisten(listenerID, eventID); 52 | } 53 | 54 | static constexpr bool isListening(EventBus_t& bus, 55 | const std::uint32_t listenerID, 56 | const event_id_t eventID) 57 | { 58 | const eventbus::stream::EventStream* stream = bus.streamForEvent(eventID); 59 | if(stream != nullptr) 60 | { 61 | return stream->hasListener(listenerID); 62 | } 63 | return false; 64 | } 65 | }; 66 | 67 | } // namespace dexode::eventbus::internal 68 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/internal/event_id.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace dexode::eventbus::internal 6 | { 7 | 8 | template 9 | struct type_id_ptr 10 | { 11 | static const T* const id; 12 | }; 13 | 14 | template 15 | const T* const type_id_ptr::id = nullptr; 16 | 17 | using event_id_t = const void*; 18 | 19 | template 20 | constexpr event_id_t event_id() // Helper for getting "type id" 21 | { 22 | return &type_id_ptr::id; 23 | } 24 | 25 | template 26 | constexpr bool validateEvent() 27 | { 28 | static_assert(std::is_const::value == false, "Struct must be without const"); 29 | static_assert(std::is_volatile::value == false, "Struct must be without volatile"); 30 | static_assert(std::is_reference::value == false, "Struct must be without reference"); 31 | static_assert(std::is_pointer::value == false, "Struct must be without pointer"); 32 | return true; 33 | } 34 | 35 | } // namespace dexode::eventbus::internal 36 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/internal/listener_traits.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace dexode::eventbus::internal 10 | { 11 | 12 | template 13 | Arg first_argument_helper(Ret (*)(Arg, Rest...)); 14 | 15 | template 16 | Arg first_argument_helper(Ret (F::*)(Arg, Rest...)); 17 | 18 | template 19 | Arg first_argument_helper(Ret (F::*)(Arg, Rest...) const); 20 | 21 | template 22 | decltype(first_argument_helper(&F::operator())) first_argument_helper(F); 23 | 24 | template 25 | using first_argument = decltype(first_argument_helper(std::declval())); 26 | 27 | } // namespace dexode::eventbus::internal 28 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/PassPerk.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #include "PassPerk.hpp" 5 | 6 | #include "dexode/eventbus/Bus.hpp" 7 | 8 | namespace dexode::eventbus::perk 9 | { 10 | 11 | Flag PassEverythingPerk::onPrePostponeEvent(PostponeHelper& postponeCall) 12 | { 13 | postponeCall.postponeCallback(*_passToBus, std::move(postponeCall.event)); 14 | return Flag::postpone_cancel; 15 | } 16 | 17 | } // namespace dexode::eventbus::perk 18 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/PassPerk.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "Perk.hpp" 9 | 10 | namespace dexode::eventbus 11 | { 12 | class PostponeHelper; 13 | class Bus; 14 | } // namespace dexode::eventbus 15 | 16 | namespace dexode::eventbus::perk 17 | { 18 | 19 | class PassEverythingPerk : public Perk 20 | { 21 | public: 22 | PassEverythingPerk(std::shared_ptr passTo) 23 | : _passToBus{std::move(passTo)} 24 | {} 25 | 26 | Flag onPrePostponeEvent(PostponeHelper& postponeCall); 27 | 28 | private: 29 | std::shared_ptr _passToBus; 30 | }; 31 | 32 | } // namespace dexode::eventbus::perk 33 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/Perk.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 23.12.2019. 3 | // 4 | #include "Perk.hpp" 5 | 6 | namespace dexode::eventbus::perk 7 | { 8 | 9 | } // namespace dexode::eventbus::perk 10 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/Perk.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 23.12.2019. 3 | // 4 | #pragma once 5 | 6 | namespace dexode::eventbus::perk 7 | { 8 | 9 | enum class Flag : int 10 | { 11 | nop, 12 | postpone_cancel, 13 | postpone_continue, 14 | }; 15 | 16 | class Perk 17 | { 18 | public: 19 | virtual ~Perk() = default; 20 | }; 21 | 22 | } // namespace dexode::eventbus::perk 23 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/PerkEventBus.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 23.12.2019. 3 | // 4 | #include "PerkEventBus.hpp" 5 | 6 | namespace dexode::eventbus::perk 7 | { 8 | 9 | PerkEventBus::RegisterHelper PerkEventBus::addPerk(std::shared_ptr perk) 10 | { 11 | auto* local = perk.get(); 12 | _perks.push_back(std::move(perk)); 13 | return RegisterHelper(this, local); 14 | } 15 | 16 | bool PerkEventBus::postponeEvent(PostponeHelper& postponeCall) 17 | { 18 | for(const auto& onPrePostpone : _onPrePostpone) 19 | { 20 | if(onPrePostpone(postponeCall) == perk::Flag::postpone_cancel) 21 | { 22 | return false; 23 | } 24 | } 25 | if(EventBus::postponeEvent(postponeCall)) 26 | { 27 | for(const auto& onPostPostpone : _onPostPostpone) 28 | { 29 | if(onPostPostpone(postponeCall) == perk::Flag::postpone_cancel) 30 | { 31 | break; 32 | } 33 | } 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | } // namespace dexode::eventbus::perk 40 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/PerkEventBus.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 23.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "Perk.hpp" 10 | #include "dexode/EventBus.hpp" 11 | 12 | namespace dexode::eventbus::perk 13 | { 14 | 15 | class PerkEventBus : public EventBus 16 | { 17 | public: 18 | class RegisterHelper 19 | { 20 | friend PerkEventBus; 21 | 22 | public: 23 | template 24 | RegisterHelper& registerPrePostpone(perk::Flag (Perk_t::*method)(PostponeHelper&)) 25 | { 26 | _bus->_onPrePostpone.push_back( 27 | std::bind(method, static_cast(_perk), std::placeholders::_1)); 28 | return *this; 29 | } 30 | 31 | template 32 | RegisterHelper& registerPostPostpone(perk::Flag (Perk_t::*method)(PostponeHelper&)) 33 | { 34 | _bus->_onPostPostpone.push_back( 35 | std::bind(method, static_cast(_perk), std::placeholders::_1)); 36 | return *this; 37 | } 38 | 39 | private: 40 | PerkEventBus* _bus; 41 | Perk* _perk; 42 | 43 | RegisterHelper(PerkEventBus* bus, Perk* perk) 44 | : _bus(bus) 45 | , _perk(perk) 46 | {} 47 | }; 48 | 49 | RegisterHelper addPerk(std::shared_ptr perk); 50 | 51 | template 52 | T* getPerk() 53 | { 54 | auto found = 55 | std::find_if(_perks.begin(), _perks.end(), [](const std::shared_ptr& perk) { 56 | return dynamic_cast(perk.get()) != nullptr; 57 | }); 58 | if(found != _perks.end()) 59 | { 60 | return static_cast(found->get()); 61 | } 62 | return nullptr; 63 | } 64 | 65 | protected: 66 | bool postponeEvent(PostponeHelper& postponeCall) override; 67 | 68 | private: 69 | std::vector> _perks; 70 | std::vector> _onPrePostpone; 71 | std::vector> _onPostPostpone; 72 | }; 73 | 74 | } // namespace dexode::eventbus::perk 75 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/TagPerk.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #include "TagPerk.hpp" 5 | 6 | namespace dexode::eventbus::perk 7 | { 8 | 9 | Flag TagPerk::onPrePostponeEvent(PostponeHelper& postponeCall) 10 | { 11 | if(auto found = _eventsToWrap.find(postponeCall.eventID); found != _eventsToWrap.end()) 12 | { 13 | found->second(postponeCall.event); 14 | return Flag::postpone_cancel; 15 | } 16 | 17 | return Flag::postpone_continue; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/TagPerk.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Perk.hpp" 12 | #include "dexode/eventbus/Bus.hpp" 13 | #include "dexode/eventbus/internal/event_id.hpp" 14 | 15 | namespace dexode::eventbus::perk 16 | { 17 | 18 | class TagPerk : public Perk 19 | { 20 | public: 21 | TagPerk(std::string tag, dexode::eventbus::Bus* owner) 22 | : _tag{std::move(tag)} 23 | , _ownerBus{owner} 24 | {} 25 | 26 | Flag onPrePostponeEvent(PostponeHelper& postponeCall); 27 | 28 | template 29 | TagPerk& wrapTag() 30 | { 31 | static_assert(internal::validateEvent(), "Invalid tag event"); 32 | static_assert(internal::validateEvent(), "Invalid event"); 33 | constexpr auto eventID = internal::event_id(); 34 | 35 | _eventsToWrap[eventID] = [this](std::any event) { 36 | TagEvent newEvent{_tag, std::move(std::any_cast(event))}; 37 | _ownerBus->postpone(std::move(newEvent)); 38 | }; 39 | return *this; 40 | } 41 | 42 | private: 43 | std::map> _eventsToWrap; 44 | std::string _tag; 45 | dexode::eventbus::Bus* _ownerBus; 46 | }; 47 | 48 | } // namespace dexode::eventbus::perk 49 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/WaitPerk.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #include "WaitPerk.hpp" 5 | 6 | namespace dexode::eventbus::perk 7 | { 8 | 9 | bool WaitPerk::wait() 10 | { 11 | using namespace std::chrono_literals; 12 | std::unique_lock lock(_waitMutex); 13 | if(_hasEvents) 14 | { 15 | _hasEvents = false; // reset, assume that processing of events took place 16 | return true; 17 | } 18 | _eventWaiting.wait(lock, [this]() { return _hasEvents; }); 19 | 20 | // At this moment we are still under mutex 21 | if(_hasEvents) 22 | { 23 | _hasEvents = false; // reset, assume that processing of events took place 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | bool WaitPerk::waitFor(const std::chrono::milliseconds timeout) 30 | { 31 | using namespace std::chrono_literals; 32 | std::unique_lock lock(_waitMutex); 33 | if(_hasEvents) 34 | { 35 | _hasEvents = false; // reset 36 | return true; 37 | } 38 | if(_eventWaiting.wait_for(lock, timeout, [this]() { return _hasEvents; })) 39 | { 40 | // At this moment we are still under mutex 41 | _hasEvents = false; // reset 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | Flag WaitPerk::onPostponeEvent(PostponeHelper&) 48 | { 49 | { 50 | std::lock_guard lock(_waitMutex); 51 | _hasEvents = true; 52 | } 53 | _eventWaiting.notify_one(); 54 | return Flag::postpone_continue; 55 | } 56 | 57 | } // namespace dexode::eventbus::perk 58 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/perk/WaitPerk.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Perk.hpp" 11 | 12 | namespace dexode::eventbus 13 | { 14 | class PostponeHelper; 15 | } 16 | 17 | namespace dexode::eventbus::perk 18 | { 19 | 20 | class WaitPerk : public Perk 21 | { 22 | public: 23 | /** 24 | * @return true when events are waiting in bus 25 | */ 26 | bool wait(); 27 | 28 | /** 29 | * @param timeout 30 | * @return true when events are waiting in bus 31 | */ 32 | bool waitFor(std::chrono::milliseconds timeout); 33 | 34 | Flag onPostponeEvent(PostponeHelper& postponeCall); 35 | 36 | private: 37 | std::condition_variable _eventWaiting; 38 | std::mutex _waitMutex; 39 | bool _hasEvents = false; 40 | }; 41 | 42 | } // namespace dexode::eventbus::perk 43 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/permission/PostponeBus.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "dexode/eventbus/Bus.hpp" 10 | 11 | namespace dexode::eventbus::permission 12 | { 13 | /** 14 | * Intention of this helper is to hide API of other bus but allow only to postpone events 15 | * So no: 16 | * - listening 17 | * - processing 18 | */ 19 | class PostponeBus 20 | { 21 | public: 22 | PostponeBus(std::shared_ptr hideBus) 23 | : _hideBus{std::move(hideBus)} 24 | {} 25 | 26 | template 27 | constexpr bool postpone(Event event) 28 | { 29 | assert(_hideBus != nullptr); 30 | return _hideBus->postpone(event); 31 | } 32 | 33 | private: 34 | std::shared_ptr _hideBus; 35 | }; 36 | 37 | } // namespace dexode::eventbus::permission 38 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/stream/EventStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace dexode::eventbus::stream 7 | { 8 | 9 | class EventStream 10 | { 11 | public: 12 | virtual ~EventStream() = default; 13 | 14 | virtual void postpone(std::any event) = 0; 15 | virtual std::size_t process(std::size_t limit) = 0; 16 | 17 | virtual bool addListener(std::uint32_t listenerID, std::any callback) = 0; 18 | virtual bool removeListener(std::uint32_t listenerID) = 0; 19 | 20 | [[nodiscard]] virtual bool hasListener(std::uint32_t listenerID) const = 0; 21 | }; 22 | 23 | class NoopEventStream : public EventStream 24 | { 25 | public: 26 | void postpone(std::any event) override 27 | { 28 | throw std::runtime_error{"Noop"}; 29 | } 30 | size_t process(std::size_t limit) override 31 | { 32 | throw std::runtime_error{"Noop"}; 33 | } 34 | bool addListener(std::uint32_t listenerID, std::any callback) override 35 | { 36 | throw std::runtime_error{"Noop"}; 37 | } 38 | bool removeListener(std::uint32_t listenerID) override 39 | { 40 | throw std::runtime_error{"Noop"}; 41 | } 42 | [[nodiscard]] bool hasListener(std::uint32_t listenerID) const override 43 | { 44 | throw std::runtime_error{"Noop"}; 45 | } 46 | }; 47 | 48 | } // namespace dexode::eventbus::stream 49 | -------------------------------------------------------------------------------- /lib/src/dexode/eventbus/stream/ProtectedEventStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dexode/eventbus/stream/EventStream.hpp" 11 | 12 | namespace dexode::eventbus::stream 13 | { 14 | 15 | template 16 | class ProtectedEventStream : public EventStream 17 | { 18 | using Callback = std::function; 19 | 20 | public: 21 | void postpone(std::any event) override 22 | { 23 | auto myEvent = std::any_cast(event); 24 | std::lock_guard writeGuard{_mutexEvent}; 25 | _queue.push_back(std::move(myEvent)); 26 | } 27 | 28 | std::size_t process(const std::size_t limit) override 29 | { 30 | std::vector processEvents; 31 | { 32 | std::lock_guard writeGuard{_mutexEvent}; 33 | if(limit >= _queue.size()) 34 | { 35 | processEvents.reserve(_queue.size()); 36 | std::swap(processEvents, _queue); 37 | } 38 | else 39 | { 40 | const auto countElements = std::min(limit, _queue.size()); 41 | processEvents.reserve(countElements); 42 | auto begin = _queue.begin(); 43 | auto end = std::next(begin, countElements); 44 | 45 | // moved-from range will still contain valid values of the appropriate type, but not 46 | // necessarily the same values as before the move. Iterators should be still valid. 47 | // see: https://en.cppreference.com/w/cpp/algorithm/move 48 | std::move(begin, end, std::back_inserter(processEvents)); 49 | _queue.erase(begin, end); 50 | } 51 | } 52 | 53 | for(const auto& event : processEvents) 54 | { 55 | // At this point we need to consider transaction safety as during some notification 56 | // we can add/remove listeners 57 | _isProcessing = true; 58 | for(auto& callback : _callbacks) 59 | { 60 | callback(event); 61 | } 62 | _isProcessing = false; 63 | 64 | flushWaitingOnes(); 65 | } 66 | 67 | return processEvents.size(); 68 | } 69 | 70 | bool addListener(const std::uint32_t listenerID, std::any callback) override 71 | { 72 | std::lock_guard writeGuard{_mutexCallbacks}; 73 | auto myCallback = std::any_cast(callback); 74 | if(_isProcessing) 75 | { 76 | _waiting.emplace_back(listenerID, std::move(myCallback)); 77 | return true; 78 | } 79 | 80 | return rawAddListener(listenerID, std::move(myCallback)); 81 | } 82 | 83 | bool removeListener(const std::uint32_t listenerID) override 84 | { 85 | std::lock_guard writeGuard{_mutexCallbacks}; 86 | if(_isProcessing) 87 | { 88 | _waiting.emplace_back(listenerID, Callback{}); 89 | return true; 90 | } 91 | 92 | return rawRemoveListener(listenerID); 93 | } 94 | 95 | [[nodiscard]] bool hasEvents() const 96 | { 97 | std::shared_lock readGuard{_mutexEvent}; 98 | return not _queue.empty(); 99 | } 100 | 101 | [[nodiscard]] bool hasListener(std::uint32_t listenerID) const override 102 | { 103 | std::shared_lock readGuard{_mutexCallbacks}; 104 | auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID); 105 | return found != _listenerIDs.end(); 106 | } 107 | 108 | private: 109 | std::vector _listenerIDs; 110 | std::vector _queue; 111 | std::vector _callbacks; 112 | 113 | std::atomic _isProcessing{false}; 114 | std::vector> _waiting; 115 | 116 | mutable std::shared_mutex _mutexEvent; 117 | mutable std::shared_mutex _mutexCallbacks; 118 | 119 | void flushWaitingOnes() 120 | { 121 | assert(_isProcessing == false); 122 | std::lock_guard writeGuard{_mutexCallbacks}; 123 | if(_waiting.empty()) 124 | { 125 | return; 126 | } 127 | 128 | for(auto&& element : _waiting) 129 | { 130 | if(element.second) // if callable it means we want to add 131 | { 132 | rawAddListener(element.first, std::move(element.second)); 133 | } 134 | else // if not callable we want to remove 135 | { 136 | rawRemoveListener(element.first); 137 | } 138 | } 139 | _waiting.clear(); 140 | } 141 | 142 | bool rawAddListener(const std::uint32_t listenerID, Callback&& callback) 143 | { 144 | auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID); 145 | if(found != _listenerIDs.end()) 146 | { 147 | /// ###### IMPORTANT ###### 148 | /// This exception has some reason. 149 | /// User should use multiple listeners instead of one. Thanks to that it makes 150 | /// it more clear what will happen when call unlisten with specific Event 151 | throw std::invalid_argument{std::string{"Already added listener for event: "} + 152 | typeid(Event).name()}; 153 | } 154 | 155 | _callbacks.push_back(std::forward(callback)); 156 | _listenerIDs.push_back(listenerID); 157 | assert(_listenerIDs.size() == _callbacks.size()); 158 | return true; 159 | } 160 | 161 | bool rawRemoveListener(const std::uint32_t listenerID) 162 | { 163 | auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID); 164 | if(found == _listenerIDs.end()) 165 | { 166 | return false; 167 | } 168 | const auto index = std::distance(_listenerIDs.begin(), found); 169 | 170 | _listenerIDs.erase(found); 171 | _callbacks.erase(std::next(_callbacks.begin(), index)); 172 | assert(_listenerIDs.size() == _callbacks.size()); 173 | return true; 174 | } 175 | }; 176 | 177 | } // namespace dexode::eventbus::stream 178 | -------------------------------------------------------------------------------- /performance/.gitignore: -------------------------------------------------------------------------------- 1 | cocos2d-x/ 2 | -------------------------------------------------------------------------------- /performance/CCNotificationCenterPerformance.patch: -------------------------------------------------------------------------------- 1 | diff --git a/cocos2dx/cocoa/CCArray.cpp b/cocos2dx/cocoa/CCArray.cpp 2 | index 12e5bb2971..0a916cd6e2 100644 3 | --- a/cocos2dx/cocoa/CCArray.cpp 4 | +++ b/cocos2dx/cocoa/CCArray.cpp 5 | @@ -134,7 +134,7 @@ CCArray* CCArray::createWithContentsOfFile(const char* pFileName) 6 | 7 | CCArray* CCArray::createWithContentsOfFileThreadSafe(const char* pFileName) 8 | { 9 | - return CCFileUtils::sharedFileUtils()->createCCArrayWithContentsOfFile(pFileName); 10 | + return nullptr; 11 | } 12 | 13 | bool CCArray::init() 14 | diff --git a/cocos2dx/cocoa/CCInteger.h b/cocos2dx/cocoa/CCInteger.h 15 | index f700a30a35..20afae5a92 100644 16 | --- a/cocos2dx/cocoa/CCInteger.h 17 | +++ b/cocos2dx/cocoa/CCInteger.h 18 | @@ -30,7 +30,6 @@ public: 19 | */ 20 | virtual void acceptVisitor(CCDataVisitor &visitor) { visitor.visit(this); } 21 | 22 | -private: 23 | int m_nValue; 24 | }; 25 | 26 | diff --git a/cocos2dx/cocoa/CCObject.cpp b/cocos2dx/cocoa/CCObject.cpp 27 | index dd74c695a3..a2b5f8b574 100644 28 | --- a/cocos2dx/cocoa/CCObject.cpp 29 | +++ b/cocos2dx/cocoa/CCObject.cpp 30 | @@ -59,15 +59,9 @@ CCObject::~CCObject(void) 31 | // if the object is referenced by Lua engine, remove it 32 | if (m_nLuaID) 33 | { 34 | - CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptObjectByCCObject(this); 35 | } 36 | else 37 | { 38 | - CCScriptEngineProtocol* pEngine = CCScriptEngineManager::sharedManager()->getScriptEngine(); 39 | - if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript) 40 | - { 41 | - pEngine->removeScriptObjectByCCObject(this); 42 | - } 43 | } 44 | } 45 | 46 | diff --git a/cocos2dx/support/CCNotificationCenter.cpp b/cocos2dx/support/CCNotificationCenter.cpp 47 | index 835854eded..b3b75fc59d 100644 48 | --- a/cocos2dx/support/CCNotificationCenter.cpp 49 | +++ b/cocos2dx/support/CCNotificationCenter.cpp 50 | @@ -177,8 +177,6 @@ void CCNotificationCenter::postNotification(const char *name, CCObject *object) 51 | { 52 | if (0 != observer->getHandler()) 53 | { 54 | - CCScriptEngineProtocol* engine = CCScriptEngineManager::sharedManager()->getScriptEngine(); 55 | - engine->executeNotificationEvent(this, name); 56 | } 57 | else 58 | { 59 | -------------------------------------------------------------------------------- /performance/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 2 | 3 | # http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html 4 | # Thanks for CATCH! 5 | 6 | find_package(Poco COMPONENTS Foundation Util) 7 | 8 | set(BENCHMARK_ENABLE_GTEST_TESTS OFF) 9 | add_subdirectory(benchmark/) 10 | 11 | if(NOT TARGET Dexode::EventBus) 12 | find_package(EventBus CONFIG REQUIRED) 13 | endif() 14 | 15 | # If you want to compare with CCNotificationCenter read about it in README and uncomment line below 16 | #INCLUDE(cocos2d-x-compare/Cocos2dxCompare.cmake) 17 | 18 | add_executable(EventBusPerformance 19 | src/EventBusPerformance.cpp 20 | ${CCNOTIFICATION_CENTER_SRC} 21 | $<$:src/PocoNotificationCenterPerformance.cpp> 22 | ) 23 | 24 | target_compile_options(EventBusPerformance PUBLIC 25 | -Wall -pedantic 26 | -Wno-unused-private-field 27 | -Wnon-virtual-dtor 28 | -Wno-gnu 29 | -Werror 30 | ) 31 | 32 | set(EVENTBUS_DEBUG_FLAGS 33 | -O0 -fno-inline 34 | -DDEBUG 35 | ) 36 | 37 | 38 | target_compile_options(EventBusPerformance PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") 39 | 40 | target_include_directories(EventBusPerformance PUBLIC 41 | src/ 42 | ${CCNOTIFICATION_CENTER_INCLUDE} 43 | ) 44 | 45 | target_link_libraries(EventBusPerformance PUBLIC 46 | Dexode::EventBus 47 | benchmark benchmark_main 48 | $<$:Poco::Foundation> 49 | $<$:Poco::Util> 50 | ) 51 | -------------------------------------------------------------------------------- /performance/README.md: -------------------------------------------------------------------------------- 1 | # Performace 2 | 3 | This is maybe not perfect but can show something. All result are run using RELESE build. 4 | Why? 5 | 6 | Sample DEBUG run: 7 | ```commandline 8 | Run on (8 X 3600 MHz CPU s) 9 | 2017-08-05 12:44:53 10 | ***WARNING*** Library was built as DEBUG. Timings may be affected. 11 | --------------------------------------------------------------- 12 | Benchmark Time CPU Iterations 13 | --------------------------------------------------------------- 14 | checkSimpleNotification 293 ns 293 ns 2319171 15 | ``` 16 | 17 | Sample RELEASE run: 18 | ```commandline 19 | Run on (8 X 3600 MHz CPU s) 20 | 2017-08-05 12:45:43 21 | --------------------------------------------------------------- 22 | Benchmark Time CPU Iterations 23 | --------------------------------------------------------------- 24 | checkSimpleNotification 6 ns 6 ns 116492914 25 | ``` 26 | 27 | So below all numbers are in release. 28 | 29 | This library as you can read in main README.md was inspired by [CCNotificationCenter](https://github.com/cocos2d/cocos2d-x/blob/v2/cocos2dx/support/CCNotificationCenter.h) from cocos2d-x game engine. 30 | So I want to present comparision of performance of this two. Of course this is only showcase. 31 | I don't want to add submodule of cocos2d-x so simply I run it only and present results. Cocos2d-x was also build as release. If you want to repeat it, here are steps I followed: 32 | ```commandline 33 | cd performance # From root of this project 34 | git clone -b v2 https://github.com/cocos2d/cocos2d-x.git #this can take some time :/ it need to download ~900 MB 35 | # Uncomment line in CMakeLists.txt which INCLUDE(Cocos2dxCompare.cmake) 36 | # Apply patch CCNotificationCenterPerformance.patch 37 | cd cocos2d-x 38 | git apply ../CCNotificationCenterPerformance.patch 39 | ``` 40 | 41 | ``` 42 | Run on (8 X 3600 MHz CPU s) 43 | 2017-08-06 17:03:13 44 | ------------------------------------------------------------------------------------------------------------------------------------------ 45 | Benchmark Time CPU Iterations UserCounters Faster 46 | ------------------------------------------------------------------------------------------------------------------------------------------ 47 | call1kLambdas_compare 2256 ns 2256 ns 327462 sum=1.21989G Compare with check1kListeners overhead is super small 48 | ------------------------------------------------------------------------------------------------------------------------------------------ 49 | checkSimpleNotification 16 ns 16 ns 42451425 sum=161.939M x 10.7 50 | checkSimpleNotification_CCNotificationCenter 172 ns 172 ns 4193935 sum=15.9986M 51 | ------------------------------------------------------------------------------------------------------------------------------------------ 52 | check10Listeners 34 ns 34 ns 20564387 sum=784.469M x 8.9 53 | check10Listeners_CCNotificationCenter 305 ns 305 ns 2208720 sum=84.256M 54 | ------------------------------------------------------------------------------------------------------------------------------------------ 55 | check100Listeners 208 ns 208 ns 3362931 sum=1.25279G x 8.4 56 | check100Listeners_CCNotificationCenter 1758 ns 1758 ns 398100 sum=151.863M 57 | ------------------------------------------------------------------------------------------------------------------------------------------ 58 | check1kListeners 2074 ns 2074 ns 340081 sum=1.2669G x 8.1 59 | check1kListeners_CCNotificationCenter 17001 ns 16999 ns 41548 sum=158.493M 60 | ------------------------------------------------------------------------------------------------------------------------------------------ 61 | check10NotificationsFor1kListeners 263 ns 263 ns 2668786 sum=-1.76281G x 53.1 62 | check10NotificationsFor1kListeners_CCNotificationCenter 13987 ns 13986 ns 51560 sum=44.2743M 63 | ------------------------------------------------------------------------------------------------------------------------------------------ 64 | check100NotificationsFor1kListeners UNKNOWN result available with EventBus 2.0.1 65 | check100NotificationsFor1kListeners_CCNotificationCenter 12128 ns 12127 ns 54017 sum=51.181M 66 | ------------------------------------------------------------------------------------------------------------------------------------------ 67 | check1kNotificationsFor1kListeners UNKNOWN result available with EventBus 2.0.1 68 | check1kNotificationsFor1kListeners_CCNotificationCenter 11940 ns 11939 ns 57722 sum=55.2338M 69 | ------------------------------------------------------------------------------------------------------------------------------------------ 70 | check100NotificationsFor10kListeners UNKNOWN result available with EventBus 2.0.1 71 | check100NotificationsFor10kListeners_CCNotificationCenter 128244 ns 128233 ns 5297 sum=49.5221M 72 | ------------------------------------------------------------------------------------------------------------------------------------------ 73 | checkNotifyFor10kListenersWhenNoOneListens 18 ns 18 ns 38976599 sum=0 x 7077 ;) 74 | checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter 127388 ns 127378 ns 5460 sum=0 75 | ``` 76 | So comparing to CCNotificationCenter, EventBus is something like ~10x FASTER especially when we have more unique notifications. 77 | -------------------------------------------------------------------------------- /performance/cocos2d-x-compare/CCNotificationCenterPerformance.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Dawid Drozd aka Gelldur on 05.08.17. 3 | // 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cocos2dx 13 | { 14 | 15 | struct SampleObserver : public cocos2d::CCObject 16 | { 17 | std::function callback; 18 | 19 | void onCall(cocos2d::CCObject* object) 20 | { 21 | callback(static_cast(object)->getValue()); 22 | } 23 | }; 24 | 25 | void checkNListeners(benchmark::State& state, const int listenersCount) 26 | { 27 | using namespace cocos2d; 28 | cocos2d::CCNotificationCenter bus; 29 | int sum = 0; 30 | for(int i = 0; i < listenersCount; ++i) 31 | { 32 | auto observer = new SampleObserver{}; 33 | observer->autorelease(); 34 | observer->callback = [&](int value) { benchmark::DoNotOptimize(sum += value * 2); }; 35 | bus.addObserver(observer, callfuncO_selector(SampleObserver::onCall), "sample", nullptr); 36 | } 37 | auto number = CCInteger::create(2); 38 | while(state.KeepRunning()) // Performance area! 39 | { 40 | bus.postNotification("sample", number); 41 | } 42 | state.counters["sum"] = sum; 43 | CCPoolManager::sharedPoolManager()->purgePoolManager(); 44 | } 45 | 46 | void checkSimpleNotification_CCNotificationCenter(benchmark::State& state) 47 | { 48 | checkNListeners(state, 1); 49 | } 50 | 51 | void check10Listeners_CCNotificationCenter(benchmark::State& state) 52 | { 53 | checkNListeners(state, 10); 54 | } 55 | 56 | void check100Listeners_CCNotificationCenter(benchmark::State& state) 57 | { 58 | checkNListeners(state, 100); 59 | } 60 | 61 | void check1kListeners_CCNotificationCenter(benchmark::State& state) 62 | { 63 | checkNListeners(state, 1000); 64 | } 65 | 66 | void checkNNotificationsForNListeners(benchmark::State& state, 67 | const int notificationsCount, 68 | const int listenersCount) 69 | { 70 | std::mt19937 generator(311281); 71 | std::uniform_int_distribution uniformDistribution(0, notificationsCount - 1); 72 | 73 | using namespace cocos2d; 74 | cocos2d::CCNotificationCenter bus; 75 | 76 | // We generate here N different notifications 77 | std::vector notifications; 78 | notifications.reserve(notificationsCount); 79 | for(int i = 0; i < notificationsCount; ++i) 80 | { 81 | notifications.emplace_back(std::string{"notify_"} + std::to_string(i)); 82 | } 83 | 84 | int sum = 0; 85 | for(int i = 0; i < listenersCount; 86 | ++i) // We register M listeners for N notifications using uniform distribution 87 | { 88 | auto observer = new SampleObserver{}; 89 | observer->autorelease(); 90 | observer->callback = [&](int value) { benchmark::DoNotOptimize(sum += value * 2); }; 91 | 92 | const auto& notification = notifications.at(uniformDistribution(generator)); 93 | bus.addObserver( 94 | observer, callfuncO_selector(SampleObserver::onCall), notification.c_str(), nullptr); 95 | } 96 | auto number = CCInteger::create(2); 97 | 98 | while(state.KeepRunning()) // Performance area! 99 | { 100 | // Pick random notification 101 | const auto& notification = notifications.at(uniformDistribution(generator)); 102 | 103 | number->m_nValue = uniformDistribution(generator); 104 | bus.postNotification(notification.c_str(), number); 105 | } 106 | state.counters["sum"] = sum; 107 | CCPoolManager::sharedPoolManager()->purgePoolManager(); 108 | } 109 | 110 | void check10NotificationsFor1kListeners_CCNotificationCenter(benchmark::State& state) 111 | { 112 | checkNNotificationsForNListeners(state, 10, 1000); 113 | } 114 | 115 | void check100NotificationsFor1kListeners_CCNotificationCenter(benchmark::State& state) 116 | { 117 | checkNNotificationsForNListeners(state, 100, 1000); 118 | } 119 | 120 | void check1kNotificationsFor1kListeners_CCNotificationCenter(benchmark::State& state) 121 | { 122 | checkNNotificationsForNListeners(state, 1000, 1000); 123 | } 124 | 125 | void check100NotificationsFor10kListeners_CCNotificationCenter(benchmark::State& state) 126 | { 127 | checkNNotificationsForNListeners(state, 100, 10000); 128 | } 129 | 130 | void checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter(benchmark::State& state) 131 | { 132 | using namespace cocos2d; 133 | cocos2d::CCNotificationCenter bus; 134 | int sum = 0; 135 | for(int i = 0; i < 10000; ++i) 136 | { 137 | auto observer = new SampleObserver{}; 138 | observer->autorelease(); 139 | observer->callback = [&](int value) { benchmark::DoNotOptimize(sum += value * 2); }; 140 | bus.addObserver(observer, callfuncO_selector(SampleObserver::onCall), "sample", nullptr); 141 | } 142 | auto number = CCInteger::create(2); 143 | while(state.KeepRunning()) // Performance area! 144 | { 145 | bus.postNotification("unknown", number); 146 | } 147 | state.counters["sum"] = sum; 148 | CCPoolManager::sharedPoolManager()->purgePoolManager(); 149 | } 150 | 151 | BENCHMARK(checkSimpleNotification_CCNotificationCenter); 152 | BENCHMARK(check10Listeners_CCNotificationCenter); 153 | BENCHMARK(check100Listeners_CCNotificationCenter); 154 | BENCHMARK(check1kListeners_CCNotificationCenter); 155 | BENCHMARK(check10NotificationsFor1kListeners_CCNotificationCenter); 156 | BENCHMARK(check100NotificationsFor1kListeners_CCNotificationCenter); 157 | BENCHMARK(check1kNotificationsFor1kListeners_CCNotificationCenter); 158 | BENCHMARK(check100NotificationsFor10kListeners_CCNotificationCenter); 159 | BENCHMARK(checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter); 160 | } // namespace cocos2dx 161 | -------------------------------------------------------------------------------- /performance/cocos2d-x-compare/Cocos2dxCompare.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(CCNOTIFICATION_CENTER_SRC 3 | cocos2d-x-compare/CCNotificationCenterPerformance.cpp 4 | cocos2d-x/cocos2dx/support/CCNotificationCenter.cpp 5 | cocos2d-x/cocos2dx/cocoa/CCObject.cpp 6 | cocos2d-x/cocos2dx/cocoa/CCArray.cpp 7 | cocos2d-x/cocos2dx/cocoa/CCInteger.h 8 | cocos2d-x/cocos2dx/cocoa/CCAutoreleasePool.cpp 9 | cocos2d-x/cocos2dx/support/data_support/ccCArray.cpp 10 | cocos2d-x/cocos2dx/cocoa/CCGeometry.cpp 11 | ) 12 | set(CCNOTIFICATION_CENTER_INCLUDE 13 | cocos2d-x/cocos2dx/ 14 | cocos2d-x/cocos2dx/include 15 | cocos2d-x/cocos2dx/platform/linux 16 | cocos2d-x/cocos2dx/kazmath/include 17 | ) 18 | 19 | add_definitions(-DLINUX) 20 | -------------------------------------------------------------------------------- /performance/src/AsyncEventBusPerformance.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 14.06.19. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace 15 | { 16 | struct SimpleEvent 17 | { 18 | std::int64_t value = 0; 19 | }; 20 | 21 | Dexode::AsyncEventBus bus; 22 | 23 | } // namespace 24 | 25 | void checkFor(benchmark::State& state) 26 | { 27 | if(state.thread_index == 0) 28 | { 29 | Dexode::TokenHolder listener {&bus}; 30 | std::uint64_t consumed = 0; 31 | listener.listen( 32 | [&consumed](const auto& event) { benchmark::DoNotOptimize(consumed += 1); }); 33 | 34 | for(auto _ : state) 35 | { 36 | //if(bus.wait()) 37 | { 38 | bus.consume(); 39 | } 40 | } 41 | state.counters["consumed"] = consumed; 42 | } 43 | else 44 | { 45 | for(auto _ : state) 46 | { 47 | bus.schedule(SimpleEvent {std::chrono::steady_clock::now().time_since_epoch().count()}); 48 | } 49 | } 50 | } 51 | 52 | BENCHMARK(checkFor)->Threads(2)->MinTime(1)->MeasureProcessCPUTime(); 53 | BENCHMARK(checkFor)->Threads(5)->MinTime(1)->MeasureProcessCPUTime(); 54 | BENCHMARK(checkFor)->Threads(10)->MinTime(1)->MeasureProcessCPUTime(); 55 | -------------------------------------------------------------------------------- /performance/src/EventBusPerformance.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Dawid Drozd aka Gelldur on 05.08.17. 3 | // 4 | #include 5 | 6 | #include 7 | 8 | #include "dexode/EventBus.hpp" 9 | #include "dexode/eventbus/strategy/Protected.hpp" 10 | #include "dexode/eventbus/strategy/Transaction.hpp" 11 | 12 | namespace 13 | { 14 | 15 | template 16 | void checkNListeners(benchmark::State& state, const int listenersCount) 17 | { 18 | Bus bus; 19 | 20 | struct SimpleEvent 21 | { 22 | int value; 23 | }; 24 | 25 | std::vector listeners; 26 | listeners.reserve(listenersCount); 27 | 28 | std::uint64_t sum = 0; 29 | for(int i = 0; i < listenersCount; ++i) 30 | { 31 | listeners.emplace_back(bus.createListener()); 32 | listeners.back().template listen( 33 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 34 | } 35 | 36 | while(state.KeepRunning()) // Performance area! 37 | { 38 | const auto event = SimpleEvent{1}; 39 | bus.post(event); 40 | } 41 | state.counters["sum"] = sum; 42 | } 43 | 44 | template 45 | void checkSimpleNotification(benchmark::State& state) 46 | { 47 | checkNListeners>(state, 1); 48 | } 49 | 50 | template 51 | void check10Listeners(benchmark::State& state) 52 | { 53 | checkNListeners>(state, 10); 54 | } 55 | 56 | template 57 | void check100Listeners(benchmark::State& state) 58 | { 59 | checkNListeners>(state, 100); 60 | } 61 | 62 | template 63 | void check1kListeners(benchmark::State& state) 64 | { 65 | checkNListeners>(state, 1000); 66 | } 67 | 68 | void call1kLambdas_compare(benchmark::State& state) 69 | { 70 | std::vector> callbacks; 71 | constexpr int listenersCount = 1000; 72 | callbacks.reserve(listenersCount); 73 | std::uint64_t sum = 0; 74 | for(int i = 0; i < listenersCount; ++i) 75 | { 76 | callbacks.emplace_back([&sum](int value) { benchmark::DoNotOptimize(sum += value); }); 77 | } 78 | 79 | while(state.KeepRunning()) // Performance area! 80 | { 81 | for(int i = 0; i < listenersCount; ++i) 82 | { 83 | callbacks[i](1); 84 | } 85 | } 86 | state.counters["sum"] = sum; 87 | } 88 | 89 | template 90 | struct UniqEvent 91 | { 92 | int value; 93 | }; 94 | 95 | template 96 | void checkNNotificationsForNListeners(benchmark::State& state, 97 | const int notificationsCount, 98 | const int listenersCount) 99 | { 100 | std::mt19937 generator(311281); 101 | std::uniform_int_distribution uniformDistribution(0, 9); 102 | 103 | Bus bus; 104 | std::uint64_t sum = 0; 105 | 106 | std::vector listeners; 107 | listeners.reserve(listenersCount); 108 | 109 | // We register M listeners for N notifications using uniform distribution 110 | for(int i = 0; i < listenersCount; ++i) 111 | { 112 | listeners.emplace_back(bus.createListener()); 113 | const auto which = uniformDistribution(generator); 114 | 115 | // We generate here N different notifications 116 | switch(which) 117 | { 118 | case 0: 119 | listeners.back().template listen>( 120 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 121 | break; 122 | case 1: 123 | listeners.back().template listen>( 124 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 125 | break; 126 | case 2: 127 | listeners.back().template listen>( 128 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 129 | break; 130 | case 3: 131 | listeners.back().template listen>( 132 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 133 | break; 134 | case 4: 135 | listeners.back().template listen>( 136 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 137 | break; 138 | case 5: 139 | listeners.back().template listen>( 140 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 141 | break; 142 | case 6: 143 | listeners.back().template listen>( 144 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 145 | break; 146 | case 7: 147 | listeners.back().template listen>( 148 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 149 | break; 150 | case 8: 151 | listeners.back().template listen>( 152 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 153 | break; 154 | case 9: 155 | listeners.back().template listen>( 156 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 157 | break; 158 | default: 159 | throw std::runtime_error{"Nope"}; 160 | } 161 | } 162 | 163 | while(state.KeepRunning()) // Performance area! 164 | { 165 | // Pick random notification 166 | const auto which = uniformDistribution(generator); 167 | // We generate here N different notifications 168 | switch(which) 169 | { 170 | case 0: 171 | bus.post(UniqEvent<0>{1}); 172 | break; 173 | case 1: 174 | bus.post(UniqEvent<1>{2}); 175 | break; 176 | case 2: 177 | bus.post(UniqEvent<2>{3}); 178 | break; 179 | case 3: 180 | bus.post(UniqEvent<3>{4}); 181 | break; 182 | case 4: 183 | bus.post(UniqEvent<4>{5}); 184 | break; 185 | case 5: 186 | bus.post(UniqEvent<5>{6}); 187 | break; 188 | case 6: 189 | bus.post(UniqEvent<6>{7}); 190 | break; 191 | case 7: 192 | bus.post(UniqEvent<7>{8}); 193 | break; 194 | case 8: 195 | bus.post(UniqEvent<8>{9}); 196 | break; 197 | case 9: 198 | bus.post(UniqEvent<9>{10}); 199 | break; 200 | default: 201 | throw std::runtime_error{"Nope"}; 202 | } 203 | } 204 | state.counters["sum"] = sum; 205 | } 206 | 207 | template 208 | void check10NotificationsFor1kListeners(benchmark::State& state) 209 | { 210 | checkNNotificationsForNListeners>(state, 10, 1000); 211 | } 212 | 213 | template 214 | void check100NotificationsFor1kListeners(benchmark::State& state) 215 | { 216 | checkNNotificationsForNListeners>(state, 100, 1000); 217 | } 218 | 219 | template 220 | void check1kNotificationsFor1kListeners(benchmark::State& state) 221 | { 222 | checkNNotificationsForNListeners>(state, 1000, 1000); 223 | } 224 | 225 | template 226 | void check100NotificationsFor10kListeners(benchmark::State& state) 227 | { 228 | checkNNotificationsForNListeners>(state, 100, 10000); 229 | } 230 | 231 | template 232 | void checkNotifyFor10kListenersWhenNoOneListens(benchmark::State& state) 233 | { 234 | using Listener = typename dexode::EventBus::Listener; 235 | dexode::EventBus bus; 236 | 237 | constexpr int listenersCount = 10000; 238 | 239 | std::uint64_t sum = 0; 240 | struct SimpleEvent 241 | { 242 | int value; 243 | }; 244 | struct UnknownEvent 245 | { 246 | int value; 247 | }; 248 | 249 | std::vector listeners; 250 | listeners.reserve(listenersCount); 251 | for(int i = 0; i < listenersCount; ++i) 252 | { 253 | listeners.emplace_back(bus.createListener()); 254 | listeners.back().template listen( 255 | [&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); }); 256 | } 257 | 258 | while(state.KeepRunning()) // Performance area! 259 | { 260 | const auto unknownEvent = UnknownEvent{2}; 261 | bus.post(unknownEvent); // this event doesn't have any listener 262 | } 263 | state.counters["sum"] = sum; 264 | } 265 | } // namespace 266 | 267 | BENCHMARK(call1kLambdas_compare); 268 | 269 | namespace strategy::Transaction 270 | { 271 | 272 | using Transaction = dexode::eventbus::strategy::Transaction; 273 | 274 | BENCHMARK_TEMPLATE(checkSimpleNotification, Transaction); 275 | BENCHMARK_TEMPLATE(check10Listeners, Transaction); 276 | BENCHMARK_TEMPLATE(check100Listeners, Transaction); 277 | BENCHMARK_TEMPLATE(check1kListeners, Transaction); 278 | 279 | BENCHMARK_TEMPLATE(check10NotificationsFor1kListeners, Transaction); 280 | BENCHMARK_TEMPLATE(check100NotificationsFor1kListeners, Transaction); 281 | BENCHMARK_TEMPLATE(check1kNotificationsFor1kListeners, Transaction); 282 | BENCHMARK_TEMPLATE(check100NotificationsFor10kListeners, Transaction); 283 | 284 | BENCHMARK_TEMPLATE(checkNotifyFor10kListenersWhenNoOneListens, Transaction); 285 | 286 | } // namespace strategy::Transaction 287 | 288 | namespace strategy::Protected 289 | { 290 | 291 | using Protected = dexode::eventbus::strategy::Protected; 292 | 293 | BENCHMARK_TEMPLATE(checkSimpleNotification, Protected); 294 | BENCHMARK_TEMPLATE(check10Listeners, Protected); 295 | BENCHMARK_TEMPLATE(check100Listeners, Protected); 296 | BENCHMARK_TEMPLATE(check1kListeners, Protected); 297 | 298 | BENCHMARK_TEMPLATE(check10NotificationsFor1kListeners, Protected); 299 | BENCHMARK_TEMPLATE(check100NotificationsFor1kListeners, Protected); 300 | BENCHMARK_TEMPLATE(check1kNotificationsFor1kListeners, Protected); 301 | BENCHMARK_TEMPLATE(check100NotificationsFor10kListeners, Protected); 302 | 303 | BENCHMARK_TEMPLATE(checkNotifyFor10kListenersWhenNoOneListens, Protected); 304 | 305 | } // namespace strategy::Protected 306 | -------------------------------------------------------------------------------- /performance/src/PocoNotificationCenterPerformance.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Dawid Drozd aka Gelldur on 24.07.18. 3 | // 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace 13 | { 14 | 15 | template 16 | struct Wrapper : public Poco::Notification 17 | { 18 | T data; 19 | 20 | Wrapper(T data) 21 | : data(std::move(data)) 22 | {} 23 | }; 24 | 25 | template 26 | class Target 27 | { 28 | public: 29 | Target(std::function callback) 30 | : _callback(std::move(callback)) 31 | {} 32 | 33 | void handle(const Poco::AutoPtr>& event) 34 | { 35 | _callback((*event.get()).data); 36 | } 37 | 38 | private: 39 | std::function _callback; 40 | }; 41 | 42 | void checkNListeners(benchmark::State& state, const int listenersCount) 43 | { 44 | Poco::NotificationCenter bus; 45 | int sum = 0; 46 | 47 | struct SimpleEvent 48 | { 49 | int value; 50 | }; 51 | 52 | using MyEvent = Wrapper; 53 | using Listener = Target; 54 | 55 | std::vector targets; 56 | targets.reserve(listenersCount + 1); 57 | 58 | for(int i = 0; i < listenersCount; ++i) 59 | { 60 | targets.emplace_back( 61 | [&](const SimpleEvent& event) { benchmark::DoNotOptimize(sum += event.value * 2); }); 62 | 63 | bus.addObserver(Poco::NObserver(targets.back(), &Listener::handle)); 64 | } 65 | 66 | while(state.KeepRunning()) // Performance area! 67 | { 68 | const Poco::AutoPtr event = new Wrapper{SimpleEvent{2}}; 69 | bus.postNotification(event); 70 | } 71 | state.counters["sum"] = sum; 72 | } 73 | 74 | void Poco_checkSimpleNotification(benchmark::State& state) 75 | { 76 | checkNListeners(state, 1); 77 | } 78 | 79 | void Poco_check10Listeners(benchmark::State& state) 80 | { 81 | checkNListeners(state, 10); 82 | } 83 | 84 | void Poco_check100Listeners(benchmark::State& state) 85 | { 86 | checkNListeners(state, 100); 87 | } 88 | 89 | void Poco_check1kListeners(benchmark::State& state) 90 | { 91 | checkNListeners(state, 1000); 92 | } 93 | 94 | BENCHMARK(Poco_checkSimpleNotification); 95 | BENCHMARK(Poco_check10Listeners); 96 | BENCHMARK(Poco_check100Listeners); 97 | BENCHMARK(Poco_check1kListeners); 98 | } 99 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 2 | 3 | # http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html 4 | # Thanks for CATCH! 5 | 6 | project(EventBusTest) 7 | 8 | # Dependencies 9 | enable_testing() 10 | if(NOT TARGET Dexode::EventBus) 11 | find_package(EventBus CONFIG REQUIRED) 12 | endif() 13 | 14 | find_package(Catch2 2.10 REQUIRED) 15 | find_package(Threads REQUIRED) 16 | 17 | add_subdirectory(unit) 18 | add_subdirectory(integration) 19 | -------------------------------------------------------------------------------- /test/integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Target definition 3 | add_executable(EventBus-IntegrationTest 4 | src/dexode/eventbus/test/SuiteWait.cpp 5 | src/main.cpp 6 | ) 7 | 8 | target_include_directories(EventBus-IntegrationTest PRIVATE src/) 9 | 10 | target_compile_options(EventBus-IntegrationTest PUBLIC 11 | -Wall -pedantic 12 | -Wno-unused-private-field 13 | -Wnon-virtual-dtor 14 | -Wno-gnu 15 | -Werror 16 | ) 17 | 18 | # Don't do such thing: 19 | # if(CMAKE_BUILD_TYPE STREQUAL DEBUG) 20 | # .... 21 | # else() 22 | # ... 23 | # endif() 24 | # 25 | # Instead do this way: (It will work for Visual Studio) 26 | # target_compile_definitions(foo PRIVATE "VERBOSITY=$,30,10>") 27 | 28 | 29 | set(EVENTBUS_DEBUG_FLAGS 30 | -O0 -fno-inline 31 | -DDEBUG 32 | #-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC 33 | ) 34 | 35 | target_compile_options(EventBus-IntegrationTest PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") 36 | 37 | target_link_libraries(EventBus-IntegrationTest PUBLIC Catch2::Catch2 Dexode::EventBus Threads::Threads) 38 | 39 | 40 | add_test(NAME EventBus.UnitTests COMMAND EventBus-IntegrationTest) 41 | -------------------------------------------------------------------------------- /test/integration/src/dexode/eventbus/test/SuiteWait.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 12.03.2020. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "dexode/EventBus.hpp" 13 | #include "dexode/eventbus/perk/PerkEventBus.hpp" 14 | #include "dexode/eventbus/perk/WaitPerk.hpp" 15 | 16 | using namespace std::chrono_literals; 17 | 18 | namespace 19 | { 20 | struct EventTest 21 | { 22 | std::string data; 23 | std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now(); 24 | }; 25 | } // namespace 26 | 27 | TEST_CASE("Should not be processed with unnecessary delay", "[concurrent][EventBus]") 28 | { 29 | auto bus = std::make_shared(); 30 | bus->addPerk(std::make_unique()) 31 | .registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent); 32 | 33 | auto* waitPerk = bus->getPerk(); 34 | REQUIRE(waitPerk != nullptr); 35 | 36 | dexode::eventbus::perk::PerkEventBus::Listener listener{bus}; 37 | listener.listen([bus](const EventTest& event) { 38 | const auto eventAge = std::chrono::duration_cast( 39 | std::chrono::steady_clock::now() - event.created); 40 | CHECK(eventAge.count() < (5ms).count()); 41 | std::cout << "Event:" << event.data << " old: " << eventAge.count() << "ms" << std::endl; 42 | std::this_thread::sleep_for(2ms); // Some heavy work when processing event 43 | bus->postpone(EventTest{"other"}); 44 | std::this_thread::sleep_for(3ms); // Some heavy work when processing event 45 | }); 46 | 47 | std::atomic isWorking = true; 48 | 49 | std::vector producers; 50 | // Worker which will send event every 500 ms 51 | producers.emplace_back([&bus, &isWorking]() { 52 | while(isWorking) 53 | { 54 | bus->postpone(EventTest{"producer1"}); 55 | std::this_thread::sleep_for(500ms); 56 | } 57 | }); 58 | 59 | for(int i = 0; i < 20;) 60 | { 61 | auto start = std::chrono::steady_clock::now(); 62 | if(waitPerk->waitFor(2000ms)) 63 | { 64 | const auto sleepTime = std::chrono::duration_cast( 65 | std::chrono::steady_clock::now() - start); 66 | 67 | std::cout << "[SUCCESS] I was sleeping for: " << sleepTime.count() << " ms i:" << i 68 | << std::endl; 69 | i += bus->process(); 70 | } 71 | else 72 | { 73 | const auto sleepTime = std::chrono::duration_cast( 74 | std::chrono::steady_clock::now() - start); 75 | CHECK(sleepTime.count() < (5ms).count()); 76 | // No events waiting for us 77 | std::cout << "I was sleeping for: " << sleepTime.count() << " ms" << std::endl; 78 | } 79 | } 80 | 81 | isWorking = false; 82 | for(auto& producer : producers) 83 | { 84 | producer.join(); 85 | } 86 | } 87 | 88 | TEST_CASE("Should wait for event being scheduled", "[concurrent][EventBus]") 89 | { 90 | auto bus = std::make_shared(); 91 | bus->addPerk(std::make_unique()) 92 | .registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent); 93 | 94 | auto* waitPerk = bus->getPerk(); 95 | REQUIRE(waitPerk != nullptr); 96 | 97 | dexode::eventbus::perk::PerkEventBus::Listener listener{bus}; 98 | listener.listen([bus](const EventTest& event) { 99 | const auto eventAge = std::chrono::duration_cast( 100 | std::chrono::steady_clock::now() - event.created); 101 | CHECK(eventAge.count() < (5ms).count()); 102 | std::cout << "Event:" << event.data << " old: " << eventAge.count() << "ms" << std::endl; 103 | }); 104 | 105 | std::atomic isWorking = true; 106 | 107 | std::vector producers; 108 | producers.emplace_back([&bus, &isWorking]() { 109 | while(isWorking) 110 | { 111 | std::this_thread::sleep_for(10ms); 112 | bus->postpone(EventTest{"producer1"}); 113 | } 114 | }); 115 | 116 | for(int i = 0; i < 20;) 117 | { 118 | auto start = std::chrono::steady_clock::now(); 119 | if(waitPerk->waitFor(40ms)) 120 | { 121 | const auto sleepTime = std::chrono::duration_cast( 122 | std::chrono::steady_clock::now() - start); 123 | CHECK(sleepTime.count() >= (9ms).count()); 124 | 125 | std::cout << "[SUCCESS] I was sleeping for: " << sleepTime.count() << " ms i:" << i 126 | << std::endl; 127 | i += bus->process(); 128 | } 129 | else 130 | { 131 | const auto sleepTime = std::chrono::duration_cast( 132 | std::chrono::steady_clock::now() - start); 133 | CHECK(sleepTime < 5ms); 134 | // No events waiting for us 135 | std::cout << "I was sleeping for: " << sleepTime.count() << " ms" << std::endl; 136 | } 137 | } 138 | 139 | isWorking = false; 140 | for(auto& producer : producers) 141 | { 142 | producer.join(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /test/integration/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /test/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Target definition 3 | add_executable(EventBus-UnitTest 4 | src/dexode/eventbus/test/event.hpp 5 | src/dexode/eventbus/test/SuiteConcurrentEventBus.cpp 6 | src/dexode/eventbus/test/SuiteEventBus.cpp 7 | src/dexode/eventbus/test/SuiteEventID.cpp 8 | src/dexode/eventbus/test/SuiteListener.cpp 9 | src/main.cpp 10 | ) 11 | target_include_directories(EventBus-UnitTest PRIVATE src/) 12 | 13 | target_compile_options(EventBus-UnitTest PUBLIC 14 | -Wall -pedantic 15 | -Wno-unused-private-field 16 | -Wnon-virtual-dtor 17 | -Wno-gnu 18 | -Werror 19 | ) 20 | 21 | # Don't do such thing: 22 | # if(CMAKE_BUILD_TYPE STREQUAL DEBUG) 23 | # .... 24 | # else() 25 | # ... 26 | # endif() 27 | # 28 | # Instead do this way: (It will work for Visual Studio) 29 | # target_compile_definitions(foo PRIVATE "VERBOSITY=$,30,10>") 30 | 31 | 32 | set(EVENTBUS_DEBUG_FLAGS 33 | -O0 -fno-inline 34 | -DDEBUG 35 | #-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC 36 | ) 37 | 38 | target_compile_options(EventBus-UnitTest PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") 39 | 40 | target_link_libraries(EventBus-UnitTest PUBLIC Catch2::Catch2 Dexode::EventBus Threads::Threads) 41 | 42 | 43 | add_test(NAME EventBus.UnitTests COMMAND EventBus-UnitTest) 44 | -------------------------------------------------------------------------------- /test/unit/src/dexode/eventbus/test/SuiteConcurrentEventBus.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "dexode/EventBus.hpp" 10 | #include "dexode/eventbus/perk/PerkEventBus.hpp" 11 | #include "dexode/eventbus/perk/WaitPerk.hpp" 12 | #include "dexode/eventbus/test/event.hpp" 13 | 14 | using namespace std::chrono_literals; 15 | 16 | namespace dexode::eventbus::test 17 | { 18 | using Listener = dexode::EventBus::Listener; 19 | 20 | struct SimpleEvent 21 | { 22 | std::thread::id id; 23 | }; 24 | 25 | constexpr auto ns2 = std::chrono::nanoseconds{2}; // GCC 7 has some issues with 2ms :/ 26 | constexpr auto ns3 = std::chrono::nanoseconds{3}; 27 | 28 | TEST_CASE("Should consume events in synchronous way When using worker threads", 29 | "[concurrent][EventBus]") 30 | { 31 | EventBus bus; 32 | auto listener = Listener::createNotOwning(bus); 33 | 34 | std::atomic counter = 0; 35 | 36 | listener.listen([&counter](const SimpleEvent& event) { 37 | // std::cout << "Event from: " << event.id << std::endl; 38 | ++counter; 39 | }); 40 | 41 | std::thread worker1{[&bus]() { 42 | for(int i = 0; i < 10; ++i) 43 | { 44 | bus.postpone(SimpleEvent{std::this_thread::get_id()}); 45 | std::this_thread::sleep_for(ns3); 46 | } 47 | }}; 48 | std::thread worker2{[&bus]() { 49 | for(int i = 0; i < 10; ++i) 50 | { 51 | bus.postpone(SimpleEvent{std::this_thread::get_id()}); 52 | std::this_thread::sleep_for(ns2); 53 | } 54 | }}; 55 | 56 | REQUIRE(counter == 0); 57 | int sumOfConsumed = 0; 58 | while(counter < 20) 59 | { 60 | REQUIRE(counter == sumOfConsumed); 61 | sumOfConsumed += bus.process(); 62 | REQUIRE(counter == sumOfConsumed); 63 | } 64 | 65 | worker1.join(); 66 | worker2.join(); 67 | } 68 | 69 | TEST_CASE("Should listen for only 1 event When call unlisten inside Listener", 70 | "[concurrent][EventBus]") 71 | { 72 | EventBus bus; 73 | auto listener = Listener::createNotOwning(bus); 74 | 75 | std::atomic counter = 0; 76 | 77 | listener.listen([&counter, &listener](const SimpleEvent& event) { 78 | // std::cout << "Event from: " << event.id << std::endl; 79 | ++counter; 80 | listener.unlistenAll(); 81 | }); 82 | 83 | std::thread worker1{[&bus]() { 84 | for(int i = 0; i < 10; ++i) 85 | { 86 | bus.postpone(SimpleEvent{std::this_thread::get_id()}); 87 | std::this_thread::sleep_for(ns3); 88 | } 89 | }}; 90 | std::thread worker2{[&bus]() { 91 | for(int i = 0; i < 10; ++i) 92 | { 93 | bus.postpone(SimpleEvent{std::this_thread::get_id()}); 94 | std::this_thread::sleep_for(ns2); 95 | } 96 | }}; 97 | 98 | std::thread worker3{[&bus, &counter]() { 99 | while(counter == 0) 100 | { 101 | bus.process(); 102 | std::this_thread::sleep_for(ns2); 103 | } 104 | for(int i = 0; i < 10; ++i) 105 | { 106 | bus.process(); 107 | std::this_thread::sleep_for(ns2); 108 | } 109 | }}; 110 | 111 | for(int i = 0; i < 10; ++i) 112 | { 113 | bus.postpone(SimpleEvent{std::this_thread::get_id()}); 114 | } 115 | 116 | worker1.join(); 117 | worker2.join(); 118 | worker3.join(); 119 | 120 | REQUIRE(counter == 1); // Should be called only once 121 | } 122 | 123 | TEST_CASE("Should wait work", "[concurrent][EventBus]") 124 | { 125 | dexode::eventbus::perk::PerkEventBus bus; 126 | bus.addPerk(std::make_unique()) 127 | .registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent); 128 | 129 | auto* waitPerk = bus.getPerk(); 130 | REQUIRE(waitPerk != nullptr); 131 | 132 | auto listener = Listener::createNotOwning(bus); 133 | listener.listen( 134 | [](const event::WaitPerk& event) { std::cout << "In WaitPerk event" << std::endl; }); 135 | listener.listen([](const event::T1& event) { std::cout << "In T1 event" << std::endl; }); 136 | 137 | // Worker which will send event every 10 ms 138 | std::atomic isWorking = true; 139 | std::atomic produced{0}; 140 | std::atomic consumed{0}; 141 | 142 | std::thread producer{[&bus, &isWorking, &produced]() { 143 | while(isWorking) 144 | { 145 | bus.postpone(event::WaitPerk{}); 146 | bus.postpone(event::T1{}); 147 | ++produced; 148 | ++produced; 149 | std::this_thread::sleep_for(5ms); 150 | } 151 | }}; 152 | 153 | for(int i = 0; i < 20; ++i) 154 | { 155 | auto start = std::chrono::steady_clock::now(); 156 | if(waitPerk->waitFor(20ms)) 157 | { 158 | int beforeConsumed = consumed; 159 | consumed += bus.process(); 160 | INFO("If events available then consumed count should change") 161 | CHECK(consumed >= beforeConsumed); 162 | } 163 | else 164 | { 165 | // No events waiting for us 166 | } 167 | 168 | std::cout << "I was sleeping for: " 169 | << std::chrono::duration_cast( 170 | std::chrono::steady_clock::now() - start) 171 | .count() 172 | << " ms, consumed:" << consumed << std::endl; 173 | } 174 | 175 | isWorking = false; 176 | producer.join(); 177 | REQUIRE(produced >= consumed); 178 | } 179 | 180 | } // namespace dexode::eventbus::test 181 | -------------------------------------------------------------------------------- /test/unit/src/dexode/eventbus/test/SuiteEventBus.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "dexode/EventBus.hpp" 6 | #include "dexode/eventbus/test/event.hpp" 7 | 8 | namespace dexode::eventbus::test 9 | { 10 | 11 | // class TestProducer 12 | //{ 13 | // public: 14 | // using Events = std::variant; 15 | // 16 | // void onUpdate(TransactionEventBus& bus) 17 | // { 18 | // bus.postpone(event::T1{}); 19 | // } 20 | // 21 | // private: 22 | //}; 23 | 24 | // TEST_CASE("Should process events independently When postponed multiple event types", 25 | // "[EventBus]") 26 | //{ 27 | // EventBus bus; 28 | // 29 | // int event1CallCount = 0; 30 | // int event2CallCount = 0; 31 | // 32 | // auto listener = Listener::createNotOwning(bus); 33 | // listener.listen([&](const event::T1& event) { ++event1CallCount; }); 34 | // listener.listen2([&](const event::T2& event) { ++event2CallCount; }); 35 | // 36 | // bus.postpone(event::T1{}); 37 | // { 38 | // event::T2 localVariable; 39 | // bus.postpone(localVariable); 40 | // } 41 | // 42 | // REQUIRE(event1CallCount == 0); 43 | // REQUIRE(event2CallCount == 0); 44 | // REQUIRE(bus.process() == 1); 45 | // REQUIRE(event1CallCount == 1); 46 | // REQUIRE(event2CallCount == 0); 47 | // 48 | // REQUIRE(bus.process() == 1); 49 | // REQUIRE(event1CallCount == 1); 50 | // REQUIRE(event2CallCount == 1); 51 | //} 52 | 53 | TEST_CASE("Should deliver event with desired value When postpone event", "[EventBus]") 54 | { 55 | EventBus bus; 56 | auto listener = EventBus::Listener::createNotOwning(bus); 57 | 58 | int callCount = 0; 59 | 60 | listener.listen([&](const event::Value& event) { 61 | REQUIRE(event.value == 3); 62 | ++callCount; 63 | }); 64 | 65 | REQUIRE(callCount == 0); 66 | bus.postpone(event::Value{3}); 67 | REQUIRE(callCount == 0); 68 | REQUIRE(bus.process() == 1); 69 | REQUIRE(callCount == 1); 70 | } 71 | 72 | TEST_CASE("Should be able to replace listener When we do listen -> unlisten -> listen", 73 | "[EventBus]") 74 | { 75 | EventBus bus; 76 | auto listener = EventBus::Listener::createNotOwning(bus); 77 | 78 | int callCount = 0; 79 | 80 | { 81 | // TODO validate other signatures is it possible to make mistake? (maybe fail to build tests 82 | // ?) 83 | listener.listen([&](const event::Value& event) { 84 | REQUIRE(event.value == 3); 85 | ++callCount; 86 | }); 87 | bus.postpone(event::Value{3}); 88 | REQUIRE(bus.process() == 1); 89 | REQUIRE(callCount == 1); 90 | } 91 | 92 | { 93 | listener.unlisten(); 94 | bus.postpone(event::Value{2}); 95 | REQUIRE(bus.process() == 1); 96 | REQUIRE(callCount == 1); 97 | } 98 | 99 | { 100 | listener.listen([&](const event::Value& event) { 101 | REQUIRE(event.value == 1); 102 | ++callCount; 103 | }); 104 | 105 | bus.postpone(event::Value{1}); 106 | REQUIRE(bus.process() == 1); 107 | REQUIRE(callCount == 2); 108 | } 109 | } 110 | 111 | TEST_CASE("Should be able to replace listener When we do listen -> unlistenAll -> listen", 112 | "[EventBus]") 113 | { 114 | EventBus bus; 115 | auto listener = EventBus::Listener::createNotOwning(bus); 116 | 117 | int callCount = 0; 118 | { 119 | // TODO validate other signatures is it possible to make mistake? (maybe fail to build tests 120 | // ?) 121 | listener.listen([&](const event::Value& event) { 122 | REQUIRE(event.value == 3); 123 | ++callCount; 124 | }); 125 | listener.listen([&](const event::T1& event) { ++callCount; }); 126 | bus.postpone(event::Value{3}); 127 | bus.postpone(event::T1{}); 128 | REQUIRE(bus.process() == 2); 129 | REQUIRE(callCount == 2); 130 | } 131 | 132 | { 133 | listener.unlistenAll(); 134 | bus.postpone(event::Value{3}); 135 | bus.postpone(event::T1{}); 136 | REQUIRE(bus.process() == 2); 137 | REQUIRE(callCount == 2); // no new calls 138 | } 139 | 140 | { 141 | listener.listen([&](const event::Value& event) { 142 | REQUIRE(event.value == 1); 143 | ++callCount; 144 | }); 145 | 146 | bus.postpone(event::Value{1}); 147 | bus.postpone(event::T1{}); 148 | REQUIRE(bus.process() == 2); 149 | REQUIRE(callCount == 3); 150 | } 151 | } 152 | 153 | TEST_CASE("Should flush events When no one listens", "[EventBus]") 154 | { 155 | EventBus bus; 156 | auto listener = EventBus::Listener::createNotOwning(bus); 157 | 158 | bus.postpone(event::Value{3}); 159 | bus.postpone(event::Value{3}); 160 | bus.postpone(event::Value{3}); 161 | REQUIRE(bus.process() == 3); // it shouldn't accumulate events 162 | } 163 | 164 | TEST_CASE("Should be able to unlisten When processing event", "[EventBus]") 165 | { 166 | EventBus bus; 167 | auto listener = EventBus::Listener::createNotOwning(bus); 168 | 169 | int callCount = 0; 170 | listener.listen([&](const event::Value& event) { 171 | ++callCount; 172 | listener.unlisten(); 173 | }); 174 | 175 | bus.postpone(event::Value{3}); 176 | bus.postpone(event::Value{3}); 177 | bus.postpone(event::Value{3}); 178 | REQUIRE(bus.process() == 3); // it shouldn't accumulate events 179 | REQUIRE(callCount == 1); 180 | } 181 | 182 | TEST_CASE("Should not fail When unlisten multiple times during processing event", "[EventBus]") 183 | { 184 | EventBus bus; 185 | auto listener = EventBus::Listener::createNotOwning(bus); 186 | 187 | int callCount = 0; 188 | listener.listen([&](const event::Value& event) { 189 | REQUIRE_NOTHROW(listener.unlisten()); 190 | REQUIRE_NOTHROW(listener.unlisten()); 191 | REQUIRE_NOTHROW(listener.unlisten()); 192 | ++callCount; 193 | }); 194 | 195 | bus.postpone(event::Value{3}); 196 | bus.postpone(event::Value{3}); 197 | bus.postpone(event::Value{3}); 198 | REQUIRE(bus.process() == 3); // it shouldn't accumulate events 199 | REQUIRE(callCount == 1); 200 | } 201 | 202 | TEST_CASE("Should fail When try to add not unique listener", "[EventBus]") 203 | { 204 | EventBus bus; 205 | auto listener = EventBus::Listener::createNotOwning(bus); 206 | 207 | REQUIRE_NOTHROW(listener.listen([](const event::T1&) {})); 208 | REQUIRE_THROWS(listener.listen([](const event::T1&) {})); // We already added listener 209 | } 210 | 211 | TEST_CASE("Should be able to add listener When processing event", "[EventBus]") 212 | { 213 | EventBus bus; 214 | auto listener = EventBus::Listener::createNotOwning(bus); 215 | auto listenerOther = EventBus::Listener::createNotOwning(bus); 216 | 217 | int callCount = 0; 218 | int callCountOther = 0; 219 | listener.listen([&](const event::Value& event) { 220 | ++callCount; 221 | if(callCount == 1) // remember that we can only add it once! 222 | { 223 | listenerOther.listen( 224 | [&callCountOther](const event::Value& event) { ++callCountOther; }); 225 | } 226 | }); 227 | 228 | { 229 | bus.postpone(event::Value{3}); 230 | REQUIRE(bus.process() == 1); 231 | REQUIRE(callCount == 1); 232 | REQUIRE(callCountOther == 0); 233 | 234 | bus.postpone(event::Value{3}); 235 | REQUIRE(bus.process() == 1); 236 | REQUIRE(callCount == 2); 237 | REQUIRE(callCountOther == 1); 238 | } 239 | { 240 | listenerOther.unlistenAll(); 241 | callCount = 0; 242 | callCountOther = 0; 243 | bus.postpone(event::Value{3}); 244 | bus.postpone(event::Value{3}); 245 | bus.postpone(event::Value{3}); 246 | REQUIRE(bus.process() == 3); // it shouldn't accumulate events 247 | REQUIRE(callCount == 3); 248 | REQUIRE(callCountOther == 2); 249 | } 250 | } 251 | 252 | TEST_CASE("Should be able to add listener and unlisten When processing event", "[EventBus]") 253 | { 254 | EventBus bus; 255 | auto listener = EventBus::Listener::createNotOwning(bus); 256 | auto listenerOther = EventBus::Listener::createNotOwning(bus); 257 | 258 | int callCount = 0; 259 | int callCountOther = 0; 260 | listener.listen([&](const event::Value& event) { 261 | ++callCount; 262 | if(callCount == 1) // remember that we can only add it once! 263 | { 264 | listenerOther.listen([&](const event::Value& event) { ++callCountOther; }); 265 | } 266 | listener.unlistenAll(); 267 | }); 268 | 269 | bus.postpone(event::Value{3}); 270 | bus.postpone(event::Value{3}); 271 | bus.postpone(event::Value{3}); 272 | REQUIRE(bus.process() == 3); 273 | REQUIRE(callCount == 1); 274 | REQUIRE(callCountOther == 2); 275 | } 276 | 277 | TEST_CASE( 278 | "Should be able to add listener and remove listener in Matryoshka style When processing event", 279 | "[EventBus]") 280 | { 281 | EventBus bus; 282 | auto listener1 = EventBus::Listener::createNotOwning(bus); 283 | auto listener2 = EventBus::Listener::createNotOwning(bus); 284 | auto listener3 = EventBus::Listener::createNotOwning(bus); 285 | 286 | int callCount = 0; 287 | 288 | listener1.listen([&](const event::Value& event) { 289 | listener1.unlistenAll(); // Level 1 290 | 291 | listener2.listen([&](const event::Value& event) { 292 | listener2.unlistenAll(); // Level 2 293 | 294 | listener3.listen([&](const event::Value& event) { 295 | listener3.unlistenAll(); // Level 3 (final) 296 | ++callCount; 297 | }); 298 | ++callCount; 299 | }); 300 | ++callCount; 301 | }); 302 | 303 | bus.postpone(event::Value{3}); 304 | bus.postpone(event::Value{3}); 305 | bus.postpone(event::Value{3}); 306 | bus.postpone(event::Value{3}); 307 | REQUIRE(bus.process() == 4); 308 | REQUIRE(callCount == 3); 309 | } 310 | 311 | TEST_CASE("Should be chain listen and unlisten When processing event", "[EventBus]") 312 | { 313 | EventBus bus; 314 | auto listener = EventBus::Listener::createNotOwning(bus); 315 | auto listenerOther = EventBus::Listener::createNotOwning(bus); 316 | 317 | int callCount = 0; 318 | int callOtherOption = 0; 319 | listener.listen([&](const event::Value& event) { 320 | ++callCount; 321 | // remember that we can only add it once! 322 | listenerOther.listen([&](const event::Value& event) { callOtherOption = 1; }); 323 | listenerOther.unlisten(); 324 | listenerOther.listen([&](const event::Value& event) { callOtherOption = 2; }); 325 | listenerOther.unlisten(); 326 | listenerOther.listen([&](const event::Value& event) { callOtherOption = 3; }); 327 | listenerOther.unlisten(); 328 | 329 | listenerOther.listen([&](const event::Value& event) { callOtherOption = 4; }); 330 | 331 | listener.unlistenAll(); 332 | }); 333 | 334 | bus.postpone(event::Value{3}); 335 | bus.postpone(event::Value{3}); 336 | bus.postpone(event::Value{3}); 337 | REQUIRE(bus.process() == 3); 338 | REQUIRE(callCount == 1); 339 | REQUIRE(callOtherOption == 4); 340 | } 341 | 342 | TEST_CASE("Should not process events When no more events", "[EventBus]") 343 | { 344 | EventBus bus; 345 | auto listener = EventBus::Listener::createNotOwning(bus); 346 | 347 | bus.postpone(event::Value{3}); 348 | bus.postpone(event::Value{3}); 349 | bus.postpone(event::Value{3}); 350 | REQUIRE(bus.process() == 3); // it shouldn't accumulate events 351 | REQUIRE(bus.process() == 0); 352 | } 353 | 354 | TEST_CASE("Should process event When listener transit", "[EventBus]") 355 | { 356 | /** 357 | * This case may be usefull when we use EventBus for some kind of state machine and we are 358 | * during transit from one state to other. 359 | */ 360 | EventBus bus; 361 | auto listenerA = EventBus::Listener::createNotOwning(bus); 362 | auto listenerB = EventBus::Listener::createNotOwning(bus); 363 | 364 | int listenerAReceiveEvent = 0; 365 | int listenerBReceiveEvent = 0; 366 | 367 | listenerA.listen([&](const event::Value& event) { ++listenerAReceiveEvent; }); 368 | 369 | REQUIRE(bus.process() == 0); 370 | 371 | // All cases should be same because of deterministic way of processing 372 | SECTION("Post event before transit") 373 | { 374 | bus.postpone(event::Value{3}); // <-- before 375 | 376 | listenerA.unlistenAll(); 377 | listenerB.listen([&](const event::Value& event) { ++listenerBReceiveEvent; }); 378 | } 379 | SECTION("Post event in transit") 380 | { 381 | listenerA.unlistenAll(); 382 | bus.postpone(event::Value{3}); // <-- in 383 | listenerB.listen([&](const event::Value& event) { ++listenerBReceiveEvent; }); 384 | } 385 | SECTION("Post event after transit") 386 | { 387 | listenerA.unlistenAll(); 388 | listenerB.listen([&](const event::Value& event) { ++listenerBReceiveEvent; }); 389 | 390 | bus.postpone(event::Value{3}); // <-- after 391 | } 392 | 393 | REQUIRE(bus.process() == 1); 394 | CHECK(listenerAReceiveEvent == 0); 395 | CHECK(listenerBReceiveEvent == 1); 396 | } 397 | 398 | TEST_CASE("Should NOT process event When listener unlisten before process", "[EventBus]") 399 | { 400 | EventBus bus; 401 | auto listener = EventBus::Listener::createNotOwning(bus); 402 | 403 | int listenerReceiveEvent = 0; 404 | 405 | listener.listen([&](const event::Value& event) { ++listenerReceiveEvent; }); 406 | 407 | REQUIRE(bus.process() == 0); 408 | bus.postpone(event::Value{3}); 409 | REQUIRE(bus.process() == 1); 410 | CHECK(listenerReceiveEvent == 1); 411 | 412 | // All cases should be same because of deterministic way of processing 413 | SECTION("Post event before unlisten") 414 | { 415 | bus.postpone(event::Value{3}); // <-- before 416 | listener.unlistenAll(); 417 | } 418 | SECTION("Post event after transit") 419 | { 420 | listener.unlistenAll(); 421 | bus.postpone(event::Value{3}); // <-- after 422 | } 423 | 424 | REQUIRE(bus.process() == 1); 425 | CHECK(listenerReceiveEvent == 1); 426 | } 427 | 428 | TEST_CASE("Should distinguish event producer When", "[EventBus]") 429 | { 430 | // EventBus bus; 431 | // 432 | // // dexode::eventbus::WrapTag e; 433 | // 434 | // int counterGui = 0; 435 | // int counterBackend = 0; 436 | // auto listener = EventBus::Listener::createNotOwning(bus); 437 | // 438 | // listener.listen([&](const event::Tag& event) { 439 | // if(event.tag == "gui") 440 | // { 441 | // ++counterGui; 442 | // } 443 | // else if(event.tag == "backend") 444 | // { 445 | // ++counterBackend; 446 | // } 447 | // else 448 | // { 449 | // FAIL(); 450 | // } 451 | // }); 452 | // 453 | // auto producerGui = [tag = Tag::gui](EventBus& bus) { 454 | // const event::T1 event; 455 | // bus.postpone(event); 456 | // }; 457 | // 458 | // auto producerBackend = [](EventBus& bus) { 459 | // const event::T1 event; 460 | // bus.postpone(Tag::backend, event); 461 | // }; 462 | // 463 | // auto producerNoTag = [](EventBus& bus) { 464 | // const event::T1 event; 465 | // bus.postpone(event); 466 | // }; 467 | // 468 | // auto producerGeneric = [](EventBus& bus, Tag tag) { 469 | // const event::T1 event; 470 | // if(tag == Tag::none) 471 | // { 472 | // bus.postpone(event); 473 | // } 474 | // else 475 | // { 476 | // bus.postpone(tag, event); 477 | // } 478 | // }; 479 | // 480 | // producerGui(bus); 481 | // producerGui(bus); 482 | // producerBackend(bus); 483 | // producerGui(bus); 484 | // producerNoTag(bus); 485 | // producerGeneric(bus, Tag::none); 486 | // producerGeneric(bus, Tag::backend); 487 | // 488 | // CHECK(bus.process() == 7); 489 | // 490 | // REQUIRE(counterGui == 3); 491 | // REQUIRE(counterBackend == 2); 492 | } 493 | 494 | } // namespace dexode::eventbus::test 495 | 496 | // TODO should listen work with bind'ing e.g. 497 | //_listener.listen( 498 | // std::bind(&BackDashboard::onLoadOrders, this, std::placeholders::_1)); 499 | -------------------------------------------------------------------------------- /test/unit/src/dexode/eventbus/test/SuiteEventID.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "dexode/eventbus/internal/event_id.hpp" 6 | 7 | using namespace dexode; 8 | 9 | namespace 10 | { 11 | struct Anonymous 12 | {}; 13 | } // namespace 14 | 15 | struct TestA 16 | { 17 | int a; 18 | }; 19 | 20 | namespace Test 21 | { 22 | struct TestA 23 | { 24 | bool b; 25 | }; 26 | 27 | namespace TestN 28 | { 29 | struct TestA 30 | { 31 | long long c; 32 | }; 33 | 34 | } // namespace TestN 35 | 36 | } // namespace Test 37 | 38 | namespace dexode::eventbus::test 39 | { 40 | 41 | TEST_CASE("Should return unique id for each event When using event_id", "[EventID]") 42 | { 43 | std::set unique; 44 | 45 | REQUIRE(unique.insert(internal::event_id()).second); 46 | REQUIRE_FALSE(unique.insert(internal::event_id()).second); // already there 47 | 48 | struct TestA // "name collision" but not quite collision 49 | {}; 50 | 51 | REQUIRE(unique.insert(internal::event_id()).second); 52 | REQUIRE(unique.insert(internal::event_id<::TestA>()).second); 53 | REQUIRE(unique.insert(internal::event_id()).second); 54 | REQUIRE(unique.insert(internal::event_id()).second); 55 | } 56 | 57 | } // namespace dexode::eventbus::test 58 | -------------------------------------------------------------------------------- /test/unit/src/dexode/eventbus/test/SuiteListener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "dexode/EventBus.hpp" 6 | #include "dexode/eventbus/test/event.hpp" 7 | 8 | namespace dexode::eventbus::test 9 | { 10 | 11 | using Listener = dexode::EventBus::Listener; 12 | 13 | TEST_CASE("Should remove all listeners When use unlistenAll", "[EventBus][Listener]") 14 | { 15 | EventBus bus; 16 | auto listener = Listener::createNotOwning(bus); 17 | 18 | int callCount = 0; 19 | listener.listen([&](const event::Value& event) { 20 | REQUIRE(event.value == 3); 21 | ++callCount; 22 | }); 23 | listener.listen([&](const event::T1& event) { ++callCount; }); 24 | 25 | bus.postpone(event::Value{3}); 26 | bus.postpone(event::T1{}); 27 | REQUIRE(bus.process() == 2); 28 | REQUIRE(callCount == 2); 29 | 30 | listener.unlistenAll(); 31 | 32 | bus.postpone(event::Value{2}); 33 | bus.postpone(event::T1{}); 34 | REQUIRE(bus.process() == 2); 35 | REQUIRE(callCount == 2); // unchanged 36 | } 37 | 38 | TEST_CASE("Should unlisten all events When listener instance is overriden", "[EventBus][Listener]") 39 | { 40 | EventBus bus; 41 | int callCount = 0; 42 | auto listener = Listener::createNotOwning(bus); 43 | listener.listen([&](const event::Value& event) { 44 | REQUIRE(event.value == 3); 45 | ++callCount; 46 | }); 47 | bus.postpone(event::Value{3}); 48 | REQUIRE(bus.process() == 1); 49 | REQUIRE(callCount == 1); 50 | 51 | listener.transfer(Listener{}); 52 | 53 | bus.postpone(event::Value{2}); 54 | REQUIRE(bus.process() == 1); 55 | REQUIRE(callCount == 1); 56 | } 57 | 58 | TEST_CASE("Should unlisten all events When listener instance is destroyed", "[EventBus][Listener]") 59 | { 60 | EventBus bus; 61 | int callCount = 0; 62 | 63 | { 64 | auto listener = Listener::createNotOwning(bus); 65 | listener.listen([&](const event::Value& event) { 66 | REQUIRE(event.value == 3); 67 | ++callCount; 68 | }); 69 | bus.postpone(event::Value{3}); 70 | REQUIRE(bus.process() == 1); 71 | REQUIRE(callCount == 1); 72 | } 73 | 74 | bus.postpone(event::Value{2}); 75 | REQUIRE(bus.process() == 1); 76 | REQUIRE(callCount == 1); 77 | } 78 | 79 | TEST_CASE("Should keep listeners When listener is moved", "[EventBus][Listener]") 80 | { 81 | auto bus = std::make_shared(); 82 | int callCount = 0; 83 | 84 | Listener transferOne; 85 | { 86 | Listener listener{bus}; 87 | listener.listen([&](const event::Value& event) { 88 | REQUIRE(event.value == 3); 89 | ++callCount; 90 | }); 91 | bus->postpone(event::Value{3}); 92 | REQUIRE(bus->process() == 1); 93 | REQUIRE(callCount == 1); 94 | 95 | transferOne.transfer(std::move(listener)); 96 | } 97 | 98 | bus->postpone(event::Value{3}); 99 | REQUIRE(bus->process() == 1); 100 | REQUIRE(callCount == 2); 101 | } 102 | 103 | TEST_CASE("Should receive event When listener added AFTER event emit but BEFORE event porcess", 104 | "[EventBus][Listener]") 105 | { 106 | auto bus = std::make_shared(); 107 | int callCount = 0; 108 | bus->postpone(event::Value{22}); 109 | 110 | Listener listener{bus}; 111 | listener.listen([&](const event::Value& event) { 112 | REQUIRE(event.value == 22); 113 | ++callCount; 114 | }); 115 | 116 | REQUIRE(callCount == 0); 117 | bus->process(); 118 | REQUIRE(callCount == 1); 119 | } 120 | 121 | TEST_CASE("Should bind listen to class method and unbind When clazz dtor is called", 122 | "[EventBus][Listener]") 123 | { 124 | auto bus = std::make_shared(); 125 | struct TestBind 126 | { 127 | int callCount = 0; 128 | Listener listener; 129 | 130 | TestBind(const std::shared_ptr& bus) 131 | : listener{bus} 132 | { 133 | listener.listen(std::bind(&TestBind::onEvent, this, std::placeholders::_1)); 134 | } 135 | 136 | void onEvent(const event::T1& event) 137 | { 138 | ++callCount; 139 | } 140 | }; 141 | 142 | bus->postpone(event::T1{}); 143 | { 144 | TestBind bindClazz{bus}; 145 | REQUIRE(bindClazz.callCount == 0); 146 | CHECK(bus->process() == 1); 147 | REQUIRE(bindClazz.callCount == 1); 148 | } 149 | bus->postpone(event::T1{}); 150 | CHECK(bus->process() == 1); 151 | } 152 | 153 | static int globalCallCount{0}; // bad practice but want to just test 154 | void freeFunction(const event::T1& event) 155 | { 156 | ++globalCallCount; 157 | } 158 | 159 | TEST_CASE("Should compile When listen in different forms", "[EventBus][Listener]") 160 | { 161 | EventBus bus; 162 | 163 | int callCount = 0; 164 | 165 | // Listen with lambda 166 | auto listener = Listener::createNotOwning(bus); 167 | listener.listen([&](const event::T1& event) { ++callCount; }); 168 | 169 | // Listen with std::function 170 | auto listener2 = Listener::createNotOwning(bus); 171 | std::function callback = [&](const event::T1&) { ++callCount; }; 172 | listener2.listen(callback); 173 | 174 | // Listen with std::bind 175 | auto listener3 = Listener::createNotOwning(bus); 176 | struct TestClazz 177 | { 178 | int clazzCallCount{0}; 179 | void onEvent(const event::T1& event) 180 | { 181 | ++clazzCallCount; 182 | } 183 | }; 184 | TestClazz clazz; 185 | listener3.listen(std::bind(&TestClazz::onEvent, &clazz, std::placeholders::_1)); 186 | 187 | // Listen with free function 188 | auto listener4 = Listener::createNotOwning(bus); 189 | listener4.listen(freeFunction); 190 | 191 | bus.postpone(event::T1{}); 192 | bus.process(); 193 | 194 | REQUIRE(globalCallCount == 1); 195 | REQUIRE(clazz.clazzCallCount == 1); 196 | REQUIRE(callCount == 2); 197 | } 198 | 199 | TEST_CASE("Should NOT be able to add multiple same event callbacks When using same listener", 200 | "[EventBus][Listener]") 201 | { 202 | /// User should use separate Listener instance as it would be unabigious what should happen when 203 | /// call unlisten 204 | EventBus bus; 205 | auto listener = Listener::createNotOwning(bus); 206 | 207 | listener.listen([](const event::Value& event) {}); 208 | REQUIRE_THROWS(listener.listen([](const event::Value& event) {})); 209 | } 210 | 211 | TEST_CASE("Should compile", "[EventBus][Listener]") 212 | { 213 | // Test case to check for compilation 214 | EventBus bus; 215 | { 216 | auto listener = Listener::createNotOwning(bus); 217 | const auto callback = [](const event::Value& event) {}; 218 | listener.listen(callback); 219 | } 220 | { 221 | auto listener = Listener::createNotOwning(bus); 222 | auto callback = [](const event::Value& event) {}; 223 | listener.listen(callback); 224 | } 225 | { 226 | auto listener = Listener::createNotOwning(bus); 227 | auto callback = [](const event::Value& event) {}; 228 | listener.listen(std::move(callback)); 229 | } 230 | { 231 | auto listener = Listener::createNotOwning(bus); 232 | listener.listen([](const event::Value& event) {}); 233 | } 234 | } 235 | 236 | class TestClazz 237 | { 238 | public: 239 | static int counter; 240 | 241 | TestClazz(int id, const std::shared_ptr& bus) 242 | : _id{id} 243 | , _listener{bus} 244 | { 245 | registerListener(); 246 | } 247 | 248 | TestClazz(TestClazz&& other) 249 | : _id{other._id} 250 | , _listener{other._listener.getBus()} 251 | { 252 | // We need to register again 253 | registerListener(); 254 | } 255 | 256 | ~TestClazz() 257 | { 258 | _id = 0; 259 | } 260 | 261 | private: 262 | int _id = 0; 263 | EventBus::Listener _listener; 264 | 265 | void registerListener() 266 | { 267 | _listener.listen([this](const event::Value& event) { 268 | if(_id == 1) 269 | { 270 | ++counter; 271 | } 272 | }); 273 | } 274 | }; 275 | 276 | int TestClazz::counter = 0; 277 | 278 | TEST_CASE("Should not allow for mistake with move ctor", "[EventBus][Listener]") 279 | { 280 | /** 281 | * Test case TAG: FORBID_MOVE_LISTENER 282 | * 283 | * This case is little bit complicated. 284 | * We can't move EventBus::Listener as it capture 'this' in ctor so whenever we would use it it 285 | * would lead to UB. 286 | */ 287 | std::shared_ptr bus = std::make_shared(); 288 | 289 | std::vector vector; 290 | vector.emplace_back(1, bus); 291 | vector.emplace_back(2, bus); 292 | vector.emplace_back(3, bus); 293 | 294 | bus->postpone(event::Value{100}); 295 | bus->process(); 296 | 297 | REQUIRE(TestClazz::counter == 1); 298 | } 299 | 300 | 301 | TEST_CASE("Should allow to check if listener is already listening", "[EventBus][Listener]") 302 | { 303 | // Related to Github Issue: https://github.com/gelldur/EventBus/issues/48 304 | EventBus bus; 305 | int callCount = 0; 306 | auto listener = Listener::createNotOwning(bus); 307 | 308 | CHECK_FALSE(listener.isListening()); 309 | 310 | bus.postpone(event::Value{3}); 311 | REQUIRE(bus.process() == 1); 312 | REQUIRE(callCount == 0); // not listening 313 | 314 | listener.listen([&](const event::Value& event) 315 | { 316 | REQUIRE(event.value == 2); 317 | ++callCount; 318 | }); 319 | CHECK(listener.isListening()); 320 | 321 | bus.postpone(event::Value{2}); 322 | REQUIRE(bus.process() == 1); 323 | REQUIRE(callCount == 1); 324 | 325 | CHECK(listener.isListening()); 326 | listener.unlisten(); 327 | CHECK_FALSE(listener.isListening()); 328 | 329 | bus.postpone(event::Value{1}); 330 | REQUIRE(bus.process() == 1); 331 | REQUIRE(callCount == 1); 332 | } 333 | 334 | } // namespace dexode::eventbus::test 335 | -------------------------------------------------------------------------------- /test/unit/src/dexode/eventbus/test/event.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 24.11.2019. 3 | // 4 | #pragma once 5 | 6 | namespace dexode::eventbus::test::event 7 | { 8 | 9 | struct T1 10 | {}; 11 | 12 | struct T2 13 | {}; 14 | 15 | struct Value 16 | { 17 | int value{-1}; 18 | }; 19 | 20 | template 21 | struct Tag 22 | { 23 | Event data; 24 | std::string tag; 25 | }; 26 | 27 | struct WaitPerk 28 | { 29 | 30 | }; 31 | 32 | } // namespace dexode::eventbus::test::event 33 | -------------------------------------------------------------------------------- /test/unit/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /use_case/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | add_subdirectory(basic/) 3 | add_subdirectory(tagged_events/) 4 | -------------------------------------------------------------------------------- /use_case/README.md: -------------------------------------------------------------------------------- 1 | # Use cases 2 | 3 | This folder should contain use cases and approaches how to use EventBus. 4 | 5 | Most important need for this is only to store needed use cases by other projects. 6 | -------------------------------------------------------------------------------- /use_case/basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | add_executable(UseCase_Basic 3 | src/main.cpp 4 | ) 5 | 6 | target_link_libraries(UseCase_Basic PRIVATE Dexode::EventBus) 7 | -------------------------------------------------------------------------------- /use_case/basic/README.md: -------------------------------------------------------------------------------- 1 | # Use case: Basic 2 | 3 | Just basic use case of EventBus. 4 | -------------------------------------------------------------------------------- /use_case/basic/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Sample code to play with! 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using EventBus = dexode::EventBus; 13 | using Listener = dexode::EventBus::Listener; 14 | 15 | namespace event // Example namespace for events 16 | { 17 | struct Gold // Event that will be proceed when our gold changes 18 | { 19 | int value = 0; 20 | }; 21 | } // namespace event 22 | 23 | enum class Monster 24 | { 25 | Frog, 26 | Tux, 27 | Shark 28 | }; 29 | 30 | class Character 31 | { 32 | public: 33 | Character(const std::shared_ptr& eventBus) 34 | : _bus{eventBus} 35 | {} 36 | 37 | void kill(Monster monsterType) 38 | { 39 | if(Monster::Frog == monsterType) 40 | { 41 | _gold += 1; 42 | } 43 | else if(Monster::Tux == monsterType) 44 | { 45 | _gold += 100; 46 | } 47 | else if(Monster::Shark == monsterType) 48 | { 49 | _gold += 25; 50 | } 51 | _bus->postpone(event::Gold{_gold}); 52 | } 53 | 54 | private: 55 | int _gold = 0; 56 | std::shared_ptr _bus; 57 | }; 58 | 59 | class UIWallet 60 | { 61 | public: 62 | UIWallet(const std::shared_ptr& eventBus) 63 | : _listener{eventBus} 64 | {} 65 | 66 | void onDraw() // Example "draw" of UI 67 | { 68 | std::cout << "Gold:" << _gold << std::endl; 69 | } 70 | 71 | void onEnter() // We could also do such things in ctor and dtor but a lot of UI has something 72 | // like this 73 | { 74 | _listener.listen( 75 | [this](const auto& event) { _gold = std::to_string(event.value); }); 76 | } 77 | 78 | void onExit() 79 | { 80 | _listener.unlistenAll(); 81 | } 82 | 83 | private: 84 | std::string _gold = "0"; 85 | Listener _listener; 86 | }; 87 | 88 | // Shop button is only enabled when we have some gold (odd decision but for sample good :P) 89 | class ShopButton 90 | { 91 | public: 92 | ShopButton(const std::shared_ptr& eventBus) 93 | : _listener{eventBus} 94 | { 95 | // We can use lambda or bind your choice 96 | _listener.listen( 97 | std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1)); 98 | // Also we use RAII idiom to handle unlisten 99 | } 100 | 101 | bool isEnabled() const 102 | { 103 | return _isEnabled; 104 | } 105 | 106 | private: 107 | Listener _listener; 108 | bool _isEnabled = false; 109 | 110 | void onGoldUpdated(const event::Gold& event) 111 | { 112 | _isEnabled = event.value > 0; 113 | std::cout << "Shop button is:" << _isEnabled << std::endl; // some kind of logs 114 | } 115 | }; 116 | 117 | int main(int argc, char* argv[]) 118 | { 119 | auto eventBus = std::make_shared(); 120 | 121 | Character characterController{eventBus}; 122 | 123 | UIWallet wallet{eventBus}; // UIWallet doesn't know anything about character 124 | // or even who store gold 125 | ShopButton shopButton{eventBus}; // ShopButton doesn't know anything about character 126 | { 127 | wallet.onEnter(); 128 | } 129 | 130 | wallet.onDraw(); 131 | { 132 | characterController.kill(Monster::Tux); 133 | eventBus->process(); 134 | } 135 | wallet.onDraw(); 136 | 137 | // It is easy to test UI eg. 138 | eventBus->postpone(event::Gold{1}); 139 | eventBus->process(); 140 | assert(shopButton.isEnabled() == true); 141 | 142 | eventBus->postpone(event::Gold{0}); 143 | eventBus->process(); 144 | assert(shopButton.isEnabled() == false); 145 | 146 | wallet.onExit(); 147 | 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /use_case/tagged_events/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | add_executable(UseCase_TaggedEvents 3 | src/Character.cpp src/Character.hpp 4 | src/event.hpp 5 | src/EventBus.hpp 6 | src/main.cpp 7 | src/Team.cpp src/Team.hpp 8 | src/Gui.cpp src/Gui.hpp) 9 | 10 | target_include_directories(UseCase_TaggedEvents PUBLIC src/) 11 | target_link_libraries(UseCase_TaggedEvents PRIVATE Dexode::EventBus) 12 | -------------------------------------------------------------------------------- /use_case/tagged_events/README.md: -------------------------------------------------------------------------------- 1 | # Use case: Tagged events 2 | 3 | ## Motivation 4 | Sometimes we have event producer but at some point we would like 5 | to reuse event producer for different configuration. In some cases we 6 | could just add some "tag" to event to resolve this issue. 7 | 8 | I would like to resolve this in other way as maybe data producer can't 9 | be aware of its "tag". Other issue is spreading "tag" requirement over 10 | whole project and maybe we don't want to modify old code. 11 | 12 | It would be nice to just add some abstraction layer which will resolve 13 | this issue. 14 | 15 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/Character.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #include "Character.hpp" 5 | 6 | #include "event.hpp" 7 | 8 | Character::Character(std::shared_ptr bus) 9 | : _bus{std::move(bus)} 10 | { 11 | (void)_iq; // probably something should use it ;) 12 | } 13 | 14 | void Character::pickGold(int goldCount) 15 | { 16 | // We could store character name as member. Sure this isn't complex example rather simple one. 17 | // Imagine few levels of composition ;) 18 | _sackGold += goldCount; 19 | _bus->postpone(event::GoldUpdate{_sackGold}); 20 | } 21 | 22 | void Character::damage(int amount) 23 | { 24 | _health -= amount; 25 | } 26 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/Character.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "EventBus.hpp" 10 | 11 | class Character 12 | { 13 | public: 14 | Character(std::shared_ptr bus); 15 | void pickGold(int goldCount); 16 | void damage(int amount); 17 | 18 | private: 19 | std::shared_ptr _bus; 20 | int _sackGold = 0; 21 | int _health = 100; 22 | int _iq = 200; 23 | }; 24 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/EventBus.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | using EventBus = dexode::EventBus; 9 | using Listener = dexode::EventBus::Listener; 10 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/Gui.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #include "Gui.hpp" 5 | 6 | #include 7 | 8 | #include "event.hpp" 9 | 10 | Gui::Gui(const std::shared_ptr& bus) 11 | : _listener{bus} 12 | { 13 | _listener.listen( 14 | [this](const event::NewTeamMember& event) { _sackOfGold.emplace(event.memberName, 0); }); 15 | 16 | _listener.listen([this](const event::TagEvent& event) { 17 | auto found = _sackOfGold.find(event.tag); 18 | if(found != _sackOfGold.end()) 19 | { 20 | found->second = event.data.goldCount; 21 | } 22 | }); 23 | } 24 | 25 | void Gui::draw() 26 | { 27 | std::cout << "-----------------------------\n"; 28 | for(const auto& player : _sackOfGold) 29 | { 30 | std::cout << "Name:" << player.first << " - gold: " << player.second << "\n"; 31 | } 32 | std::cout << "-----------------------------" << std::endl; 33 | } 34 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/Gui.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "EventBus.hpp" 10 | 11 | class Gui 12 | { 13 | public: 14 | Gui(const std::shared_ptr& bus); 15 | 16 | void draw(); 17 | 18 | private: 19 | EventBus::Listener _listener; 20 | std::map _sackOfGold; 21 | }; 22 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/Team.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #include "Team.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "event.hpp" 11 | 12 | Team::Team(std::shared_ptr bus) 13 | : _bus(std::move(bus)) 14 | {} 15 | 16 | Character& Team::getMember(const std::string& name) 17 | { 18 | auto found = std::find(_names.begin(), _names.end(), name); 19 | if(found == _names.end()) 20 | { 21 | throw std::out_of_range("No such team member: " + name); 22 | } 23 | return _squad.at(std::distance(_names.begin(), found)); 24 | } 25 | 26 | void Team::addPlayer(const std::string& name) 27 | { 28 | auto characterBus = std::make_shared(); 29 | { 30 | auto tagPerk = std::make_unique(name, _bus.get()); 31 | tagPerk->wrapTag>(); 32 | 33 | characterBus->addPerk(std::move(tagPerk)) 34 | .registerPrePostpone(&dexode::eventbus::perk::TagPerk::onPrePostponeEvent); 35 | } 36 | characterBus->addPerk(std::make_unique(_bus)) 37 | .registerPrePostpone(&dexode::eventbus::perk::PassEverythingPerk::onPrePostponeEvent); 38 | 39 | _squad.emplace_back(characterBus); 40 | _names.push_back(name); 41 | 42 | _bus->postpone(event::NewTeamMember{name}); 43 | } 44 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/Team.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "Character.hpp" 10 | 11 | class Team 12 | { 13 | public: 14 | Team(std::shared_ptr bus); 15 | void addPlayer(const std::string& name); 16 | 17 | Character& getMember(const std::string& name); 18 | 19 | private: 20 | std::vector _names; 21 | std::vector _squad; 22 | 23 | std::shared_ptr _bus; 24 | }; 25 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/event.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by gelldur on 22.12.2019. 3 | // 4 | #pragma once 5 | 6 | namespace event 7 | { 8 | 9 | struct GoldUpdate 10 | { 11 | int goldCount; 12 | }; 13 | 14 | struct NewTeamMember 15 | { 16 | std::string memberName; 17 | }; 18 | 19 | template 20 | struct TagEvent 21 | { 22 | using Event = T; 23 | std::string tag; 24 | Event data; 25 | }; 26 | 27 | } // namespace event 28 | -------------------------------------------------------------------------------- /use_case/tagged_events/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "EventBus.hpp" 7 | #include "Gui.hpp" 8 | #include "Team.hpp" 9 | 10 | int main(int argc, char* argv[]) 11 | { 12 | auto eventBus = std::make_shared(); 13 | 14 | Gui gui{eventBus}; 15 | Team myTeam{eventBus}; 16 | 17 | auto updateFrame = [&]() { 18 | // single frame update ;) 19 | eventBus->process(); 20 | gui.draw(); 21 | std::cout << "###################################################\n"; 22 | std::cout << "###################################################" << std::endl; 23 | }; 24 | 25 | { // single update 26 | myTeam.addPlayer("Gelldur"); 27 | updateFrame(); 28 | } 29 | 30 | { // single update 31 | myTeam.getMember("Gelldur").pickGold(100); 32 | updateFrame(); 33 | } 34 | 35 | { // single update 36 | myTeam.addPlayer("Gosia"); 37 | myTeam.addPlayer("Dexter"); 38 | updateFrame(); 39 | } 40 | 41 | return 0; 42 | } 43 | --------------------------------------------------------------------------------