Reorganize project structure

commit f7dd4172cf535cf52601a8819cf5c8bfabcd1fe4
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 31 10:26:27 2018 +0200

    Improve Travis script

    Fixed after reordering project structure

commit d054e5c91762da15defa458404e355d7c670e301
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Mon Jul 30 15:11:21 2018 +0200

    Update Travis CI for linux and OSX

commit 63395f5a7e3dd9f2a52b2d6a254da89ec1d6e5e9
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Fri Jul 27 14:38:40 2018 +0200

    Secure EventBus from wrong usage

    For example user previously could do such thing:

    bus.listen<const MyEvent>(...)

    bus.listen<MyEvent>(...)

    Those we 2 different events :/

commit f9195316d3ba6313ee425e3194b65b32fe52d641
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Thu Jul 26 12:44:17 2018 +0200

    Update for better managing Debug/Release

    Updated project for easy switch between debug/release versions of library

    Thanks to that we can do only find_package and don't have to care about if's switching between
    debug/release

    Thanks: https://github.com/forexample/package-example

commit 7d708959d9e96176875ca882f0f69a72622added
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Wed Jul 25 13:45:55 2018 +0200

    Update clang-format style

commit beb1d3b863379490f321e43f3e42ab272954ea67
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 16:05:16 2018 +0200

    Add some docs

commit ca450dfeee2d4bc604bbb9bf0599f373c21a4173
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 15:49:52 2018 +0200

    Remove not needed includes

commit 6473b80e8e60408675bcc4adc88653390576c4bd
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 15:41:57 2018 +0200

    Code format

commit 8abb56e1dd4b71df2b05bb34bef0530567e4ff2e
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 15:41:40 2018 +0200

    Improve performance

    Thanks to that we don't need RTTI

commit 1feacbb1f9ae6a5ac2209a6dc1df5c868ead8fd4
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 15:14:16 2018 +0200

    Remove trash

commit b5dc5c05589b969dd61eb65b68e4cdce69c5a5fb
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 15:00:15 2018 +0200

    Fix include path

commit 9939fd09805191f0bdada6cb85193a291d519116
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 14:51:02 2018 +0200

    Update install lib

commit 9eaa09f9ec5a29045b03ffc7632878863a2b2b9b
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 14:50:47 2018 +0200

    Fix

commit 7a5b3323af0b728f7e511ac22ff5027c6d06402e
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 14:16:12 2018 +0200

    Update README

commit beb6599ee4385fdffc747dc866db46e160be1358
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 14:16:05 2018 +0200

    Add performance compare to Poco::NotifactionCenter

commit 1d25b997580a9ee09c9db86135b4ca9e1b1a10c6
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 14:14:08 2018 +0200

    Update clang-format

commit 4f4cb4a7e8a849c067a42085eb3e76c3df894bc7
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 13:04:30 2018 +0200

    Remove bad flag

    It is only working for GCC

commit 66a7945084607f94d9d0c803008398e8d281fd06
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 13:03:15 2018 +0200

    Remove deprecated stuff

    It was breaking encapsulation

commit 1e7500607b42bff3632250f623888b95a503dfd0
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 13:01:37 2018 +0200

    Update sample

commit 11a146bb9145fa55f9b9a39a9e033387007a7151
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 13:01:27 2018 +0200

    Add clang-format rules

commit 685562c632d9751f50a2f05b92ef9ebf53a5d6e0
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 12:47:10 2018 +0200

    Reorganize project layout

    Inspired by: https://www.youtube.com/watch?v=6sWec7b0JIc

commit 40d1d6487814730533d7dd7cbedbaf2b4e34ef19
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 12:45:55 2018 +0200

    Remove Catch2 submodule

    Switch to own dependency

commit ca21df04f392adcb027a5b4f25ffac085b51f48c
Author: Dawid Drozd <dawid.drozd@mobica.com>
Date:   Tue Jul 24 11:28:51 2018 +0200

    Remove old code
This commit is contained in:
Dawid Drozd 2018-07-31 11:33:35 +02:00
parent 7ae52d926d
commit 3688c145cb
21 changed files with 834 additions and 759 deletions

127
.clang-format Normal file
View File

@ -0,0 +1,127 @@
# 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 6.0.1
# Common settings
BasedOnStyle: WebKit
TabWidth: 4
IndentWidth: 4
UseTab: Always
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: Cpp11
AccessModifierOffset: -4
AlignAfterOpenBracket: true
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
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
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IndentCaseLabels: false
FixNamespaceComments: true
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
JavaScriptQuotes: Double
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: Never
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceAfterTemplateKeyword: true
SortUsingDeclarations: true
SortIncludes: true
# Comments are for developers, they should arrange them
ReflowComments: false
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash
---

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "test/Catch"]
path = test/Catch
url = https://github.com/philsquared/Catch.git
[submodule "performance/benchmark"] [submodule "performance/benchmark"]
path = performance/benchmark path = performance/benchmark
url = https://github.com/google/benchmark.git url = https://github.com/google/benchmark.git

View File

