From 2c1b0f4c2852857bfae03d86dbab3591140f1aa3 Mon Sep 17 00:00:00 2001 From: Dominik Meyer Date: Wed, 4 Aug 2021 09:53:57 +0200 Subject: [PATCH] ADD: first version --- .gitignore | 2 + CMakeLists.txt | 82 +++++++ cmake/Modules/compdb.cmake | 31 +++ cmake/Modules/doxygen.cmake | 132 +++++++++++ include/EventManager/Event.hpp | 99 ++++++++ include/EventManager/Manager.hpp | 190 ++++++++++++++++ include/EventManager/Participant.hpp | 152 +++++++++++++ src/EventManager/Event.cpp | 34 +++ src/EventManager/Manager.cpp | 326 +++++++++++++++++++++++++++ src/EventManager/Participant.cpp | 120 ++++++++++ tests/test_basic.cpp | 164 ++++++++++++++ tests/test_event.cpp | 55 +++++ 12 files changed, 1387 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/Modules/compdb.cmake create mode 100644 cmake/Modules/doxygen.cmake create mode 100644 include/EventManager/Event.hpp create mode 100644 include/EventManager/Manager.hpp create mode 100644 include/EventManager/Participant.hpp create mode 100644 src/EventManager/Event.cpp create mode 100644 src/EventManager/Manager.cpp create mode 100644 src/EventManager/Participant.cpp create mode 100644 tests/test_basic.cpp create mode 100644 tests/test_event.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6536ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e6d889d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,82 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# +# Copyright 2021 Dominik Meyer +# This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +# + +cmake_minimum_required (VERSION 3.1 FATAL_ERROR) +project (EventManager VERSION 0.0.1 LANGUAGES CXX) +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules ) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +include(compdb) + +find_package (Threads REQUIRED) + +option(EM_TESTS "ENABLE/DISABLE all tests for EventManager" ON) +IF(${EM_TESTS}) + message(STATUS "EventManager tests enabled") + add_subdirectory(libs/Catch2) + include(CTest) + include(libs/Catch2/contrib/Catch.cmake) +ELSE() + message(STATUS "EventManager tests disabled") +ENDIF() + + +# +# all source files for the server library +# +SET(EVENTMANAGER_SOURCES + include/EventManager/Event.hpp + src/EventManager/Event.cpp + include/EventManager/Participant.hpp + src/EventManager/Participant.cpp + include/EventManager/Manager.hpp + src/EventManager/Manager.cpp +) + +add_library(objlib OBJECT ${EVENTMANAGER_SOURCES}) +set_property(TARGET objlib PROPERTY POSITION_INDEPENDENT_CODE 1) +target_include_directories(objlib + PUBLIC + include + PRIVATE + src +) +target_link_libraries(objlib PUBLIC Threads::Threads) + +add_library(eventmanager SHARED $) +target_include_directories(eventmanager + PUBLIC + include + PRIVATE + src +) +target_link_libraries(eventmanager PUBLIC Threads::Threads) + +add_library(eventmanager-static STATIC $) +target_include_directories(eventmanager-static + PUBLIC + include + PRIVATE + src +) +target_link_libraries(eventmanager-static PUBLIC Threads::Threads) + +# +# add tests as executable +# +add_executable(test_event tests/test_event.cpp) +target_link_libraries(test_event Catch2::Catch2 eventmanager-static) +catch_discover_tests(test_event) + +add_executable(test_basic tests/test_basic.cpp) +target_link_libraries(test_basic Catch2::Catch2 eventmanager-static) +catch_discover_tests(test_basic) diff --git a/cmake/Modules/compdb.cmake b/cmake/Modules/compdb.cmake new file mode 100644 index 0000000..89b7244 --- /dev/null +++ b/cmake/Modules/compdb.cmake @@ -0,0 +1,31 @@ + # This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at https://mozilla.org/MPL/2.0/. + # + # Copyright 2021 Dominik Meyer + # This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git + + +find_program(COMPDB_PATH + NAME compdb + PATHS ~/.local/bin/ + /bin + /sbin + /usr/bin + /usr/sbin + /usr/local/bin + /usr/local/sbin + ) + + + +if (COMPDB_PATH) + IF(NOT TARGET COMPD) + add_custom_target(COMPD + ALL + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${COMPDB_PATH} -p ${CMAKE_CURRENT_BINARY_DIR} list >compile_commands.json + ) + endif() +endif() diff --git a/cmake/Modules/doxygen.cmake b/cmake/Modules/doxygen.cmake new file mode 100644 index 0000000..754f35e --- /dev/null +++ b/cmake/Modules/doxygen.cmake @@ -0,0 +1,132 @@ +# +# Copyright (C) 2018 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +find_package(Doxygen) + +option(BUILD_DOCUMENTATION "Build API documentation using Doxygen. (make doc)" + ${DOXYGEN_FOUND}) + +# Builds doxygen documentation with a default 'Doxyfile.in' or with a specified +# one, and can make the results installable (under the `doc` install target) +# +# This can only be used once per project, as each target generated is as +# `doc-${PROJECT_NAME}` unless TARGET_NAME is specified. +# ~~~ +# Optional Arguments: +# +# ADD_TO_DOC +# If specified, adds this generated target to be a dependency of the more general +# `doc` target. +# +# INSTALLABLE +# Adds the generated documentation to the generic `install` target, under the +# `documentation` installation group. +# +# PROCESS_DOXYFILE +# If set, then will process the found Doxyfile through the CMAKE `configure_file` +# function for macro replacements before using it. (@ONLY) +# +# TARGET_NAME +# The name to give the doc target. (Default: doc-${PROJECT_NAME}) +# +# OUTPUT_DIR +# The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc) +# +# INSTALL_PATH +# The path to install the documenttation under. (if not specified, defaults to +# 'share/${PROJECT_NAME}) +# +# DOXYFILE_PATH +# The given doxygen file to use/process. (Defaults to'${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile') +# ~~~ +function(build_docs) + set(OPTIONS ADD_TO_DOC INSTALLABLE PROCESS_DOXYFILE) + set(SINGLE_VALUE_KEYWORDS + TARGET_NAME + INSTALL_PATH + DOXYFILE_PATH + OUTPUT_DIR) + set(MULTI_VALUE_KEYWORDS) + cmake_parse_arguments(build_docs + "${OPTIONS}" + "${SINGLE_VALUE_KEYWORDS}" + "${MULTI_VALUE_KEYWORDS}" + ${ARGN}) + + if(BUILD_DOCUMENTATION) + if(NOT DOXYGEN_FOUND) + message(FATAL_ERROR "Doxygen is needed to build the documentation.") + endif() + + if(NOT build_docs_DOXYFILE_PATH) + set(DOXYFILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile) + elseif(EXISTS ${build_docs_DOXYFILE_PATH}) + set(DOXYFILE_PATH ${build_docs_DOXYFILE_PATH}) + else() + set(DOXYFILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${build_docs_DOXYFILE_PATH}) + endif() + + if(NOT EXISTS ${DOXYFILE_PATH}) + message( + SEND_ERROR + "Could not find Doxyfile to use for procesing documentation at: ${DOXYFILE_PATH}" + ) + return() + endif() + + if(build_docs_PROCESS_DOXYFILE) + set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + configure_file(${DOXYFILE_PATH} ${DOXYFILE} @ONLY) + else() + set(DOXYFILE ${DOXYFILE_PATH}) + endif() + + if(build_docs_OUTPUT_DIR) + set(OUT_DIR ${build_docs_OUTPUT_DIR}) + else() + set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doc) + endif() + + file(MAKE_DIRECTORY ${OUT_DIR}) + + if(build_docs_TARGET_NAME) + set(TARGET_NAME ${build_docs_TARGET_NAME}) + else() + set(TARGET_NAME doc-${PROJECT_NAME}) + endif() + + add_custom_target(${TARGET_NAME} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE} + WORKING_DIRECTORY ${OUT_DIR} + VERBATIM) + + if(build_docs_ADD_TO_DOC) + if(NOT TARGET doc) + add_custom_target(doc) + endif() + + add_dependencies(doc ${TARGET_NAME}) + endif() + + if(build_docs_INSTALLABLE) + if(NOT build_docs_INSTALL_PATH) + set(build_docs_INSTALL_PATH share/${PROJECT_NAME}) + endif() + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/ + COMPONENT documentation + DESTINATION ${build_docs_INSTALL_PATH}) + endif() + endif() +endfunction() diff --git a/include/EventManager/Event.hpp b/include/EventManager/Event.hpp new file mode 100644 index 0000000..02b7d02 --- /dev/null +++ b/include/EventManager/Event.hpp @@ -0,0 +1,99 @@ +#pragma once +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ + #include + #include + #include + #include + + namespace EventManager + { + + // forward declaration of a participant + class Participant; + + /// Eventtype to notify all participants that a shutdown is immanent + const static std::uint32_t EVENT_TYPE_SHUTDOWN = 0; + + class Event + { + private: + /// the type of the event + std::uint32_t type_; + + /// the id which uniquly identifies the event + std::uint64_t id_; + + /// a possible response id, identifying if this event is in repsonse to another event + std::uint64_t responseId_; + + /// identifies if this event is a response to another event + std::atomic isResponse_; + + /// emitter of the event + std::shared_ptr emitter_; + + public: + /** + * @brief constructor for creating a simple event + * + * @param type - what kinf of event is this + */ + Event(std::uint32_t type); + + /** + * @brief Constructor to create a response Event + */ + Event(std::uint32_t type, const EventManager::Event &event); + + /** + * @brief Constructor to create a response Event + */ + Event(std::uint32_t type, const std::shared_ptr event); + + /** + * @brief return the id of the event + */ + std::uint64_t id() const { return id_;} + + /** + * @brief return the response id if this event is a response + */ + std::uint64_t responseId() const { + if (!isResponse_) + { + throw std::runtime_error("is not a response event"); + } + return responseId_; + } + + /** + * @brief check if the event is a response + */ + bool isResponse() const { return isResponse_;} + + /** + * @brief return the type of the event + */ + std::uint32_t type() const { return type_;} + + /** + * @brief set the emitter of the event + */ + void emitter(std::shared_ptr participant) { emitter_=participant;} + + /** + * @brief return the emitter of the event + */ + std::shared_ptr emitter() const {return emitter_;} + + }; // + }; // namespace EventManager diff --git a/include/EventManager/Manager.hpp b/include/EventManager/Manager.hpp new file mode 100644 index 0000000..fbcb0b5 --- /dev/null +++ b/include/EventManager/Manager.hpp @@ -0,0 +1,190 @@ +#pragma once +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + + namespace EventManager + { + + // forward declaration of EventManager::Participant + class Participant; + + class Manager + { + /// the thread the event manager is transmitting events in + std::unique_ptr mainThread_; + + /// is the main thread running + std::atomic isMainThreadRunning_; + + /// stop the main thread + std::atomic stopMainThread_; + + /// the thread the event manager is scheduling plugins in + std::unique_ptr schedulingThread_; + + /// is the scheduling thread running + std::atomic isSchedulingThreadRunning_; + + /// stop the scheduling thread + std::atomic stopSchedulingThread_; + + /// map holding all the event type and plugin combinations + std::map>> eventMap_; + + /// queue for incomng events + std::queue> eventQueue_; + + /// mutex to protect the event queue + std::mutex mutexEventQueue_; + + /// condition variable to wake of thread on new emit + std::condition_variable newEventInQueue_; + + /// list of all plugins requiring scheduling + std::list> schedulingParticipants_; + + /// mutex to protect schedulingPlugins_ + std::mutex mutexSchedulingParticipants_; + + /* + * all private methods + */ + + /** + * @brief the method running in the main thread + */ + void mainProcess_(); + + /** + * @brief the method running in the scheduling thread + */ + void schedulingProcess_(); + + /** + * @brief process one event (call all the participants) + */ + void processEvent(const std::shared_ptr event); + + /** + * @brief start the main thread + */ + void startMain_(); + + /** + * @brief stop the main thread + */ + void stopMain_(); + + /** + * @brief start the scheduling thread + */ + void startScheduling_(); + + /** + * @brief stop the scheduling thread + */ + void stopScheduling_(); + + + public: + /** + * @brief The constructor for the event manager + * + * Just initializes all attributes to its starting value + */ + Manager() : mainThread_(nullptr), isMainThreadRunning_(false), + stopMainThread_(false), schedulingThread_(nullptr), + isSchedulingThreadRunning_(false), stopSchedulingThread_(false){} + + + ~Manager(); + /** + * @brief start the event manager + */ + void start(); + + /** + * @brief stop the event manager + */ + void stop(); + + /** + * @brief check if the eventmanager is running + */ + bool isRunning(); + + /** + * @brief emit an event and make sure it is delivered to all subscribed plugins + * + * @param event - the event to emit + */ + void emit(std::shared_ptr event); + + /** + * @brief subscribe a plugin for the given event type + * + * @param type - the event type to subscribe to + * @param plugin - shared pointer to the plugin which subscribes + */ + void subscribe(std::uint32_t type, std::shared_ptr plugin); + + /** + * @brief unsubscribe a plugin from the given event type + * + * @param type - the event type to unsubscribe from + * @param plugin - shared pointer to the plugin which unsubscribes + */ + void unsubscribe(std::uint32_t type, std::shared_ptr plugin); + + /** + * @brief unsubscribe a plugin from the all event types + * + * @param plugin - shared pointer to the plugin which unsubscribes + */ + void unsubscribe(std::shared_ptr plugin); + + /** + * @brief check if there are any subscriptions within the event manager + */ + bool empty() const; + + /** + * @brief Wait for the EventManager to become empty. + * + * @param timeout - how many milliseconds to wait for EventManager becoming empty + * + * @return true - EventManager is empty + * @return false - EventManager is not empty + */ + bool waitEmpty(std::uint32_t timeoutMS) const; + + /** + * @brief schedule the given plugin regularly + * + * @param plugin - the plugin to schedule + */ + void schedule(std::shared_ptr plugin); + + }; // class Manager + + }; //namespace EventManager diff --git a/include/EventManager/Participant.hpp b/include/EventManager/Participant.hpp new file mode 100644 index 0000000..685b8be --- /dev/null +++ b/include/EventManager/Participant.hpp @@ -0,0 +1,152 @@ +#pragma once +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ + #include + #include + #include + #include + #include + #include + #include + + #include + + namespace EventManager + { + + // forward declaration for the EventManager::Manager + class Manager; + + /** + * @brief The entity participating in the event system. + */ + class Participant : public std::enable_shared_from_this + { + private: + + /// pointer to the event manager + std::shared_ptr manager_; + + /// is the participant scheduled by the EventManager::Manager + std::atomic isScheduledByManager_; + + /// queue for incomng events + std::queue> eventQueue_; + + /// mutex to protect the event queue + std::mutex mutexEventQueue_; + + /// condition variable to wake of thread on new trigger + std::condition_variable newEventInQueue_; + + /// has the participant locked the queue itself already + std::atomic isQueueLocked_; + + /* + * all private methods + */ + + /** + * @brief Method called if the participant is scheduled by EventManager::Manager + * + * This method needs to be implemented by the child class. + * Please make sure this method returns as fast as possible! + * No endless loops are supported. + * Just process some incoming events and then return! + */ + virtual void schedule_() { throw std::runtime_error(std::string(__PRETTY_FUNCTION__) + " not implemented");} + + + protected: + + /** + * @brief check if events are available + */ + bool _hasEvents(); + + /** + * @brief Lock the queue to process all events + */ + void _lockQueue(); + + /** + * @brief UnLock the queue to process all events + */ + void _unlockQueue(); + + /** + * @brief fetch one event from the queue + */ + std::shared_ptr _fetchEvent(); + + /** + * @brief wait for a new event + */ + void _waitForEvent(); + + + /** + * @brief This method subscribes the participant to an event type + * + * @param type - the event type to subscribe this participant to + */ + void _subscribe(std::uint32_t type); + + /** + * @brief unsubscribe the participant from the given event type + */ + void _unsubscribe(std::uint32_t type); + + /** + * @brief unsubscribe the participant from the all event types + */ + void _unsubscribe(); + + /** + * @brief Method to emit an event to the event manager + * + * @param event - the event to emit + */ + void _emit(std::shared_ptr event); + + /** + * @brief enable scheduling of this particpant through the EventManager::Manager + */ + void _enableScheduling(); + + /** + * @brief check if the participant is scheduled by event manager + */ + bool isScheduledByManager() const {return isScheduledByManager_;} + + public: + /** + * @brief Constructor setting the participant up for use + */ + Participant(); + + void setManager(std::shared_ptr manager) { manager_=manager;_subscribe(EVENT_TYPE_SHUTDOWN);} + + /** + * @brief Method called by the EventManager::Manager to schedule the particpant + * + */ + void schedule() {schedule_();}; + + + /** + * @brief emit an event to this participant + */ + void emit(std::shared_ptr event); + + };// + + }; // namespace EventManager diff --git a/src/EventManager/Event.cpp b/src/EventManager/Event.cpp new file mode 100644 index 0000000..2a0296b --- /dev/null +++ b/src/EventManager/Event.cpp @@ -0,0 +1,34 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ + #include + #include + + EventManager::Event::Event(std::uint32_t type) : type_(type), responseId_(0), isResponse_(false), emitter_(nullptr) + { + std::random_device rd; + std::mt19937 rng(rd()); + + std::uniform_int_distribution dist(1,std::numeric_limits::max()); + + id_ = dist(rng); + } + + EventManager::Event::Event(std::uint32_t type, const EventManager::Event &event) : Event(type) + { + responseId_=event.id(); + isResponse_=true; + } + +EventManager::Event::Event(std::uint32_t type, const std::shared_ptr event) : Event(type) +{ + responseId_=event->id(); + isResponse_=true; +} diff --git a/src/EventManager/Manager.cpp b/src/EventManager/Manager.cpp new file mode 100644 index 0000000..4afb5bb --- /dev/null +++ b/src/EventManager/Manager.cpp @@ -0,0 +1,326 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ + #include + #include + #include + + void EventManager::Manager::startMain_() + { + + if (isMainThreadRunning_) + { + throw std::runtime_error("Main thread already running"); + } + + stopMainThread_=false; + + mainThread_ = std::make_unique(&EventManager::Manager::mainProcess_,this); + + std::int32_t timeout = 6000; + + while(!isMainThreadRunning_ && timeout > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timeout-=100; + } + + if (timeout <= 0) + { + stopMainThread_=true; + throw std::runtime_error("EventManager: can not start main thread"); + } + + } + + void EventManager::Manager::startScheduling_() + { + + if (isSchedulingThreadRunning_) + { + throw std::runtime_error("Scheduling thread already running"); + } + + stopSchedulingThread_=false; + + schedulingThread_ = std::make_unique(&EventManager::Manager::schedulingProcess_,this); + + std::int32_t timeout = 6000; + + while(!isSchedulingThreadRunning_ && timeout > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timeout-=100; + } + + if (timeout <= 0) + { + stopSchedulingThread_=true; + throw std::runtime_error("EventManager: can not start scheduling thread"); + } + + } + + + void EventManager::Manager::stopMain_() + { + std::int32_t timeout = 6000; + stopMainThread_=true; + newEventInQueue_.notify_one(); + + while(isMainThreadRunning_ && timeout > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timeout-=100; + } + + if (timeout <= 0) + { + throw std::runtime_error("can not stop main thread"); + } + + mainThread_->join(); + } + + void EventManager::Manager::stopScheduling_() + { + std::int32_t timeout = 6000; + stopSchedulingThread_=true; + + while(isSchedulingThreadRunning_ && timeout > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timeout-=100; + } + + if (timeout <= 0) + { + throw std::runtime_error("can not stop scheduling thread"); + } + + schedulingThread_->join(); + } + + void EventManager::Manager::start() + { + startMain_(); + try { + startScheduling_(); + } catch (std::exception &e) + { + stopMain_(); + throw e; + } + } + + void EventManager::Manager::stop() + { + stopMain_(); + stopScheduling_(); + } + + EventManager::Manager::~Manager() + { + if (isMainThreadRunning_) + { + stopMain_(); + } + + if (isSchedulingThreadRunning_) + { + stopScheduling_(); + } + } + + void EventManager::Manager::processEvent(const std::shared_ptr event) + { + + auto it = eventMap_.find(event->type()); + + if (it != eventMap_.end()) + { + + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + if (event->emitter() != *it2) + { + (*it2)->emit(event); + } + } + + } + } + + void EventManager::Manager::mainProcess_() + { + isMainThreadRunning_=true; + + while(!stopMainThread_) + { + std::unique_lock lock(mutexEventQueue_); + newEventInQueue_.wait(lock); + while(!eventQueue_.empty()) + { + std::shared_ptr event = eventQueue_.front(); + eventQueue_.pop(); + processEvent(event); + } + + lock.unlock(); + + } + + isMainThreadRunning_=false; + + } + + void EventManager::Manager::schedulingProcess_() + { + isSchedulingThreadRunning_=true; + + while(!stopSchedulingThread_) + { + mutexSchedulingParticipants_.lock(); + if (!schedulingParticipants_.empty()) + { + for (auto it = schedulingParticipants_.begin(); it != schedulingParticipants_.end(); ++it) + { + (*it)->schedule(); + } + } + mutexSchedulingParticipants_.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + isSchedulingThreadRunning_=false; + + } + + + void EventManager::Manager::subscribe(std::uint32_t type, std::shared_ptr participant) + { + + // check if participant is already registered + auto it = eventMap_.find(type); + + if (it != eventMap_.end()) + { + + auto it2 = std::find(it->second.begin(), it->second.end(),participant); + + if (it2 == it->second.end()) + { + it->second.push_back(participant); + } + + } + else + { + eventMap_[type].push_back(participant); + } + + } + + void EventManager::Manager::unsubscribe(std::uint32_t type, std::shared_ptr participant) + { + + auto it = eventMap_.find(type); + + if (it == eventMap_.end()) + { + return; + } + + auto it2 = std::find(it->second.begin(), it->second.end(),participant); + + if (it2 != it->second.end()) + { + it->second.erase(it2); + } + + } + + void EventManager::Manager::unsubscribe(std::shared_ptr participant) + { + + for (auto it = eventMap_.begin(); it != eventMap_.end(); ++it) + { + unsubscribe(it->first,participant); + } + + } + + + void EventManager::Manager::emit(const std::shared_ptr event) + { + { + std::lock_guard lock(mutexEventQueue_); + eventQueue_.push(event); + } + newEventInQueue_.notify_one(); + + } + + bool EventManager::Manager::isRunning() + { + + if (isMainThreadRunning_ && isSchedulingThreadRunning_) + { + return true; + } + + return false; + } + + bool EventManager::Manager::empty() const + { + bool isEmpty=true; + + for (auto it = eventMap_.begin(); it != eventMap_.end(); ++it) + { + if ( !(*it).second.empty()) + { + isEmpty=false; + } + } + + return isEmpty; + } + + bool EventManager::Manager::waitEmpty(std::uint32_t timeoutMS) const + { + std::uint32_t timeout=timeoutMS; + + while(!empty() && timeout > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timeout-=100; + } + + if (timeout == 0) + { + return false; + } + + return true; + + } + + void EventManager::Manager::schedule(std::shared_ptr participant) + { + std::lock_guard guard(mutexSchedulingParticipants_); + + auto it = std::find(schedulingParticipants_.begin(), schedulingParticipants_.end(), participant); + + if (it == schedulingParticipants_.end()) + { + schedulingParticipants_.push_back(participant); + } + } diff --git a/src/EventManager/Participant.cpp b/src/EventManager/Participant.cpp new file mode 100644 index 0000000..d0a3e20 --- /dev/null +++ b/src/EventManager/Participant.cpp @@ -0,0 +1,120 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ + #include + #include + #include + +EventManager::Participant::Participant() : manager_(nullptr), + isScheduledByManager_(false), isQueueLocked_(false) +{ + +} + +void EventManager::Participant::emit(std::shared_ptr event) +{ + { + std::lock_guard lock(mutexEventQueue_); + eventQueue_.push(event); + } + newEventInQueue_.notify_one(); +} + +bool EventManager::Participant::_hasEvents() +{ + if (isQueueLocked_) + { + return !eventQueue_.empty(); + } + + std::lock_guard guard(mutexEventQueue_); + + + return !eventQueue_.empty(); +} + +void EventManager::Participant::_lockQueue() +{ + mutexEventQueue_.lock(); + isQueueLocked_=true; +} + +void EventManager::Participant::_unlockQueue() +{ + mutexEventQueue_.unlock(); + isQueueLocked_=false; +} + +std::shared_ptr EventManager::Participant::_fetchEvent() +{ + if (!isQueueLocked_) + { + throw std::runtime_error("queue not locked"); + } + + std::shared_ptr event = eventQueue_.front(); + eventQueue_.pop(); + + return event; +} + +void EventManager::Participant::_waitForEvent() +{ + std::unique_lock lock(mutexEventQueue_); + newEventInQueue_.wait(lock); + isQueueLocked_=true; +} + +void EventManager::Participant::_enableScheduling() +{ + if (manager_ == nullptr) + { + throw std::runtime_error("no event manager set yet"); + } + manager_->schedule(shared_from_this()); + isScheduledByManager_=true; +} + +void EventManager::Participant::_subscribe(std::uint32_t type) +{ + if (manager_ == nullptr) + { + throw std::runtime_error("no event manager set yet"); + } + manager_->subscribe(type, shared_from_this()); +} + +void EventManager::Participant::_unsubscribe(std::uint32_t type) +{ + if (manager_ == nullptr) + { + throw std::runtime_error("no event manager set yet"); + } + manager_->unsubscribe(type, shared_from_this()); +} + +void EventManager::Participant::_unsubscribe() +{ + if (manager_ == nullptr) + { + throw std::runtime_error("no event manager set yet"); + } + manager_->unsubscribe(shared_from_this()); +} + +void EventManager::Participant::_emit(std::shared_ptr event) +{ + if (manager_ == nullptr) + { + throw std::runtime_error("no event manager set yet"); + } + event->emitter(shared_from_this()); + manager_->emit(event); +} diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp new file mode 100644 index 0000000..57ae894 --- /dev/null +++ b/tests/test_basic.cpp @@ -0,0 +1,164 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ +#define CATCH_CONFIG_MAIN +#include +#include +#include +#include +#include +#include + + +const static std::uint32_t TEST_EVENT0 = 10; +const static std::uint32_t TEST_EVENT1 = 20; + +class myParticipant : public EventManager::Participant +{ + private: + bool receivedEvent_; + + std::uint32_t eventType_; + + std::uint32_t id_; + + void shutdown_() { + _unsubscribe(); + } + + void schedule_() { + std::shared_ptr event = nullptr; + + _lockQueue(); + if (_hasEvents()) + { + event = _fetchEvent(); + } + _unlockQueue(); + if (event == nullptr) + { + return; + } + + if (event->type() == EventManager::EVENT_TYPE_SHUTDOWN) + { + shutdown_(); + } + else if (event->type() == eventType_) + { + receivedEvent_=true; + } + } + public: + myParticipant(std::uint32_t id,std::uint32_t eventType) : EventManager::Participant() + { + id_=id; + receivedEvent_=false; + eventType_=eventType; + + } + + bool eventReceived() const { return receivedEvent_;} + + void init() { + _subscribe(eventType_); + _enableScheduling(); + } +}; + + +SCENARIO("Basic Usage of EventManager", "[Manager]") +{ + GIVEN("an EventManager::Manager and two participants") + { + + std::shared_ptr manager; + REQUIRE_NOTHROW([&]() + { + manager = std::make_shared(); + }()); + + REQUIRE(manager->empty() == true); + + std::shared_ptr participant0; + REQUIRE_NOTHROW([&]() + { + participant0 = std::make_shared(0,TEST_EVENT0); + participant0->setManager(manager); + participant0->init(); + }()); + + REQUIRE(manager->empty() == false); + + std::shared_ptr participant1; + REQUIRE_NOTHROW([&]() + { + participant1 = std::make_shared(1,TEST_EVENT1); + participant1->setManager(manager); + participant1->init(); + }()); + + REQUIRE(manager->empty() == false); + + REQUIRE_NOTHROW([&]() + { + manager->start(); + }()); + + REQUIRE(manager->isRunning() == true); + + WHEN("emitting shutdown event") + { + std::shared_ptr shutdown = std::make_shared(EventManager::EVENT_TYPE_SHUTDOWN); + + manager->emit(shutdown); + manager->waitEmpty(3000); + + + THEN("participants are shutting down") + { + REQUIRE(manager->empty() == true); + + REQUIRE_NOTHROW([&]() + { + manager->stop(); + }()); + + REQUIRE(manager->isRunning() == false); + } + } + + WHEN("emitting events event") + { + std::shared_ptr shutdown = std::make_shared(EventManager::EVENT_TYPE_SHUTDOWN); + std::shared_ptr e0 = std::make_shared(TEST_EVENT0); + std::shared_ptr e1 = std::make_shared(TEST_EVENT1); + manager->emit(e0); + manager->emit(e1); + manager->emit(shutdown); + manager->waitEmpty(3000); + + + THEN("participants recevied events and are shutting down") + { + REQUIRE(manager->empty() == true); + REQUIRE(participant0->eventReceived()); + REQUIRE(participant1->eventReceived()); + + REQUIRE_NOTHROW([&]() + { + manager->stop(); + }()); + + REQUIRE(manager->isRunning() == false); + } + } + } +} diff --git a/tests/test_event.cpp b/tests/test_event.cpp new file mode 100644 index 0000000..cc371b3 --- /dev/null +++ b/tests/test_event.cpp @@ -0,0 +1,55 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* Copyright 2021 Dominik Meyer +* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git +*/ + + /** @file */ +#define CATCH_CONFIG_MAIN +#include +#include +#include +#include + +SCENARIO("Use Event Class", "[Event]") +{ + GIVEN("nothing") + { + WHEN("creating an Event from scratch") + { + std::unique_ptr e = std::make_unique(10); + + THEN("the attributes can be correctly fetched") + { + REQUIRE(e->id() >= std::numeric_limits::min()); + REQUIRE(e->id() <= std::numeric_limits::max()); + REQUIRE(e->isResponse() == false); + REQUIRE_THROWS([&]() + { + e->responseId(); + }()); + } + } + WHEN("creating an response Event") + { + std::unique_ptr e = std::make_unique(10); + std::unique_ptr r = std::make_unique(10, *e); + + THEN("the attributes can be correctly fetched") + { + REQUIRE(r->id() >= std::numeric_limits::min()); + REQUIRE(r->id() <= std::numeric_limits::max()); + REQUIRE(r->isResponse() == true); + std::uint64_t rid=0; + REQUIRE_NOTHROW([&]() + { + rid=r->responseId(); + }()); + REQUIRE(rid == e->id()); + } + } + } +}