Browse Source

ADD: first version

main
Dominik Meyer 3 months ago
parent
commit
2c1b0f4c28
Signed by: byterazor GPG Key ID: B4C312B600606B64
  1. 2
      .gitignore
  2. 82
      CMakeLists.txt
  3. 31
      cmake/Modules/compdb.cmake
  4. 132
      cmake/Modules/doxygen.cmake
  5. 99
      include/EventManager/Event.hpp
  6. 190
      include/EventManager/Manager.hpp
  7. 152
      include/EventManager/Participant.hpp
  8. 34
      src/EventManager/Event.cpp
  9. 326
      src/EventManager/Manager.cpp
  10. 120
      src/EventManager/Participant.cpp
  11. 164
      tests/test_basic.cpp
  12. 55
      tests/test_event.cpp

2
.gitignore

@ -0,0 +1,2 @@
build
compile_commands.json

82
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 <dmeyer@federationhq.de>
# 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_OBJECTS:objlib>)
target_include_directories(eventmanager
PUBLIC
include
PRIVATE
src
)
target_link_libraries(eventmanager PUBLIC Threads::Threads)
add_library(eventmanager-static STATIC $<TARGET_OBJECTS:objlib>)
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)

31
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 <dmeyer@federationhq.de>
# 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()

132
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 <str>
# The name to give the doc target. (Default: doc-${PROJECT_NAME})
#
# OUTPUT_DIR <str>
# The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc)
#
# INSTALL_PATH <str>
# The path to install the documenttation under. (if not specified, defaults to
# 'share/${PROJECT_NAME})
#
# DOXYFILE_PATH <str>
# 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()

99
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#include <cstdint>
#include <atomic>
#include <stdexcept>
#include <memory>
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<bool> isResponse_;
/// emitter of the event
std::shared_ptr<EventManager::Participant> 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<EventManager::Event> 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<EventManager::Participant> participant) { emitter_=participant;}
/**
* @brief return the emitter of the event
*/
std::shared_ptr<EventManager::Participant> emitter() const {return emitter_;}
}; //
}; // namespace EventManager

190
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#include <memory>
#include <atomic>
#include <stdexcept>
#include <string>
#include <mutex>
#include <queue>
#include <thread>
#include <list>
#include <map>
#include <condition_variable>
#include <EventManager/Event.hpp>
namespace EventManager
{
// forward declaration of EventManager::Participant
class Participant;
class Manager
{
/// the thread the event manager is transmitting events in
std::unique_ptr<std::thread> mainThread_;
/// is the main thread running
std::atomic<bool> isMainThreadRunning_;
/// stop the main thread
std::atomic<bool> stopMainThread_;
/// the thread the event manager is scheduling plugins in
std::unique_ptr<std::thread> schedulingThread_;
/// is the scheduling thread running
std::atomic<bool> isSchedulingThreadRunning_;
/// stop the scheduling thread
std::atomic<bool> stopSchedulingThread_;
/// map holding all the event type and plugin combinations
std::map<std::uint32_t, std::list<std::shared_ptr<EventManager::Participant>>> eventMap_;
/// queue for incomng events
std::queue<std::shared_ptr<EventManager::Event>> 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<std::shared_ptr<EventManager::Participant>> 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<EventManager::Event> 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<EventManager::Event> 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<EventManager::Participant> 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<EventManager::Participant> plugin);
/**
* @brief unsubscribe a plugin from the all event types
*
* @param plugin - shared pointer to the plugin which unsubscribes
*/
void unsubscribe(std::shared_ptr<EventManager::Participant> 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<EventManager::Participant> plugin);
}; // class Manager
}; //namespace EventManager

152
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#include <memory>
#include <atomic>
#include <stdexcept>
#include <string>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <EventManager/Event.hpp>
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<Participant>
{
private:
/// pointer to the event manager
std::shared_ptr<EventManager::Manager> manager_;
/// is the participant scheduled by the EventManager::Manager
std::atomic<bool> isScheduledByManager_;
/// queue for incomng events
std::queue<std::shared_ptr<EventManager::Event>> 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<bool> 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<EventManager::Event> _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<EventManager::Event> 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<EventManager::Manager> 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<EventManager::Event> event);
};//
}; // namespace EventManager

34
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#include <EventManager/Event.hpp>
#include <random>
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<std::mt19937::result_type> dist(1,std::numeric_limits<int>::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<EventManager::Event> event) : Event(type)
{
responseId_=event->id();
isResponse_=true;
}