@ -1,37 +1,54 @@
os: linux
dist: trusty
sudo: require
language: cpp language: cpp
sudo: false
addons: common_sources: &all_sources
apt: - ubuntu-toolchain-r-test
sources: - llvm-toolchain-trusty
- llvm-toolchain-precise-3.9 - llvm-toolchain-trusty-3.9
- ubuntu-toolchain-r-test - llvm-toolchain-trusty-4.0
packages: - llvm-toolchain-trusty-5.0
- clang-3.9 - llvm-toolchain-trusty-6.0
- gcc-6
- g++-6
matrix: matrix:
include: exclude: # On OSX g++ is a symlink to clang++ by default
- compiler: gcc - os: osx
env: COMPILER=g++-6 compiler: gcc
- compiler: clang include:
env: COMPILER=clang++-3.9 - os: linux
compiler: gcc
addons:
apt:
sources: *all_sources
packages: ['gcc-7', 'g++-7', 'cmake']
env: COMPILER='g++-7'
- os: linux
compiler: clang
addons:
apt:
sources: *all_sources
packages: ['clang-6.0', 'libstdc++-6-dev', 'cmake']
env: COMPILER='clang++-6.0'
- os: osx
osx_image: xcode9.4
compiler: clang
env: COMPILER='clang++'
before_install: before_install:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi # Install catch 2 dependency
- wget https://github.com/catchorg/Catch2/archive/v2.2.3.zip
before_script: - unzip v2.2.3.zip && cd Catch2-2.2.3 && mkdir -p build/ && cd build/
- if [ "$COMPILER" = "g++-6" ]; then export CXX="g++-6" CC="gcc-6"; fi - cmake -DCMAKE_BUILD_TYPE=Release .. -DCMAKE_INSTALL_PREFIX=~/.local/ && cmake --build . --target install
- if [ "$COMPILER" = "clang++-3.9" ]; then export CXX="clang++-3.9" CC="clang-3.9"; fi - cd ../..
script: script:
- mkdir build-debug && cd build-debug && cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=YES .. && cmake --build . #Build & Install library
- ./test/EventBusTest # Run tests - (mkdir -p lib/build-debug/ && cd lib/build-debug && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . --target install)
- cd .. # exit from build-debug - (mkdir -p lib/build-release/ && cd lib/build-release && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . --target install)
- mkdir build-release && cd build-release && cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=YES .. && cmake --build . # Run tests
- ./test/EventBusTest # Run tests - (mkdir -p test/build-debug/ && cd test/build-debug && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . && ctest -V .)
- (mkdir -p test/build-release/ && cd test/build-release && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local/ .. && cmake --build . && ctest -V .)

View File

@ -1,66 +1,20 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.2 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
OPTION(PERFORMANCE "Enable/Disable performance subdirectory" OFF) # Layout of project is inspired by: https://youtu.be/6sWec7b0JIc?t=20m50s
# This top level CMakeLists should be used for development
# BUILD_SHARED_LIBS can controll build type! project(EventBusDev)
PROJECT(EventBus
VERSION 2.2.0
LANGUAGES CXX
)
ADD_LIBRARY(EventBus add_subdirectory(lib/)
src/eventbus/EventCollector.cpp include/eventbus/EventCollector.h
include/eventbus/EventBus.h
)
ADD_LIBRARY(Dexode::EventBus ALIAS EventBus)
TARGET_INCLUDE_DIRECTORIES(EventBus PUBLIC enable_testing()
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/> add_subdirectory(test/)
$<INSTALL_INTERFACE:include/> add_subdirectory(sample/)
PRIVATE src/ add_subdirectory(performance/)
)
TARGET_COMPILE_OPTIONS(EventBus PRIVATE target_compile_options(EventBus PUBLIC
-Wall -pedantic -Wall -pedantic
-Wnon-virtual-dtor -Wnon-virtual-dtor
-Werror -Werror
-Wno-error=deprecated-declarations -Wno-error=deprecated-declarations
) )
IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
TARGET_COMPILE_OPTIONS(EventBus PRIVATE
-Wno-error=unknown-argument
-Wno-error=unused-command-line-argument
)
ENDIF()
SET_TARGET_PROPERTIES(EventBus PROPERTIES
CXX_STANDARD 14
)
IF(NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
TARGET_COMPILE_FEATURES(EventBus
PUBLIC cxx_auto_type
)
ENDIF()
INSTALL(TARGETS EventBus EXPORT EventBusConfig
ARCHIVE DESTINATION lib/
LIBRARY DESTINATION lib/
RUNTIME DESTINATION bin/
INCLUDES DESTINATION include/
)
INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION include/ FILES_MATCHING PATTERN "*.h*")
INSTALL(EXPORT EventBusConfig
DESTINATION cmake/
NAMESPACE Dexode::
)
EXPORT(TARGETS EventBus FILE EventBusConfig.cmake)
IF(BUILD_TESTING)
ADD_SUBDIRECTORY(test/)
ENDIF()
IF(PERFORMANCE)
ADD_SUBDIRECTORY(performance/)
ENDIF()

View File

@ -180,8 +180,12 @@ TARGET_LINK_LIBRARIES(MyExecutable PUBLIC Dexode::EventBus)
Also if you want you can install library and add it at any way you want. Also if you want you can install library and add it at any way you want.
Eg. Eg.
```commandline ```commandline
mkdir Release && cd Release mkdir -p lib/build/
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./install .. cd lib/build
cmake -DCMAKE_BUILD_TYPE=Relase -DCMAKE_INSTALL_PREFIX=~/.local/ ..
cmake --build . --target install
# OR
make && make install make && make install
``` ```
@ -205,6 +209,7 @@ checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter 127388 ns
- Thread safe EventBus ? - Thread safe EventBus ?
- Verbose messages for easy debugging ? - Verbose messages for easy debugging ?
- Generating graph flow ? - Generating graph flow ?
- Add nice documentation [like in POCO](https://pocoproject.org/slides/090-NotificationsEvents.pdf)
- ... - ...
# Issues ? # Issues ?
@ -217,6 +222,11 @@ Please report here issue / question / whatever in 99% I will answer ;)
- [swietlana](https://github.com/swietlana) for english correction and support ;) - [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 - [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 # License
EventBus source code can be used according to the **Apache License, Version 2.0**. EventBus source code can be used according to the **Apache License, Version 2.0**.

104
lib/CMakeLists.txt Normal file
View File

@ -0,0 +1,104 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
# BUILD_SHARED_LIBS can controll build type!
project(EventBus
VERSION 2.2.0
LANGUAGES CXX
)
# Dependencies
# No dependencies for EventBus yay!
# Introduce variables:
# * CMAKE_INSTALL_LIBDIR
# * CMAKE_INSTALL_BINDIR
# * CMAKE_INSTALL_INCLUDEDIR
include(GNUInstallDirs)
# 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}")
# Library definition
add_library(EventBus
src/EventCollector.cpp include/eventbus/EventCollector.h
include/eventbus/EventBus.h
)
add_library(Dexode::EventBus ALIAS EventBus)
target_compile_features(EventBus PUBLIC cxx_std_14)
target_include_directories(EventBus PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:include/>
PRIVATE lib/src/
)
# 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}"
)
# Export headers
install(DIRECTORY include/eventbus
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}"
)
# Config
# * <prefix>/lib/cmake/EventBus/EventBusTargets.cmake
install(EXPORT "${TARGETS_EXPORT_NAME}"
DESTINATION "${config_install_dir}"
NAMESPACE "${namespace}"
)

