fix remove submodule
Some checks failed
rpcrypto-build / build (Debug, himix200.toolchain.cmake) (push) Failing after 1m0s
rpcrypto-build / build (Release, himix200.toolchain.cmake) (push) Failing after 1m4s
rpcrypto-build / build (Release, hisiv510.toolchain.cmake) (push) Failing after 1m6s
linux-hisiv500-gcc / linux-gcc-hisiv500 (push) Failing after 1m10s
rpcrypto-build / build (Debug, hisiv510.toolchain.cmake) (push) Failing after 1m24s
linux-mips64-gcc / linux-gcc-mips64el (push) Successful in 1m46s
linux-x64-gcc / linux-gcc (push) Successful in 1m56s
Some checks failed
rpcrypto-build / build (Debug, himix200.toolchain.cmake) (push) Failing after 1m0s
rpcrypto-build / build (Release, himix200.toolchain.cmake) (push) Failing after 1m4s
rpcrypto-build / build (Release, hisiv510.toolchain.cmake) (push) Failing after 1m6s
linux-hisiv500-gcc / linux-gcc-hisiv500 (push) Failing after 1m10s
rpcrypto-build / build (Debug, hisiv510.toolchain.cmake) (push) Failing after 1m24s
linux-mips64-gcc / linux-gcc-mips64el (push) Successful in 1m46s
linux-x64-gcc / linux-gcc (push) Successful in 1m56s
This commit is contained in:
parent
d5ae2b9797
commit
0886112d1b
@ -1 +0,0 @@
|
||||
Subproject commit 62046399d423b74fd2612c85ef287bc6d309e0b4
|
144
3party/EventBus/.clang-format
Normal file
144
3party/EventBus/.clang-format
Normal file
@ -0,0 +1,144 @@
|
||||
# Checkout config tool: https://zed0.co.uk/clang-format-configurator/
|
||||
# Or http://cf.monofraps.net/
|
||||
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
# https://github.com/01org/parameter-framework/blob/master/.clang-format
|
||||
|
||||
# Tested on: clang-format version 8.0.0
|
||||
# Version 1.2
|
||||
|
||||
|
||||
# Common settings
|
||||
BasedOnStyle: WebKit
|
||||
TabWidth: 4
|
||||
IndentWidth: 4
|
||||
UseTab: ForContinuationAndIndentation
|
||||
ColumnLimit: 100
|
||||
|
||||
# Other languages JavaScript, Proto
|
||||
|
||||
---
|
||||
Language: Cpp
|
||||
|
||||
# http://releases.llvm.org/6.0.1/tools/clang/docs/ClangFormatStyleOptions.html#disabling-formatting-on-a-piece-of-code
|
||||
# int formatted_code;
|
||||
# // clang-format off
|
||||
# void unformatted_code ;
|
||||
# // clang-format on
|
||||
# void formatted_code_again;
|
||||
|
||||
DisableFormat: false
|
||||
Standard: Auto
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: true
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlinesLeft: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
|
||||
# Configure each individual brace in BraceWrapping
|
||||
BreakBeforeBraces: Custom
|
||||
# Control of individual brace wrapping cases
|
||||
BraceWrapping: {
|
||||
AfterClass: 'true'
|
||||
AfterControlStatement: 'true'
|
||||
AfterEnum: 'true'
|
||||
AfterFunction: 'true'
|
||||
AfterNamespace: 'true'
|
||||
AfterStruct: 'true'
|
||||
AfterUnion: 'true'
|
||||
BeforeCatch: 'true'
|
||||
BeforeElse: 'true'
|
||||
IndentBraces: 'false'
|
||||
AfterExternBlock: 'true'
|
||||
SplitEmptyFunction: 'false'
|
||||
SplitEmptyRecord: 'false'
|
||||
SplitEmptyNamespace: 'true'
|
||||
}
|
||||
|
||||
BreakAfterJavaFieldAnnotations: true
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
BreakStringLiterals: true
|
||||
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
DerivePointerAlignment: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
|
||||
IncludeCategories:
|
||||
- Regex: '^<.*\..+'
|
||||
Priority: 2
|
||||
- Regex: '^<.*'
|
||||
Priority: 1
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IndentCaseLabels: false
|
||||
FixNamespaceComments: true
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
JavaScriptQuotes: Double
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000000
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
|
||||
PointerAlignment: Left
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: Never
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
|
||||
SortUsingDeclarations: true
|
||||
SortIncludes: true
|
||||
|
||||
# Comments are for developers, they should arrange them but now we allow for ReflowComments
|
||||
# It should add at least 1 space after //
|
||||
ReflowComments: true
|
||||
|
||||
IncludeBlocks: Regroup
|
||||
IndentPPDirectives: AfterHash
|
||||
|
||||
#AllowShortLambdasOnASingleLine: All
|
||||
---
|
23
3party/EventBus/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
23
3party/EventBus/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Sample code or PR with unit test :) (preferable)
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Build:**
|
||||
- compiler: [e.g. clang, gcc]
|
||||
- Version [e.g. 6.0.0]
|
||||
- Link type [static, shared, buildin]
|
||||
- Any specific flags ?
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
17
3party/EventBus/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
3party/EventBus/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
35
3party/EventBus/.gitignore
vendored
Normal file
35
3party/EventBus/.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
build/
|
||||
cmake-build*/
|
||||
.idea/
|
||||
|
||||
Release/
|
||||
Debug/
|
3
3party/EventBus/.gitmodules
vendored
Normal file
3
3party/EventBus/.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "performance/benchmark"]
|
||||
path = performance/benchmark
|
||||
url = https://github.com/google/benchmark.git
|
98
3party/EventBus/.travis.yml
Normal file
98
3party/EventBus/.travis.yml
Normal file
@ -0,0 +1,98 @@
|
||||
language: cpp
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
exclude: # On OSX g++ is a symlink to clang++ by default
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
|
||||
include:
|
||||
# Bionic Ubuntu 18.04
|
||||
- os: linux
|
||||
name: "Bionic - gcc 7.4.0"
|
||||
dist: bionic # We can't install packages on Bionic, not sure why. Travis ignores addons...
|
||||
compiler: gcc
|
||||
# Bionic Ubuntu 18.04
|
||||
- os: linux
|
||||
name: "Bionic - clang 7"
|
||||
dist: bionic # We can't install packages on Bionic, not sure why. Travis ignores addons...
|
||||
compiler: clang
|
||||
|
||||
# clang-6
|
||||
- os: linux
|
||||
name: "Xenial - clang 6"
|
||||
dist: xenial
|
||||
env: [USE_CC='/usr/bin/clang-6.0', USE_CXX='/usr/bin/clang++-6.0']
|
||||
addons:
|
||||
apt:
|
||||
packages: ['clang-6.0', 'cmake', 'libstdc++-9-dev', 'libstdc++6']
|
||||
sources: ['ubuntu-toolchain-r-test', 'ubuntu-toolchain-r-test']
|
||||
# clang-8
|
||||
- os: linux
|
||||
name: "Xenial - clang 8"
|
||||
dist: xenial
|
||||
env: [USE_CC='/usr/bin/clang-8', USE_CXX='/usr/bin/clang++-8']
|
||||
addons:
|
||||
apt:
|
||||
packages: ['clang-8', 'cmake', 'libstdc++-9-dev', 'libstdc++6']
|
||||
sources: ['llvm-toolchain-xenial-8', 'ubuntu-toolchain-r-test']
|
||||
# clang-9
|
||||
- os: linux
|
||||
name: "Xenial - clang 9"
|
||||
dist: xenial
|
||||
env: [USE_CC='/usr/bin/clang-9', USE_CXX='/usr/bin/clang++-9']
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- sourceline: 'deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-9 main'
|
||||
key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key'
|
||||
packages: ['clang-9', 'cmake', 'libstdc++-9-dev', 'libstdc++6']
|
||||
|
||||
# gcc-8
|
||||
- os: linux
|
||||
name: "Xenial - gcc 8"
|
||||
dist: xenial
|
||||
env: [USE_CC='/usr/bin/gcc-8', USE_CXX='/usr/bin/g++-8']
|
||||
addons:
|
||||
apt:
|
||||
packages: ['gcc-8', 'g++-8', 'cmake']
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
# gcc-9
|
||||
- os: linux
|
||||
name: "Xenial - gcc 9"
|
||||
dist: xenial
|
||||
env: [USE_CC='/usr/bin/gcc-9', USE_CXX='/usr/bin/g++-9']
|
||||
addons:
|
||||
apt:
|
||||
packages: ['gcc-9', 'g++-9', 'cmake']
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
|
||||
- os: osx
|
||||
osx_image: xcode10.3
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode11.3
|
||||
compiler: clang
|
||||
|
||||
|
||||
before_install:
|
||||
# Override compilers set by Travis
|
||||
- if [ ! -z "${USE_CC}" ]; then echo "Override CC=${USE_CC}"; eval "CC=${USE_CC}"; fi
|
||||
- if [ ! -z "${USE_CXX}" ]; then echo "Override CXX=${USE_CXX}"; eval "CXX=${USE_CXX}"; fi
|
||||
- echo ${CC} && ${CC} --version
|
||||
- echo ${CXX} && ${CXX} --version
|
||||
# Install catch 2 dependency
|
||||
- wget https://github.com/catchorg/Catch2/archive/v2.11.1.zip
|
||||
- unzip v2.11.1.zip && cd Catch2-2.11.1 && mkdir -p build/ && cd build/
|
||||
- cmake -DCMAKE_BUILD_TYPE=Release .. -DCMAKE_CXX_STANDARD=14 -DCMAKE_INSTALL_PREFIX=~/.local/ && cmake --build . --target install
|
||||
- cd ../..
|
||||
|
||||
script:
|
||||
#Build & Install library
|
||||
- (mkdir -p lib/build-debug/ && cd lib/build-debug && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . --target install)
|
||||
- (mkdir -p lib/build-release/ && cd lib/build-release && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . --target install)
|
||||
# Run tests
|
||||
- (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 .)
|
||||
- (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 .)
|
||||
|
107
3party/EventBus/CMakeLists.txt
Normal file
107
3party/EventBus/CMakeLists.txt
Normal file
@ -0,0 +1,107 @@
|
||||
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
|
||||
|
||||
# Layout of project is inspired by: https://youtu.be/6sWec7b0JIc?t=20m50s This
|
||||
# top level CMakeLists should be used for development
|
||||
|
||||
project(EventBusDev LANGUAGES CXX)
|
||||
|
||||
option(ENABLE_TEST "Enable test" OFF)
|
||||
option(ENABLE_PERFORMANCE "Enable performance subproject" OFF)
|
||||
option(ENABLE_LIBCXX "Enable build with libc++" OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if(ENABLE_LIBCXX)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_EXE_LINKER_FLAGS
|
||||
"${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi")
|
||||
else()
|
||||
message(FATAL_ERROR "C++ compiler should be set to clang")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build Types
|
||||
set(CMAKE_BUILD_TYPE
|
||||
${CMAKE_BUILD_TYPE}
|
||||
CACHE
|
||||
STRING
|
||||
"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel tsan asan lsan msan ubsan"
|
||||
FORCE)
|
||||
|
||||
# ThreadSanitizer
|
||||
set(CMAKE_C_FLAGS_TSAN
|
||||
"-fsanitize=thread -g -O1"
|
||||
CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds."
|
||||
FORCE)
|
||||
set(CMAKE_CXX_FLAGS_TSAN
|
||||
"-fsanitize=thread -g -O1"
|
||||
CACHE STRING
|
||||
"Flags used by the C++ compiler during ThreadSanitizer builds." FORCE)
|
||||
|
||||
# AddressSanitize
|
||||
set(CMAKE_C_FLAGS_ASAN
|
||||
"-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1"
|
||||
CACHE STRING "Flags used by the C compiler during AddressSanitizer builds."
|
||||
FORCE)
|
||||
set(CMAKE_CXX_FLAGS_ASAN
|
||||
"-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1"
|
||||
CACHE STRING
|
||||
"Flags used by the C++ compiler during AddressSanitizer builds."
|
||||
FORCE)
|
||||
|
||||
# LeakSanitizer
|
||||
set(CMAKE_C_FLAGS_LSAN
|
||||
"-fsanitize=leak -fno-omit-frame-pointer -g -O1"
|
||||
CACHE STRING "Flags used by the C compiler during LeakSanitizer builds."
|
||||
FORCE)
|
||||
set(CMAKE_CXX_FLAGS_LSAN
|
||||
"-fsanitize=leak -fno-omit-frame-pointer -g -O1"
|
||||
CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds."
|
||||
FORCE)
|
||||
|
||||
# Remember to switch lib:
|
||||
# https://stackoverflow.com/questions/20617788/using-memory-sanitizer-with-libstdc/20784130#20784130
|
||||
# use ENABLE_LIBCXX MemorySanitizer
|
||||
set(CMAKE_C_FLAGS_MSAN
|
||||
"-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2"
|
||||
CACHE STRING "Flags used by the C compiler during MemorySanitizer builds."
|
||||
FORCE)
|
||||
set(CMAKE_CXX_FLAGS_MSAN
|
||||
"-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2"
|
||||
CACHE STRING
|
||||
"Flags used by the C++ compiler during MemorySanitizer builds." FORCE)
|
||||
|
||||
# UndefinedBehaviour
|
||||
set(CMAKE_C_FLAGS_UBSAN
|
||||
"-fsanitize=undefined"
|
||||
CACHE
|
||||
STRING
|
||||
"Flags used by the C compiler during UndefinedBehaviourSanitizer builds."
|
||||
FORCE)
|
||||
set(CMAKE_CXX_FLAGS_UBSAN
|
||||
"-fsanitize=undefined"
|
||||
CACHE
|
||||
STRING
|
||||
"Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds."
|
||||
FORCE)
|
||||
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(use_case)
|
||||
|
||||
if(ENABLE_TEST)
|
||||
enable_testing()
|
||||
add_subdirectory(test/)
|
||||
endif()
|
||||
|
||||
if(ENABLE_PERFORMANCE)
|
||||
add_subdirectory(performance/)
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
target_compile_options(
|
||||
EventBus PRIVATE -Wall -pedantic -Wnon-virtual-dtor -Werror
|
||||
-Wno-error=deprecated-declarations)
|
||||
endif()
|
202
3party/EventBus/LICENSE
Normal file
202
3party/EventBus/LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
179
3party/EventBus/README.md
Normal file
179
3party/EventBus/README.md
Normal file
@ -0,0 +1,179 @@
|
||||
# EventBus [![GitHub version](https://badge.fury.io/gh/gelldur%2FEventBus.svg)](https://badge.fury.io/gh/gelldur%2FEventBus)
|
||||
[![Build status for Travis](https://travis-ci.org/gelldur/EventBus.svg?branch=master)](https://travis-ci.org/gelldur/EventBus)
|
||||
[![Build status for Appveyor](https://ci.appveyor.com/api/projects/status/github/gelldur/EventBus)](https://ci.appveyor.com/project/gelldur/EventBus)
|
||||
|
||||
Simple and very fast event bus.
|
||||
The EventBus library is a convenient realization of the observer pattern.
|
||||
It works perfectly to supplement the implementation of MVC logic (model-view-controller) in event-driven UIs
|
||||
|
||||
General concept
|
||||
![EventBus Diagram](docs/res/EventBus-concept.png)
|
||||
|
||||
|
||||
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)
|
||||
from [cocos2d-x](https://github.com/cocos2d/cocos2d-x) library. Of course C++11 support was mandatory at that moment.
|
||||
|
||||
|
||||
EventBus main goals:
|
||||
- Fast
|
||||
- Easy to use
|
||||
- Strongly typed
|
||||
- Free
|
||||
- tiny (~37 KB)
|
||||
- Decouples notification senders and receivers
|
||||
- on every platform you need (cross-platform)
|
||||
|
||||
# Brief @ presentation
|
||||
Presentation [google docs](https://docs.google.com/presentation/d/1apAlKcVWo9FcqkPqL8108a1Fy9LGmhgLT56hSVpoI3w/edit?usp=sharing)
|
||||
|
||||
# Sample / use cases
|
||||
You can checkout [use_case/](use_case/)
|
||||
If you want to play with sample online checkout this link: [wandbox.org](https://wandbox.org/permlink/VWo2acOX6hxUfV1Q)
|
||||
|
||||
# Usage
|
||||
0. Store bus
|
||||
|
||||
```cpp
|
||||
// store it in controller / singleton / std::sharted_ptr whatever you want
|
||||
auto bus = std::make_shared<EventBus>();
|
||||
```
|
||||
|
||||
1. Define events
|
||||
|
||||
```cpp
|
||||
namespace event // optional namespace
|
||||
{
|
||||
struct Gold
|
||||
{
|
||||
int goldReceived = 0;
|
||||
};
|
||||
|
||||
struct OK {}; // Simple event when user press "OK" button
|
||||
}
|
||||
```
|
||||
|
||||
2. Subscribe
|
||||
|
||||
```cpp
|
||||
// ...
|
||||
dexode::EventBus::Listener listener{bus};
|
||||
listener.listen([](const event::Gold& event) // listen with lambda
|
||||
{
|
||||
std::cout << "I received gold: " << event.goldReceived << " 💰" << std::endl;
|
||||
});
|
||||
|
||||
HudLayer* hudLayer;
|
||||
// Hud layer will receive info about gold
|
||||
hudLayer->listener.listen<event::Gold>(std::bind(&HudLayer::onGoldReceived, hudLayer, std::placeholders::_1));
|
||||
```
|
||||
|
||||
3. Spread the news
|
||||
|
||||
```cpp
|
||||
//Inform listeners about event
|
||||
bus->postpone(event::Gold{12}); // 1 way
|
||||
bus->postpone<event::Gold>({12}); // 2 way
|
||||
|
||||
event::Gold myGold{12};
|
||||
bus->postpone(myGold); // 3 way
|
||||
```
|
||||
|
||||
Checkout [tests](test/) or [use cases](use_case/) for more examples. Or create issue what isn't clear :)
|
||||
|
||||
# Add to your project
|
||||
EventBus can be added as `ADD_SUBDIRECTORY` to your cmake file.
|
||||
Then simply link it via `TARGET_LINK_LIBRARIES`
|
||||
Example:
|
||||
```
|
||||
# No tests/benchmarks target won't be added. Root CMakeLists is for development.
|
||||
ADD_SUBDIRECTORY(path/to/EventBus/lib)
|
||||
ADD_EXECUTABLE(MyExecutable
|
||||
main.cpp
|
||||
)
|
||||
|
||||
TARGET_LINK_LIBRARIES(MyExecutable PUBLIC Dexode::EventBus)
|
||||
```
|
||||
|
||||
Also if you want you can install library and add it at any way you want.
|
||||
Eg.
|
||||
```commandline
|
||||
mkdir -p lib/build/
|
||||
cd lib/build
|
||||
cmake -DCMAKE_BUILD_TYPE=Relase -DCMAKE_INSTALL_PREFIX=~/.local/ ..
|
||||
|
||||
cmake --build . --target install
|
||||
# OR
|
||||
make && make install
|
||||
```
|
||||
|
||||
Now in `Release/install` library is placed.
|
||||
|
||||
Or, you can install the library through your package manager (dpkg, rpm, etc).
|
||||
E.g.
|
||||
```commandline
|
||||
mkdir -p lib/build/
|
||||
cd lib/build
|
||||
|
||||
# For most RH-based Distributions
|
||||
cmake -DCMAKE_BUILD_TYPE=Relase -DCPACK_GENERATOR="RPM" ..
|
||||
|
||||
# For most Debian-based Distributions
|
||||
cmake -DCMAKE_BUILD_TYPE=Relase -DCPACK_GENERATOR="DEB" ..
|
||||
|
||||
# Or for both of them
|
||||
cmake -DCMAKE_BUILD_TYPE=Relase -DCPACK_GENERATOR="RPM;DEB" ..
|
||||
|
||||
cmake --build . --target package
|
||||
# Or
|
||||
make package
|
||||
|
||||
# For most Debian-based systems
|
||||
sudo dpkg -i EventBus*.deb
|
||||
|
||||
# For most RH-based systems
|
||||
sudo rpm -i EventBus*.rpm
|
||||
# OR
|
||||
sudo yum install EventBus*.rpm
|
||||
```
|
||||
|
||||
# Performance (could be outdated)
|
||||
I have prepared some performance results. You can read about them [here](performance/README.md)
|
||||
Small example:
|
||||
|
||||
```
|
||||
check10NotificationsFor1kListeners 263 ns 263 ns 2668786 sum=-1.76281G
|
||||
check10NotificationsFor1kListeners_CCNotificationCenter 11172 ns 11171 ns 62865 sum=54.023M
|
||||
|
||||
checkNotifyFor10kListenersWhenNoOneListens 18 ns 18 ns 38976599 sum=0
|
||||
checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter 127388 ns 127378 ns 5460 sum=0
|
||||
```
|
||||
|
||||
# Issues ? [![GitHub issues](https://img.shields.io/github/issues/gelldur/EventBus.svg)](https://github.com/gelldur/EventBus/issues)
|
||||
Please report here issue / question / whatever, there is chance 99% I will answer ;)
|
||||
|
||||
If you have any questions or want to chat use gitter.
|
||||
|
||||
[![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)
|
||||
|
||||
# Tesing and metrics
|
||||
// TODO
|
||||
|
||||
|
||||
[![LoC](https://tokei.rs/b1/github/gelldur/EventBus)](https://github.com/gelldur/EventBus)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/gelldur/EventBus/badge.svg?branch=master)](https://coveralls.io/github/gelldur/EventBus?branch=master)
|
||||
|
||||
# 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)
|
||||
- [staakk](https://github.com/stanislawkabacinski) for fixing windows ;) [53d5026](https://github.com/gelldur/EventBus/commit/53d5026cad24810e82cd8d4a43d58cbfe329c502)
|
||||
- [kuhar](https://github.com/kuhar) for his advice and suggestions for EventBus
|
||||
- [swietlana](https://github.com/swietlana) for English correction and support ;)
|
||||
- [ruslo](https://github.com/ruslo) for this great example: https://github.com/forexample/package-example
|
||||
|
||||
## For modern cmake refer
|
||||
- https://github.com/forexample/package-example
|
||||
- https://www.youtube.com/watch?v=6sWec7b0JIc
|
||||
|
||||
# License
|
||||
EventBus source code can be used according to the **Apache License, Version 2.0**.
|
||||
For more information see [LICENSE](LICENSE) file
|
||||
|
||||
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)
|
46
3party/EventBus/docs/CODE_OF_CONDUCT.md
Normal file
46
3party/EventBus/docs/CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
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.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Scope
|
||||
|
||||
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.
|
||||
|
||||
## Enforcement
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
51
3party/EventBus/docs/CONTRIBUTING.md
Normal file
51
3party/EventBus/docs/CONTRIBUTING.md
Normal file
@ -0,0 +1,51 @@
|
||||
## How Can I Contribute?
|
||||
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
Just do it we will work out something ;)
|
||||
Just try to keep good quality of your work.
|
||||
|
||||
## Styleguides
|
||||
|
||||
### Common Convencions
|
||||
|
||||
* Use tabs instead spaces (tab = 4 spaces)
|
||||
|
||||
### Git Commit Messages
|
||||
|
||||
Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this:
|
||||
|
||||
$ git commit -m "A brief summary of the commit
|
||||
>
|
||||
> A paragraph describing what changed and its impact."
|
||||
|
||||
* Use the present tense ("Add feature" not "Added feature")
|
||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
* Limit the first line to 72 characters or less
|
||||
* Reference issues and pull requests liberally after the first line
|
||||
* When only changing documentation, include `[ci skip]` in the commit message
|
||||
* Consider starting the commit message with an applicable emoji:
|
||||
* :art: `:art:` when improving the format/structure of the code
|
||||
* :racehorse: `:racehorse:` when improving performance
|
||||
* :non-potable_water: `:non-potable_water:` when plugging memory leaks
|
||||
* :memo: `:memo:` when writing docs
|
||||
* :penguin: `:penguin:` when fixing something on Linux
|
||||
* :apple: `:apple:` when fixing something on macOS
|
||||
* :checkered_flag: `:checkered_flag:` when fixing something on Windows
|
||||
* :bug: `:bug:` when fixing a bug
|
||||
* :fire: `:fire:` when removing code or files
|
||||
* :green_heart: `:green_heart:` when fixing the CI build
|
||||
* :white_check_mark: `:white_check_mark:` when adding tests
|
||||
* :lock: `:lock:` when dealing with security
|
||||
* :arrow_up: `:arrow_up:` when upgrading dependencies
|
||||
* :arrow_down: `:arrow_down:` when downgrading dependencies
|
||||
* :shirt: `:shirt:` when removing linter warnings
|
||||
|
||||
### C++ Styleguide
|
||||
|
||||
Style is definied in [clang-format file](/.clang-format), so please just run `clang-format in root directory :) Style now should be fine.
|
||||
|
||||
In other cases please follow style form older peaces of code :)
|
||||
|
||||
|
||||
Thanks
|
1
3party/EventBus/docs/FAQ.md
Normal file
1
3party/EventBus/docs/FAQ.md
Normal file
@ -0,0 +1 @@
|
||||
# FAQ
|
6
3party/EventBus/docs/THANKS.md
Normal file
6
3party/EventBus/docs/THANKS.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Big thanks to:
|
||||
|
||||
- Nice article about template arguments deduction:
|
||||
|
||||
["Function templates - deduce template arguments or pass explicitly?"](https://foonathan.net/2016/11/template-argument-deduction/)
|
||||
- TBC ...
|
1
3party/EventBus/docs/res/EventBus-concept.draw.io.xml
Normal file
1
3party/EventBus/docs/res/EventBus-concept.draw.io.xml
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="Chrome" modified="2020-04-12T14:28:37.939Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36" etag="WlXnalW0tExYR9JEFqnc" version="12.9.11" type="device"><diagram id="SfB6skvfT3f-U5eaWSQF" name="Page-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==</diagram></mxfile>
|
BIN
3party/EventBus/docs/res/EventBus-concept.png
Normal file
BIN
3party/EventBus/docs/res/EventBus-concept.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
129
3party/EventBus/lib/CMakeLists.txt
Normal file
129
3party/EventBus/lib/CMakeLists.txt
Normal file
@ -0,0 +1,129 @@
|
||||
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
|
||||
|
||||
# Use ';' to specify multiple e.g. ZIP;TGZ;DEB
|
||||
set(CPACK_GENERATOR
|
||||
""
|
||||
CACHE STRING "Set packages CPack should build e.g. ZIP;TGZ;DEB")
|
||||
|
||||
# BUILD_SHARED_LIBS can controll build type!
|
||||
project(
|
||||
EventBus
|
||||
VERSION 3.0.4
|
||||
LANGUAGES CXX)
|
||||
|
||||
# Dependencies
|
||||
find_package(Threads)
|
||||
|
||||
# Introduce variables: * CMAKE_INSTALL_LIBDIR * CMAKE_INSTALL_BINDIR *
|
||||
# CMAKE_INSTALL_INCLUDEDIR
|
||||
include(GNUInstallDirs)
|
||||
include(cmake/InstallHelp.cmake)
|
||||
|
||||
# Layout. This works for all platforms: * <prefix>/lib*/cmake/<PROJECT-NAME> *
|
||||
# <prefix>/lib*/ * <prefix>/include/
|
||||
set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
|
||||
set(EventBus_PUBLIC_HEADERS
|
||||
src/dexode/EventBus.hpp
|
||||
src/dexode/eventbus/Bus.hpp
|
||||
src/dexode/eventbus/internal/event_id.hpp
|
||||
src/dexode/eventbus/internal/listener_traits.hpp
|
||||
src/dexode/eventbus/internal/ListenerAttorney.hpp
|
||||
src/dexode/eventbus/Listener.hpp
|
||||
src/dexode/eventbus/perk/PassPerk.hpp
|
||||
src/dexode/eventbus/perk/Perk.hpp
|
||||
src/dexode/eventbus/perk/PerkEventBus.hpp
|
||||
src/dexode/eventbus/perk/TagPerk.hpp
|
||||
src/dexode/eventbus/perk/WaitPerk.hpp
|
||||
src/dexode/eventbus/permission/PostponeBus.hpp
|
||||
src/dexode/eventbus/stream/EventStream.hpp
|
||||
src/dexode/eventbus/stream/ProtectedEventStream.hpp)
|
||||
|
||||
# Library definition
|
||||
add_library(
|
||||
EventBus
|
||||
${EventBus_PUBLIC_HEADERS}
|
||||
src/dexode/EventBus.cpp
|
||||
src/dexode/eventbus/perk/PassPerk.cpp
|
||||
src/dexode/eventbus/perk/Perk.cpp
|
||||
src/dexode/eventbus/perk/PerkEventBus.cpp
|
||||
src/dexode/eventbus/perk/TagPerk.cpp
|
||||
src/dexode/eventbus/perk/WaitPerk.cpp)
|
||||
add_library(Dexode::EventBus ALIAS EventBus)
|
||||
|
||||
# Why C++ 17 needed: - std::shared_mutex used - nested namespaces e.g.
|
||||
# my::name::space
|
||||
|
||||
set_target_properties(
|
||||
EventBus
|
||||
PROPERTIES CXX_STANDARD 11
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
CXX_EXTENSIONS NO # -std=c++17 rather than -std=gnu++17
|
||||
)
|
||||
|
||||
target_compile_features(EventBus PUBLIC cxx_std_17)
|
||||
|
||||
target_include_directories(
|
||||
EventBus PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/>
|
||||
$<INSTALL_INTERFACE:include/>)
|
||||
target_link_libraries(EventBus ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
# Add definitions for targets Values: * Debug: -DEVENTBUS_DEBUG=1 * Release:
|
||||
# -DEVENTBUS_DEBUG=0 * other: -DEVENTBUS_DEBUG=0
|
||||
target_compile_definitions(EventBus PUBLIC "EVENTBUS_DEBUG=$<CONFIG:Debug>")
|
||||
|
||||
# Configuration
|
||||
set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake")
|
||||
set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake")
|
||||
set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
|
||||
set(namespace "Dexode::")
|
||||
|
||||
# Targets: * <prefix>/lib/libEventBus.a * header location after install:
|
||||
# <prefix>/include/eventbus/EventBus.h * headers can be included by C++ code
|
||||
# `#include <eventbus/EventBus.h>`
|
||||
install(
|
||||
TARGETS EventBus
|
||||
EXPORT "${TARGETS_EXPORT_NAME}"
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
INCLUDES
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
# Include module with fuction 'write_basic_package_version_file'
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
# Configure '<PROJECT-NAME>ConfigVersion.cmake' Use: * PROJECT_VERSION
|
||||
write_basic_package_version_file("${version_config}"
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
|
||||
# Configure '<PROJECT-NAME>Config.cmake' Use variables: * TARGETS_EXPORT_NAME *
|
||||
# PROJECT_NAME
|
||||
configure_package_config_file("cmake/Config.cmake.in" "${project_config}"
|
||||
INSTALL_DESTINATION "${config_install_dir}")
|
||||
|
||||
# Config * <prefix>/lib/cmake/EventBusventBusConfig.cmake *
|
||||
# <prefix>/lib/cmake/EventBus/EventBusConfigVersion.cmake
|
||||
install(
|
||||
FILES "${project_config}" "${version_config}"
|
||||
DESTINATION "${config_install_dir}"
|
||||
# We don't want someone by accident modify his installed files
|
||||
PERMISSIONS OWNER_EXECUTE OWNER_READ)
|
||||
|
||||
# Config * <prefix>/lib/cmake/EventBus/EventBusTargets.cmake
|
||||
install(
|
||||
EXPORT "${TARGETS_EXPORT_NAME}"
|
||||
DESTINATION "${config_install_dir}"
|
||||
NAMESPACE "${namespace}"
|
||||
# We don't want someone by accident modify his installed files
|
||||
PERMISSIONS OWNER_EXECUTE OWNER_READ)
|
||||
|
||||
# Export headers (Install public headers)
|
||||
install_public_headers_with_directory(EventBus_PUBLIC_HEADERS "src/")
|
||||
|
||||
# Cpack configuration
|
||||
if(NOT CPACK_GENERATOR STREQUAL "")
|
||||
include(cmake/EventBus_CPack.cmake)
|
||||
enable_cpack(${CPACK_GENERATOR})
|
||||
endif()
|
4
3party/EventBus/lib/cmake/Config.cmake.in
Normal file
4
3party/EventBus/lib/cmake/Config.cmake.in
Normal file
@ -0,0 +1,4 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
18
3party/EventBus/lib/cmake/EventBus_CPack.cmake
Normal file
18
3party/EventBus/lib/cmake/EventBus_CPack.cmake
Normal file
@ -0,0 +1,18 @@
|
||||
# CPack Configuration
|
||||
function(enable_cpack generator)
|
||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
|
||||
set(CPACK_GENERATOR ${generator})
|
||||
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
|
||||
set(CPACK_PACKAGE_RELEASE 1)
|
||||
set(CPACK_PACKAGE_CONTACT "gelldur")
|
||||
set(CPACK_PACKAGE_VENDOR "gelldur")
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}")
|
||||
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
|
||||
"${CMAKE_INSTALL_PREFIX}"
|
||||
"${CMAKE_INSTALL_PREFIX}/include"
|
||||
"${CMAKE_INSTALL_PREFIX}/lib64"
|
||||
"${CMAKE_INSTALL_PREFIX}/lib"
|
||||
)
|
||||
include(CPack)
|
||||
endfunction()
|
19
3party/EventBus/lib/cmake/InstallHelp.cmake
Normal file
19
3party/EventBus/lib/cmake/InstallHelp.cmake
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Installs files with preserving paths.
|
||||
#
|
||||
# Example usage:
|
||||
# install_public_headers_with_directory(MyHeadersList "src/")
|
||||
#
|
||||
macro(install_public_headers_with_directory HEADER_LIST IGNORE_PREFIX)
|
||||
foreach(HEADER ${${HEADER_LIST}})
|
||||
get_filename_component(DIR ${HEADER} DIRECTORY)
|
||||
string(REPLACE ${IGNORE_PREFIX} "" DIR ${DIR})
|
||||
install(
|
||||
FILES ${HEADER}
|
||||
DESTINATION include/${DIR}
|
||||
# We don't want someone by accident modify his installed files
|
||||
PERMISSIONS OWNER_EXECUTE OWNER_READ
|
||||
)
|
||||
endforeach(HEADER)
|
||||
|
||||
endmacro(install_public_headers_with_directory)
|
144
3party/EventBus/lib/src/dexode/EventBus.cpp
Normal file
144
3party/EventBus/lib/src/dexode/EventBus.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
//
|
||||
// Created by gelldur on 26.11.2019.
|
||||
//
|
||||
#include "EventBus.hpp"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace dexode
|
||||
{
|
||||
|
||||
std::size_t EventBus::processLimit(const std::size_t limit)
|
||||
{
|
||||
std::size_t processCount{0};
|
||||
std::lock_guard writeGuardProcess{_mutexProcess}; // Only one process at the time
|
||||
|
||||
std::vector<std::unique_ptr<eventbus::stream::EventStream>> eventStreams;
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexStreams};
|
||||
std::swap(eventStreams, _eventStreams); // move data FROM member
|
||||
}
|
||||
|
||||
// Now if any setStream would be called it doesn't conflict without our process call
|
||||
for(auto& eventStream : eventStreams)
|
||||
{
|
||||
const auto runProcessCount = eventStream->process(limit);
|
||||
processCount += runProcessCount;
|
||||
if(processCount >= limit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexStreams};
|
||||
if(!_eventStreams.empty())
|
||||
{
|
||||
// If anything was added then we need to add those elements
|
||||
std::move(_eventStreams.begin(), _eventStreams.end(), std::back_inserter(eventStreams));
|
||||
}
|
||||
std::swap(eventStreams, _eventStreams); // move data TO member
|
||||
|
||||
// Check do we need remove something
|
||||
if(_eventStreams.size() != _eventToStream.size())
|
||||
{
|
||||
auto removeFrom = std::remove_if(
|
||||
_eventStreams.begin(), _eventStreams.end(), [this](const auto& eventStream) {
|
||||
for(const auto& element : _eventToStream)
|
||||
{
|
||||
// Don't remove if we point to the same place (is it UB ?)
|
||||
if(element.second == eventStream.get())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
assert(removeFrom != _eventStreams.end());
|
||||
_eventStreams.erase(removeFrom, _eventStreams.end());
|
||||
}
|
||||
}
|
||||
|
||||
return processCount;
|
||||
}
|
||||
|
||||
eventbus::stream::EventStream* EventBus::findStream(
|
||||
const eventbus::internal::event_id_t eventID) const
|
||||
{
|
||||
std::shared_lock readGuard{_mutexStreams};
|
||||
return findStreamUnsafe(eventID);
|
||||
}
|
||||
|
||||
void EventBus::unlistenAll(const std::uint32_t listenerID)
|
||||
{
|
||||
std::shared_lock readGuard{_mutexStreams};
|
||||
for(const auto& eventStream : _eventToStream)
|
||||
{
|
||||
eventStream.second->removeListener(listenerID);
|
||||
}
|
||||
}
|
||||
|
||||
eventbus::stream::EventStream* EventBus::findStreamUnsafe(
|
||||
const eventbus::internal::event_id_t eventID) const
|
||||
{
|
||||
auto lookup = _eventToStream.find(eventID);
|
||||
return lookup != _eventToStream.end() ? lookup->second : nullptr;
|
||||
}
|
||||
|
||||
eventbus::stream::EventStream* EventBus::obtainStream(
|
||||
const eventbus::internal::event_id_t eventID,
|
||||
eventbus::CreateStreamCallback createStreamCallback)
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexStreams};
|
||||
auto* found = findStreamUnsafe(eventID);
|
||||
if(found != nullptr)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto stream = createStreamCallback();
|
||||
_eventStreams.push_back(std::move(stream));
|
||||
_eventToStream[eventID] = _eventStreams.back().get();
|
||||
return _eventStreams.back().get();
|
||||
}
|
||||
}
|
||||
|
||||
eventbus::stream::EventStream* EventBus::streamForEvent(
|
||||
eventbus::internal::event_id_t eventID) const
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexStreams};
|
||||
auto* found = findStreamUnsafe(eventID);
|
||||
if(found != nullptr)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EventBus::postponeEvent(eventbus::PostponeHelper& postponeCall)
|
||||
{
|
||||
auto* eventStream = obtainStream(postponeCall.eventID, postponeCall.createStreamCallback);
|
||||
eventStream->postpone(std::move(postponeCall.event));
|
||||
return true;
|
||||
}
|
||||
|
||||
eventbus::stream::EventStream* EventBus::listen(const std::uint32_t,
|
||||
const eventbus::internal::event_id_t eventID,
|
||||
eventbus::CreateStreamCallback createStreamCallback)
|
||||
{
|
||||
auto* eventStream = obtainStream(eventID, createStreamCallback);
|
||||
return eventStream;
|
||||
}
|
||||
|
||||
void EventBus::unlisten(const std::uint32_t listenerID,
|
||||
const eventbus::internal::event_id_t eventID)
|
||||
{
|
||||
eventbus::stream::EventStream* eventStream = findStream(eventID);
|
||||
if(eventStream != nullptr)
|
||||
{
|
||||
eventStream->removeListener(listenerID);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dexode
|
58
3party/EventBus/lib/src/dexode/EventBus.hpp
Normal file
58
3party/EventBus/lib/src/dexode/EventBus.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Created by gelldur on 26.11.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <iterator>
|
||||
|
||||
#include "dexode/eventbus/Bus.hpp"
|
||||
|
||||
namespace dexode
|
||||
{
|
||||
|
||||
class EventBus : public dexode::eventbus::Bus
|
||||
{
|
||||
template <typename>
|
||||
friend class dexode::eventbus::internal::ListenerAttorney;
|
||||
|
||||
public:
|
||||
std::size_t process() override
|
||||
{
|
||||
return processLimit(std::numeric_limits<std::size_t>::max());
|
||||
}
|
||||
|
||||
std::size_t processLimit(std::size_t limit);
|
||||
|
||||
protected:
|
||||
eventbus::stream::EventStream* streamForEvent(
|
||||
eventbus::internal::event_id_t eventID) const override;
|
||||
|
||||
eventbus::stream::EventStream* obtainStream(
|
||||
eventbus::internal::event_id_t eventID,
|
||||
eventbus::CreateStreamCallback createStreamCallback);
|
||||
|
||||
bool postponeEvent(eventbus::PostponeHelper& postponeCall) override;
|
||||
eventbus::stream::EventStream* findStream(eventbus::internal::event_id_t eventID) const;
|
||||
|
||||
void unlistenAll(std::uint32_t listenerID) override;
|
||||
eventbus::stream::EventStream* listen(
|
||||
std::uint32_t listenerID,
|
||||
eventbus::internal::event_id_t eventID,
|
||||
eventbus::CreateStreamCallback createStreamCallback) override;
|
||||
void unlisten(std::uint32_t listenerID, eventbus::internal::event_id_t eventID) override;
|
||||
|
||||
private:
|
||||
mutable std::shared_mutex _mutexStreams;
|
||||
std::shared_mutex _mutexProcess;
|
||||
std::vector<std::unique_ptr<eventbus::stream::EventStream>> _eventStreams;
|
||||
std::map<eventbus::internal::event_id_t, eventbus::stream::EventStream*> _eventToStream;
|
||||
|
||||
eventbus::stream::EventStream* findStreamUnsafe(eventbus::internal::event_id_t eventID) const;
|
||||
};
|
||||
|
||||
} // namespace dexode
|
129
3party/EventBus/lib/src/dexode/eventbus/Bus.hpp
Normal file
129
3party/EventBus/lib/src/dexode/eventbus/Bus.hpp
Normal file
@ -0,0 +1,129 @@
|
||||
//
|
||||
// Created by gelldur on 26.11.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "dexode/eventbus/Listener.hpp"
|
||||
#include "dexode/eventbus/internal/ListenerAttorney.hpp"
|
||||
#include "dexode/eventbus/internal/event_id.hpp"
|
||||
#include "dexode/eventbus/stream/ProtectedEventStream.hpp"
|
||||
|
||||
namespace dexode::eventbus
|
||||
{
|
||||
|
||||
class Bus;
|
||||
|
||||
template <typename Event>
|
||||
using DefaultEventStream = eventbus::stream::ProtectedEventStream<Event>;
|
||||
using CreateStreamCallback = std::unique_ptr<eventbus::stream::EventStream> (*const)();
|
||||
using PostponeCallback = bool (*const)(Bus& bus, std::any event);
|
||||
|
||||
template <typename Event>
|
||||
bool postpone(Bus& bus, std::any event);
|
||||
|
||||
template <typename Event>
|
||||
std::unique_ptr<eventbus::stream::EventStream> createDefaultEventStream()
|
||||
{
|
||||
return std::make_unique<DefaultEventStream<Event>>();
|
||||
}
|
||||
|
||||
class PostponeHelper
|
||||
{
|
||||
public:
|
||||
internal::event_id_t eventID = nullptr;
|
||||
std::any event;
|
||||
|
||||
PostponeCallback postponeCallback = nullptr; // function pointer
|
||||
CreateStreamCallback createStreamCallback = nullptr; // function pointer
|
||||
|
||||
PostponeHelper(const internal::event_id_t eventId,
|
||||
std::any&& event,
|
||||
PostponeCallback postponeCallback,
|
||||
CreateStreamCallback createStreamCallback)
|
||||
: eventID(eventId)
|
||||
, event(std::forward<std::any>(event))
|
||||
, postponeCallback(postponeCallback)
|
||||
, createStreamCallback(createStreamCallback)
|
||||
{}
|
||||
|
||||
template <typename Event>
|
||||
static PostponeHelper create(std::any&& event)
|
||||
{
|
||||
return PostponeHelper{internal::event_id<Event>(),
|
||||
std::forward<std::any>(event),
|
||||
postpone<Event>,
|
||||
createDefaultEventStream<Event>};
|
||||
}
|
||||
|
||||
~PostponeHelper() = default;
|
||||
};
|
||||
|
||||
class Bus
|
||||
{
|
||||
template <typename>
|
||||
friend class dexode::eventbus::internal::ListenerAttorney;
|
||||
|
||||
public:
|
||||
using Listener = eventbus::Listener<dexode::eventbus::Bus>;
|
||||
|
||||
Bus() = default;
|
||||
virtual ~Bus() = default;
|
||||
|
||||
virtual std::size_t process() = 0;
|
||||
|
||||
template <typename Event>
|
||||
bool postpone(Event event)
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
auto postponeCall = PostponeHelper::create<Event>(std::move(event));
|
||||
return postponeEvent(postponeCall);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool postponeEvent(PostponeHelper& postponeCall) = 0;
|
||||
virtual eventbus::stream::EventStream* listen(std::uint32_t listenerID,
|
||||
internal::event_id_t eventID,
|
||||
CreateStreamCallback createStreamCallback) = 0;
|
||||
|
||||
virtual void unlistenAll(std::uint32_t listenerID) = 0;
|
||||
virtual void unlisten(std::uint32_t listenerID, internal::event_id_t eventID) = 0;
|
||||
|
||||
virtual eventbus::stream::EventStream* streamForEvent(
|
||||
eventbus::internal::event_id_t eventID) const = 0;
|
||||
|
||||
private:
|
||||
std::atomic<std::uint32_t> _lastID{0};
|
||||
|
||||
std::uint32_t newListenerID()
|
||||
{
|
||||
return ++_lastID; // used for generate unique listeners ID's
|
||||
}
|
||||
|
||||
template <class Event>
|
||||
void listen(const std::uint32_t listenerID, std::function<void(const Event&)>&& callback)
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
assert(callback && "callback should be valid"); // Check for valid object
|
||||
|
||||
constexpr auto eventID = internal::event_id<Event>();
|
||||
|
||||
auto* eventStream = listen(listenerID, eventID, createDefaultEventStream<Event>);
|
||||
if(eventStream != nullptr) // maybe someone don't want add listener
|
||||
{
|
||||
eventStream->addListener(listenerID,
|
||||
std::forward<std::function<void(const Event&)>>(callback));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Event>
|
||||
bool postpone(Bus& bus, std::any event)
|
||||
{
|
||||
return bus.postpone(std::move(std::any_cast<Event>(event)));
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus
|
152
3party/EventBus/lib/src/dexode/eventbus/Listener.hpp
Normal file
152
3party/EventBus/lib/src/dexode/eventbus/Listener.hpp
Normal file
@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "dexode/eventbus/internal/ListenerAttorney.hpp"
|
||||
#include "dexode/eventbus/internal/event_id.hpp"
|
||||
#include "dexode/eventbus/internal/listener_traits.hpp"
|
||||
|
||||
namespace dexode::eventbus
|
||||
{
|
||||
|
||||
template <class Bus>
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
explicit Listener() = default; // Dummy listener
|
||||
|
||||
explicit Listener(std::shared_ptr<Bus> bus)
|
||||
: _id{internal::ListenerAttorney<Bus>::newListenerID(*bus)}
|
||||
, _bus{std::move(bus)}
|
||||
{}
|
||||
|
||||
static Listener createNotOwning(Bus& bus)
|
||||
{
|
||||
// This isn't safe but nice for playing around
|
||||
return Listener{std::shared_ptr<Bus>{&bus, [](Bus*) {}}};
|
||||
}
|
||||
|
||||
Listener(const Listener& other) = delete;
|
||||
// To see why move is disabled search for tag: FORBID_MOVE_LISTENER in tests
|
||||
// Long story short, what if we capture 'this' in lambda during registering listener in ctor
|
||||
Listener(Listener&& other) = delete;
|
||||
|
||||
~Listener()
|
||||
{
|
||||
if(_bus != nullptr) // could be moved
|
||||
{
|
||||
unlistenAll();
|
||||
}
|
||||
}
|
||||
|
||||
Listener& operator=(const Listener& other) = delete;
|
||||
// To see why move is disabled search for tag: FORBID_MOVE_LISTENER in tests
|
||||
Listener& operator=(Listener&& other) = delete;
|
||||
|
||||
template <class Event, typename _ = void>
|
||||
constexpr void listen(std::function<void(const Event&)>&& callback)
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
listenToCallback<Event>(std::forward<std::function<void(const Event&)>>(callback));
|
||||
}
|
||||
|
||||
template <class EventCallback, typename Event = internal::first_argument<EventCallback>>
|
||||
constexpr void listen(EventCallback&& callback)
|
||||
{
|
||||
static_assert(std::is_const_v<std::remove_reference_t<Event>>, "Event should be const");
|
||||
static_assert(std::is_reference_v<Event>, "Event should be const & (reference)");
|
||||
using PureEvent = std::remove_const_t<std::remove_reference_t<Event>>;
|
||||
static_assert(internal::validateEvent<PureEvent>(), "Invalid event");
|
||||
|
||||
listenToCallback<PureEvent>(std::forward<EventCallback>(callback));
|
||||
}
|
||||
|
||||
template <class Event>
|
||||
void listenToCallback(std::function<void(const Event&)>&& callback)
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
if(_bus == nullptr)
|
||||
{
|
||||
throw std::runtime_error{"bus is null"};
|
||||
}
|
||||
|
||||
internal::ListenerAttorney<Bus>::template listen<Event>(
|
||||
*_bus, _id, std::forward<std::function<void(const Event&)>>(callback));
|
||||
}
|
||||
|
||||
template <class Event>
|
||||
void listenToCallback(const std::function<void(const Event&)>& callback)
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
if(_bus == nullptr)
|
||||
{
|
||||
throw std::runtime_error{"bus is null"};
|
||||
}
|
||||
internal::ListenerAttorney<Bus>::template listen<Event>(
|
||||
*_bus, _id, std::function<void(const Event&)>{callback});
|
||||
}
|
||||
|
||||
void unlistenAll()
|
||||
{
|
||||
if(_bus == nullptr)
|
||||
{
|
||||
throw std::runtime_error{"bus is null"};
|
||||
}
|
||||
internal::ListenerAttorney<Bus>::unlistenAll(*_bus, _id);
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
void unlisten()
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
if(_bus == nullptr)
|
||||
{
|
||||
throw std::runtime_error{"bus is null"};
|
||||
}
|
||||
internal::ListenerAttorney<Bus>::unlisten(*_bus, _id, internal::event_id<Event>());
|
||||
}
|
||||
|
||||
// We want more explicit move so user knows what is going on
|
||||
void transfer(Listener&& from)
|
||||
{
|
||||
if(this == &from)
|
||||
{
|
||||
throw std::runtime_error("Self transfer not allowed");
|
||||
}
|
||||
|
||||
if(_bus != nullptr)
|
||||
{
|
||||
unlistenAll(); // remove previous
|
||||
}
|
||||
// we don't have to reset listener ID as bus is moved and we won't call unlistenAll
|
||||
_id = from._id;
|
||||
_bus = std::move(from._bus);
|
||||
}
|
||||
|
||||
const std::shared_ptr<Bus>& getBus() const
|
||||
{
|
||||
return _bus;
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
[[nodiscard]] bool isListening() const
|
||||
{
|
||||
static_assert(internal::validateEvent<Event>(), "Invalid event");
|
||||
if(_bus == nullptr)
|
||||
{
|
||||
throw std::runtime_error{"bus is null"};
|
||||
}
|
||||
return internal::ListenerAttorney<Bus>::isListening(*_bus
|
||||
, _id
|
||||
, internal::event_id<Event>());
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint32_t _id = 0;
|
||||
std::shared_ptr<Bus> _bus = nullptr;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus
|
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Created by gelldur on 30.10.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "dexode/eventbus/internal/event_id.hpp"
|
||||
#include "dexode/eventbus/stream/EventStream.hpp"
|
||||
|
||||
namespace dexode::eventbus
|
||||
{
|
||||
template <typename>
|
||||
class Listener;
|
||||
|
||||
} // namespace dexode::eventbus
|
||||
|
||||
namespace dexode::eventbus::internal
|
||||
{
|
||||
|
||||
template <typename EventBus_t>
|
||||
class ListenerAttorney
|
||||
{
|
||||
template <typename>
|
||||
friend class dexode::eventbus::Listener;
|
||||
|
||||
private:
|
||||
static constexpr std::uint32_t newListenerID(EventBus_t& bus)
|
||||
{
|
||||
return bus.newListenerID();
|
||||
}
|
||||
|
||||
template <class Event>
|
||||
static constexpr void listen(EventBus_t& bus,
|
||||
const std::uint32_t listenerID,
|
||||
std::function<void(const Event&)>&& callback)
|
||||
{
|
||||
bus.template listen<Event>(listenerID,
|
||||
std::forward<std::function<void(const Event&)>>(callback));
|
||||
}
|
||||
|
||||
static constexpr void unlistenAll(EventBus_t& bus, const std::uint32_t listenerID)
|
||||
{
|
||||
bus.unlistenAll(listenerID);
|
||||
}
|
||||
|
||||
static constexpr void unlisten(EventBus_t& bus,
|
||||
const std::uint32_t listenerID,
|
||||
const event_id_t eventID)
|
||||
{
|
||||
bus.unlisten(listenerID, eventID);
|
||||
}
|
||||
|
||||
static constexpr bool isListening(EventBus_t& bus,
|
||||
const std::uint32_t listenerID,
|
||||
const event_id_t eventID)
|
||||
{
|
||||
const eventbus::stream::EventStream* stream = bus.streamForEvent(eventID);
|
||||
if(stream != nullptr)
|
||||
{
|
||||
return stream->hasListener(listenerID);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::internal
|
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace dexode::eventbus::internal
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
struct type_id_ptr
|
||||
{
|
||||
static const T* const id;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const T* const type_id_ptr<T>::id = nullptr;
|
||||
|
||||
using event_id_t = const void*;
|
||||
|
||||
template <typename T>
|
||||
constexpr event_id_t event_id() // Helper for getting "type id"
|
||||
{
|
||||
return &type_id_ptr<T>::id;
|
||||
}
|
||||
|
||||
template <class Event>
|
||||
constexpr bool validateEvent()
|
||||
{
|
||||
static_assert(std::is_const<Event>::value == false, "Struct must be without const");
|
||||
static_assert(std::is_volatile<Event>::value == false, "Struct must be without volatile");
|
||||
static_assert(std::is_reference<Event>::value == false, "Struct must be without reference");
|
||||
static_assert(std::is_pointer<Event>::value == false, "Struct must be without pointer");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::internal
|
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace dexode::eventbus::internal
|
||||
{
|
||||
|
||||
template <typename Ret, typename Arg, typename... Rest>
|
||||
Arg first_argument_helper(Ret (*)(Arg, Rest...));
|
||||
|
||||
template <typename Ret, typename F, typename Arg, typename... Rest>
|
||||
Arg first_argument_helper(Ret (F::*)(Arg, Rest...));
|
||||
|
||||
template <typename Ret, typename F, typename Arg, typename... Rest>
|
||||
Arg first_argument_helper(Ret (F::*)(Arg, Rest...) const);
|
||||
|
||||
template <typename F>
|
||||
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);
|
||||
|
||||
template <typename T>
|
||||
using first_argument = decltype(first_argument_helper(std::declval<T>()));
|
||||
|
||||
} // namespace dexode::eventbus::internal
|
17
3party/EventBus/lib/src/dexode/eventbus/perk/PassPerk.cpp
Normal file
17
3party/EventBus/lib/src/dexode/eventbus/perk/PassPerk.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#include "PassPerk.hpp"
|
||||
|
||||
#include "dexode/eventbus/Bus.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
Flag PassEverythingPerk::onPrePostponeEvent(PostponeHelper& postponeCall)
|
||||
{
|
||||
postponeCall.postponeCallback(*_passToBus, std::move(postponeCall.event));
|
||||
return Flag::postpone_cancel;
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
32
3party/EventBus/lib/src/dexode/eventbus/perk/PassPerk.hpp
Normal file
32
3party/EventBus/lib/src/dexode/eventbus/perk/PassPerk.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Perk.hpp"
|
||||
|
||||
namespace dexode::eventbus
|
||||
{
|
||||
class PostponeHelper;
|
||||
class Bus;
|
||||
} // namespace dexode::eventbus
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
class PassEverythingPerk : public Perk
|
||||
{
|
||||
public:
|
||||
PassEverythingPerk(std::shared_ptr<dexode::eventbus::Bus> passTo)
|
||||
: _passToBus{std::move(passTo)}
|
||||
{}
|
||||
|
||||
Flag onPrePostponeEvent(PostponeHelper& postponeCall);
|
||||
|
||||
private:
|
||||
std::shared_ptr<dexode::eventbus::Bus> _passToBus;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
9
3party/EventBus/lib/src/dexode/eventbus/perk/Perk.cpp
Normal file
9
3party/EventBus/lib/src/dexode/eventbus/perk/Perk.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by gelldur on 23.12.2019.
|
||||
//
|
||||
#include "Perk.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
22
3party/EventBus/lib/src/dexode/eventbus/perk/Perk.hpp
Normal file
22
3party/EventBus/lib/src/dexode/eventbus/perk/Perk.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by gelldur on 23.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
enum class Flag : int
|
||||
{
|
||||
nop,
|
||||
postpone_cancel,
|
||||
postpone_continue,
|
||||
};
|
||||
|
||||
class Perk
|
||||
{
|
||||
public:
|
||||
virtual ~Perk() = default;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
@ -0,0 +1,39 @@
|
||||
//
|
||||
// Created by gelldur on 23.12.2019.
|
||||
//
|
||||
#include "PerkEventBus.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
PerkEventBus::RegisterHelper PerkEventBus::addPerk(std::shared_ptr<Perk> perk)
|
||||
{
|
||||
auto* local = perk.get();
|
||||
_perks.push_back(std::move(perk));
|
||||
return RegisterHelper(this, local);
|
||||
}
|
||||
|
||||
bool PerkEventBus::postponeEvent(PostponeHelper& postponeCall)
|
||||
{
|
||||
for(const auto& onPrePostpone : _onPrePostpone)
|
||||
{
|
||||
if(onPrePostpone(postponeCall) == perk::Flag::postpone_cancel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(EventBus::postponeEvent(postponeCall))
|
||||
{
|
||||
for(const auto& onPostPostpone : _onPostPostpone)
|
||||
{
|
||||
if(onPostPostpone(postponeCall) == perk::Flag::postpone_cancel)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
@ -0,0 +1,74 @@
|
||||
//
|
||||
// Created by gelldur on 23.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Perk.hpp"
|
||||
#include "dexode/EventBus.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
class PerkEventBus : public EventBus
|
||||
{
|
||||
public:
|
||||
class RegisterHelper
|
||||
{
|
||||
friend PerkEventBus;
|
||||
|
||||
public:
|
||||
template <typename Perk_t>
|
||||
RegisterHelper& registerPrePostpone(perk::Flag (Perk_t::*method)(PostponeHelper&))
|
||||
{
|
||||
_bus->_onPrePostpone.push_back(
|
||||
std::bind(method, static_cast<Perk_t*>(_perk), std::placeholders::_1));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Perk_t>
|
||||
RegisterHelper& registerPostPostpone(perk::Flag (Perk_t::*method)(PostponeHelper&))
|
||||
{
|
||||
_bus->_onPostPostpone.push_back(
|
||||
std::bind(method, static_cast<Perk_t*>(_perk), std::placeholders::_1));
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
PerkEventBus* _bus;
|
||||
Perk* _perk;
|
||||
|
||||
RegisterHelper(PerkEventBus* bus, Perk* perk)
|
||||
: _bus(bus)
|
||||
, _perk(perk)
|
||||
{}
|
||||
};
|
||||
|
||||
RegisterHelper addPerk(std::shared_ptr<Perk> perk);
|
||||
|
||||
template <typename T>
|
||||
T* getPerk()
|
||||
{
|
||||
auto found =
|
||||
std::find_if(_perks.begin(), _perks.end(), [](const std::shared_ptr<Perk>& perk) {
|
||||
return dynamic_cast<T*>(perk.get()) != nullptr;
|
||||
});
|
||||
if(found != _perks.end())
|
||||
{
|
||||
return static_cast<T*>(found->get());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool postponeEvent(PostponeHelper& postponeCall) override;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Perk>> _perks;
|
||||
std::vector<std::function<perk::Flag(PostponeHelper&)>> _onPrePostpone;
|
||||
std::vector<std::function<perk::Flag(PostponeHelper&)>> _onPostPostpone;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
20
3party/EventBus/lib/src/dexode/eventbus/perk/TagPerk.cpp
Normal file
20
3party/EventBus/lib/src/dexode/eventbus/perk/TagPerk.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#include "TagPerk.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
Flag TagPerk::onPrePostponeEvent(PostponeHelper& postponeCall)
|
||||
{
|
||||
if(auto found = _eventsToWrap.find(postponeCall.eventID); found != _eventsToWrap.end())
|
||||
{
|
||||
found->second(postponeCall.event);
|
||||
return Flag::postpone_cancel;
|
||||
}
|
||||
|
||||
return Flag::postpone_continue;
|
||||
}
|
||||
|
||||
}
|
48
3party/EventBus/lib/src/dexode/eventbus/perk/TagPerk.hpp
Normal file
48
3party/EventBus/lib/src/dexode/eventbus/perk/TagPerk.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "Perk.hpp"
|
||||
#include "dexode/eventbus/Bus.hpp"
|
||||
#include "dexode/eventbus/internal/event_id.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
class TagPerk : public Perk
|
||||
{
|
||||
public:
|
||||
TagPerk(std::string tag, dexode::eventbus::Bus* owner)
|
||||
: _tag{std::move(tag)}
|
||||
, _ownerBus{owner}
|
||||
{}
|
||||
|
||||
Flag onPrePostponeEvent(PostponeHelper& postponeCall);
|
||||
|
||||
template <typename TagEvent>
|
||||
TagPerk& wrapTag()
|
||||
{
|
||||
static_assert(internal::validateEvent<TagEvent>(), "Invalid tag event");
|
||||
static_assert(internal::validateEvent<typename TagEvent::Event>(), "Invalid event");
|
||||
constexpr auto eventID = internal::event_id<typename TagEvent::Event>();
|
||||
|
||||
_eventsToWrap[eventID] = [this](std::any event) {
|
||||
TagEvent newEvent{_tag, std::move(std::any_cast<typename TagEvent::Event>(event))};
|
||||
_ownerBus->postpone<TagEvent>(std::move(newEvent));
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<internal::event_id_t, std::function<void(std::any)>> _eventsToWrap;
|
||||
std::string _tag;
|
||||
dexode::eventbus::Bus* _ownerBus;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
57
3party/EventBus/lib/src/dexode/eventbus/perk/WaitPerk.cpp
Normal file
57
3party/EventBus/lib/src/dexode/eventbus/perk/WaitPerk.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#include "WaitPerk.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
bool WaitPerk::wait()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::unique_lock<std::mutex> lock(_waitMutex);
|
||||
if(_hasEvents)
|
||||
{
|
||||
_hasEvents = false; // reset, assume that processing of events took place
|
||||
return true;
|
||||
}
|
||||
_eventWaiting.wait(lock, [this]() { return _hasEvents; });
|
||||
|
||||
// At this moment we are still under mutex
|
||||
if(_hasEvents)
|
||||
{
|
||||
_hasEvents = false; // reset, assume that processing of events took place
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WaitPerk::waitFor(const std::chrono::milliseconds timeout)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::unique_lock<std::mutex> lock(_waitMutex);
|
||||
if(_hasEvents)
|
||||
{
|
||||
_hasEvents = false; // reset
|
||||
return true;
|
||||
}
|
||||
if(_eventWaiting.wait_for(lock, timeout, [this]() { return _hasEvents; }))
|
||||
{
|
||||
// At this moment we are still under mutex
|
||||
_hasEvents = false; // reset
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Flag WaitPerk::onPostponeEvent(PostponeHelper&)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_waitMutex);
|
||||
_hasEvents = true;
|
||||
}
|
||||
_eventWaiting.notify_one();
|
||||
return Flag::postpone_continue;
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
42
3party/EventBus/lib/src/dexode/eventbus/perk/WaitPerk.hpp
Normal file
42
3party/EventBus/lib/src/dexode/eventbus/perk/WaitPerk.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "Perk.hpp"
|
||||
|
||||
namespace dexode::eventbus
|
||||
{
|
||||
class PostponeHelper;
|
||||
}
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
class WaitPerk : public Perk
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @return true when events are waiting in bus
|
||||
*/
|
||||
bool wait();
|
||||
|
||||
/**
|
||||
* @param timeout
|
||||
* @return true when events are waiting in bus
|
||||
*/
|
||||
bool waitFor(std::chrono::milliseconds timeout);
|
||||
|
||||
Flag onPostponeEvent(PostponeHelper& postponeCall);
|
||||
|
||||
private:
|
||||
std::condition_variable _eventWaiting;
|
||||
std::mutex _waitMutex;
|
||||
bool _hasEvents = false;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by gelldur on 24.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "dexode/eventbus/Bus.hpp"
|
||||
|
||||
namespace dexode::eventbus::permission
|
||||
{
|
||||
/**
|
||||
* Intention of this helper is to hide API of other bus but allow only to postpone events
|
||||
* So no:
|
||||
* - listening
|
||||
* - processing
|
||||
*/
|
||||
class PostponeBus
|
||||
{
|
||||
public:
|
||||
PostponeBus(std::shared_ptr<Bus> hideBus)
|
||||
: _hideBus{std::move(hideBus)}
|
||||
{}
|
||||
|
||||
template <typename Event>
|
||||
constexpr bool postpone(Event event)
|
||||
{
|
||||
assert(_hideBus != nullptr);
|
||||
return _hideBus->postpone(event);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Bus> _hideBus;
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::permission
|
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <cstdint>
|
||||
|
||||
namespace dexode::eventbus::stream
|
||||
{
|
||||
|
||||
class EventStream
|
||||
{
|
||||
public:
|
||||
virtual ~EventStream() = default;
|
||||
|
||||
virtual void postpone(std::any event) = 0;
|
||||
virtual std::size_t process(std::size_t limit) = 0;
|
||||
|
||||
virtual bool addListener(std::uint32_t listenerID, std::any callback) = 0;
|
||||
virtual bool removeListener(std::uint32_t listenerID) = 0;
|
||||
|
||||
[[nodiscard]] virtual bool hasListener(std::uint32_t listenerID) const = 0;
|
||||
};
|
||||
|
||||
class NoopEventStream : public EventStream
|
||||
{
|
||||
public:
|
||||
void postpone(std::any event) override
|
||||
{
|
||||
throw std::runtime_error{"Noop"};
|
||||
}
|
||||
size_t process(std::size_t limit) override
|
||||
{
|
||||
throw std::runtime_error{"Noop"};
|
||||
}
|
||||
bool addListener(std::uint32_t listenerID, std::any callback) override
|
||||
{
|
||||
throw std::runtime_error{"Noop"};
|
||||
}
|
||||
bool removeListener(std::uint32_t listenerID) override
|
||||
{
|
||||
throw std::runtime_error{"Noop"};
|
||||
}
|
||||
[[nodiscard]] bool hasListener(std::uint32_t listenerID) const override
|
||||
{
|
||||
throw std::runtime_error{"Noop"};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::stream
|
@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "dexode/eventbus/stream/EventStream.hpp"
|
||||
|
||||
namespace dexode::eventbus::stream
|
||||
{
|
||||
|
||||
template <typename Event, typename CallbackReturn = void, typename... ExtraArgTypes>
|
||||
class ProtectedEventStream : public EventStream
|
||||
{
|
||||
using Callback = std::function<CallbackReturn(const Event&, ExtraArgTypes...)>;
|
||||
|
||||
public:
|
||||
void postpone(std::any event) override
|
||||
{
|
||||
auto myEvent = std::any_cast<Event>(event);
|
||||
std::lock_guard writeGuard{_mutexEvent};
|
||||
_queue.push_back(std::move(myEvent));
|
||||
}
|
||||
|
||||
std::size_t process(const std::size_t limit) override
|
||||
{
|
||||
std::vector<Event> processEvents;
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexEvent};
|
||||
if(limit >= _queue.size())
|
||||
{
|
||||
processEvents.reserve(_queue.size());
|
||||
std::swap(processEvents, _queue);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto countElements = std::min(limit, _queue.size());
|
||||
processEvents.reserve(countElements);
|
||||
auto begin = _queue.begin();
|
||||
auto end = std::next(begin, countElements);
|
||||
|
||||
// moved-from range will still contain valid values of the appropriate type, but not
|
||||
// necessarily the same values as before the move. Iterators should be still valid.
|
||||
// see: https://en.cppreference.com/w/cpp/algorithm/move
|
||||
std::move(begin, end, std::back_inserter(processEvents));
|
||||
_queue.erase(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& event : processEvents)
|
||||
{
|
||||
// At this point we need to consider transaction safety as during some notification
|
||||
// we can add/remove listeners
|
||||
_isProcessing = true;
|
||||
for(auto& callback : _callbacks)
|
||||
{
|
||||
callback(event);
|
||||
}
|
||||
_isProcessing = false;
|
||||
|
||||
flushWaitingOnes();
|
||||
}
|
||||
|
||||
return processEvents.size();
|
||||
}
|
||||
|
||||
bool addListener(const std::uint32_t listenerID, std::any callback) override
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexCallbacks};
|
||||
auto myCallback = std::any_cast<Callback>(callback);
|
||||
if(_isProcessing)
|
||||
{
|
||||
_waiting.emplace_back(listenerID, std::move(myCallback));
|
||||
return true;
|
||||
}
|
||||
|
||||
return rawAddListener(listenerID, std::move(myCallback));
|
||||
}
|
||||
|
||||
bool removeListener(const std::uint32_t listenerID) override
|
||||
{
|
||||
std::lock_guard writeGuard{_mutexCallbacks};
|
||||
if(_isProcessing)
|
||||
{
|
||||
_waiting.emplace_back(listenerID, Callback{});
|
||||
return true;
|
||||
}
|
||||
|
||||
return rawRemoveListener(listenerID);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasEvents() const
|
||||
{
|
||||
std::shared_lock readGuard{_mutexEvent};
|
||||
return not _queue.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasListener(std::uint32_t listenerID) const override
|
||||
{
|
||||
std::shared_lock readGuard{_mutexCallbacks};
|
||||
auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID);
|
||||
return found != _listenerIDs.end();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::uint32_t> _listenerIDs;
|
||||
std::vector<Event> _queue;
|
||||
std::vector<Callback> _callbacks;
|
||||
|
||||
std::atomic<bool> _isProcessing{false};
|
||||
std::vector<std::pair<std::uint32_t, Callback>> _waiting;
|
||||
|
||||
mutable std::shared_mutex _mutexEvent;
|
||||
mutable std::shared_mutex _mutexCallbacks;
|
||||
|
||||
void flushWaitingOnes()
|
||||
{
|
||||
assert(_isProcessing == false);
|
||||
std::lock_guard writeGuard{_mutexCallbacks};
|
||||
if(_waiting.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto&& element : _waiting)
|
||||
{
|
||||
if(element.second) // if callable it means we want to add
|
||||
{
|
||||
rawAddListener(element.first, std::move(element.second));
|
||||
}
|
||||
else // if not callable we want to remove
|
||||
{
|
||||
rawRemoveListener(element.first);
|
||||
}
|
||||
}
|
||||
_waiting.clear();
|
||||
}
|
||||
|
||||
bool rawAddListener(const std::uint32_t listenerID, Callback&& callback)
|
||||
{
|
||||
auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID);
|
||||
if(found != _listenerIDs.end())
|
||||
{
|
||||
/// ###### IMPORTANT ######
|
||||
/// This exception has some reason.
|
||||
/// User should use multiple listeners instead of one. Thanks to that it makes
|
||||
/// it more clear what will happen when call unlisten<Event> with specific Event
|
||||
throw std::invalid_argument{std::string{"Already added listener for event: "} +
|
||||
typeid(Event).name()};
|
||||
}
|
||||
|
||||
_callbacks.push_back(std::forward<Callback>(callback));
|
||||
_listenerIDs.push_back(listenerID);
|
||||
assert(_listenerIDs.size() == _callbacks.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rawRemoveListener(const std::uint32_t listenerID)
|
||||
{
|
||||
auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID);
|
||||
if(found == _listenerIDs.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto index = std::distance(_listenerIDs.begin(), found);
|
||||
|
||||
_listenerIDs.erase(found);
|
||||
_callbacks.erase(std::next(_callbacks.begin(), index));
|
||||
assert(_listenerIDs.size() == _callbacks.size());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::stream
|
1
3party/EventBus/performance/.gitignore
vendored
Normal file
1
3party/EventBus/performance/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
cocos2d-x/
|
@ -0,0 +1,58 @@
|
||||
diff --git a/cocos2dx/cocoa/CCArray.cpp b/cocos2dx/cocoa/CCArray.cpp
|
||||
index 12e5bb2971..0a916cd6e2 100644
|
||||
--- a/cocos2dx/cocoa/CCArray.cpp
|
||||
+++ b/cocos2dx/cocoa/CCArray.cpp
|
||||
@@ -134,7 +134,7 @@ CCArray* CCArray::createWithContentsOfFile(const char* pFileName)
|
||||
|
||||
CCArray* CCArray::createWithContentsOfFileThreadSafe(const char* pFileName)
|
||||
{
|
||||
- return CCFileUtils::sharedFileUtils()->createCCArrayWithContentsOfFile(pFileName);
|
||||
+ return nullptr;
|
||||
}
|
||||
|
||||
bool CCArray::init()
|
||||
diff --git a/cocos2dx/cocoa/CCInteger.h b/cocos2dx/cocoa/CCInteger.h
|
||||
index f700a30a35..20afae5a92 100644
|
||||
--- a/cocos2dx/cocoa/CCInteger.h
|
||||
+++ b/cocos2dx/cocoa/CCInteger.h
|
||||
@@ -30,7 +30,6 @@ public:
|
||||
*/
|
||||
virtual void acceptVisitor(CCDataVisitor &visitor) { visitor.visit(this); }
|
||||
|
||||
-private:
|
||||
int m_nValue;
|
||||
};
|
||||
|
||||
diff --git a/cocos2dx/cocoa/CCObject.cpp b/cocos2dx/cocoa/CCObject.cpp
|
||||
index dd74c695a3..a2b5f8b574 100644
|
||||
--- a/cocos2dx/cocoa/CCObject.cpp
|
||||
+++ b/cocos2dx/cocoa/CCObject.cpp
|
||||
@@ -59,15 +59,9 @@ CCObject::~CCObject(void)
|
||||
// if the object is referenced by Lua engine, remove it
|
||||
if (m_nLuaID)
|
||||
{
|
||||
- CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptObjectByCCObject(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
- CCScriptEngineProtocol* pEngine = CCScriptEngineManager::sharedManager()->getScriptEngine();
|
||||
- if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript)
|
||||
- {
|
||||
- pEngine->removeScriptObjectByCCObject(this);
|
||||
- }
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/cocos2dx/support/CCNotificationCenter.cpp b/cocos2dx/support/CCNotificationCenter.cpp
|
||||
index 835854eded..b3b75fc59d 100644
|
||||
--- a/cocos2dx/support/CCNotificationCenter.cpp
|
||||
+++ b/cocos2dx/support/CCNotificationCenter.cpp
|
||||
@@ -177,8 +177,6 @@ void CCNotificationCenter::postNotification(const char *name, CCObject *object)
|
||||
{
|
||||
if (0 != observer->getHandler())
|
||||
{
|
||||
- CCScriptEngineProtocol* engine = CCScriptEngineManager::sharedManager()->getScriptEngine();
|
||||
- engine->executeNotificationEvent(this, name);
|
||||
}
|
||||
else
|
||||
{
|
50
3party/EventBus/performance/CMakeLists.txt
Normal file
50
3party/EventBus/performance/CMakeLists.txt
Normal file
@ -0,0 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
|
||||
|
||||
# http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html
|
||||
# Thanks for CATCH!
|
||||
|
||||
find_package(Poco COMPONENTS Foundation Util)
|
||||
|
||||
set(BENCHMARK_ENABLE_GTEST_TESTS OFF)
|
||||
add_subdirectory(benchmark/)
|
||||
|
||||
if(NOT TARGET Dexode::EventBus)
|
||||
find_package(EventBus CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
# If you want to compare with CCNotificationCenter read about it in README and uncomment line below
|
||||
#INCLUDE(cocos2d-x-compare/Cocos2dxCompare.cmake)
|
||||
|
||||
add_executable(EventBusPerformance
|
||||
src/EventBusPerformance.cpp
|
||||
${CCNOTIFICATION_CENTER_SRC}
|
||||
$<$<BOOL:${Poco_FOUND}>:src/PocoNotificationCenterPerformance.cpp>
|
||||
)
|
||||
|
||||
target_compile_options(EventBusPerformance PUBLIC
|
||||
-Wall -pedantic
|
||||
-Wno-unused-private-field
|
||||
-Wnon-virtual-dtor
|
||||
-Wno-gnu
|
||||
-Werror
|
||||
)
|
||||
|
||||
set(EVENTBUS_DEBUG_FLAGS
|
||||
-O0 -fno-inline
|
||||
-DDEBUG
|
||||
)
|
||||
|
||||
|
||||
target_compile_options(EventBusPerformance PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>")
|
||||
|
||||
target_include_directories(EventBusPerformance PUBLIC
|
||||
src/
|
||||
${CCNOTIFICATION_CENTER_INCLUDE}
|
||||
)
|
||||
|
||||
target_link_libraries(EventBusPerformance PUBLIC
|
||||
Dexode::EventBus
|
||||
benchmark benchmark_main
|
||||
$<$<BOOL:${Poco_FOUND}>:Poco::Foundation>
|
||||
$<$<BOOL:${Poco_FOUND}>:Poco::Util>
|
||||
)
|
76
3party/EventBus/performance/README.md
Normal file
76
3party/EventBus/performance/README.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Performace
|
||||
|
||||
This is maybe not perfect but can show something. All result are run using RELESE build.
|
||||
Why?
|
||||
|
||||
Sample DEBUG run:
|
||||
```commandline
|
||||
Run on (8 X 3600 MHz CPU s)
|
||||
2017-08-05 12:44:53
|
||||
***WARNING*** Library was built as DEBUG. Timings may be affected.
|
||||
---------------------------------------------------------------
|
||||
Benchmark Time CPU Iterations
|
||||
---------------------------------------------------------------
|
||||
checkSimpleNotification 293 ns 293 ns 2319171
|
||||
```
|
||||
|
||||
Sample RELEASE run:
|
||||
```commandline
|
||||
Run on (8 X 3600 MHz CPU s)
|
||||
2017-08-05 12:45:43
|
||||
---------------------------------------------------------------
|
||||
Benchmark Time CPU Iterations
|
||||
---------------------------------------------------------------
|
||||
checkSimpleNotification 6 ns 6 ns 116492914
|
||||
```
|
||||
|
||||
So below all numbers are in release.
|
||||
|
||||
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.
|
||||
So I want to present comparision of performance of this two. Of course this is only showcase.
|
||||
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:
|
||||
```commandline
|
||||
cd performance # From root of this project
|
||||
git clone -b v2 https://github.com/cocos2d/cocos2d-x.git #this can take some time :/ it need to download ~900 MB
|
||||
# Uncomment line in CMakeLists.txt which INCLUDE(Cocos2dxCompare.cmake)
|
||||
# Apply patch CCNotificationCenterPerformance.patch
|
||||
cd cocos2d-x
|
||||
git apply ../CCNotificationCenterPerformance.patch
|
||||
```
|
||||
|
||||
```
|
||||
Run on (8 X 3600 MHz CPU s)
|
||||
2017-08-06 17:03:13
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Benchmark Time CPU Iterations UserCounters Faster
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
call1kLambdas_compare 2256 ns 2256 ns 327462 sum=1.21989G Compare with check1kListeners overhead is super small
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
checkSimpleNotification 16 ns 16 ns 42451425 sum=161.939M x 10.7
|
||||
checkSimpleNotification_CCNotificationCenter 172 ns 172 ns 4193935 sum=15.9986M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check10Listeners 34 ns 34 ns 20564387 sum=784.469M x 8.9
|
||||
check10Listeners_CCNotificationCenter 305 ns 305 ns 2208720 sum=84.256M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check100Listeners 208 ns 208 ns 3362931 sum=1.25279G x 8.4
|
||||
check100Listeners_CCNotificationCenter 1758 ns 1758 ns 398100 sum=151.863M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check1kListeners 2074 ns 2074 ns 340081 sum=1.2669G x 8.1
|
||||
check1kListeners_CCNotificationCenter 17001 ns 16999 ns 41548 sum=158.493M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check10NotificationsFor1kListeners 263 ns 263 ns 2668786 sum=-1.76281G x 53.1
|
||||
check10NotificationsFor1kListeners_CCNotificationCenter 13987 ns 13986 ns 51560 sum=44.2743M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check100NotificationsFor1kListeners UNKNOWN result available with EventBus 2.0.1
|
||||
check100NotificationsFor1kListeners_CCNotificationCenter 12128 ns 12127 ns 54017 sum=51.181M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check1kNotificationsFor1kListeners UNKNOWN result available with EventBus 2.0.1
|
||||
check1kNotificationsFor1kListeners_CCNotificationCenter 11940 ns 11939 ns 57722 sum=55.2338M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
check100NotificationsFor10kListeners UNKNOWN result available with EventBus 2.0.1
|
||||
check100NotificationsFor10kListeners_CCNotificationCenter 128244 ns 128233 ns 5297 sum=49.5221M
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
checkNotifyFor10kListenersWhenNoOneListens 18 ns 18 ns 38976599 sum=0 x 7077 ;)
|
||||
checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter 127388 ns 127378 ns 5460 sum=0
|
||||
```
|
||||
So comparing to CCNotificationCenter, EventBus is something like ~10x FASTER especially when we have more unique notifications.
|
@ -0,0 +1,160 @@
|
||||
//
|
||||
// Created by Dawid Drozd aka Gelldur on 05.08.17.
|
||||
//
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <cocoa/CCInteger.h>
|
||||
#include <cocos2d-x/cocos2dx/cocoa/CCAutoreleasePool.h>
|
||||
#include <support/CCNotificationCenter.h>
|
||||
|
||||
namespace cocos2dx
|
||||
{
|
||||
|
||||
struct SampleObserver : public cocos2d::CCObject
|
||||
{
|
||||
std::function<void(int)> callback;
|
||||
|
||||
void onCall(cocos2d::CCObject* object)
|
||||
{
|
||||
callback(static_cast<cocos2d::CCInteger*>(object)->getValue());
|
||||
}
|
||||
};
|
||||
|
||||
void checkNListeners(benchmark::State& state, const int listenersCount)
|
||||
{
|
||||
using namespace cocos2d;
|
||||
cocos2d::CCNotificationCenter bus;
|
||||
int sum = 0;
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
auto observer = new SampleObserver{};
|
||||
observer->autorelease();
|
||||
observer->callback = [&](int value) { benchmark::DoNotOptimize(sum += value * 2); };
|
||||
bus.addObserver(observer, callfuncO_selector(SampleObserver::onCall), "sample", nullptr);
|
||||
}
|
||||
auto number = CCInteger::create(2);
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
bus.postNotification("sample", number);
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
CCPoolManager::sharedPoolManager()->purgePoolManager();
|
||||
}
|
||||
|
||||
void checkSimpleNotification_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 1);
|
||||
}
|
||||
|
||||
void check10Listeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 10);
|
||||
}
|
||||
|
||||
void check100Listeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 100);
|
||||
}
|
||||
|
||||
void check1kListeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 1000);
|
||||
}
|
||||
|
||||
void checkNNotificationsForNListeners(benchmark::State& state,
|
||||
const int notificationsCount,
|
||||
const int listenersCount)
|
||||
{
|
||||
std::mt19937 generator(311281);
|
||||
std::uniform_int_distribution<int> uniformDistribution(0, notificationsCount - 1);
|
||||
|
||||
using namespace cocos2d;
|
||||
cocos2d::CCNotificationCenter bus;
|
||||
|
||||
// We generate here N different notifications
|
||||
std::vector<std::string> notifications;
|
||||
notifications.reserve(notificationsCount);
|
||||
for(int i = 0; i < notificationsCount; ++i)
|
||||
{
|
||||
notifications.emplace_back(std::string{"notify_"} + std::to_string(i));
|
||||
}
|
||||
|
||||
int sum = 0;
|
||||
for(int i = 0; i < listenersCount;
|
||||
++i) // We register M listeners for N notifications using uniform distribution
|
||||
{
|
||||
auto observer = new SampleObserver{};
|
||||
observer->autorelease();
|
||||
observer->callback = [&](int value) { benchmark::DoNotOptimize(sum += value * 2); };
|
||||
|
||||
const auto& notification = notifications.at(uniformDistribution(generator));
|
||||
bus.addObserver(
|
||||
observer, callfuncO_selector(SampleObserver::onCall), notification.c_str(), nullptr);
|
||||
}
|
||||
auto number = CCInteger::create(2);
|
||||
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
// Pick random notification
|
||||
const auto& notification = notifications.at(uniformDistribution(generator));
|
||||
|
||||
number->m_nValue = uniformDistribution(generator);
|
||||
bus.postNotification(notification.c_str(), number);
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
CCPoolManager::sharedPoolManager()->purgePoolManager();
|
||||
}
|
||||
|
||||
void check10NotificationsFor1kListeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners(state, 10, 1000);
|
||||
}
|
||||
|
||||
void check100NotificationsFor1kListeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners(state, 100, 1000);
|
||||
}
|
||||
|
||||
void check1kNotificationsFor1kListeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners(state, 1000, 1000);
|
||||
}
|
||||
|
||||
void check100NotificationsFor10kListeners_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners(state, 100, 10000);
|
||||
}
|
||||
|
||||
void checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter(benchmark::State& state)
|
||||
{
|
||||
using namespace cocos2d;
|
||||
cocos2d::CCNotificationCenter bus;
|
||||
int sum = 0;
|
||||
for(int i = 0; i < 10000; ++i)
|
||||
{
|
||||
auto observer = new SampleObserver{};
|
||||
observer->autorelease();
|
||||
observer->callback = [&](int value) { benchmark::DoNotOptimize(sum += value * 2); };
|
||||
bus.addObserver(observer, callfuncO_selector(SampleObserver::onCall), "sample", nullptr);
|
||||
}
|
||||
auto number = CCInteger::create(2);
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
bus.postNotification("unknown", number);
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
CCPoolManager::sharedPoolManager()->purgePoolManager();
|
||||
}
|
||||
|
||||
BENCHMARK(checkSimpleNotification_CCNotificationCenter);
|
||||
BENCHMARK(check10Listeners_CCNotificationCenter);
|
||||
BENCHMARK(check100Listeners_CCNotificationCenter);
|
||||
BENCHMARK(check1kListeners_CCNotificationCenter);
|
||||
BENCHMARK(check10NotificationsFor1kListeners_CCNotificationCenter);
|
||||
BENCHMARK(check100NotificationsFor1kListeners_CCNotificationCenter);
|
||||
BENCHMARK(check1kNotificationsFor1kListeners_CCNotificationCenter);
|
||||
BENCHMARK(check100NotificationsFor10kListeners_CCNotificationCenter);
|
||||
BENCHMARK(checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter);
|
||||
} // namespace cocos2dx
|
@ -0,0 +1,19 @@
|
||||
|
||||
set(CCNOTIFICATION_CENTER_SRC
|
||||
cocos2d-x-compare/CCNotificationCenterPerformance.cpp
|
||||
cocos2d-x/cocos2dx/support/CCNotificationCenter.cpp
|
||||
cocos2d-x/cocos2dx/cocoa/CCObject.cpp
|
||||
cocos2d-x/cocos2dx/cocoa/CCArray.cpp
|
||||
cocos2d-x/cocos2dx/cocoa/CCInteger.h
|
||||
cocos2d-x/cocos2dx/cocoa/CCAutoreleasePool.cpp
|
||||
cocos2d-x/cocos2dx/support/data_support/ccCArray.cpp
|
||||
cocos2d-x/cocos2dx/cocoa/CCGeometry.cpp
|
||||
)
|
||||
set(CCNOTIFICATION_CENTER_INCLUDE
|
||||
cocos2d-x/cocos2dx/
|
||||
cocos2d-x/cocos2dx/include
|
||||
cocos2d-x/cocos2dx/platform/linux
|
||||
cocos2d-x/cocos2dx/kazmath/include
|
||||
)
|
||||
|
||||
add_definitions(-DLINUX)
|
54
3party/EventBus/performance/src/AsyncEventBusPerformance.cpp
Normal file
54
3party/EventBus/performance/src/AsyncEventBusPerformance.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// Created by gelldur on 14.06.19.
|
||||
//
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include <eventbus/AsyncEventBus.h>
|
||||
#include <eventbus/TokenHolder.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SimpleEvent
|
||||
{
|
||||
std::int64_t value = 0;
|
||||
};
|
||||
|
||||
Dexode::AsyncEventBus bus;
|
||||
|
||||
} // namespace
|
||||
|
||||
void checkFor(benchmark::State& state)
|
||||
{
|
||||
if(state.thread_index == 0)
|
||||
{
|
||||
Dexode::TokenHolder<Dexode::AsyncEventBus> listener {&bus};
|
||||
std::uint64_t consumed = 0;
|
||||
listener.listen<SimpleEvent>(
|
||||
[&consumed](const auto& event) { benchmark::DoNotOptimize(consumed += 1); });
|
||||
|
||||
for(auto _ : state)
|
||||
{
|
||||
//if(bus.wait())
|
||||
{
|
||||
bus.consume();
|
||||
}
|
||||
}
|
||||
state.counters["consumed"] = consumed;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto _ : state)
|
||||
{
|
||||
bus.schedule(SimpleEvent {std::chrono::steady_clock::now().time_since_epoch().count()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(checkFor)->Threads(2)->MinTime(1)->MeasureProcessCPUTime();
|
||||
BENCHMARK(checkFor)->Threads(5)->MinTime(1)->MeasureProcessCPUTime();
|
||||
BENCHMARK(checkFor)->Threads(10)->MinTime(1)->MeasureProcessCPUTime();
|
305
3party/EventBus/performance/src/EventBusPerformance.cpp
Normal file
305
3party/EventBus/performance/src/EventBusPerformance.cpp
Normal file
@ -0,0 +1,305 @@
|
||||
//
|
||||
// Created by Dawid Drozd aka Gelldur on 05.08.17.
|
||||
//
|
||||
#include <random>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "dexode/EventBus.hpp"
|
||||
#include "dexode/eventbus/strategy/Protected.hpp"
|
||||
#include "dexode/eventbus/strategy/Transaction.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <class Bus>
|
||||
void checkNListeners(benchmark::State& state, const int listenersCount)
|
||||
{
|
||||
Bus bus;
|
||||
|
||||
struct SimpleEvent
|
||||
{
|
||||
int value;
|
||||
};
|
||||
|
||||
std::vector<typename Bus::Listener> listeners;
|
||||
listeners.reserve(listenersCount);
|
||||
|
||||
std::uint64_t sum = 0;
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
listeners.emplace_back(bus.createListener());
|
||||
listeners.back().template listen<SimpleEvent>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
}
|
||||
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
const auto event = SimpleEvent{1};
|
||||
bus.post(event);
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void checkSimpleNotification(benchmark::State& state)
|
||||
{
|
||||
checkNListeners<dexode::EventBus<Strategy>>(state, 1);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check10Listeners(benchmark::State& state)
|
||||
{
|
||||
checkNListeners<dexode::EventBus<Strategy>>(state, 10);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check100Listeners(benchmark::State& state)
|
||||
{
|
||||
checkNListeners<dexode::EventBus<Strategy>>(state, 100);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check1kListeners(benchmark::State& state)
|
||||
{
|
||||
checkNListeners<dexode::EventBus<Strategy>>(state, 1000);
|
||||
}
|
||||
|
||||
void call1kLambdas_compare(benchmark::State& state)
|
||||
{
|
||||
std::vector<std::function<void(int)>> callbacks;
|
||||
constexpr int listenersCount = 1000;
|
||||
callbacks.reserve(listenersCount);
|
||||
std::uint64_t sum = 0;
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
callbacks.emplace_back([&sum](int value) { benchmark::DoNotOptimize(sum += value); });
|
||||
}
|
||||
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
callbacks[i](1);
|
||||
}
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
}
|
||||
|
||||
template <int N>
|
||||
struct UniqEvent
|
||||
{
|
||||
int value;
|
||||
};
|
||||
|
||||
template <class Bus>
|
||||
void checkNNotificationsForNListeners(benchmark::State& state,
|
||||
const int notificationsCount,
|
||||
const int listenersCount)
|
||||
{
|
||||
std::mt19937 generator(311281);
|
||||
std::uniform_int_distribution<int> uniformDistribution(0, 9);
|
||||
|
||||
Bus bus;
|
||||
std::uint64_t sum = 0;
|
||||
|
||||
std::vector<typename Bus::Listener> listeners;
|
||||
listeners.reserve(listenersCount);
|
||||
|
||||
// We register M listeners for N notifications using uniform distribution
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
listeners.emplace_back(bus.createListener());
|
||||
const auto which = uniformDistribution(generator);
|
||||
|
||||
// We generate here N different notifications
|
||||
switch(which)
|
||||
{
|
||||
case 0:
|
||||
listeners.back().template listen<UniqEvent<0>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 1:
|
||||
listeners.back().template listen<UniqEvent<1>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 2:
|
||||
listeners.back().template listen<UniqEvent<2>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 3:
|
||||
listeners.back().template listen<UniqEvent<3>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 4:
|
||||
listeners.back().template listen<UniqEvent<4>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 5:
|
||||
listeners.back().template listen<UniqEvent<5>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 6:
|
||||
listeners.back().template listen<UniqEvent<6>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 7:
|
||||
listeners.back().template listen<UniqEvent<7>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 8:
|
||||
listeners.back().template listen<UniqEvent<8>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
case 9:
|
||||
listeners.back().template listen<UniqEvent<9>>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error{"Nope"};
|
||||
}
|
||||
}
|
||||
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
// Pick random notification
|
||||
const auto which = uniformDistribution(generator);
|
||||
// We generate here N different notifications
|
||||
switch(which)
|
||||
{
|
||||
case 0:
|
||||
bus.post(UniqEvent<0>{1});
|
||||
break;
|
||||
case 1:
|
||||
bus.post(UniqEvent<1>{2});
|
||||
break;
|
||||
case 2:
|
||||
bus.post(UniqEvent<2>{3});
|
||||
break;
|
||||
case 3:
|
||||
bus.post(UniqEvent<3>{4});
|
||||
break;
|
||||
case 4:
|
||||
bus.post(UniqEvent<4>{5});
|
||||
break;
|
||||
case 5:
|
||||
bus.post(UniqEvent<5>{6});
|
||||
break;
|
||||
case 6:
|
||||
bus.post(UniqEvent<6>{7});
|
||||
break;
|
||||
case 7:
|
||||
bus.post(UniqEvent<7>{8});
|
||||
break;
|
||||
case 8:
|
||||
bus.post(UniqEvent<8>{9});
|
||||
break;
|
||||
case 9:
|
||||
bus.post(UniqEvent<9>{10});
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error{"Nope"};
|
||||
}
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check10NotificationsFor1kListeners(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners<dexode::EventBus<Strategy>>(state, 10, 1000);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check100NotificationsFor1kListeners(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners<dexode::EventBus<Strategy>>(state, 100, 1000);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check1kNotificationsFor1kListeners(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners<dexode::EventBus<Strategy>>(state, 1000, 1000);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void check100NotificationsFor10kListeners(benchmark::State& state)
|
||||
{
|
||||
checkNNotificationsForNListeners<dexode::EventBus<Strategy>>(state, 100, 10000);
|
||||
}
|
||||
|
||||
template <class Strategy>
|
||||
void checkNotifyFor10kListenersWhenNoOneListens(benchmark::State& state)
|
||||
{
|
||||
using Listener = typename dexode::EventBus<Strategy>::Listener;
|
||||
dexode::EventBus<Strategy> bus;
|
||||
|
||||
constexpr int listenersCount = 10000;
|
||||
|
||||
std::uint64_t sum = 0;
|
||||
struct SimpleEvent
|
||||
{
|
||||
int value;
|
||||
};
|
||||
struct UnknownEvent
|
||||
{
|
||||
int value;
|
||||
};
|
||||
|
||||
std::vector<Listener> listeners;
|
||||
listeners.reserve(listenersCount);
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
listeners.emplace_back(bus.createListener());
|
||||
listeners.back().template listen<SimpleEvent>(
|
||||
[&sum](const auto& event) { benchmark::DoNotOptimize(sum += event.value); });
|
||||
}
|
||||
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
const auto unknownEvent = UnknownEvent{2};
|
||||
bus.post(unknownEvent); // this event doesn't have any listener
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BENCHMARK(call1kLambdas_compare);
|
||||
|
||||
namespace strategy::Transaction
|
||||
{
|
||||
|
||||
using Transaction = dexode::eventbus::strategy::Transaction;
|
||||
|
||||
BENCHMARK_TEMPLATE(checkSimpleNotification, Transaction);
|
||||
BENCHMARK_TEMPLATE(check10Listeners, Transaction);
|
||||
BENCHMARK_TEMPLATE(check100Listeners, Transaction);
|
||||
BENCHMARK_TEMPLATE(check1kListeners, Transaction);
|
||||
|
||||
BENCHMARK_TEMPLATE(check10NotificationsFor1kListeners, Transaction);
|
||||
BENCHMARK_TEMPLATE(check100NotificationsFor1kListeners, Transaction);
|
||||
BENCHMARK_TEMPLATE(check1kNotificationsFor1kListeners, Transaction);
|
||||
BENCHMARK_TEMPLATE(check100NotificationsFor10kListeners, Transaction);
|
||||
|
||||
BENCHMARK_TEMPLATE(checkNotifyFor10kListenersWhenNoOneListens, Transaction);
|
||||
|
||||
} // namespace strategy::Transaction
|
||||
|
||||
namespace strategy::Protected
|
||||
{
|
||||
|
||||
using Protected = dexode::eventbus::strategy::Protected;
|
||||
|
||||
BENCHMARK_TEMPLATE(checkSimpleNotification, Protected);
|
||||
BENCHMARK_TEMPLATE(check10Listeners, Protected);
|
||||
BENCHMARK_TEMPLATE(check100Listeners, Protected);
|
||||
BENCHMARK_TEMPLATE(check1kListeners, Protected);
|
||||
|
||||
BENCHMARK_TEMPLATE(check10NotificationsFor1kListeners, Protected);
|
||||
BENCHMARK_TEMPLATE(check100NotificationsFor1kListeners, Protected);
|
||||
BENCHMARK_TEMPLATE(check1kNotificationsFor1kListeners, Protected);
|
||||
BENCHMARK_TEMPLATE(check100NotificationsFor10kListeners, Protected);
|
||||
|
||||
BENCHMARK_TEMPLATE(checkNotifyFor10kListenersWhenNoOneListens, Protected);
|
||||
|
||||
} // namespace strategy::Protected
|
@ -0,0 +1,98 @@
|
||||
//
|
||||
// Created by Dawid Drozd aka Gelldur on 24.07.18.
|
||||
//
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include <Poco/NObserver.h>
|
||||
#include <Poco/Notification.h>
|
||||
#include <Poco/NotificationCenter.h>
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <class T>
|
||||
struct Wrapper : public Poco::Notification
|
||||
{
|
||||
T data;
|
||||
|
||||
Wrapper(T data)
|
||||
: data(std::move(data))
|
||||
{}
|
||||
};
|
||||
|
||||
template <class Egg>
|
||||
class Target
|
||||
{
|
||||
public:
|
||||
Target(std::function<void(const Egg&)> callback)
|
||||
: _callback(std::move(callback))
|
||||
{}
|
||||
|
||||
void handle(const Poco::AutoPtr<Wrapper<Egg>>& event)
|
||||
{
|
||||
_callback((*event.get()).data);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(const Egg&)> _callback;
|
||||
};
|
||||
|
||||
void checkNListeners(benchmark::State& state, const int listenersCount)
|
||||
{
|
||||
Poco::NotificationCenter bus;
|
||||
int sum = 0;
|
||||
|
||||
struct SimpleEvent
|
||||
{
|
||||
int value;
|
||||
};
|
||||
|
||||
using MyEvent = Wrapper<SimpleEvent>;
|
||||
using Listener = Target<SimpleEvent>;
|
||||
|
||||
std::vector<Listener> targets;
|
||||
targets.reserve(listenersCount + 1);
|
||||
|
||||
for(int i = 0; i < listenersCount; ++i)
|
||||
{
|
||||
targets.emplace_back(
|
||||
[&](const SimpleEvent& event) { benchmark::DoNotOptimize(sum += event.value * 2); });
|
||||
|
||||
bus.addObserver(Poco::NObserver<Listener, MyEvent>(targets.back(), &Listener::handle));
|
||||
}
|
||||
|
||||
while(state.KeepRunning()) // Performance area!
|
||||
{
|
||||
const Poco::AutoPtr<MyEvent> event = new Wrapper<SimpleEvent>{SimpleEvent{2}};
|
||||
bus.postNotification(event);
|
||||
}
|
||||
state.counters["sum"] = sum;
|
||||
}
|
||||
|
||||
void Poco_checkSimpleNotification(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 1);
|
||||
}
|
||||
|
||||
void Poco_check10Listeners(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 10);
|
||||
}
|
||||
|
||||
void Poco_check100Listeners(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 100);
|
||||
}
|
||||
|
||||
void Poco_check1kListeners(benchmark::State& state)
|
||||
{
|
||||
checkNListeners(state, 1000);
|
||||
}
|
||||
|
||||
BENCHMARK(Poco_checkSimpleNotification);
|
||||
BENCHMARK(Poco_check10Listeners);
|
||||
BENCHMARK(Poco_check100Listeners);
|
||||
BENCHMARK(Poco_check1kListeners);
|
||||
}
|
18
3party/EventBus/test/CMakeLists.txt
Normal file
18
3party/EventBus/test/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
|
||||
|
||||
# http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html
|
||||
# Thanks for CATCH!
|
||||
|
||||
project(EventBusTest)
|
||||
|
||||
# Dependencies
|
||||
enable_testing()
|
||||
if(NOT TARGET Dexode::EventBus)
|
||||
find_package(EventBus CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Catch2 2.10 REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_subdirectory(unit)
|
||||
add_subdirectory(integration)
|
40
3party/EventBus/test/integration/CMakeLists.txt
Normal file
40
3party/EventBus/test/integration/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
||||
#
|
||||
# Target definition
|
||||
add_executable(EventBus-IntegrationTest
|
||||
src/dexode/eventbus/test/SuiteWait.cpp
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(EventBus-IntegrationTest PRIVATE src/)
|
||||
|
||||
target_compile_options(EventBus-IntegrationTest PUBLIC
|
||||
-Wall -pedantic
|
||||
-Wno-unused-private-field
|
||||
-Wnon-virtual-dtor
|
||||
-Wno-gnu
|
||||
-Werror
|
||||
)
|
||||
|
||||
# Don't do such thing:
|
||||
# if(CMAKE_BUILD_TYPE STREQUAL DEBUG)
|
||||
# ....
|
||||
# else()
|
||||
# ...
|
||||
# endif()
|
||||
#
|
||||
# Instead do this way: (It will work for Visual Studio)
|
||||
# target_compile_definitions(foo PRIVATE "VERBOSITY=$<IF:$<BOOL:${VERBOSE}>,30,10>")
|
||||
|
||||
|
||||
set(EVENTBUS_DEBUG_FLAGS
|
||||
-O0 -fno-inline
|
||||
-DDEBUG
|
||||
#-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
|
||||
)
|
||||
|
||||
target_compile_options(EventBus-IntegrationTest PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>")
|
||||
|
||||
target_link_libraries(EventBus-IntegrationTest PUBLIC Catch2::Catch2 Dexode::EventBus Threads::Threads)
|
||||
|
||||
|
||||
add_test(NAME EventBus.UnitTests COMMAND EventBus-IntegrationTest)
|
@ -0,0 +1,144 @@
|
||||
//
|
||||
// Created by gelldur on 12.03.2020.
|
||||
//
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "dexode/EventBus.hpp"
|
||||
#include "dexode/eventbus/perk/PerkEventBus.hpp"
|
||||
#include "dexode/eventbus/perk/WaitPerk.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct EventTest
|
||||
{
|
||||
std::string data;
|
||||
std::chrono::steady_clock::time_point created = std::chrono::steady_clock::now();
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Should not be processed with unnecessary delay", "[concurrent][EventBus]")
|
||||
{
|
||||
auto bus = std::make_shared<dexode::eventbus::perk::PerkEventBus>();
|
||||
bus->addPerk(std::make_unique<dexode::eventbus::perk::WaitPerk>())
|
||||
.registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent);
|
||||
|
||||
auto* waitPerk = bus->getPerk<dexode::eventbus::perk::WaitPerk>();
|
||||
REQUIRE(waitPerk != nullptr);
|
||||
|
||||
dexode::eventbus::perk::PerkEventBus::Listener listener{bus};
|
||||
listener.listen([bus](const EventTest& event) {
|
||||
const auto eventAge = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - event.created);
|
||||
CHECK(eventAge.count() < (5ms).count());
|
||||
std::cout << "Event:" << event.data << " old: " << eventAge.count() << "ms" << std::endl;
|
||||
std::this_thread::sleep_for(2ms); // Some heavy work when processing event
|
||||
bus->postpone(EventTest{"other"});
|
||||
std::this_thread::sleep_for(3ms); // Some heavy work when processing event
|
||||
});
|
||||
|
||||
std::atomic<bool> isWorking = true;
|
||||
|
||||
std::vector<std::thread> producers;
|
||||
// Worker which will send event every 500 ms
|
||||
producers.emplace_back([&bus, &isWorking]() {
|
||||
while(isWorking)
|
||||
{
|
||||
bus->postpone(EventTest{"producer1"});
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
});
|
||||
|
||||
for(int i = 0; i < 20;)
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
if(waitPerk->waitFor(2000ms))
|
||||
{
|
||||
const auto sleepTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
|
||||
std::cout << "[SUCCESS] I was sleeping for: " << sleepTime.count() << " ms i:" << i
|
||||
<< std::endl;
|
||||
i += bus->process();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto sleepTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
CHECK(sleepTime.count() < (5ms).count());
|
||||
// No events waiting for us
|
||||
std::cout << "I was sleeping for: " << sleepTime.count() << " ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
isWorking = false;
|
||||
for(auto& producer : producers)
|
||||
{
|
||||
producer.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Should wait for event being scheduled", "[concurrent][EventBus]")
|
||||
{
|
||||
auto bus = std::make_shared<dexode::eventbus::perk::PerkEventBus>();
|
||||
bus->addPerk(std::make_unique<dexode::eventbus::perk::WaitPerk>())
|
||||
.registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent);
|
||||
|
||||
auto* waitPerk = bus->getPerk<dexode::eventbus::perk::WaitPerk>();
|
||||
REQUIRE(waitPerk != nullptr);
|
||||
|
||||
dexode::eventbus::perk::PerkEventBus::Listener listener{bus};
|
||||
listener.listen([bus](const EventTest& event) {
|
||||
const auto eventAge = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - event.created);
|
||||
CHECK(eventAge.count() < (5ms).count());
|
||||
std::cout << "Event:" << event.data << " old: " << eventAge.count() << "ms" << std::endl;
|
||||
});
|
||||
|
||||
std::atomic<bool> isWorking = true;
|
||||
|
||||
std::vector<std::thread> producers;
|
||||
producers.emplace_back([&bus, &isWorking]() {
|
||||
while(isWorking)
|
||||
{
|
||||
std::this_thread::sleep_for(10ms);
|
||||
bus->postpone(EventTest{"producer1"});
|
||||
}
|
||||
});
|
||||
|
||||
for(int i = 0; i < 20;)
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
if(waitPerk->waitFor(40ms))
|
||||
{
|
||||
const auto sleepTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
CHECK(sleepTime.count() >= (9ms).count());
|
||||
|
||||
std::cout << "[SUCCESS] I was sleeping for: " << sleepTime.count() << " ms i:" << i
|
||||
<< std::endl;
|
||||
i += bus->process();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto sleepTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start);
|
||||
CHECK(sleepTime < 5ms);
|
||||
// No events waiting for us
|
||||
std::cout << "I was sleeping for: " << sleepTime.count() << " ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
isWorking = false;
|
||||
for(auto& producer : producers)
|
||||
{
|
||||
producer.join();
|
||||
}
|
||||
}
|
2
3party/EventBus/test/integration/src/main.cpp
Normal file
2
3party/EventBus/test/integration/src/main.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
43
3party/EventBus/test/unit/CMakeLists.txt
Normal file
43
3party/EventBus/test/unit/CMakeLists.txt
Normal file
@ -0,0 +1,43 @@
|
||||
#
|
||||
# Target definition
|
||||
add_executable(EventBus-UnitTest
|
||||
src/dexode/eventbus/test/event.hpp
|
||||
src/dexode/eventbus/test/SuiteConcurrentEventBus.cpp
|
||||
src/dexode/eventbus/test/SuiteEventBus.cpp
|
||||
src/dexode/eventbus/test/SuiteEventID.cpp
|
||||
src/dexode/eventbus/test/SuiteListener.cpp
|
||||
src/main.cpp
|
||||
)
|
||||
target_include_directories(EventBus-UnitTest PRIVATE src/)
|
||||
|
||||
target_compile_options(EventBus-UnitTest PUBLIC
|
||||
-Wall -pedantic
|
||||
-Wno-unused-private-field
|
||||
-Wnon-virtual-dtor
|
||||
-Wno-gnu
|
||||
-Werror
|
||||
)
|
||||
|
||||
# Don't do such thing:
|
||||
# if(CMAKE_BUILD_TYPE STREQUAL DEBUG)
|
||||
# ....
|
||||
# else()
|
||||
# ...
|
||||
# endif()
|
||||
#
|
||||
# Instead do this way: (It will work for Visual Studio)
|
||||
# target_compile_definitions(foo PRIVATE "VERBOSITY=$<IF:$<BOOL:${VERBOSE}>,30,10>")
|
||||
|
||||
|
||||
set(EVENTBUS_DEBUG_FLAGS
|
||||
-O0 -fno-inline
|
||||
-DDEBUG
|
||||
#-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
|
||||
)
|
||||
|
||||
target_compile_options(EventBus-UnitTest PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>")
|
||||
|
||||
target_link_libraries(EventBus-UnitTest PUBLIC Catch2::Catch2 Dexode::EventBus Threads::Threads)
|
||||
|
||||
|
||||
add_test(NAME EventBus.UnitTests COMMAND EventBus-UnitTest)
|
@ -0,0 +1,180 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "dexode/EventBus.hpp"
|
||||
#include "dexode/eventbus/perk/PerkEventBus.hpp"
|
||||
#include "dexode/eventbus/perk/WaitPerk.hpp"
|
||||
#include "dexode/eventbus/test/event.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace dexode::eventbus::test
|
||||
{
|
||||
using Listener = dexode::EventBus::Listener;
|
||||
|
||||
struct SimpleEvent
|
||||
{
|
||||
std::thread::id id;
|
||||
};
|
||||
|
||||
constexpr auto ns2 = std::chrono::nanoseconds{2}; // GCC 7 has some issues with 2ms :/
|
||||
constexpr auto ns3 = std::chrono::nanoseconds{3};
|
||||
|
||||
TEST_CASE("Should consume events in synchronous way When using worker threads",
|
||||
"[concurrent][EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
|
||||
std::atomic<int> counter = 0;
|
||||
|
||||
listener.listen([&counter](const SimpleEvent& event) {
|
||||
// std::cout << "Event from: " << event.id << std::endl;
|
||||
++counter;
|
||||
});
|
||||
|
||||
std::thread worker1{[&bus]() {
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
bus.postpone(SimpleEvent{std::this_thread::get_id()});
|
||||
std::this_thread::sleep_for(ns3);
|
||||
}
|
||||
}};
|
||||
std::thread worker2{[&bus]() {
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
bus.postpone(SimpleEvent{std::this_thread::get_id()});
|
||||
std::this_thread::sleep_for(ns2);
|
||||
}
|
||||
}};
|
||||
|
||||
REQUIRE(counter == 0);
|
||||
int sumOfConsumed = 0;
|
||||
while(counter < 20)
|
||||
{
|
||||
REQUIRE(counter == sumOfConsumed);
|
||||
sumOfConsumed += bus.process();
|
||||
REQUIRE(counter == sumOfConsumed);
|
||||
}
|
||||
|
||||
worker1.join();
|
||||
worker2.join();
|
||||
}
|
||||
|
||||
TEST_CASE("Should listen for only 1 event When call unlisten inside Listener",
|
||||
"[concurrent][EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
|
||||
std::atomic<int> counter = 0;
|
||||
|
||||
listener.listen<SimpleEvent>([&counter, &listener](const SimpleEvent& event) {
|
||||
// std::cout << "Event from: " << event.id << std::endl;
|
||||
++counter;
|
||||
listener.unlistenAll();
|
||||
});
|
||||
|
||||
std::thread worker1{[&bus]() {
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
bus.postpone(SimpleEvent{std::this_thread::get_id()});
|
||||
std::this_thread::sleep_for(ns3);
|
||||
}
|
||||
}};
|
||||
std::thread worker2{[&bus]() {
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
bus.postpone(SimpleEvent{std::this_thread::get_id()});
|
||||
std::this_thread::sleep_for(ns2);
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread worker3{[&bus, &counter]() {
|
||||
while(counter == 0)
|
||||
{
|
||||
bus.process();
|
||||
std::this_thread::sleep_for(ns2);
|
||||
}
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
bus.process();
|
||||
std::this_thread::sleep_for(ns2);
|
||||
}
|
||||
}};
|
||||
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
bus.postpone(SimpleEvent{std::this_thread::get_id()});
|
||||
}
|
||||
|
||||
worker1.join();
|
||||
worker2.join();
|
||||
worker3.join();
|
||||
|
||||
REQUIRE(counter == 1); // Should be called only once
|
||||
}
|
||||
|
||||
TEST_CASE("Should wait work", "[concurrent][EventBus]")
|
||||
{
|
||||
dexode::eventbus::perk::PerkEventBus bus;
|
||||
bus.addPerk(std::make_unique<dexode::eventbus::perk::WaitPerk>())
|
||||
.registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent);
|
||||
|
||||
auto* waitPerk = bus.getPerk<dexode::eventbus::perk::WaitPerk>();
|
||||
REQUIRE(waitPerk != nullptr);
|
||||
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
listener.listen(
|
||||
[](const event::WaitPerk& event) { std::cout << "In WaitPerk event" << std::endl; });
|
||||
listener.listen([](const event::T1& event) { std::cout << "In T1 event" << std::endl; });
|
||||
|
||||
// Worker which will send event every 10 ms
|
||||
std::atomic<bool> isWorking = true;
|
||||
std::atomic<int> produced{0};
|
||||
std::atomic<int> consumed{0};
|
||||
|
||||
std::thread producer{[&bus, &isWorking, &produced]() {
|
||||
while(isWorking)
|
||||
{
|
||||
bus.postpone(event::WaitPerk{});
|
||||
bus.postpone(event::T1{});
|
||||
++produced;
|
||||
++produced;
|
||||
std::this_thread::sleep_for(5ms);
|
||||
}
|
||||
}};
|
||||
|
||||
for(int i = 0; i < 20; ++i)
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
if(waitPerk->waitFor(20ms))
|
||||
{
|
||||
int beforeConsumed = consumed;
|
||||
consumed += bus.process();
|
||||
INFO("If events available then consumed count should change")
|
||||
CHECK(consumed >= beforeConsumed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No events waiting for us
|
||||
}
|
||||
|
||||
std::cout << "I was sleeping for: "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count()
|
||||
<< " ms, consumed:" << consumed << std::endl;
|
||||
}
|
||||
|
||||
isWorking = false;
|
||||
producer.join();
|
||||
REQUIRE(produced >= consumed);
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::test
|
@ -0,0 +1,498 @@
|
||||
#include <variant>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "dexode/EventBus.hpp"
|
||||
#include "dexode/eventbus/test/event.hpp"
|
||||
|
||||
namespace dexode::eventbus::test
|
||||
{
|
||||
|
||||
// class TestProducer
|
||||
//{
|
||||
// public:
|
||||
// using Events = std::variant<event::T1, event::T2>;
|
||||
//
|
||||
// void onUpdate(TransactionEventBus& bus)
|
||||
// {
|
||||
// bus.postpone(event::T1{});
|
||||
// }
|
||||
//
|
||||
// private:
|
||||
//};
|
||||
|
||||
// TEST_CASE("Should process events independently When postponed multiple event types",
|
||||
// "[EventBus]")
|
||||
//{
|
||||
// EventBus bus;
|
||||
//
|
||||
// int event1CallCount = 0;
|
||||
// int event2CallCount = 0;
|
||||
//
|
||||
// auto listener = Listener::createNotOwning(bus);
|
||||
// listener.listen<event::T1>([&](const event::T1& event) { ++event1CallCount; });
|
||||
// listener.listen2([&](const event::T2& event) { ++event2CallCount; });
|
||||
//
|
||||
// bus.postpone(event::T1{});
|
||||
// {
|
||||
// event::T2 localVariable;
|
||||
// bus.postpone(localVariable);
|
||||
// }
|
||||
//
|
||||
// REQUIRE(event1CallCount == 0);
|
||||
// REQUIRE(event2CallCount == 0);
|
||||
// REQUIRE(bus.process<event::T1>() == 1);
|
||||
// REQUIRE(event1CallCount == 1);
|
||||
// REQUIRE(event2CallCount == 0);
|
||||
//
|
||||
// REQUIRE(bus.process<event::T2>() == 1);
|
||||
// REQUIRE(event1CallCount == 1);
|
||||
// REQUIRE(event2CallCount == 1);
|
||||
//}
|
||||
|
||||
TEST_CASE("Should deliver event with desired value When postpone event", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
|
||||
REQUIRE(callCount == 0);
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(callCount == 0);
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should be able to replace listener When we do listen -> unlisten -> listen",
|
||||
"[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
|
||||
{
|
||||
// TODO validate other signatures is it possible to make mistake? (maybe fail to build tests
|
||||
// ?)
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
{
|
||||
listener.unlisten<event::Value>();
|
||||
bus.postpone(event::Value{2});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
{
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 1);
|
||||
++callCount;
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{1});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Should be able to replace listener When we do listen -> unlistenAll -> listen",
|
||||
"[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
{
|
||||
// TODO validate other signatures is it possible to make mistake? (maybe fail to build tests
|
||||
// ?)
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
listener.listen([&](const event::T1& event) { ++callCount; });
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::T1{});
|
||||
REQUIRE(bus.process() == 2);
|
||||
REQUIRE(callCount == 2);
|
||||
}
|
||||
|
||||
{
|
||||
listener.unlistenAll();
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::T1{});
|
||||
REQUIRE(bus.process() == 2);
|
||||
REQUIRE(callCount == 2); // no new calls
|
||||
}
|
||||
|
||||
{
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 1);
|
||||
++callCount;
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{1});
|
||||
bus.postpone(event::T1{});
|
||||
REQUIRE(bus.process() == 2);
|
||||
REQUIRE(callCount == 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Should flush events When no one listens", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
|
||||
}
|
||||
|
||||
TEST_CASE("Should be able to unlisten When processing event", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
listener.listen([&](const event::Value& event) {
|
||||
++callCount;
|
||||
listener.unlisten<event::Value>();
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should not fail When unlisten multiple times during processing event", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE_NOTHROW(listener.unlisten<event::Value>());
|
||||
REQUIRE_NOTHROW(listener.unlisten<event::Value>());
|
||||
REQUIRE_NOTHROW(listener.unlisten<event::Value>());
|
||||
++callCount;
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should fail When try to add not unique listener", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
REQUIRE_NOTHROW(listener.listen([](const event::T1&) {}));
|
||||
REQUIRE_THROWS(listener.listen([](const event::T1&) {})); // We already added listener
|
||||
}
|
||||
|
||||
TEST_CASE("Should be able to add listener When processing event", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
auto listenerOther = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
int callCountOther = 0;
|
||||
listener.listen([&](const event::Value& event) {
|
||||
++callCount;
|
||||
if(callCount == 1) // remember that we can only add it once!
|
||||
{
|
||||
listenerOther.listen(
|
||||
[&callCountOther](const event::Value& event) { ++callCountOther; });
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
REQUIRE(callCountOther == 0);
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 2);
|
||||
REQUIRE(callCountOther == 1);
|
||||
}
|
||||
{
|
||||
listenerOther.unlistenAll();
|
||||
callCount = 0;
|
||||
callCountOther = 0;
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
|
||||
REQUIRE(callCount == 3);
|
||||
REQUIRE(callCountOther == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Should be able to add listener and unlisten When processing event", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
auto listenerOther = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
int callCountOther = 0;
|
||||
listener.listen([&](const event::Value& event) {
|
||||
++callCount;
|
||||
if(callCount == 1) // remember that we can only add it once!
|
||||
{
|
||||
listenerOther.listen([&](const event::Value& event) { ++callCountOther; });
|
||||
}
|
||||
listener.unlistenAll();
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3);
|
||||
REQUIRE(callCount == 1);
|
||||
REQUIRE(callCountOther == 2);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Should be able to add listener and remove listener in Matryoshka style When processing event",
|
||||
"[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener1 = EventBus::Listener::createNotOwning(bus);
|
||||
auto listener2 = EventBus::Listener::createNotOwning(bus);
|
||||
auto listener3 = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
|
||||
listener1.listen([&](const event::Value& event) {
|
||||
listener1.unlistenAll(); // Level 1
|
||||
|
||||
listener2.listen([&](const event::Value& event) {
|
||||
listener2.unlistenAll(); // Level 2
|
||||
|
||||
listener3.listen([&](const event::Value& event) {
|
||||
listener3.unlistenAll(); // Level 3 (final)
|
||||
++callCount;
|
||||
});
|
||||
++callCount;
|
||||
});
|
||||
++callCount;
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 4);
|
||||
REQUIRE(callCount == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Should be chain listen and unlisten When processing event", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
auto listenerOther = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
int callOtherOption = 0;
|
||||
listener.listen([&](const event::Value& event) {
|
||||
++callCount;
|
||||
// remember that we can only add it once!
|
||||
listenerOther.listen([&](const event::Value& event) { callOtherOption = 1; });
|
||||
listenerOther.unlisten<event::Value>();
|
||||
listenerOther.listen([&](const event::Value& event) { callOtherOption = 2; });
|
||||
listenerOther.unlisten<event::Value>();
|
||||
listenerOther.listen([&](const event::Value& event) { callOtherOption = 3; });
|
||||
listenerOther.unlisten<event::Value>();
|
||||
|
||||
listenerOther.listen([&](const event::Value& event) { callOtherOption = 4; });
|
||||
|
||||
listener.unlistenAll();
|
||||
});
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3);
|
||||
REQUIRE(callCount == 1);
|
||||
REQUIRE(callOtherOption == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Should not process events When no more events", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
|
||||
REQUIRE(bus.process() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Should process event When listener transit", "[EventBus]")
|
||||
{
|
||||
/**
|
||||
* This case may be usefull when we use EventBus for some kind of state machine and we are
|
||||
* during transit from one state to other.
|
||||
*/
|
||||
EventBus bus;
|
||||
auto listenerA = EventBus::Listener::createNotOwning(bus);
|
||||
auto listenerB = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int listenerAReceiveEvent = 0;
|
||||
int listenerBReceiveEvent = 0;
|
||||
|
||||
listenerA.listen([&](const event::Value& event) { ++listenerAReceiveEvent; });
|
||||
|
||||
REQUIRE(bus.process() == 0);
|
||||
|
||||
// All cases should be same because of deterministic way of processing
|
||||
SECTION("Post event before transit")
|
||||
{
|
||||
bus.postpone(event::Value{3}); // <-- before
|
||||
|
||||
listenerA.unlistenAll();
|
||||
listenerB.listen([&](const event::Value& event) { ++listenerBReceiveEvent; });
|
||||
}
|
||||
SECTION("Post event in transit")
|
||||
{
|
||||
listenerA.unlistenAll();
|
||||
bus.postpone(event::Value{3}); // <-- in
|
||||
listenerB.listen([&](const event::Value& event) { ++listenerBReceiveEvent; });
|
||||
}
|
||||
SECTION("Post event after transit")
|
||||
{
|
||||
listenerA.unlistenAll();
|
||||
listenerB.listen([&](const event::Value& event) { ++listenerBReceiveEvent; });
|
||||
|
||||
bus.postpone(event::Value{3}); // <-- after
|
||||
}
|
||||
|
||||
REQUIRE(bus.process() == 1);
|
||||
CHECK(listenerAReceiveEvent == 0);
|
||||
CHECK(listenerBReceiveEvent == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should NOT process event When listener unlisten before process", "[EventBus]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
|
||||
int listenerReceiveEvent = 0;
|
||||
|
||||
listener.listen([&](const event::Value& event) { ++listenerReceiveEvent; });
|
||||
|
||||
REQUIRE(bus.process() == 0);
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
CHECK(listenerReceiveEvent == 1);
|
||||
|
||||
// All cases should be same because of deterministic way of processing
|
||||
SECTION("Post event before unlisten")
|
||||
{
|
||||
bus.postpone(event::Value{3}); // <-- before
|
||||
listener.unlistenAll();
|
||||
}
|
||||
SECTION("Post event after transit")
|
||||
{
|
||||
listener.unlistenAll();
|
||||
bus.postpone(event::Value{3}); // <-- after
|
||||
}
|
||||
|
||||
REQUIRE(bus.process() == 1);
|
||||
CHECK(listenerReceiveEvent == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should distinguish event producer When", "[EventBus]")
|
||||
{
|
||||
// EventBus bus;
|
||||
//
|
||||
// // dexode::eventbus::WrapTag<event::T1, Tag, Tag::gui> e;
|
||||
//
|
||||
// int counterGui = 0;
|
||||
// int counterBackend = 0;
|
||||
// auto listener = EventBus::Listener::createNotOwning(bus);
|
||||
//
|
||||
// listener.listen([&](const event::Tag<event::T1>& event) {
|
||||
// if(event.tag == "gui")
|
||||
// {
|
||||
// ++counterGui;
|
||||
// }
|
||||
// else if(event.tag == "backend")
|
||||
// {
|
||||
// ++counterBackend;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// FAIL();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// auto producerGui = [tag = Tag::gui](EventBus& bus) {
|
||||
// const event::T1 event;
|
||||
// bus.postpone(event);
|
||||
// };
|
||||
//
|
||||
// auto producerBackend = [](EventBus& bus) {
|
||||
// const event::T1 event;
|
||||
// bus.postpone(Tag::backend, event);
|
||||
// };
|
||||
//
|
||||
// auto producerNoTag = [](EventBus& bus) {
|
||||
// const event::T1 event;
|
||||
// bus.postpone(event);
|
||||
// };
|
||||
//
|
||||
// auto producerGeneric = [](EventBus& bus, Tag tag) {
|
||||
// const event::T1 event;
|
||||
// if(tag == Tag::none)
|
||||
// {
|
||||
// bus.postpone(event);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// bus.postpone(tag, event);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// producerGui(bus);
|
||||
// producerGui(bus);
|
||||
// producerBackend(bus);
|
||||
// producerGui(bus);
|
||||
// producerNoTag(bus);
|
||||
// producerGeneric(bus, Tag::none);
|
||||
// producerGeneric(bus, Tag::backend);
|
||||
//
|
||||
// CHECK(bus.process() == 7);
|
||||
//
|
||||
// REQUIRE(counterGui == 3);
|
||||
// REQUIRE(counterBackend == 2);
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::test
|
||||
|
||||
// TODO should listen work with bind'ing e.g.
|
||||
//_listener.listen<dashboard::back::events::LoadOrders>(
|
||||
// std::bind(&BackDashboard::onLoadOrders, this, std::placeholders::_1));
|
@ -0,0 +1,57 @@
|
||||
#include <set>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "dexode/eventbus/internal/event_id.hpp"
|
||||
|
||||
using namespace dexode;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Anonymous
|
||||
{};
|
||||
} // namespace
|
||||
|
||||
struct TestA
|
||||
{
|
||||
int a;
|
||||
};
|
||||
|
||||
namespace Test
|
||||
{
|
||||
struct TestA
|
||||
{
|
||||
bool b;
|
||||
};
|
||||
|
||||
namespace TestN
|
||||
{
|
||||
struct TestA
|
||||
{
|
||||
long long c;
|
||||
};
|
||||
|
||||
} // namespace TestN
|
||||
|
||||
} // namespace Test
|
||||
|
||||
namespace dexode::eventbus::test
|
||||
{
|
||||
|
||||
TEST_CASE("Should return unique id for each event When using event_id<Event>", "[EventID]")
|
||||
{
|
||||
std::set<eventbus::internal::event_id_t> unique;
|
||||
|
||||
REQUIRE(unique.insert(internal::event_id<Anonymous>()).second);
|
||||
REQUIRE_FALSE(unique.insert(internal::event_id<Anonymous>()).second); // already there
|
||||
|
||||
struct TestA // "name collision" but not quite collision
|
||||
{};
|
||||
|
||||
REQUIRE(unique.insert(internal::event_id<TestA>()).second);
|
||||
REQUIRE(unique.insert(internal::event_id<::TestA>()).second);
|
||||
REQUIRE(unique.insert(internal::event_id<Test::TestA>()).second);
|
||||
REQUIRE(unique.insert(internal::event_id<Test::TestN::TestA>()).second);
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::test
|
@ -0,0 +1,334 @@
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "dexode/EventBus.hpp"
|
||||
#include "dexode/eventbus/test/event.hpp"
|
||||
|
||||
namespace dexode::eventbus::test
|
||||
{
|
||||
|
||||
using Listener = dexode::EventBus::Listener;
|
||||
|
||||
TEST_CASE("Should remove all listeners When use unlistenAll", "[EventBus][Listener]")
|
||||
{
|
||||
EventBus bus;
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
|
||||
int callCount = 0;
|
||||
listener.listen([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
listener.listen([&](const event::T1& event) { ++callCount; });
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
bus.postpone(event::T1{});
|
||||
REQUIRE(bus.process() == 2);
|
||||
REQUIRE(callCount == 2);
|
||||
|
||||
listener.unlistenAll();
|
||||
|
||||
bus.postpone(event::Value{2});
|
||||
bus.postpone(event::T1{});
|
||||
REQUIRE(bus.process() == 2);
|
||||
REQUIRE(callCount == 2); // unchanged
|
||||
}
|
||||
|
||||
TEST_CASE("Should unlisten all events When listener instance is overriden", "[EventBus][Listener]")
|
||||
{
|
||||
EventBus bus;
|
||||
int callCount = 0;
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
listener.listen<event::Value>([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
|
||||
listener.transfer(Listener{});
|
||||
|
||||
bus.postpone(event::Value{2});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should unlisten all events When listener instance is destroyed", "[EventBus][Listener]")
|
||||
{
|
||||
EventBus bus;
|
||||
int callCount = 0;
|
||||
|
||||
{
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
listener.listen<event::Value>([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
bus.postpone(event::Value{2});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should keep listeners When listener is moved", "[EventBus][Listener]")
|
||||
{
|
||||
auto bus = std::make_shared<EventBus>();
|
||||
int callCount = 0;
|
||||
|
||||
Listener transferOne;
|
||||
{
|
||||
Listener listener{bus};
|
||||
listener.listen<event::Value>([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 3);
|
||||
++callCount;
|
||||
});
|
||||
bus->postpone(event::Value{3});
|
||||
REQUIRE(bus->process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
|
||||
transferOne.transfer(std::move(listener));
|
||||
}
|
||||
|
||||
bus->postpone(event::Value{3});
|
||||
REQUIRE(bus->process() == 1);
|
||||
REQUIRE(callCount == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Should receive event When listener added AFTER event emit but BEFORE event porcess",
|
||||
"[EventBus][Listener]")
|
||||
{
|
||||
auto bus = std::make_shared<EventBus>();
|
||||
int callCount = 0;
|
||||
bus->postpone(event::Value{22});
|
||||
|
||||
Listener listener{bus};
|
||||
listener.listen<event::Value>([&](const event::Value& event) {
|
||||
REQUIRE(event.value == 22);
|
||||
++callCount;
|
||||
});
|
||||
|
||||
REQUIRE(callCount == 0);
|
||||
bus->process();
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Should bind listen to class method and unbind When clazz dtor is called",
|
||||
"[EventBus][Listener]")
|
||||
{
|
||||
auto bus = std::make_shared<EventBus>();
|
||||
struct TestBind
|
||||
{
|
||||
int callCount = 0;
|
||||
Listener listener;
|
||||
|
||||
TestBind(const std::shared_ptr<EventBus>& bus)
|
||||
: listener{bus}
|
||||
{
|
||||
listener.listen<event::T1>(std::bind(&TestBind::onEvent, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void onEvent(const event::T1& event)
|
||||
{
|
||||
++callCount;
|
||||
}
|
||||
};
|
||||
|
||||
bus->postpone(event::T1{});
|
||||
{
|
||||
TestBind bindClazz{bus};
|
||||
REQUIRE(bindClazz.callCount == 0);
|
||||
CHECK(bus->process() == 1);
|
||||
REQUIRE(bindClazz.callCount == 1);
|
||||
}
|
||||
bus->postpone(event::T1{});
|
||||
CHECK(bus->process() == 1);
|
||||
}
|
||||
|
||||
static int globalCallCount{0}; // bad practice but want to just test
|
||||
void freeFunction(const event::T1& event)
|
||||
{
|
||||
++globalCallCount;
|
||||
}
|
||||
|
||||
TEST_CASE("Should compile When listen in different forms", "[EventBus][Listener]")
|
||||
{
|
||||
EventBus bus;
|
||||
|
||||
int callCount = 0;
|
||||
|
||||
// Listen with lambda
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
listener.listen([&](const event::T1& event) { ++callCount; });
|
||||
|
||||
// Listen with std::function
|
||||
auto listener2 = Listener::createNotOwning(bus);
|
||||
std::function<void(const event::T1&)> callback = [&](const event::T1&) { ++callCount; };
|
||||
listener2.listen(callback);
|
||||
|
||||
// Listen with std::bind
|
||||
auto listener3 = Listener::createNotOwning(bus);
|
||||
struct TestClazz
|
||||
{
|
||||
int clazzCallCount{0};
|
||||
void onEvent(const event::T1& event)
|
||||
{
|
||||
++clazzCallCount;
|
||||
}
|
||||
};
|
||||
TestClazz clazz;
|
||||
listener3.listen<event::T1>(std::bind(&TestClazz::onEvent, &clazz, std::placeholders::_1));
|
||||
|
||||
// Listen with free function
|
||||
auto listener4 = Listener::createNotOwning(bus);
|
||||
listener4.listen(freeFunction);
|
||||
|
||||
bus.postpone(event::T1{});
|
||||
bus.process();
|
||||
|
||||
REQUIRE(globalCallCount == 1);
|
||||
REQUIRE(clazz.clazzCallCount == 1);
|
||||
REQUIRE(callCount == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Should NOT be able to add multiple same event callbacks When using same listener",
|
||||
"[EventBus][Listener]")
|
||||
{
|
||||
/// User should use separate Listener instance as it would be unabigious what should happen when
|
||||
/// call unlisten<Event>
|
||||
EventBus bus;
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
|
||||
listener.listen([](const event::Value& event) {});
|
||||
REQUIRE_THROWS(listener.listen([](const event::Value& event) {}));
|
||||
}
|
||||
|
||||
TEST_CASE("Should compile", "[EventBus][Listener]")
|
||||
{
|
||||
// Test case to check for compilation
|
||||
EventBus bus;
|
||||
{
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
const auto callback = [](const event::Value& event) {};
|
||||
listener.listen(callback);
|
||||
}
|
||||
{
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
auto callback = [](const event::Value& event) {};
|
||||
listener.listen(callback);
|
||||
}
|
||||
{
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
auto callback = [](const event::Value& event) {};
|
||||
listener.listen(std::move(callback));
|
||||
}
|
||||
{
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
listener.listen([](const event::Value& event) {});
|
||||
}
|
||||
}
|
||||
|
||||
class TestClazz
|
||||
{
|
||||
public:
|
||||
static int counter;
|
||||
|
||||
TestClazz(int id, const std::shared_ptr<EventBus>& bus)
|
||||
: _id{id}
|
||||
, _listener{bus}
|
||||
{
|
||||
registerListener();
|
||||
}
|
||||
|
||||
TestClazz(TestClazz&& other)
|
||||
: _id{other._id}
|
||||
, _listener{other._listener.getBus()}
|
||||
{
|
||||
// We need to register again
|
||||
registerListener();
|
||||
}
|
||||
|
||||
~TestClazz()
|
||||
{
|
||||
_id = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int _id = 0;
|
||||
EventBus::Listener _listener;
|
||||
|
||||
void registerListener()
|
||||
{
|
||||
_listener.listen([this](const event::Value& event) {
|
||||
if(_id == 1)
|
||||
{
|
||||
++counter;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
int TestClazz::counter = 0;
|
||||
|
||||
TEST_CASE("Should not allow for mistake with move ctor", "[EventBus][Listener]")
|
||||
{
|
||||
/**
|
||||
* Test case TAG: FORBID_MOVE_LISTENER
|
||||
*
|
||||
* This case is little bit complicated.
|
||||
* We can't move EventBus::Listener as it capture 'this' in ctor so whenever we would use it it
|
||||
* would lead to UB.
|
||||
*/
|
||||
std::shared_ptr<EventBus> bus = std::make_shared<EventBus>();
|
||||
|
||||
std::vector<TestClazz> vector;
|
||||
vector.emplace_back(1, bus);
|
||||
vector.emplace_back(2, bus);
|
||||
vector.emplace_back(3, bus);
|
||||
|
||||
bus->postpone(event::Value{100});
|
||||
bus->process();
|
||||
|
||||
REQUIRE(TestClazz::counter == 1);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Should allow to check if listener is already listening", "[EventBus][Listener]")
|
||||
{
|
||||
// Related to Github Issue: https://github.com/gelldur/EventBus/issues/48
|
||||
EventBus bus;
|
||||
int callCount = 0;
|
||||
auto listener = Listener::createNotOwning(bus);
|
||||
|
||||
CHECK_FALSE(listener.isListening<event::Value>());
|
||||
|
||||
bus.postpone(event::Value{3});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 0); // not listening
|
||||
|
||||
listener.listen<event::Value>([&](const event::Value& event)
|
||||
{
|
||||
REQUIRE(event.value == 2);
|
||||
++callCount;
|
||||
});
|
||||
CHECK(listener.isListening<event::Value>());
|
||||
|
||||
bus.postpone(event::Value{2});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
|
||||
CHECK(listener.isListening<event::Value>());
|
||||
listener.unlisten<event::Value>();
|
||||
CHECK_FALSE(listener.isListening<event::Value>());
|
||||
|
||||
bus.postpone(event::Value{1});
|
||||
REQUIRE(bus.process() == 1);
|
||||
REQUIRE(callCount == 1);
|
||||
}
|
||||
|
||||
} // namespace dexode::eventbus::test
|
32
3party/EventBus/test/unit/src/dexode/eventbus/test/event.hpp
Normal file
32
3party/EventBus/test/unit/src/dexode/eventbus/test/event.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by gelldur on 24.11.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace dexode::eventbus::test::event
|
||||
{
|
||||
|
||||
struct T1
|
||||
{};
|
||||
|
||||
struct T2
|
||||
{};
|
||||
|
||||
struct Value
|
||||
{
|
||||
int value{-1};
|
||||
};
|
||||
|
||||
template <typename Event>
|
||||
struct Tag
|
||||
{
|
||||
Event data;
|
||||
std::string tag;
|
||||
};
|
||||
|
||||
struct WaitPerk
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
} // namespace dexode::eventbus::test::event
|
2
3party/EventBus/test/unit/src/main.cpp
Normal file
2
3party/EventBus/test/unit/src/main.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
3
3party/EventBus/use_case/CMakeLists.txt
Normal file
3
3party/EventBus/use_case/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
#
|
||||
add_subdirectory(basic/)
|
||||
add_subdirectory(tagged_events/)
|
5
3party/EventBus/use_case/README.md
Normal file
5
3party/EventBus/use_case/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Use cases
|
||||
|
||||
This folder should contain use cases and approaches how to use EventBus.
|
||||
|
||||
Most important need for this is only to store needed use cases by other projects.
|
6
3party/EventBus/use_case/basic/CMakeLists.txt
Normal file
6
3party/EventBus/use_case/basic/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
#
|
||||
add_executable(UseCase_Basic
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(UseCase_Basic PRIVATE Dexode::EventBus)
|
3
3party/EventBus/use_case/basic/README.md
Normal file
3
3party/EventBus/use_case/basic/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Use case: Basic
|
||||
|
||||
Just basic use case of EventBus.
|
149
3party/EventBus/use_case/basic/src/main.cpp
Normal file
149
3party/EventBus/use_case/basic/src/main.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @brief Sample code to play with!
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <dexode/EventBus.hpp>
|
||||
|
||||
using EventBus = dexode::EventBus;
|
||||
using Listener = dexode::EventBus::Listener;
|
||||
|
||||
namespace event // Example namespace for events
|
||||
{
|
||||
struct Gold // Event that will be proceed when our gold changes
|
||||
{
|
||||
int value = 0;
|
||||
};
|
||||
} // namespace event
|
||||
|
||||
enum class Monster
|
||||
{
|
||||
Frog,
|
||||
Tux,
|
||||
Shark
|
||||
};
|
||||
|
||||
class Character
|
||||
{
|
||||
public:
|
||||
Character(const std::shared_ptr<EventBus>& eventBus)
|
||||
: _bus{eventBus}
|
||||
{}
|
||||
|
||||
void kill(Monster monsterType)
|
||||
{
|
||||
if(Monster::Frog == monsterType)
|
||||
{
|
||||
_gold += 1;
|
||||
}
|
||||
else if(Monster::Tux == monsterType)
|
||||
{
|
||||
_gold += 100;
|
||||
}
|
||||
else if(Monster::Shark == monsterType)
|
||||
{
|
||||
_gold += 25;
|
||||
}
|
||||
_bus->postpone(event::Gold{_gold});
|
||||
}
|
||||
|
||||
private:
|
||||
int _gold = 0;
|
||||
std::shared_ptr<EventBus> _bus;
|
||||
};
|
||||
|
||||
class UIWallet
|
||||
{
|
||||
public:
|
||||
UIWallet(const std::shared_ptr<EventBus>& eventBus)
|
||||
: _listener{eventBus}
|
||||
{}
|
||||
|
||||
void onDraw() // Example "draw" of UI
|
||||
{
|
||||
std::cout << "Gold:" << _gold << std::endl;
|
||||
}
|
||||
|
||||
void onEnter() // We could also do such things in ctor and dtor but a lot of UI has something
|
||||
// like this
|
||||
{
|
||||
_listener.listen<event::Gold>(
|
||||
[this](const auto& event) { _gold = std::to_string(event.value); });
|
||||
}
|
||||
|
||||
void onExit()
|
||||
{
|
||||
_listener.unlistenAll();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _gold = "0";
|
||||
Listener _listener;
|
||||
};
|
||||
|
||||
// Shop button is only enabled when we have some gold (odd decision but for sample good :P)
|
||||
class ShopButton
|
||||
{
|
||||
public:
|
||||
ShopButton(const std::shared_ptr<EventBus>& eventBus)
|
||||
: _listener{eventBus}
|
||||
{
|
||||
// We can use lambda or bind your choice
|
||||
_listener.listen<event::Gold>(
|
||||
std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1));
|
||||
// Also we use RAII idiom to handle unlisten
|
||||
}
|
||||
|
||||
bool isEnabled() const
|
||||
{
|
||||
return _isEnabled;
|
||||
}
|
||||
|
||||
private:
|
||||
Listener _listener;
|
||||
bool _isEnabled = false;
|
||||
|
||||
void onGoldUpdated(const event::Gold& event)
|
||||
{
|
||||
_isEnabled = event.value > 0;
|
||||
std::cout << "Shop button is:" << _isEnabled << std::endl; // some kind of logs
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
auto eventBus = std::make_shared<EventBus>();
|
||||
|
||||
Character characterController{eventBus};
|
||||
|
||||
UIWallet wallet{eventBus}; // UIWallet doesn't know anything about character
|
||||
// or even who store gold
|
||||
ShopButton shopButton{eventBus}; // ShopButton doesn't know anything about character
|
||||
{
|
||||
wallet.onEnter();
|
||||
}
|
||||
|
||||
wallet.onDraw();
|
||||
{
|
||||
characterController.kill(Monster::Tux);
|
||||
eventBus->process();
|
||||
}
|
||||
wallet.onDraw();
|
||||
|
||||
// It is easy to test UI eg.
|
||||
eventBus->postpone(event::Gold{1});
|
||||
eventBus->process();
|
||||
assert(shopButton.isEnabled() == true);
|
||||
|
||||
eventBus->postpone(event::Gold{0});
|
||||
eventBus->process();
|
||||
assert(shopButton.isEnabled() == false);
|
||||
|
||||
wallet.onExit();
|
||||
|
||||
return 0;
|
||||
}
|
11
3party/EventBus/use_case/tagged_events/CMakeLists.txt
Normal file
11
3party/EventBus/use_case/tagged_events/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
add_executable(UseCase_TaggedEvents
|
||||
src/Character.cpp src/Character.hpp
|
||||
src/event.hpp
|
||||
src/EventBus.hpp
|
||||
src/main.cpp
|
||||
src/Team.cpp src/Team.hpp
|
||||
src/Gui.cpp src/Gui.hpp)
|
||||
|
||||
target_include_directories(UseCase_TaggedEvents PUBLIC src/)
|
||||
target_link_libraries(UseCase_TaggedEvents PRIVATE Dexode::EventBus)
|
14
3party/EventBus/use_case/tagged_events/README.md
Normal file
14
3party/EventBus/use_case/tagged_events/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Use case: Tagged events
|
||||
|
||||
## Motivation
|
||||
Sometimes we have event producer but at some point we would like
|
||||
to reuse event producer for different configuration. In some cases we
|
||||
could just add some "tag" to event to resolve this issue.
|
||||
|
||||
I would like to resolve this in other way as maybe data producer can't
|
||||
be aware of its "tag". Other issue is spreading "tag" requirement over
|
||||
whole project and maybe we don't want to modify old code.
|
||||
|
||||
It would be nice to just add some abstraction layer which will resolve
|
||||
this issue.
|
||||
|
25
3party/EventBus/use_case/tagged_events/src/Character.cpp
Normal file
25
3party/EventBus/use_case/tagged_events/src/Character.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#include "Character.hpp"
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
Character::Character(std::shared_ptr<EventBus> bus)
|
||||
: _bus{std::move(bus)}
|
||||
{
|
||||
(void)_iq; // probably something should use it ;)
|
||||
}
|
||||
|
||||
void Character::pickGold(int goldCount)
|
||||
{
|
||||
// We could store character name as member. Sure this isn't complex example rather simple one.
|
||||
// Imagine few levels of composition ;)
|
||||
_sackGold += goldCount;
|
||||
_bus->postpone(event::GoldUpdate{_sackGold});
|
||||
}
|
||||
|
||||
void Character::damage(int amount)
|
||||
{
|
||||
_health -= amount;
|
||||
}
|
23
3party/EventBus/use_case/tagged_events/src/Character.hpp
Normal file
23
3party/EventBus/use_case/tagged_events/src/Character.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "EventBus.hpp"
|
||||
|
||||
class Character
|
||||
{
|
||||
public:
|
||||
Character(std::shared_ptr<EventBus> bus);
|
||||
void pickGold(int goldCount);
|
||||
void damage(int amount);
|
||||
|
||||
private:
|
||||
std::shared_ptr<EventBus> _bus;
|
||||
int _sackGold = 0;
|
||||
int _health = 100;
|
||||
int _iq = 200;
|
||||
};
|
9
3party/EventBus/use_case/tagged_events/src/EventBus.hpp
Normal file
9
3party/EventBus/use_case/tagged_events/src/EventBus.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <dexode/EventBus.hpp>
|
||||
|
||||
using EventBus = dexode::EventBus;
|
||||
using Listener = dexode::EventBus::Listener;
|
33
3party/EventBus/use_case/tagged_events/src/Gui.cpp
Normal file
33
3party/EventBus/use_case/tagged_events/src/Gui.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#include "Gui.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
Gui::Gui(const std::shared_ptr<EventBus>& bus)
|
||||
: _listener{bus}
|
||||
{
|
||||
_listener.listen(
|
||||
[this](const event::NewTeamMember& event) { _sackOfGold.emplace(event.memberName, 0); });
|
||||
|
||||
_listener.listen([this](const event::TagEvent<event::GoldUpdate>& event) {
|
||||
auto found = _sackOfGold.find(event.tag);
|
||||
if(found != _sackOfGold.end())
|
||||
{
|
||||
found->second = event.data.goldCount;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Gui::draw()
|
||||
{
|
||||
std::cout << "-----------------------------\n";
|
||||
for(const auto& player : _sackOfGold)
|
||||
{
|
||||
std::cout << "Name:" << player.first << " - gold: " << player.second << "\n";
|
||||
}
|
||||
std::cout << "-----------------------------" << std::endl;
|
||||
}
|
21
3party/EventBus/use_case/tagged_events/src/Gui.hpp
Normal file
21
3party/EventBus/use_case/tagged_events/src/Gui.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "EventBus.hpp"
|
||||
|
||||
class Gui
|
||||
{
|
||||
public:
|
||||
Gui(const std::shared_ptr<EventBus>& bus);
|
||||
|
||||
void draw();
|
||||
|
||||
private:
|
||||
EventBus::Listener _listener;
|
||||
std::map<std::string, int> _sackOfGold;
|
||||
};
|
43
3party/EventBus/use_case/tagged_events/src/Team.cpp
Normal file
43
3party/EventBus/use_case/tagged_events/src/Team.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#include "Team.hpp"
|
||||
|
||||
#include <dexode/eventbus/perk/PassPerk.hpp>
|
||||
#include <dexode/eventbus/perk/PerkEventBus.hpp>
|
||||
#include <dexode/eventbus/perk/TagPerk.hpp>
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
Team::Team(std::shared_ptr<EventBus> bus)
|
||||
: _bus(std::move(bus))
|
||||
{}
|
||||
|
||||
Character& Team::getMember(const std::string& name)
|
||||
{
|
||||
auto found = std::find(_names.begin(), _names.end(), name);
|
||||
if(found == _names.end())
|
||||
{
|
||||
throw std::out_of_range("No such team member: " + name);
|
||||
}
|
||||
return _squad.at(std::distance(_names.begin(), found));
|
||||
}
|
||||
|
||||
void Team::addPlayer(const std::string& name)
|
||||
{
|
||||
auto characterBus = std::make_shared<dexode::eventbus::perk::PerkEventBus>();
|
||||
{
|
||||
auto tagPerk = std::make_unique<dexode::eventbus::perk::TagPerk>(name, _bus.get());
|
||||
tagPerk->wrapTag<event::TagEvent<event::GoldUpdate>>();
|
||||
|
||||
characterBus->addPerk(std::move(tagPerk))
|
||||
.registerPrePostpone(&dexode::eventbus::perk::TagPerk::onPrePostponeEvent);
|
||||
}
|
||||
characterBus->addPerk(std::make_unique<dexode::eventbus::perk::PassEverythingPerk>(_bus))
|
||||
.registerPrePostpone(&dexode::eventbus::perk::PassEverythingPerk::onPrePostponeEvent);
|
||||
|
||||
_squad.emplace_back(characterBus);
|
||||
_names.push_back(name);
|
||||
|
||||
_bus->postpone(event::NewTeamMember{name});
|
||||
}
|
24
3party/EventBus/use_case/tagged_events/src/Team.hpp
Normal file
24
3party/EventBus/use_case/tagged_events/src/Team.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Character.hpp"
|
||||
|
||||
class Team
|
||||
{
|
||||
public:
|
||||
Team(std::shared_ptr<EventBus> bus);
|
||||
void addPlayer(const std::string& name);
|
||||
|
||||
Character& getMember(const std::string& name);
|
||||
|
||||
private:
|
||||
std::vector<std::string> _names;
|
||||
std::vector<Character> _squad;
|
||||
|
||||
std::shared_ptr<EventBus> _bus;
|
||||
};
|
27
3party/EventBus/use_case/tagged_events/src/event.hpp
Normal file
27
3party/EventBus/use_case/tagged_events/src/event.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by gelldur on 22.12.2019.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace event
|
||||
{
|
||||
|
||||
struct GoldUpdate
|
||||
{
|
||||
int goldCount;
|
||||
};
|
||||
|
||||
struct NewTeamMember
|
||||
{
|
||||
std::string memberName;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct TagEvent
|
||||
{
|
||||
using Event = T;
|
||||
std::string tag;
|
||||
Event data;
|
||||
};
|
||||
|
||||
} // namespace event
|
42
3party/EventBus/use_case/tagged_events/src/main.cpp
Normal file
42
3party/EventBus/use_case/tagged_events/src/main.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "EventBus.hpp"
|
||||
#include "Gui.hpp"
|
||||
#include "Team.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
auto eventBus = std::make_shared<EventBus>();
|
||||
|
||||
Gui gui{eventBus};
|
||||
Team myTeam{eventBus};
|
||||
|
||||
auto updateFrame = [&]() {
|
||||
// single frame update ;)
|
||||
eventBus->process();
|
||||
gui.draw();
|
||||
std::cout << "###################################################\n";
|
||||
std::cout << "###################################################" << std::endl;
|
||||
};
|
||||
|
||||
{ // single update
|
||||
myTeam.addPlayer("Gelldur");
|
||||
updateFrame();
|
||||
}
|
||||
|
||||
{ // single update
|
||||
myTeam.getMember("Gelldur").pickGold(100);
|
||||
updateFrame();
|
||||
}
|
||||
|
||||
{ // single update
|
||||
myTeam.addPlayer("Gosia");
|
||||
myTeam.addPlayer("Dexter");
|
||||
updateFrame();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user