diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..526a376 --- /dev/null +++ b/.clang-format @@ -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 +--- diff --git a/.gitmodules b/.gitmodules index bb0e98b..17626a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "test/Catch"] - path = test/Catch - url = https://github.com/philsquared/Catch.git [submodule "performance/benchmark"] path = performance/benchmark url = https://github.com/google/benchmark.git diff --git a/.travis.yml b/.travis.yml index 8f2c5d8..8f0629c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,37 +1,54 @@ -os: linux -dist: trusty -sudo: require language: cpp +sudo: false -addons: - apt: - sources: - - llvm-toolchain-precise-3.9 - - ubuntu-toolchain-r-test - packages: - - clang-3.9 - - gcc-6 - - g++-6 +common_sources: &all_sources + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty + - llvm-toolchain-trusty-3.9 + - llvm-toolchain-trusty-4.0 + - llvm-toolchain-trusty-5.0 + - llvm-toolchain-trusty-6.0 matrix: - include: - - compiler: gcc - env: COMPILER=g++-6 + exclude: # On OSX g++ is a symlink to clang++ by default + - os: osx + compiler: gcc - - compiler: clang - env: COMPILER=clang++-3.9 + include: + - 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: - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi - -before_script: - - if [ "$COMPILER" = "g++-6" ]; then export CXX="g++-6" CC="gcc-6"; fi - - if [ "$COMPILER" = "clang++-3.9" ]; then export CXX="clang++-3.9" CC="clang-3.9"; fi + # Install catch 2 dependency + - wget https://github.com/catchorg/Catch2/archive/v2.2.3.zip + - unzip v2.2.3.zip && cd Catch2-2.2.3 && mkdir -p build/ && cd build/ + - cmake -DCMAKE_BUILD_TYPE=Release .. -DCMAKE_INSTALL_PREFIX=~/.local/ && cmake --build . --target install + - cd ../.. script: - - mkdir build-debug && cd build-debug && cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=YES .. && cmake --build . - - ./test/EventBusTest # Run tests - - cd .. # exit from build-debug - - mkdir build-release && cd build-release && cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=YES .. && cmake --build . - - ./test/EventBusTest # Run tests + #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 -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 .) + diff --git a/CMakeLists.txt b/CMakeLists.txt index dea4d7c..f93f0a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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(EventBus - VERSION 2.2.0 - LANGUAGES CXX - ) +project(EventBusDev) -ADD_LIBRARY(EventBus - src/eventbus/EventCollector.cpp include/eventbus/EventCollector.h - include/eventbus/EventBus.h - ) -ADD_LIBRARY(Dexode::EventBus ALIAS EventBus) +add_subdirectory(lib/) -TARGET_INCLUDE_DIRECTORIES(EventBus PUBLIC - $ - $ - PRIVATE src/ - ) +enable_testing() +add_subdirectory(test/) +add_subdirectory(sample/) +add_subdirectory(performance/) -TARGET_COMPILE_OPTIONS(EventBus PRIVATE +target_compile_options(EventBus PUBLIC -Wall -pedantic -Wnon-virtual-dtor -Werror -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() diff --git a/README.md b/README.md index 6771850..85dcd08 100644 --- a/README.md +++ b/README.md @@ -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. Eg. ```commandline -mkdir Release && cd Release -cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./install .. +mkdir -p lib/build/ +cd lib/build +cmake -DCMAKE_BUILD_TYPE=Relase -DCMAKE_INSTALL_PREFIX=~/.local/ .. + +cmake --build . --target install +# OR make && make install ``` @@ -205,6 +209,7 @@ checkNotifyFor10kListenersWhenNoOneListens_CCNotificationCenter 127388 ns - Thread safe EventBus ? - Verbose messages for easy debugging ? - Generating graph flow ? +- Add nice documentation [like in POCO](https://pocoproject.org/slides/090-NotificationsEvents.pdf) - ... # 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 ;) - [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**. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..7131941 --- /dev/null +++ b/lib/CMakeLists.txt @@ -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: +# * /lib*/cmake/ +# * /lib*/ +# * /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 + $ + $ + 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=$") + + +# 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: +# * /lib/libEventBus.a +# * header location after install: /include/eventbus/EventBus.h +# * headers can be included by C++ code `#include ` +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 'ConfigVersion.cmake' +# Use: +# * PROJECT_VERSION +write_basic_package_version_file( + "${version_config}" COMPATIBILITY SameMajorVersion +) + +# Configure '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 +# * /lib/cmake/EventBusventBusConfig.cmake +# * /lib/cmake/EventBus/EventBusConfigVersion.cmake +install( + FILES "${project_config}" "${version_config}" + DESTINATION "${config_install_dir}" +) + +# Config +# * /lib/cmake/EventBus/EventBusTargets.cmake +install(EXPORT "${TARGETS_EXPORT_NAME}" + DESTINATION "${config_install_dir}" + NAMESPACE "${namespace}" + ) diff --git a/lib/cmake/Config.cmake.in b/lib/cmake/Config.cmake.in new file mode 100644 index 0000000..b06af21 --- /dev/null +++ b/lib/cmake/Config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/eventbus/EventBus.h b/lib/include/eventbus/EventBus.h similarity index 64% rename from include/eventbus/EventBus.h rename to lib/include/eventbus/EventBus.h index df4a8f7..d4ffc7e 100644 --- a/include/eventbus/EventBus.h +++ b/lib/include/eventbus/EventBus.h @@ -5,15 +5,28 @@ #include #include #include -#include -#include #include namespace Dexode { +template +void type_id() // Helper for getting "type id" +{} + +using type_id_t = void (*)(); // Function pointer + class EventBus { + template + constexpr void validateEvent() + { + static_assert(std::is_const::value == false, "Struct must be without const"); + static_assert(std::is_volatile::value == false, "Struct must be without volatile"); + static_assert(std::is_reference::value == false, "Struct must be without reference"); + static_assert(std::is_pointer::value == false, "Struct must be without pointer"); + } + public: EventBus() = default; @@ -35,9 +48,11 @@ public: * @param callback - your callback to handle event * @return token used for unlisten */ - template + template int listen(const std::function& callback) { + validateEvent(); + const int token = ++_tokener; listen(token, callback); return token; @@ -48,15 +63,17 @@ public: * @param token - unique token for identification receiver. Simply pass token from @see EventBus::listen * @param callback - your callback to handle event */ - template + template void listen(const int token, const std::function& callback) { + validateEvent(); + using Vector = VectorImpl; - assert(callback && "callback should be valid");//Check for valid object + assert(callback && "callback should be valid"); //Check for valid object - std::unique_ptr& vector = _callbacks[getTypeId()]; - if (vector == nullptr) + std::unique_ptr& vector = _callbacks[type_id]; + if(vector == nullptr) { vector.reset(new Vector{}); } @@ -70,7 +87,7 @@ public: */ void unlistenAll(const int token) { - for (auto& element : _callbacks) + for(auto& element : _callbacks) { element.second->remove(token); } @@ -80,11 +97,13 @@ public: * @tparam Event - type you want to unlisten. @see Notiier::listen * @param token - token from EventBus::listen */ - template + template void unlisten(const int token) { - auto found = _callbacks.find(getTypeId()); - if (found != _callbacks.end()) + validateEvent(); + + auto found = _callbacks.find(type_id); + if(found != _callbacks.end()) { found->second->remove(token); } @@ -95,15 +114,17 @@ public: * * @param event your event struct */ - template + template void notify(const Event& event) { - using Vector = VectorImpl; - const auto typeId = getTypeId();//TODO think about constexpr - auto found = _callbacks.find(typeId); - if (found == _callbacks.end()) + using CleanEventType = typename std::remove_const::type; + validateEvent(); + + using Vector = VectorImpl; + auto found = _callbacks.find(type_id); + if(found == _callbacks.end()) { - return;// no such notifications + return; // no such notifications } std::unique_ptr& vector = found->second; @@ -111,7 +132,7 @@ public: Vector* vectorImpl = static_cast(vector.get()); vectorImpl->beginTransaction(); - for (const auto& element : vectorImpl->container) + for(const auto& element : vectorImpl->container) { element.second(event); } @@ -126,7 +147,7 @@ private: virtual void remove(const int token) = 0; }; - template + template struct VectorImpl : public VectorInterface { using CallbackType = std::function; @@ -139,19 +160,18 @@ private: virtual void remove(const int token) override { - if (inTransaction > 0) + if(inTransaction > 0) { toRemove.push_back(token); return; } //Invalidation rules: https://stackoverflow.com/questions/6438086/iterator-invalidation-rules - auto removeFrom = std::remove_if(container.begin(), container.end() - , [token](const ContainerElement& element) - { - return element.first == token; - }); - if (removeFrom != container.end()) + auto removeFrom = std::remove_if( + container.begin(), container.end(), [token](const ContainerElement& element) { + return element.first == token; + }); + if(removeFrom != container.end()) { container.erase(removeFrom, container.end()); } @@ -159,7 +179,7 @@ private: void add(const int token, const CallbackType& callback) { - if (inTransaction > 0) + if(inTransaction > 0) { toAdd.emplace_back(token, callback); } @@ -177,20 +197,20 @@ private: void commitTransaction() { --inTransaction; - if (inTransaction > 0) + if(inTransaction > 0) { return; } inTransaction = 0; - if (toAdd.empty() == false) + if(toAdd.empty() == false) { container.insert(container.end(), toAdd.begin(), toAdd.end()); toAdd.clear(); } - if (toRemove.empty() == false) + if(toRemove.empty() == false) { - for (auto token : toRemove) + for(auto token : toRemove) { remove(token); } @@ -200,14 +220,7 @@ private: }; int _tokener = 0; - std::map> _callbacks; - - template - static std::size_t getTypeId() - { - //std::hash{}(typeid(T).name() is slower - return typeid(T).hash_code(); - } + std::map> _callbacks; }; } /* namespace Dexode */ diff --git a/include/eventbus/EventCollector.h b/lib/include/eventbus/EventCollector.h similarity index 64% rename from include/eventbus/EventCollector.h rename to lib/include/eventbus/EventCollector.h index 001ea84..333099e 100644 --- a/include/eventbus/EventCollector.h +++ b/lib/include/eventbus/EventCollector.h @@ -11,36 +11,6 @@ namespace Dexode { -class [[deprecated("This class will be removed, is breaking API")]] BusAttorney -{ -public: - BusAttorney(std::shared_ptr bus) - : _bus(std::move(bus)) - { - } - - /** - * Notify all listeners for event - * - * @param event your event struct - */ - template - [[deprecated]] - void notify(const Event& event) const - { - _bus->notify(event); - } - - [[deprecated]] - std::shared_ptr extract() const - { - return _bus; - } - -private: - std::shared_ptr _bus; -}; - class EventCollector { public: @@ -93,10 +63,6 @@ public: bool isUsing(const std::shared_ptr& 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: int _token = 0; std::shared_ptr _bus; diff --git a/src/eventbus/EventCollector.cpp b/lib/src/EventCollector.cpp similarity index 94% rename from src/eventbus/EventCollector.cpp rename to lib/src/EventCollector.cpp index f3101d1..f15dad2 100644 --- a/src/eventbus/EventCollector.cpp +++ b/lib/src/EventCollector.cpp @@ -49,6 +49,7 @@ EventCollector::~EventCollector() EventCollector& EventCollector::operator=(const EventCollector& other) { + std::unique_ptr vector3; if (this == &other) { return *this; @@ -86,11 +87,6 @@ void EventCollector::unlistenAll() } } -BusAttorney EventCollector::getBus() const -{ - return BusAttorney{_bus}; -} - bool EventCollector::isUsing(const std::shared_ptr& bus) const { return _bus == bus; diff --git a/performance/CMakeLists.txt b/performance/CMakeLists.txt index cdf2132..be1d169 100644 --- a/performance/CMakeLists.txt +++ b/performance/CMakeLists.txt @@ -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 # 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 #INCLUDE(cocos2d-x-compare/Cocos2dxCompare.cmake) -ADD_EXECUTABLE(EventBusPerformance - eventbus/EventBusPerformance.cpp +add_executable(EventBusPerformance + src/EventBusPerformance.cpp ${CCNOTIFICATION_CENTER_SRC} + $<$:src/PocoNotificationCenterPerformance.cpp> ) -TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC +target_compile_options(EventBusPerformance PUBLIC -Wall -pedantic -Wno-unused-private-field -Wnon-virtual-dtor @@ -19,27 +28,22 @@ TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC -Werror ) -SET(EVENTBUS_DEBUG_FLAGS +set(EVENTBUS_DEBUG_FLAGS -O0 -fno-inline -DDEBUG - -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC ) -SET(EVENTBUS_RELEASE_FLAGS - -DNDEBUG - ) -TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") -TARGET_COMPILE_OPTIONS(EventBusPerformance PUBLIC "$<$:${EVENTBUS_RELEASE_FLAGS}>") +target_compile_options(EventBusPerformance PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") -SET_TARGET_PROPERTIES(EventBusPerformance PROPERTIES - CXX_STANDARD 14 - CXX_STANDARD_REQUIRED YES - ) - -TARGET_INCLUDE_DIRECTORIES(EventBusPerformance PUBLIC - ./ +target_include_directories(EventBusPerformance PUBLIC + src/ ${CCNOTIFICATION_CENTER_INCLUDE} ) -TARGET_LINK_LIBRARIES(EventBusPerformance PUBLIC Dexode::EventBus benchmark) +target_link_libraries(EventBusPerformance PUBLIC + Dexode::EventBus + benchmark + $<$:Poco::Foundation> + $<$:Poco::Util> + ) \ No newline at end of file diff --git a/performance/eventbus/EventBusPerformance.cpp b/performance/eventbus/EventBusPerformance.cpp deleted file mode 100644 index 41bdd49..0000000 --- a/performance/eventbus/EventBusPerformance.cpp +++ /dev/null @@ -1,284 +0,0 @@ -// -// Created by Dawid Drozd aka Gelldur on 05.08.17. -// -#include - -#include -#include - -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([&](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> 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 -struct SimpleEvent -{ - int value = N; -}; - -void checkNNotificationsForNListeners(benchmark::State& state, const int notificationsCount, const int listenersCount) -{ - std::mt19937 generator(311281); - std::uniform_int_distribution 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>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 1: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 2: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 3: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 4: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 5: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 6: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 7: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 8: - bus.listen>([&](const auto& event) - { - benchmark::DoNotOptimize(sum += event.value * - 2);//we use it to prevent some? optimizations - }); - break; - case 9: - bus.listen>([&](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([&](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() diff --git a/performance/src/EventBusPerformance.cpp b/performance/src/EventBusPerformance.cpp new file mode 100644 index 0000000..11363a0 --- /dev/null +++ b/performance/src/EventBusPerformance.cpp @@ -0,0 +1,269 @@ +// +// Created by Dawid Drozd aka Gelldur on 05.08.17. +// +#include + +#include +#include + +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( + [&](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> 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 +struct SimpleEvent +{ + int value = N; +}; + +void checkNNotificationsForNListeners(benchmark::State& state, + const int notificationsCount, + const int listenersCount) +{ + std::mt19937 generator(311281); + std::uniform_int_distribution 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>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 1: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 2: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 3: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 4: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 5: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 6: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 7: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 8: + bus.listen>([&](const auto& event) { + benchmark::DoNotOptimize(sum += event.value * + 2); // we use it to prevent some? optimizations + }); + break; + case 9: + bus.listen>([&](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( + [&](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() diff --git a/performance/src/PocoNotificationCenterPerformance.cpp b/performance/src/PocoNotificationCenterPerformance.cpp new file mode 100644 index 0000000..bbf7a47 --- /dev/null +++ b/performance/src/PocoNotificationCenterPerformance.cpp @@ -0,0 +1,101 @@ +// +// Created by Dawid Drozd aka Gelldur on 24.07.18. +// +#include +#include + +#include + +#include +#include +#include + +namespace +{ + +template +struct Wrapper : public Poco::Notification +{ + T data; + + Wrapper(T data) + : data(std::move(data)) + { + } +}; + +template +class Target +{ +public: + Target(std::function callback) + : _callback(std::move(callback)) + { + } + + void handle(const Poco::AutoPtr>& event) + { + _callback((*event.get()).data); + } + +private: + std::function _callback; +}; + +void checkNListeners(benchmark::State& state, const int listenersCount) +{ + Poco::NotificationCenter bus; + int sum = 0; + + struct SimpleEvent + { + int value; + }; + + using MyEvent = Wrapper; + using Listener = Target; + + std::vector 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(targets.back(), &Listener::handle)); + } + + while(state.KeepRunning()) // Performance area! + { + const Poco::AutoPtr event = new Wrapper{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); +} \ No newline at end of file diff --git a/sample/CMakeLists.txt b/sample/CMakeLists.txt index ac97228..3b07737 100644 --- a/sample/CMakeLists.txt +++ b/sample/CMakeLists.txt @@ -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 ) -TARGET_COMPILE_OPTIONS(Sample PUBLIC - -Wall -pedantic - -Wno-unused-private-field - -Wnon-virtual-dtor - -Wno-gnu - -Werror - ) +if(NOT TARGET Dexode::EventBus) + find_package(EventBus CONFIG REQUIRED) +endif() -SET_TARGET_PROPERTIES(Sample PROPERTIES - CXX_STANDARD 14 - ) - -ADD_SUBDIRECTORY(../ EventBus/) - -TARGET_LINK_LIBRARIES(Sample PRIVATE Dexode::EventBus) +target_link_libraries(Sample PRIVATE Dexode::EventBus) diff --git a/sample/src/main.cpp b/sample/src/main.cpp index fe7ac5b..96dcf8b 100644 --- a/sample/src/main.cpp +++ b/sample/src/main.cpp @@ -2,45 +2,48 @@ * @brief Sample code to play with! */ -#include -#include -#include #include +#include +#include +#include #include #include -namespace Event //Example namespace for events +namespace Event // Example namespace for events { - -struct Gold { int value = 0; }; //Event that will be proceed when our gold changes - +struct Gold // Event that will be proceed when our gold changes +{ + int value = 0; +}; } enum class Monster { - Frog, Tux, Shark + Frog, + Tux, + Shark }; -class CharacterController // some kind of character controller +class Character { public: - CharacterController(const std::shared_ptr& eventBus) - : _bus{eventBus} + Character(const std::shared_ptr& eventBus) + : _bus{eventBus} { } void kill(Monster monsterType) { - if (Monster::Frog == monsterType) + if(Monster::Frog == monsterType) { _gold += 1; } - else if (Monster::Tux == monsterType) + else if(Monster::Tux == monsterType) { _gold += 100; } - else if (Monster::Shark == monsterType) + else if(Monster::Shark == monsterType) { _gold += 25; } @@ -48,7 +51,7 @@ public: } private: - int _gold = 0;//Controller stores how much gold we have + int _gold = 0; std::shared_ptr _bus; }; @@ -56,21 +59,20 @@ class UIWallet { public: UIWallet(const std::shared_ptr& eventBus) - : _listener{eventBus} + : _listener{eventBus} { } - void onDraw()//Example "draw" of UI + 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 + void onEnter() // We could also do such things in ctor and dtor but a lot of UI has something + // like this { - _listener.listen([this](const auto& event) - { - _gold = std::to_string(event.value); - }); + _listener.listen( + [this](const auto& event) { _gold = std::to_string(event.value); }); } void onExit() @@ -83,23 +85,17 @@ private: 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: ShopButton(const std::shared_ptr& eventBus) - : _listener{eventBus} + : _listener{eventBus} { - } - - void onEnter() - { - //We can use lambda or bind your choice - _listener.listen(std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1)); - } - - void onExit() - { - _listener.unlistenAll();//unlistenAll is also called when listener is destroyed + // We can use lambda or bind your choice + _listener.listen( + std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1)); + // Also we use RAII idiom to handle unlisten } bool isEnabled() const @@ -114,32 +110,36 @@ private: void onGoldUpdated(const Event::Gold& event) { _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[]) { - std::shared_ptr eventBus = std::shared_ptr(new Dexode::EventBus{}); + std::shared_ptr eventBus = std::make_shared(); - CharacterController characterController{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 + 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(); - shopButton.onEnter(); } - characterController.kill(Monster::Tux); - + wallet.onDraw(); + { + characterController.kill(Monster::Tux); + } wallet.onDraw(); - //It is easy to test UI eg. + // It is easy to test UI eg. eventBus->notify(Event::Gold{1}); assert(shopButton.isEnabled() == true); eventBus->notify(Event::Gold{0}); assert(shopButton.isEnabled() == false); + wallet.onExit(); + return 0; } diff --git a/src/experimental/eventbus/EventBus.h b/src/experimental/eventbus/EventBus.h deleted file mode 100644 index efd2203..0000000 --- a/src/experimental/eventbus/EventBus.h +++ /dev/null @@ -1,203 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#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 - int listen(const std::function& callback) - { - const int token = ++_tokener; - listen(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 - void listen(const int token, const std::function& callback) - { - using Vector = VectorImpl; - - assert(callback && "callback should be valid");//Check for valid object - - std::unique_ptr& vector = _callbacks[getTypeId()]; - if (vector == nullptr) - { - vector.reset(new Vector{}); - } - - assert(dynamic_cast(vector.get())); - Vector* vectorImpl = static_cast(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 - void unlisten(const int token) - { - auto found = _callbacks.find(getTypeId()); - if (found != _callbacks.end()) - { - found->second->remove(token); - } - } - - /** - * Notify all listeners for event - * - * @param event your event struct - */ - template - void notify(const Event& event) - { - using Vector = VectorImpl; - // constexpr auto typeId = getTypeId(); - const auto typeId = getTypeId(); - auto found = _callbacks.find(typeId); - if (found == _callbacks.end()) - { - return;// no such notifications - } - - std::unique_ptr& vector = found->second; - assert(dynamic_cast(vector.get())); - Vector* vectorImpl = static_cast(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 - struct VectorImpl : public VectorInterface - { - using CallbackType = std::function; - using ContainerType = std::list>; - 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& 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> _callbacks; - - template - static const std::size_t getTypeId() - { - //std::hash{}(typeid(T).name() is slower - return typeid(T).hash_code(); - } -}; - -} /* namespace Dexode */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 327631e..b200fde 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 # Thanks for CATCH! -ADD_EXECUTABLE(EventBusTest - eventbus/EventCollectorTest.cpp - eventbus/NotifierTest.cpp +project(EventBusTest) + +# 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 - CXX_STANDARD 14 - CXX_STANDARD_REQUIRED YES - ) - -TARGET_COMPILE_OPTIONS(EventBusTest PUBLIC +target_compile_options(EventBusTest PUBLIC -Wall -pedantic + -Wno-unused-private-field -Wnon-virtual-dtor + -Wno-gnu -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=$,30,10>") + + +set(EVENTBUS_DEBUG_FLAGS -O0 -fno-inline -DDEBUG - -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC + #-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC ) -SET(EVENTBUS_RELEASE_FLAGS - -DNDEBUG - ) +target_compile_options(EventBusTest PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") -TARGET_COMPILE_OPTIONS(EventBusTest PUBLIC "$<$:${EVENTBUS_DEBUG_FLAGS}>") -TARGET_COMPILE_OPTIONS(EventBusTest PUBLIC "$<$:${EVENTBUS_RELEASE_FLAGS}>") +target_link_libraries(EventBusTest PUBLIC Dexode::EventBus Catch2::Catch) -TARGET_INCLUDE_DIRECTORIES(EventBusTest PUBLIC Catch/single_include/) -TARGET_LINK_LIBRARIES(EventBusTest PUBLIC Dexode::EventBus) - +add_test(NAME EventBus.UnitTests COMMAND EventBusTest) diff --git a/test/Catch b/test/Catch deleted file mode 160000 index ee67ac6..0000000 --- a/test/Catch +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ee67ac6b7c3595251342671485c65cf81d725896 diff --git a/test/eventbus/EventCollectorTest.cpp b/test/src/EventCollectorTest.cpp similarity index 100% rename from test/eventbus/EventCollectorTest.cpp rename to test/src/EventCollectorTest.cpp diff --git a/test/eventbus/NotifierTest.cpp b/test/src/NotifierTest.cpp similarity index 100% rename from test/eventbus/NotifierTest.cpp rename to test/src/NotifierTest.cpp