326
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#include <EventManager/Manager.hpp>
#include <EventManager/Participant.hpp>
#include <iostream>
void EventManager::Manager::startMain_()
{
if (isMainThreadRunning_)
{
throw std::runtime_error("Main thread already running");
}
stopMainThread_=false;
mainThread_ = std::make_unique<std::thread>(&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<std::thread>(&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<EventManager::Event> 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<std::mutex> lock(mutexEventQueue_);
newEventInQueue_.wait(lock);
while(!eventQueue_.empty())
{
std::shared_ptr<EventManager::Event> 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<EventManager::Participant> 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<EventManager::Participant> 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<EventManager::Participant> participant)
{
for (auto it = eventMap_.begin(); it != eventMap_.end(); ++it)
{
unsubscribe(it->first,participant);
}
}
void EventManager::Manager::emit(const std::shared_ptr<EventManager::Event> event)
{
{
std::lock_guard<std::mutex> 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<EventManager::Participant> participant)
{
std::lock_guard<std::mutex> guard(mutexSchedulingParticipants_);
auto it = std::find(schedulingParticipants_.begin(), schedulingParticipants_.end(), participant);
if (it == schedulingParticipants_.end())
{
schedulingParticipants_.push_back(participant);
}
}

120
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#include <EventManager/Participant.hpp>
#include <EventManager/Manager.hpp>
#include <iostream>
EventManager::Participant::Participant() : manager_(nullptr),
isScheduledByManager_(false), isQueueLocked_(false)
{
}
void EventManager::Participant::emit(std::shared_ptr<EventManager::Event> event)
{
{
std::lock_guard<std::mutex> lock(mutexEventQueue_);
eventQueue_.push(event);
}
newEventInQueue_.notify_one();
}
bool EventManager::Participant::_hasEvents()
{
if (isQueueLocked_)
{
return !eventQueue_.empty();
}
std::lock_guard<std::mutex> 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::Event> EventManager::Participant::_fetchEvent()
{
if (!isQueueLocked_)
{
throw std::runtime_error("queue not locked");
}
std::shared_ptr<EventManager::Event> event = eventQueue_.front();
eventQueue_.pop();
return event;
}
void EventManager::Participant::_waitForEvent()
{
std::unique_lock<std::mutex> 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<EventManager::Event> event)
{
if (manager_ == nullptr)
{
throw std::runtime_error("no event manager set yet");
}
event->emitter(shared_from_this());
manager_->emit(event);
}

164
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include <EventManager/Event.hpp>
#include <EventManager/Manager.hpp>
#include <EventManager/Participant.hpp>
#include <memory>
#include <limits>
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<EventManager::Event> 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<EventManager::Manager> manager;
REQUIRE_NOTHROW([&]()
{
manager = std::make_shared<EventManager::Manager>();
}());
REQUIRE(manager->empty() == true);
std::shared_ptr<myParticipant> participant0;
REQUIRE_NOTHROW([&]()
{
participant0 = std::make_shared<myParticipant>(0,TEST_EVENT0);
participant0->setManager(manager);
participant0->init();
}());
REQUIRE(manager->empty() == false);
std::shared_ptr<myParticipant> participant1;
REQUIRE_NOTHROW([&]()
{
participant1 = std::make_shared<myParticipant>(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<EventManager::Event> shutdown = std::make_shared<EventManager::Event>(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<EventManager::Event> shutdown = std::make_shared<EventManager::Event>(EventManager::EVENT_TYPE_SHUTDOWN);
std::shared_ptr<EventManager::Event> e0 = std::make_shared<EventManager::Event>(TEST_EVENT0);
std::shared_ptr<EventManager::Event> e1 = std::make_shared<EventManager::Event>(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);
}
}
}
}

55
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 <dmeyer@federationhq.de>
* This file is part of the EventManager distribution hosted at https://gitea.federationhq.de/byterazor/EventManager.git
*/
/** @file */
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include <EventManager/Event.hpp>
#include <memory>
#include <limits>
SCENARIO("Use Event Class", "[Event]")
{
GIVEN("nothing")
{
WHEN("creating an Event from scratch")
{
std::unique_ptr<EventManager::Event> e = std::make_unique<EventManager::Event>(10);
THEN("the attributes can be correctly fetched")
{
REQUIRE(e->id() >= std::numeric_limits<std::uint64_t>::min());
REQUIRE(e->id() <= std::numeric_limits<std::uint64_t>::max());
REQUIRE(e->isResponse() == false);
REQUIRE_THROWS([&]()
{
e->responseId();
}());
}
}
WHEN("creating an response Event")
{
std::unique_ptr<EventManager::Event> e = std::make_unique<EventManager::Event>(10);
std::unique_ptr<EventManager::Event> r = std::make_unique<EventManager::Event>(10, *e);
THEN("the attributes can be correctly fetched")
{
REQUIRE(r->id() >= std::numeric_limits<std::uint64_t>::min());
REQUIRE(r->id() <= std::numeric_limits<std::uint64_t>::max());
REQUIRE(r->isResponse() == true);
std::uint64_t rid=0;
REQUIRE_NOTHROW([&]()
{
rid=r->responseId();
}());
REQUIRE(rid == e->id());
}
}
}
}
Loading…
Cancel
Save