View File

@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
check_required_components("@PROJECT_NAME@")

View File

@ -5,15 +5,28 @@
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string>
#include <typeinfo>
#include <vector> #include <vector>
namespace Dexode namespace Dexode
{ {
template <typename>
void type_id() // Helper for getting "type id"
{}
using type_id_t = void (*)(); // Function pointer
class EventBus class EventBus
{ {
template <class Event>
constexpr void 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");
}
public: public:
EventBus() = default; EventBus() = default;
@ -35,9 +48,11 @@ public:
* @param callback - your callback to handle event * @param callback - your callback to handle event
* @return token used for unlisten * @return token used for unlisten
*/ */
template<typename Event> template <typename Event>
int listen(const std::function<void(const Event&)>& callback) int listen(const std::function<void(const Event&)>& callback)
{ {
validateEvent<Event>();
const int token = ++_tokener; const int token = ++_tokener;
listen<Event>(token, callback); listen<Event>(token, callback);
return token; return token;
@ -48,15 +63,17 @@ public:
* @param token - unique token for identification receiver. Simply pass token from @see EventBus::listen * @param token - unique token for identification receiver. Simply pass token from @see EventBus::listen
* @param callback - your callback to handle event * @param callback - your callback to handle event
*/ */
template<typename Event> template <typename Event>
void listen(const int token, const std::function<void(const Event&)>& callback) void listen(const int token, const std::function<void(const Event&)>& callback)
{ {
validateEvent<Event>();
using Vector = VectorImpl<Event>; using Vector = VectorImpl<Event>;
assert(callback && "callback should be valid");//Check for valid object assert(callback && "callback should be valid"); //Check for valid object
std::unique_ptr<VectorInterface>& vector = _callbacks[getTypeId<Event>()]; std::unique_ptr<VectorInterface>& vector = _callbacks[type_id<Event>];
if (vector == nullptr) if(vector == nullptr)
{ {
vector.reset(new Vector{}); vector.reset(new Vector{});
} }
@ -70,7 +87,7 @@ public:
*/ */
void unlistenAll(const int token) void unlistenAll(const int token)
{ {
for (auto& element : _callbacks) for(auto& element : _callbacks)
{ {
element.second->remove(token); element.second->remove(token);
} }
@ -80,11 +97,13 @@ public:
* @tparam Event - type you want to unlisten. @see Notiier::listen * @tparam Event - type you want to unlisten. @see Notiier::listen
* @param token - token from EventBus::listen * @param token - token from EventBus::listen
*/ */
template<typename Event> template <typename Event>
void unlisten(const int token) void unlisten(const int token)
{ {
auto found = _callbacks.find(getTypeId<Event>()); validateEvent<Event>();
if (found != _callbacks.end())
auto found = _callbacks.find(type_id<Event>);
if(found != _callbacks.end())
{ {
found->second->remove(token); found->second->remove(token);
} }
@ -95,15 +114,17 @@ public:
* *
* @param event your event struct * @param event your event struct
*/ */
template<typename Event> template <typename Event>
void notify(const Event& event) void notify(const Event& event)
{ {
using Vector = VectorImpl<Event>; using CleanEventType = typename std::remove_const<Event>::type;
const auto typeId = getTypeId<Event>();//TODO think about constexpr validateEvent<CleanEventType>();
auto found = _callbacks.find(typeId);
if (found == _callbacks.end()) using Vector = VectorImpl<CleanEventType>;
auto found = _callbacks.find(type_id<CleanEventType>);
if(found == _callbacks.end())
{ {
return;// no such notifications return; // no such notifications
} }
std::unique_ptr<VectorInterface>& vector = found->second; std::unique_ptr<VectorInterface>& vector = found->second;
@ -111,7 +132,7 @@ public:
Vector* vectorImpl = static_cast<Vector*>(vector.get()); Vector* vectorImpl = static_cast<Vector*>(vector.get());
vectorImpl->beginTransaction(); vectorImpl->beginTransaction();
for (const auto& element : vectorImpl->container) for(const auto& element : vectorImpl->container)
{ {
element.second(event); element.second(event);
} }
@ -126,7 +147,7 @@ private:
virtual void remove(const int token) = 0; virtual void remove(const int token) = 0;
}; };
template<typename Event> template <typename Event>
struct VectorImpl : public VectorInterface struct VectorImpl : public VectorInterface
{ {
using CallbackType = std::function<void(const Event&)>; using CallbackType = std::function<void(const Event&)>;
@ -139,19 +160,18 @@ private:
virtual void remove(const int token) override virtual void remove(const int token) override
{ {
if (inTransaction > 0) if(inTransaction > 0)
{ {
toRemove.push_back(token); toRemove.push_back(token);
return; return;
} }
//Invalidation rules: https://stackoverflow.com/questions/6438086/iterator-invalidation-rules //Invalidation rules: https://stackoverflow.com/questions/6438086/iterator-invalidation-rules
auto removeFrom = std::remove_if(container.begin(), container.end() auto removeFrom = std::remove_if(
, [token](const ContainerElement& element) container.begin(), container.end(), [token](const ContainerElement& element) {
{ return element.first == token;
return element.first == token; });
}); if(removeFrom != container.end())
if (removeFrom != container.end())
{ {
container.erase(removeFrom, container.end()); container.erase(removeFrom, container.end());
} }
@ -159,7 +179,7 @@ private:
void add(const int token, const CallbackType& callback) void add(const int token, const CallbackType& callback)
{ {
if (inTransaction > 0) if(inTransaction > 0)
{ {
toAdd.emplace_back(token, callback); toAdd.emplace_back(token, callback);
} }
@ -177,20 +197,20 @@ private:
void commitTransaction() void commitTransaction()
{ {
--inTransaction; --inTransaction;
if (inTransaction > 0) if(inTransaction > 0)
{ {
return; return;
} }
inTransaction = 0; inTransaction = 0;
if (toAdd.empty() == false) if(toAdd.empty() == false)
{ {
container.insert(container.end(), toAdd.begin(), toAdd.end()); container.insert(container.end(), toAdd.begin(), toAdd.end());
toAdd.clear(); toAdd.clear();
} }
if (toRemove.empty() == false) if(toRemove.empty() == false)
{ {
for (auto token : toRemove) for(auto token : toRemove)
{ {
remove(token); remove(token);
} }
@ -200,14 +220,7 @@ private:
}; };
int _tokener = 0; int _tokener = 0;
std::map<std::size_t, std::unique_ptr<VectorInterface>> _callbacks; std::map<type_id_t, std::unique_ptr<VectorInterface>> _callbacks;
template<typename T>
static std::size_t getTypeId()
{
//std::hash<std::string>{}(typeid(T).name() is slower
return typeid(T).hash_code();
}
}; };
} /* namespace Dexode */ } /* namespace Dexode */

View File

@ -11,36 +11,6 @@
namespace Dexode namespace Dexode
{ {
class [[deprecated("This class will be removed, is breaking API")]] BusAttorney
{
public:
BusAttorney(std::shared_ptr<EventBus> bus)
: _bus(std::move(bus))
{
}
/**
* Notify all listeners for event
*
* @param event your event struct
*/
template<typename Event>
[[deprecated]]
void notify(const Event& event) const
{
_bus->notify(event);
}
[[deprecated]]
std::shared_ptr<EventBus> extract() const
{
return _bus;
}
private:
std::shared_ptr<EventBus> _bus;
};
class EventCollector class EventCollector
{ {
public: public:
@ -93,10 +63,6 @@ public:
bool isUsing(const std::shared_ptr<EventBus>& bus) const; bool isUsing(const std::shared_ptr<EventBus>& bus) const;
///I wan't explicitly say getBus. Ok we could add method for notify but this is more explicit
[[deprecated("This method will be removed, is breaking encapsulation")]]
BusAttorney getBus() const;
private: private:
int _token = 0; int _token = 0;
std::shared_ptr<EventBus> _bus; std::shared_ptr<EventBus> _bus;

View File

@ -49,6 +49,7 @@ EventCollector::~EventCollector()
EventCollector& EventCollector::operator=(const EventCollector& other) EventCollector& EventCollector::operator=(const EventCollector& other)
{ {
std::unique_ptr<int> vector3;
if (this == &other) if (this == &other)
{ {
return *this; return *this;
@ -86,11 +87,6 @@ void EventCollector::unlistenAll()
} }
} }
BusAttorney EventCollector::getBus() const
{
return BusAttorney{_bus};
}
bool EventCollector::isUsing(const std::shared_ptr<EventBus>& bus) const bool EventCollector::isUsing(const std::shared_ptr<EventBus>& bus) const
{ {
return _bus == bus; return _bus == bus;

View File

@ -1,17 +1,26 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
# http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html # http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html
# Thanks for CATCH! # Thanks for CATCH!
ADD_SUBDIRECTORY(benchmark/) find_package(Poco COMPONENTS Foundation Util)
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 # If you want to compare with CCNotificationCenter read about it in README and uncomment line below
#INCLUDE(cocos2d-x-compare/Cocos2dxCompare.cmake) #INCLUDE(cocos2d-x-compare/Cocos2dxCompare.cmake)
ADD_EXECUTABLE(EventBusPerformance add_executable(EventBusPerformance
eventbus/EventBusPerformance.cpp src/EventBusPerformance.cpp
${CCNOTIFICATION_CENTER_SRC} ${CCNOTIFICATION_CENTER_SRC}
$<$<BOOL:${Poco_FOUND}>:src/PocoNotificationCenterPerformance.cpp>
) )
TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC target_compile_options(EventBusPerformance PUBLIC
-Wall -pedantic -Wall -pedantic
-Wno-unused-private-field -Wno-unused-private-field
-Wnon-virtual-dtor -Wnon-virtual-dtor
@ -19,27 +28,22 @@ TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC
-Werror -Werror
) )
SET(EVENTBUS_DEBUG_FLAGS set(EVENTBUS_DEBUG_FLAGS
-O0 -fno-inline -O0 -fno-inline
-DDEBUG -DDEBUG
-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
) )
SET(EVENTBUS_RELEASE_FLAGS
-DNDEBUG
)
TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>") target_compile_options(EventBusPerformance PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>")
TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC "$<$<CONFIG:RELEASE>:${EVENTBUS_RELEASE_FLAGS}>")
SET_TARGET_PROPERTIES(EventBusPerformance PROPERTIES target_include_directories(EventBusPerformance PUBLIC
CXX_STANDARD 14 src/
CXX_STANDARD_REQUIRED YES
)
TARGET_INCLUDE_DIRECTORIES(EventBusPerformance PUBLIC
./
${CCNOTIFICATION_CENTER_INCLUDE} ${CCNOTIFICATION_CENTER_INCLUDE}
) )
TARGET_LINK_LIBRARIES(EventBusPerformance PUBLIC Dexode::EventBus benchmark) target_link_libraries(EventBusPerformance PUBLIC
Dexode::EventBus
benchmark
$<$<BOOL:${Poco_FOUND}>:Poco::Foundation>
$<$<BOOL:${Poco_FOUND}>:Poco::Util>
)

View File

@ -1,284 +0,0 @@
//
// Created by Dawid Drozd aka Gelldur on 05.08.17.
//
#include <random>
#include <benchmark/benchmark.h>
#include <eventbus/EventBus.h>
namespace
{
void checkNListeners(benchmark::State& state, const int listenersCount)
{
Dexode::EventBus bus;
int sum = 0;
struct SimpleEvent
{
int value;
};
for (int i = 0; i < listenersCount; ++i)
{
bus.listen<SimpleEvent>([&](const SimpleEvent& event)
{
benchmark::DoNotOptimize(sum += event.value * 2);
});
}
const auto event = SimpleEvent{2};
while (state.KeepRunning())//Performance area!
{
bus.notify(event);
}
state.counters["sum"] = sum;
}
void checkSimpleNotification(benchmark::State& state)
{
checkNListeners(state, 1);
}
void check10Listeners(benchmark::State& state)
{
checkNListeners(state, 10);
}
void check100Listeners(benchmark::State& state)
{
checkNListeners(state, 100);
}
void check1kListeners(benchmark::State& state)
{
checkNListeners(state, 1000);
}
void call1kLambdas_compare(benchmark::State& state)
{
int sum = 0;
std::vector<std::function<void(int)>> callbacks;
callbacks.reserve(1000);
for (int i = 0; i < 1000; ++i)
{
callbacks.emplace_back([&](int value)
{
benchmark::DoNotOptimize(sum += value * 2);
});
}
while (state.KeepRunning())//Performance area!
{
for (int i = 0; i < 1000; ++i)
// for (auto& callback :callbacks)
{
callbacks[i](2);
}
}
state.counters["sum"] = sum;
}
template<int N>
struct SimpleEvent
{
int value = N;
};
void checkNNotificationsForNListeners(benchmark::State& state, const int notificationsCount, const int listenersCount)
{
std::mt19937 generator(311281);
std::uniform_int_distribution<int> uniformDistribution(0, notificationsCount - 1);
Dexode::EventBus bus;
int sum = 0;
for (int i = 0; i < listenersCount; ++i)//We register M listeners for N notifications using uniform distribution
{
const auto which = uniformDistribution(generator);
//We generate here N different notifications
switch (which)
{
case 0:
bus.listen<SimpleEvent<0>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 1:
bus.listen<SimpleEvent<1>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 2:
bus.listen<SimpleEvent<2>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 3:
bus.listen<SimpleEvent<3>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 4:
bus.listen<SimpleEvent<4>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 5:
bus.listen<SimpleEvent<5>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 6:
bus.listen<SimpleEvent<6>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 7:
bus.listen<SimpleEvent<7>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 8:
bus.listen<SimpleEvent<8>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
case 9:
bus.listen<SimpleEvent<9>>([&](const auto& event)
{
benchmark::DoNotOptimize(sum += event.value *
2);//we use it to prevent some? optimizations
});
break;
default:
assert(false);
}
}
while (state.KeepRunning())//Performance area!
{
//Pick random notification
const auto which = uniformDistribution(generator);
const auto number = uniformDistribution(generator);
//We generate here N different notifications
switch (which)
{
case 0:
bus.notify(SimpleEvent<0>{number});
break;
case 1:
bus.notify(SimpleEvent<1>{number});
break;
case 2:
bus.notify(SimpleEvent<2>{number});
break;
case 3:
bus.notify(SimpleEvent<3>{number});
break;
case 4:
bus.notify(SimpleEvent<4>{number});
break;
case 5:
bus.notify(SimpleEvent<5>{number});
break;
case 6:
bus.notify(SimpleEvent<6>{number});
break;
case 7:
bus.notify(SimpleEvent<7>{number});
break;
case 8:
bus.notify(SimpleEvent<8>{number});
break;
case 9:
bus.notify(SimpleEvent<9>{number});
break;
default:
assert(false);
}
}
state.counters["sum"] = sum;
}
void check10NotificationsFor1kListeners(benchmark::State& state)
{
checkNNotificationsForNListeners(state, 10, 1000);
}
//Sorry not available
//void check100NotificationsFor1kListeners(benchmark::State& state)
//{
// checkNNotificationsForNListeners(state, 100, 1000);
//}
//
//void check1kNotificationsFor1kListeners(benchmark::State& state)
//{
// checkNNotificationsForNListeners(state, 1000, 1000);
//}
//
//void check100NotificationsFor10kListeners(benchmark::State& state)
//{
// checkNNotificationsForNListeners(state, 100, 10000);
//}
void checkNotifyFor10kListenersWhenNoOneListens(benchmark::State& state)
{
Dexode::EventBus bus;
int sum = 0;
struct SimpleEvent
{
int value;
};
struct UnknownEvent
{
int value;
};
for (int i = 0; i < 10000; ++i)
{
bus.listen<SimpleEvent>([&](const SimpleEvent& event)
{
benchmark::DoNotOptimize(sum += event.value * 2);
});
}
const auto unknownEvent = UnknownEvent{2};
while (state.KeepRunning())//Performance area!
{
bus.notify(unknownEvent);
}
state.counters["sum"] = sum;
}
}
BENCHMARK(call1kLambdas_compare);
BENCHMARK(checkSimpleNotification);
BENCHMARK(check10Listeners);
BENCHMARK(check100Listeners);
BENCHMARK(check1kListeners);
BENCHMARK(check10NotificationsFor1kListeners);
//BENCHMARK(check100NotificationsFor1kListeners); //Not available
//BENCHMARK(check1kNotificationsFor1kListeners); //Not available
//BENCHMARK(check100NotificationsFor10kListeners); //Not available
BENCHMARK(checkNotifyFor10kListenersWhenNoOneListens);
BENCHMARK_MAIN()

View File

@ -0,0 +1,269 @@
//
// Created by Dawid Drozd aka Gelldur on 05.08.17.
//
#include <random>
#include <benchmark/benchmark.h>
#include <eventbus/EventBus.h>
namespace
{
void checkNListeners(benchmark::State& state, const int listenersCount)
{
Dexode::EventBus bus;
int sum = 0;
struct SimpleEvent
{
int value;
};
for(int i = 0; i < listenersCount; ++i)
{
bus.listen<SimpleEvent>(
[&](const SimpleEvent& event) { benchmark::DoNotOptimize(sum += event.value * 2); });
}
while(state.KeepRunning()) // Performance area!
{
const auto event = SimpleEvent{2};
bus.notify(event);
}
state.counters["sum"] = sum;
}
void checkSimpleNotification(benchmark::State& state)
{
checkNListeners(state, 1);
}
void check10Listeners(benchmark::State& state)
{
checkNListeners(state, 10);
}
void check100Listeners(benchmark::State& state)
{
checkNListeners(state, 100);
}
void check1kListeners(benchmark::State& state)
{
checkNListeners(state, 1000);
}
void call1kLambdas_compare(benchmark::State& state)
{
int sum = 0;
std::vector<std::function<void(int)>> callbacks;
callbacks.reserve(1000);
for(int i = 0; i < 1000; ++i)
{
callbacks.emplace_back([&](int value) { benchmark::DoNotOptimize(sum += value * 2); });
}
while(state.KeepRunning()) // Performance area!
{
for(int i = 0; i < 1000; ++i)
// for (auto& callback :callbacks)
{
callbacks[i](2);
}
}
state.counters["sum"] = sum;
}
template <int N>
struct SimpleEvent
{
int value = N;
};
void checkNNotificationsForNListeners(benchmark::State& state,
const int notificationsCount,
const int listenersCount)
{
std::mt19937 generator(311281);
std::uniform_int_distribution<int> uniformDistribution(0, notificationsCount - 1);
Dexode::EventBus bus;
int sum = 0;
for(int i = 0; i < listenersCount;
++i) // We register M listeners for N notifications using uniform distribution
{
const auto which = uniformDistribution(generator);
// We generate here N different notifications
switch(which)
{
case 0:
bus.listen<SimpleEvent<0>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 1:
bus.listen<SimpleEvent<1>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 2:
bus.listen<SimpleEvent<2>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 3:
bus.listen<SimpleEvent<3>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 4:
bus.listen<SimpleEvent<4>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 5:
bus.listen<SimpleEvent<5>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 6:
bus.listen<SimpleEvent<6>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 7:
bus.listen<SimpleEvent<7>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 8:
bus.listen<SimpleEvent<8>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
case 9:
bus.listen<SimpleEvent<9>>([&](const auto& event) {
benchmark::DoNotOptimize(sum += event.value *
2); // we use it to prevent some? optimizations
});
break;
default:
assert(false);
}
}
while(state.KeepRunning()) // Performance area!
{
// Pick random notification
const auto which = uniformDistribution(generator);
const auto number = uniformDistribution(generator);
// We generate here N different notifications
switch(which)
{
case 0:
bus.notify(SimpleEvent<0>{number});
break;
case 1:
bus.notify(SimpleEvent<1>{number});
break;
case 2:
bus.notify(SimpleEvent<2>{number});
break;
case 3:
bus.notify(SimpleEvent<3>{number});
break;
case 4:
bus.notify(SimpleEvent<4>{number});
break;
case 5:
bus.notify(SimpleEvent<5>{number});
break;
case 6:
bus.notify(SimpleEvent<6>{number});
break;
case 7:
bus.notify(SimpleEvent<7>{number});
break;
case 8:
bus.notify(SimpleEvent<8>{number});
break;
case 9:
bus.notify(SimpleEvent<9>{number});
break;
default:
assert(false);
}
}
state.counters["sum"] = sum;
}
void check10NotificationsFor1kListeners(benchmark::State& state)
{
checkNNotificationsForNListeners(state, 10, 1000);
}
// Sorry not available
// void check100NotificationsFor1kListeners(benchmark::State& state)
//{
// checkNNotificationsForNListeners(state, 100, 1000);
//}
//
// void check1kNotificationsFor1kListeners(benchmark::State& state)
//{
// checkNNotificationsForNListeners(state, 1000, 1000);
//}
//
// void check100NotificationsFor10kListeners(benchmark::State& state)
//{
// checkNNotificationsForNListeners(state, 100, 10000);
//}
void checkNotifyFor10kListenersWhenNoOneListens(benchmark::State& state)
{
Dexode::EventBus bus;
int sum = 0;
struct SimpleEvent
{
int value;
};
struct UnknownEvent
{
int value;
};
for(int i = 0; i < 10000; ++i)
{
bus.listen<SimpleEvent>(
[&](const SimpleEvent& event) { benchmark::DoNotOptimize(sum += event.value * 2); });
}
const auto unknownEvent = UnknownEvent{2};
while(state.KeepRunning()) // Performance area!
{
bus.notify(unknownEvent);
}
state.counters["sum"] = sum;
}
}
BENCHMARK(call1kLambdas_compare);
BENCHMARK(checkSimpleNotification);
BENCHMARK(check10Listeners);
BENCHMARK(check100Listeners);
BENCHMARK(check1kListeners);
BENCHMARK(check10NotificationsFor1kListeners);
// BENCHMARK(check100NotificationsFor1kListeners); //Not available
// BENCHMARK(check1kNotificationsFor1kListeners); //Not available
// BENCHMARK(check100NotificationsFor10kListeners); //Not available
BENCHMARK(checkNotifyFor10kListenersWhenNoOneListens);
BENCHMARK_MAIN()

View File

@ -0,0 +1,101 @@
//
// Created by Dawid Drozd aka Gelldur on 24.07.18.
//
#include <functional>
#include <random>
#include <benchmark/benchmark.h>
#include <Poco/NObserver.h>
#include <Poco/Notification.h>
#include <Poco/NotificationCenter.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);
}

View File

@ -1,23 +1,13 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.2 FATAL_ERROR) cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
PROJECT(Sample LANGUAGES CXX) project(Sample LANGUAGES CXX)
ADD_EXECUTABLE(Sample add_executable(Sample
src/main.cpp src/main.cpp
) )
TARGET_COMPILE_OPTIONS(Sample PUBLIC if(NOT TARGET Dexode::EventBus)
-Wall -pedantic find_package(EventBus CONFIG REQUIRED)
-Wno-unused-private-field endif()
-Wnon-virtual-dtor
-Wno-gnu
-Werror
)
SET_TARGET_PROPERTIES(Sample PROPERTIES target_link_libraries(Sample PRIVATE Dexode::EventBus)
CXX_STANDARD 14
)
ADD_SUBDIRECTORY(../ EventBus/)
TARGET_LINK_LIBRARIES(Sample PRIVATE Dexode::EventBus)

View File

@ -2,45 +2,48 @@
* @brief Sample code to play with! * @brief Sample code to play with!
*/ */
#include <memory>
#include <iostream>
#include <string>
#include <cassert> #include <cassert>
#include <iostream>
#include <memory>
#include <string>
#include <eventbus/EventBus.h> #include <eventbus/EventBus.h>
#include <eventbus/EventCollector.h> #include <eventbus/EventCollector.h>
namespace Event //Example namespace for events namespace Event // Example namespace for events
{ {
struct Gold // Event that will be proceed when our gold changes
struct Gold { int value = 0; }; //Event that will be proceed when our gold changes {
int value = 0;
};
} }
enum class Monster enum class Monster
{ {
Frog, Tux, Shark Frog,
Tux,
Shark
}; };
class CharacterController // some kind of character controller class Character
{ {
public: public:
CharacterController(const std::shared_ptr<Dexode::EventBus>& eventBus) Character(const std::shared_ptr<Dexode::EventBus>& eventBus)
: _bus{eventBus} : _bus{eventBus}
{ {
} }
void kill(Monster monsterType) void kill(Monster monsterType)
{ {
if (Monster::Frog == monsterType) if(Monster::Frog == monsterType)
{ {
_gold += 1; _gold += 1;
} }
else if (Monster::Tux == monsterType) else if(Monster::Tux == monsterType)
{ {
_gold += 100; _gold += 100;
} }
else if (Monster::Shark == monsterType) else if(Monster::Shark == monsterType)
{ {
_gold += 25; _gold += 25;
} }
@ -48,7 +51,7 @@ public:
} }
private: private:
int _gold = 0;//Controller stores how much gold we have int _gold = 0;
std::shared_ptr<Dexode::EventBus> _bus; std::shared_ptr<Dexode::EventBus> _bus;
}; };
@ -56,21 +59,20 @@ class UIWallet
{ {
public: public:
UIWallet(const std::shared_ptr<Dexode::EventBus>& eventBus) UIWallet(const std::shared_ptr<Dexode::EventBus>& eventBus)
: _listener{eventBus} : _listener{eventBus}
{ {
} }
void onDraw()//Example "draw" of UI void onDraw() // Example "draw" of UI
{ {
std::cout << "Gold:" << _gold << std::endl; 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 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) _listener.listen<Event::Gold>(
{ [this](const auto& event) { _gold = std::to_string(event.value); });
_gold = std::to_string(event.value);
});
} }
void onExit() void onExit()
@ -83,23 +85,17 @@ private:
Dexode::EventCollector _listener; Dexode::EventCollector _listener;
}; };
class ShopButton // Shop button is only enabled when we have some gold (odd decision but for sample good :P) class ShopButton // Shop button is only enabled when we have some gold (odd decision but for sample
// good :P)
{ {
public: public:
ShopButton(const std::shared_ptr<Dexode::EventBus>& eventBus) ShopButton(const std::shared_ptr<Dexode::EventBus>& eventBus)
: _listener{eventBus} : _listener{eventBus}
{ {
} // We can use lambda or bind your choice
_listener.listen<Event::Gold>(
void onEnter() std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1));
{ // Also we use RAII idiom to handle unlisten
//We can use lambda or bind your choice
_listener.listen<Event::Gold>(std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1));
}
void onExit()
{
_listener.unlistenAll();//unlistenAll is also called when listener is destroyed
} }
bool isEnabled() const bool isEnabled() const
@ -114,32 +110,36 @@ private:
void onGoldUpdated(const Event::Gold& event) void onGoldUpdated(const Event::Gold& event)
{ {
_isEnabled = event.value > 0; _isEnabled = event.value > 0;
std::cout << "Shop button is:" << _isEnabled << std::endl;//some kind of logs std::cout << "Shop button is:" << _isEnabled << std::endl; // some kind of logs
} }
}; };
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
std::shared_ptr<Dexode::EventBus> eventBus = std::shared_ptr<Dexode::EventBus>(new Dexode::EventBus{}); std::shared_ptr<Dexode::EventBus> eventBus = std::make_shared<Dexode::EventBus>();
CharacterController characterController{eventBus}; Character characterController{eventBus};
UIWallet wallet{eventBus};//UIWallet doesn't know anything about character or even who store gold UIWallet wallet{eventBus}; // UIWallet doesn't know anything about character
ShopButton shopButton{eventBus};//ShopButton doesn't know anything about character // or even who store gold
ShopButton shopButton{eventBus}; // ShopButton doesn't know anything about character
{ {
wallet.onEnter(); wallet.onEnter();
shopButton.onEnter();
} }
characterController.kill(Monster::Tux); wallet.onDraw();
{
characterController.kill(Monster::Tux);
}
wallet.onDraw(); wallet.onDraw();
//It is easy to test UI eg. // It is easy to test UI eg.
eventBus->notify(Event::Gold{1}); eventBus->notify(Event::Gold{1});
assert(shopButton.isEnabled() == true); assert(shopButton.isEnabled() == true);
eventBus->notify(Event::Gold{0}); eventBus->notify(Event::Gold{0});
assert(shopButton.isEnabled() == false); assert(shopButton.isEnabled() == false);
wallet.onExit();
return 0; return 0;
} }

View File

@ -1,203 +0,0 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <typeinfo>
#include <list>
#if __cplusplus < 201103L
#error This library needs at least a C++11 compliant compiler
#endif
// This version isn't transaction safe as normal EventBus
namespace Dexode
{
class EventBus
{
public:
EventBus() = default;
~EventBus()
{
_callbacks.clear();
}
EventBus(const EventBus&) = delete;
EventBus(EventBus&&) = delete;
EventBus& operator=(EventBus&&) = delete;
EventBus& operator=(const EventBus&) = delete;
/**
* Register listener for event. Returns token used for unlisten.
*
* @tparam Event - type you want to listen for
* @param callback - your callback to handle event
* @return token used for unlisten
*/
template<typename Event>
int listen(const std::function<void(const Event&)>& callback)
{
const int token = ++_tokener;
listen<Event>(token, callback);
return token;
}
/**
* @tparam Event - type you want to listen for
* @param token - unique token for identification receiver. Simply pass token from @see EventBus::listen
* @param callback - your callback to handle event
*/
template<typename Event>
void listen(const int token, const std::function<void(const Event&)>& callback)
{
using Vector = VectorImpl<Event>;
assert(callback && "callback should be valid");//Check for valid object
std::unique_ptr<VectorInterface>& vector = _callbacks[getTypeId<Event>()];
if (vector == nullptr)
{
vector.reset(new Vector{});
}
assert(dynamic_cast<Vector*>(vector.get()));
Vector* vectorImpl = static_cast<Vector*>(vector.get());
vectorImpl->container.emplace_back(std::make_pair(callback, token));
}
/**
* @param token - token from EventBus::listen
*/
void unlistenAll(const int token)
{
for (auto& element : _callbacks)
{
element.second->remove(token);
}
}
/**
* @tparam Event - type you want to unlisten. @see Notiier::listen
* @param token - token from EventBus::listen
*/
template<typename Event>
void unlisten(const int token)
{
auto found = _callbacks.find(getTypeId<Event>());
if (found != _callbacks.end())
{
found->second->remove(token);
}
}
/**
* Notify all listeners for event
*
* @param event your event struct
*/
template<typename Event>
void notify(const Event& event)
{
using Vector = VectorImpl<Event>;
// constexpr auto typeId = getTypeId<Event>();
const auto typeId = getTypeId<Event>();
auto found = _callbacks.find(typeId);
if (found == _callbacks.end())
{
return;// no such notifications
}
std::unique_ptr<VectorInterface>& vector = found->second;
assert(dynamic_cast<Vector*>(vector.get()));
Vector* vectorImpl = static_cast<Vector*>(vector.get());
if (vectorImpl->container.empty())
{
return;
}
auto it = vectorImpl->container.begin();
while (it != vectorImpl->container.end())
{
vectorImpl->currentIterator = std::next(it);
it->first(event);//Order may change eg. some unlisten during call
it = vectorImpl->currentIterator;
}
vectorImpl->currentIterator = vectorImpl->container.end();
}
private:
struct VectorInterface
{
virtual ~VectorInterface() = default;
virtual void remove(const int token) = 0;
virtual void removeAll() = 0;
};
template<typename Event>
struct VectorImpl : public VectorInterface
{
using CallbackType = std::function<void(const Event&)>;
using ContainerType = std::list<std::pair<CallbackType, int>>;
ContainerType container;
typename ContainerType::iterator currentIterator = container.end();
//Invalidation rules: https://stackoverflow.com/questions/6438086/iterator-invalidation-rules
virtual void removeAll() override
{
container.clear();
currentIterator = container.end();
}
virtual void remove(const int token) override
{
auto removeIfPredicate = [token](const std::pair<CallbackType, int>& element)
{
return element.second == token;
};
//It is safe to remove
if (currentIterator == container.end())
{
container.remove_if(removeIfPredicate);
return;
}
//During iteration
ContainerType unvisited;
unvisited.splice(unvisited.begin(), container, currentIterator, container.end());
container.remove_if(removeIfPredicate);//could invalidate currentIterator
unvisited.remove_if(removeIfPredicate);
const auto distance = unvisited.size();
container.splice(container.end(), unvisited);
if (container.empty())
{
currentIterator = container.end();
return;
}
currentIterator = std::prev(container.end(), distance);
}
};
int _tokener = 0;
std::map<std::size_t, std::unique_ptr<VectorInterface>> _callbacks;
template<typename T>
static const std::size_t getTypeId()
{
//std::hash<std::string>{}(typeid(T).name() is slower
return typeid(T).hash_code();
}
};
} /* namespace Dexode */

View File

@ -1,42 +1,53 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
# http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html # http://www.levelofindirection.com/journal/2010/12/28/unit-testing-in-c-and-objective-c-just-got-easier.html
# Thanks for CATCH! # Thanks for CATCH!
ADD_EXECUTABLE(EventBusTest project(EventBusTest)
eventbus/EventCollectorTest.cpp
eventbus/NotifierTest.cpp # Dependencies
enable_testing()
if (NOT TARGET Dexode::EventBus)
find_package(EventBus CONFIG REQUIRED)
endif ()
# From 2.3.X they broke back compatibility
find_package(Catch2 2.2 REQUIRED)
# Target definition
add_executable(EventBusTest
src/EventCollectorTest.cpp
src/NotifierTest.cpp
) )
SET_TARGET_PROPERTIES(EventBusTest PROPERTIES target_compile_options(EventBusTest PUBLIC
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
)
TARGET_COMPILE_OPTIONS(EventBusTest PUBLIC
-Wall -pedantic -Wall -pedantic
-Wno-unused-private-field
-Wnon-virtual-dtor -Wnon-virtual-dtor
-Wno-gnu
-Werror -Werror
-Wno-error=deprecated-declarations
) )
IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
TARGET_COMPILE_OPTIONS(EventBusTest PRIVATE
-Wno-error=unknown-argument
)
ENDIF()
SET(EVENTBUS_DEBUG_FLAGS # 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 -O0 -fno-inline
-DDEBUG -DDEBUG
-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC #-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
) )
SET(EVENTBUS_RELEASE_FLAGS target_compile_options(EventBusTest PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>")
-DNDEBUG
)
TARGET_COMPILE_OPTIONS(EventBusTest PUBLIC "$<$<CONFIG:DEBUG>:${EVENTBUS_DEBUG_FLAGS}>") target_link_libraries(EventBusTest PUBLIC Dexode::EventBus Catch2::Catch)
TARGET_COMPILE_OPTIONS(EventBusTest PUBLIC "$<$<CONFIG:RELEASE>:${EVENTBUS_RELEASE_FLAGS}>")
TARGET_INCLUDE_DIRECTORIES(EventBusTest PUBLIC Catch/single_include/) add_test(NAME EventBus.UnitTests COMMAND EventBusTest)
TARGET_LINK_LIBRARIES(EventBusTest PUBLIC Dexode::EventBus)

@ -1 +0,0 @@
Subproject commit ee67ac6b7c3595251342671485c65cf81d725896