New EventBus 3.0

This commit is contained in:
Dawid Drozd 2019-12-27 11:21:43 +01:00
parent 6baa41917e
commit 2bc2858a8a
61 changed files with 2285 additions and 1509 deletions

View File

@ -4,7 +4,7 @@
# https://github.com/01org/parameter-framework/blob/master/.clang-format
# Tested on: clang-format version 8.0.0
# Version 1.1
# Version 1.2
# Common settings
@ -27,13 +27,14 @@ Language: Cpp
# void formatted_code_again;
DisableFormat: false
Standard: Cpp11
Standard: Auto
AccessModifierOffset: -4
AlignAfterOpenBracket: true
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: true
@ -105,13 +106,16 @@ NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000000
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000000
PointerAlignment: Left
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true

View File

@ -11,14 +11,13 @@ option(ENABLE_PERFORMANCE "Enable performance subproject" OFF)
set(CMAKE_CXX_STANDARD 17)
add_subdirectory(lib/)
add_subdirectory(use_case/)
if(ENABLE_TEST)
enable_testing()
add_subdirectory(test/)
endif()
add_subdirectory(sample/)
if(ENABLE_PERFORMANCE)
add_subdirectory(performance/)
endif()

View File

@ -33,18 +33,32 @@ include(cmake/InstallHelp.cmake)
# * <prefix>/include/
set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
set(EventBus_PUBLIC_HEADERS
src/dexode/EventBus.hpp
src/dexode/eventbus/Bus.hpp
src/dexode/eventbus/internal/event_id.hpp
src/dexode/eventbus/internal/listener_traits.hpp
src/dexode/eventbus/internal/ListenerAttorney.hpp
src/dexode/eventbus/Listener.hpp
src/dexode/eventbus/perk/PassPerk.hpp
src/dexode/eventbus/perk/Perk.hpp
src/dexode/eventbus/perk/PerkEventBus.hpp
src/dexode/eventbus/perk/TagPerk.hpp
src/dexode/eventbus/perk/WaitPerk.hpp
src/dexode/eventbus/permission/PostponeBus.hpp
src/dexode/eventbus/stream/EventStream.hpp
src/dexode/eventbus/stream/ProtectedEventStream.hpp
)
# Library definition
add_library(EventBus
src/dexode/EventBus.hpp
src/dexode/eventbus/internal/AsyncCallbackVector.h
src/dexode/eventbus/internal/CallbackVector.h
src/dexode/eventbus/internal/common.h
src/dexode/eventbus/internal/ListenerAttorney.hpp
src/dexode/eventbus/internal/TransactionCallbackVector.h
src/dexode/eventbus/Listener.hpp
src/dexode/eventbus/strategy/Protected.cpp src/dexode/eventbus/strategy/Protected.hpp
src/dexode/eventbus/strategy/Transaction.hpp
src/dexode/eventbus/TagEventBus.hpp
${EventBus_PUBLIC_HEADERS}
src/dexode/EventBus.cpp
src/dexode/eventbus/perk/PassPerk.cpp
src/dexode/eventbus/perk/Perk.cpp
src/dexode/eventbus/perk/PerkEventBus.cpp
src/dexode/eventbus/perk/TagPerk.cpp
src/dexode/eventbus/perk/WaitPerk.cpp
)
add_library(Dexode::EventBus ALIAS EventBus)
@ -55,16 +69,6 @@ target_include_directories(EventBus PUBLIC
$<INSTALL_INTERFACE:include/>
)
set(EventBus_PUBLIC_HEADERS
src/dexode/EventBus.hpp
src/dexode/eventbus/internal/AsyncCallbackVector.h
src/dexode/eventbus/internal/CallbackVector.h
src/dexode/eventbus/internal/common.h
src/dexode/eventbus/internal/TransactionCallbackVector.h
src/dexode/eventbus/Listener.hpp
src/dexode/eventbus/strategy/Protected.hpp
src/dexode/eventbus/strategy/Transaction.hpp
)
# Add definitions for targets
# Values:
# * Debug: -DEVENTBUS_DEBUG=1

130
lib/src/dexode/EventBus.cpp Normal file
View File

@ -0,0 +1,130 @@
//
// Created by gelldur on 26.11.2019.
//
#include "EventBus.hpp"
namespace dexode
{
std::size_t EventBus::processLimit(const std::size_t limit)
{
std::size_t processCount{0};
std::lock_guard writeGuardProcess{_mutexProcess}; // Only one process at the time
std::vector<std::unique_ptr<eventbus::stream::EventStream>> eventStreams;
{
std::lock_guard writeGuard{_mutexStreams};
std::swap(eventStreams, _eventStreams); // move data FROM member
}
// Now if any setStream would be called it doesn't conflict without our process call
for(auto& eventStream : eventStreams)
{
const auto runProcessCount = eventStream->process(limit);
processCount += runProcessCount;
if(processCount >= limit)
{
break;
}
}
{
std::lock_guard writeGuard{_mutexStreams};
if(not _eventStreams.empty())
{
// If anything was added then we need to add those elements
std::move(_eventStreams.begin(), _eventStreams.end(), std::back_inserter(eventStreams));
}
std::swap(eventStreams, _eventStreams); // move data TO member
// Check do we need remove something
if(_eventStreams.size() != _eventToStream.size())
{
auto removeFrom = std::remove_if(
_eventStreams.begin(), _eventStreams.end(), [this](const auto& eventStream) {
for(const auto& element : _eventToStream)
{
// Don't remove if we point to the same place (is it UB ?)
if(element.second == eventStream.get())
{
return false;
}
}
return true;
});
assert(removeFrom != _eventStreams.end());
_eventStreams.erase(removeFrom, _eventStreams.end());
}
}
return processCount;
}
eventbus::stream::EventStream* EventBus::findStream(
const eventbus::internal::event_id_t eventID) const
{
std::shared_lock readGuard{_mutexStreams};
return findStreamUnsafe(eventID);
}
void EventBus::unlistenAll(const std::uint32_t listenerID)
{
std::shared_lock readGuard{_mutexStreams};
for(auto& eventStream : _eventToStream)
{
eventStream.second->removeListener(listenerID);
}
}
eventbus::stream::EventStream* EventBus::findStreamUnsafe(
const eventbus::internal::event_id_t eventID) const
{
auto lookup = _eventToStream.find(eventID);
return lookup != _eventToStream.end() ? lookup->second : nullptr;
}
eventbus::stream::EventStream* EventBus::obtainStream(
const eventbus::internal::event_id_t eventID,
eventbus::CreateStreamCallback createStreamCallback)
{
std::lock_guard writeGuard{_mutexStreams};
auto* found = findStreamUnsafe(eventID);
if(found != nullptr)
{
return found;
}
else
{
auto stream = createStreamCallback();
_eventStreams.push_back(std::move(stream));
_eventToStream[eventID] = _eventStreams.back().get();
return _eventStreams.back().get();
}
}
bool EventBus::postponeEvent(eventbus::PostponeHelper& postponeCall)
{
auto* eventStream = obtainStream(postponeCall.eventID, postponeCall.createStreamCallback);
eventStream->postpone(std::move(postponeCall.event));
return true;
}
eventbus::stream::EventStream* EventBus::listen(const std::uint32_t,
const eventbus::internal::event_id_t eventID,
eventbus::CreateStreamCallback createStreamCallback)
{
auto* eventStream = obtainStream(eventID, createStreamCallback);
return eventStream;
}
void EventBus::unlisten(const std::uint32_t listenerID,
const eventbus::internal::event_id_t eventID)
{
auto* eventStream = findStream(eventID);
if(eventStream != nullptr)
{
eventStream->removeListener(listenerID);
}
}
} // namespace dexode

View File

@ -1,100 +1,54 @@
//
// Created by gelldur on 26.11.2019.
//
#pragma once
#include <atomic>
#include <cassert>
#include <functional>
#include <any>
#include <limits>
#include <map>
#include <memory>
#include <shared_mutex>
#include "dexode/eventbus/Listener.hpp"
#include "dexode/eventbus/internal/common.h"
#include "dexode/eventbus/Bus.hpp"
namespace dexode
{
template <class Strategy>
class EventBus
class EventBus : public dexode::eventbus::Bus
{
template <typename>
friend class dexode::eventbus::internal::ListenerAttorney;
public:
using Listener = eventbus::Listener<EventBus<Strategy>>;
constexpr EventBus() = default;
~EventBus() = default;
EventBus(const EventBus&) = delete;
EventBus(EventBus&&) = delete;
EventBus& operator=(EventBus&&) = delete;
EventBus& operator=(const EventBus&) = delete;
template <typename Event>
constexpr void post(const Event& event)
{
static_assert(eventbus::internal::validateEvent<Event>(), "Invalid event");
_base.template post<Event>(event);
}
template <typename Event>
constexpr void postpone(Event event)
{
static_assert(eventbus::internal::validateEvent<Event>(), "Invalid event");
_base.template postpone<Event>(std::move(event));
}
constexpr std::size_t processAll()
std::size_t process() override
{
return processLimit(std::numeric_limits<std::size_t>::max());
}
constexpr std::size_t processLimit(const std::size_t maxCountOfEvents)
{
return _base.processLimit(maxCountOfEvents);
}
std::size_t processLimit(std::size_t limit);
[[nodiscard]] constexpr std::size_t getPostponeEventCount() const
{
return _base.getQueueEventCount();
}
protected:
eventbus::stream::EventStream* obtainStream(
eventbus::internal::event_id_t eventID,
eventbus::CreateStreamCallback createStreamCallback);
Strategy& getStrategy()
{
return _base;
}
bool postponeEvent(eventbus::PostponeHelper& postponeCall) override;
eventbus::stream::EventStream* findStream(eventbus::internal::event_id_t eventID) const;
void unlistenAll(std::uint32_t listenerID) override;
eventbus::stream::EventStream* listen(
std::uint32_t listenerID,
eventbus::internal::event_id_t eventID,
eventbus::CreateStreamCallback createStreamCallback) override;
void unlisten(std::uint32_t listenerID, eventbus::internal::event_id_t eventID) override;
private:
std::atomic<std::uint32_t> _lastID{0};
Strategy _base;
mutable std::shared_mutex _mutexStreams;
std::shared_mutex _mutexProcess;
std::vector<std::unique_ptr<eventbus::stream::EventStream>> _eventStreams;
std::map<eventbus::internal::event_id_t, eventbus::stream::EventStream*> _eventToStream;
std::uint32_t newListenerID()
{
return ++_lastID;
}
template <class Event>
constexpr void listen(const std::uint32_t listenerID,
std::function<void(const Event&)>&& callback)
{
static_assert(eventbus::internal::validateEvent<Event>(), "Invalid event");
assert(callback && "callback should be valid"); // Check for valid object
_base.template listen<Event>(listenerID,
std::forward<std::function<void(const Event&)>>(callback));
}
constexpr void unlistenAll(const std::uint32_t listenerID)
{
_base.unlistenAll(listenerID);
}
template <typename Event>
constexpr void unlisten(const std::uint32_t listenerID)
{
static_assert(eventbus::internal::validateEvent<Event>(), "Invalid event");
const auto eventID = eventbus::internal::event_id<Event>;
_base.template unlisten(listenerID, eventID);
}
eventbus::stream::EventStream* findStreamUnsafe(eventbus::internal::event_id_t eventID) const;
};
} // namespace dexode

View File

@ -0,0 +1,126 @@
//
// Created by gelldur on 26.11.2019.
//
#pragma once
#include <any>
#include <atomic>
#include <memory>
#include "dexode/eventbus/Listener.hpp"
#include "dexode/eventbus/internal/ListenerAttorney.hpp"
#include "dexode/eventbus/internal/event_id.hpp"
#include "dexode/eventbus/stream/ProtectedEventStream.hpp"
namespace dexode::eventbus
{
class Bus;
template <typename Event>
using DefaultEventStream = eventbus::stream::ProtectedEventStream<Event>;
using CreateStreamCallback = std::unique_ptr<eventbus::stream::EventStream> (*const)();
using PostponeCallback = bool (*const)(Bus& bus, std::any event);
template <typename Event>
bool postpone(Bus& bus, std::any event);
template <typename Event>
std::unique_ptr<eventbus::stream::EventStream> createDefaultEventStream()
{
return std::make_unique<DefaultEventStream<Event>>();
}
class PostponeHelper
{
public:
internal::event_id_t eventID = nullptr;
std::any event;
PostponeCallback postponeCallback = nullptr; // function pointer
CreateStreamCallback createStreamCallback = nullptr; // function pointer
PostponeHelper(const internal::event_id_t eventId,
std::any&& event,
PostponeCallback postponeCallback,
CreateStreamCallback createStreamCallback)
: eventID(eventId)
, event(std::forward<std::any>(event))
, postponeCallback(postponeCallback)
, createStreamCallback(createStreamCallback)
{}
template <typename Event>
static PostponeHelper create(std::any&& event)
{
return PostponeHelper{internal::event_id<Event>(),
std::forward<std::any>(event),
postpone<Event>,
createDefaultEventStream<Event>};
}
~PostponeHelper() = default;
};
class Bus
{
template <typename>
friend class dexode::eventbus::internal::ListenerAttorney;
public:
using Listener = eventbus::Listener<dexode::eventbus::Bus>;
Bus() = default;
virtual ~Bus() = default;
virtual std::size_t process() = 0;
template <typename Event>
bool postpone(Event event)
{
static_assert(internal::validateEvent<Event>(), "Invalid event");
auto postponeCall = PostponeHelper::create<Event>(std::move(event));
return postponeEvent(postponeCall);
}
protected:
virtual bool postponeEvent(PostponeHelper& postponeCall) = 0;
virtual eventbus::stream::EventStream* listen(std::uint32_t listenerID,
internal::event_id_t eventID,
CreateStreamCallback createStreamCallback) = 0;
virtual void unlistenAll(std::uint32_t listenerID) = 0;
virtual void unlisten(std::uint32_t listenerID, internal::event_id_t eventID) = 0;
private:
std::atomic<std::uint32_t> _lastID{0};
std::uint32_t newListenerID()
{
return ++_lastID; // used for generate unique listeners ID's
}
template <class Event>
void listen(const std::uint32_t listenerID, std::function<void(const Event&)>&& callback)
{
static_assert(internal::validateEvent<Event>(), "Invalid event");
assert(callback && "callback should be valid"); // Check for valid object
constexpr auto eventID = internal::event_id<Event>();
auto* eventStream = listen(listenerID, eventID, createDefaultEventStream<Event>);
if(eventStream != nullptr) // maybe someone don't want add listener
{
eventStream->addListener(listenerID,
std::forward<std::function<void(const Event&)>>(callback));
}
}
};
template <typename Event>
bool postpone(Bus& bus, std::any event)
{
return bus.postpone(std::move(std::any_cast<Event>(event)));
}
} // namespace dexode::eventbus

View File

@ -5,6 +5,8 @@
#include <memory>
#include "dexode/eventbus/internal/ListenerAttorney.hpp"
#include "dexode/eventbus/internal/event_id.hpp"
#include "dexode/eventbus/internal/listener_traits.hpp"
namespace dexode::eventbus
{
@ -28,8 +30,9 @@ public:
Listener(const Listener& other) = delete;
Listener(Listener&& other)
: _id(other._id)
Listener(Listener&& other) noexcept
: _id(other._id) // we don't have to reset listener ID as _bus is moved and we won't call
// unlistenAll
, _bus(std::move(other._bus))
{}
@ -54,15 +57,35 @@ public:
{
unlistenAll(); // remove previous
}
// we don't have reset listener ID as bus is moved and we won't call unlistenAll
_id = other._id;
_bus = std::move(other._bus);
return *this;
}
template <class Event>
void listen(std::function<void(const Event&)>&& callback)
template <class Event, typename _ = void>
constexpr void listen(std::function<void(const Event&)>&& callback)
{
static_assert(internal::validateEvent<Event>(), "Invalid event");
listenToCallback<Event>(std::forward<std::function<void(const Event&)>>(callback));
}
template <class EventCallback, typename Event = internal::first_argument<EventCallback>>
constexpr void listen(EventCallback&& callback)
{
static_assert(std::is_const_v<std::remove_reference_t<Event>>, "Event should be const");
static_assert(std::is_reference_v<Event>, "Event should be const & (reference)");
using PureEvent = std::remove_const_t<std::remove_reference_t<Event>>;
static_assert(internal::validateEvent<PureEvent>(), "Invalid event");
listenToCallback<PureEvent>(std::forward<EventCallback>(callback));
}
template <class Event>
void listenToCallback(std::function<void(const Event&)>&& callback)
{
static_assert(internal::validateEvent<Event>(), "Invalid event");
if(_bus == nullptr)
{
throw std::runtime_error{"bus is null"};
@ -72,6 +95,18 @@ public:
*_bus, _id, std::forward<std::function<void(const Event&)>>(callback));
}
template <class Event>
void listenToCallback(const std::function<void(const Event&)>& callback)
{
static_assert(internal::validateEvent<Event>(), "Invalid event");
if(_bus == nullptr)
{
throw std::runtime_error{"bus is null"};
}
internal::ListenerAttorney<Bus>::template listen<Event>(
*_bus, _id, std::function<void(const Event&)>{callback});
}
void unlistenAll()
{
if(_bus == nullptr)
@ -84,11 +119,12 @@ public:
template <typename Event>
void unlisten()
{
static_assert(internal::validateEvent<Event>(), "Invalid event");
if(_bus == nullptr)
{
throw std::runtime_error{"bus is null"};
}
internal::ListenerAttorney<Bus>::template unlisten<Event>(*_bus, _id);
internal::ListenerAttorney<Bus>::unlisten(*_bus, _id, internal::event_id<Event>());
}
private:

View File

@ -1,121 +0,0 @@
//
// Created by gelldur on 30.10.2019.
//
#pragma once
#include <map>
#include <memory>
#include <string>
#include "dexode/EventBus.hpp"
namespace dexode::eventbus
{
template <class Strategy>
class TagEventBus
{
template <typename>
friend class dexode::eventbus::internal::ListenerAttorney;
using EventBus_t = EventBus<Strategy>;
public:
using ListenerAll = eventbus::Listener<TagEventBus<Strategy>>;
using Listener = typename EventBus<Strategy>::Listener; // alias
TagEventBus(const std::vector<std::string>& tags)
{
for(const auto& tag : tags)
{
_tagToBus.emplace(tag, std::make_shared<EventBus_t>());
}
}
~TagEventBus() = default;
TagEventBus(const TagEventBus&) = delete;
TagEventBus(TagEventBus&&) = delete;
TagEventBus& operator=(TagEventBus&&) = delete;
TagEventBus& operator=(const TagEventBus&) = delete;
template <typename Event>
void post(const Event& event)
{
_allBus.post(event);
for(auto& element : _tagToBus)
{
element.second->post(event);
}
}
template <typename Event>
void postpone(Event event)
{
_allBus.postpone(event);
for(auto& element : _tagToBus)
{
element.second->postpone(event);
}
}
template <typename Event>
void post(const std::string& tag, const Event& event)
{
_allBus.post(event);
_tagToBus.at(tag)->post(event);
}
template <typename Event>
void postpone(const std::string& tag, Event event)
{
_allBus.postpone(event);
_tagToBus.at(tag)->postpone(event);
}
constexpr std::size_t processAll()
{
return processLimit(std::numeric_limits<std::size_t>::max());
}
constexpr std::size_t processLimit(const std::size_t maxCountOfEvents)
{
return _allBus.processLimit(maxCountOfEvents);
}
const std::shared_ptr<EventBus<Strategy>>& get(const std::string& tag)
{
return _tagToBus.at(tag);
}
private:
EventBus_t _allBus;
std::map<std::string, std::shared_ptr<EventBus_t>> _tagToBus;
constexpr std::uint32_t newListenerID()
{
return internal::ListenerAttorney<EventBus_t>::newListenerID(_allBus);
}
template <class Event>
constexpr void listen(const std::uint32_t listenerID,
std::function<void(const Event&)> callback)
{
internal::ListenerAttorney<EventBus_t>::template listen<Event>(
_allBus, listenerID, std::move(callback));
}
constexpr void unlistenAll(const std::uint32_t listenerID)
{
internal::ListenerAttorney<EventBus_t>::unlistenAll(_allBus, listenerID);
}
template <typename Event>
constexpr void unlisten(const std::uint32_t listenerID)
{
internal::ListenerAttorney<EventBus_t>::template unlisten<Event>(_allBus, listenerID);
}
};
} // namespace dexode::eventbus

View File

@ -1,37 +0,0 @@
#pragma once
#include <algorithm>
#include <functional>
#include <vector>
#include "CallbackVector.h"
namespace dexode::eventbus::internal
{
template <typename Event>
struct AsyncCallbackVector : public CallbackVector
{
using CallbackType = std::function<void(const Event&)>;
using ContainerElement = std::pair<int, CallbackType>;
std::vector<ContainerElement> container;
virtual void remove(const int token) override
{
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());
}
}
void add(const int token, CallbackType callback)
{
container.emplace_back(token, std::move(callback));
}
};
} // namespace dexode::eventbus::internal

View File

@ -1,13 +0,0 @@
#pragma once
namespace dexode::eventbus::internal
{
struct CallbackVector
{
virtual ~CallbackVector() = default;
virtual void remove(const int token) = 0;
};
} // namespace dexode::eventbus::internal

View File

@ -3,15 +3,15 @@
//
#pragma once
//#include "dexode/EventBus.hpp"
#include <functional>
#include "dexode/eventbus/internal/event_id.hpp"
namespace dexode::eventbus
{
template <typename>
class Listener;
template <typename>
class TagEventBus;
} // namespace dexode::eventbus
namespace dexode::eventbus::internal
@ -23,9 +23,6 @@ class ListenerAttorney
template <typename>
friend class dexode::eventbus::Listener;
template <typename>
friend class eventbus::TagEventBus;
private:
static constexpr std::uint32_t newListenerID(EventBus_t& bus)
{
@ -46,10 +43,11 @@ private:
bus.unlistenAll(listenerID);
}
template <typename Event>
static constexpr void unlisten(EventBus_t& bus, const std::uint32_t listenerID)
static constexpr void unlisten(EventBus_t& bus,
const std::uint32_t listenerID,
const event_id_t eventID)
{
bus.template unlisten<Event>(listenerID);
bus.unlisten(listenerID, eventID);
}
};

View File

@ -1,85 +0,0 @@
#pragma once
#include <algorithm>
#include <functional>
#include <vector>
#include "CallbackVector.h"
namespace dexode::eventbus::internal
{
template <typename Event>
struct TransactionCallbackVector : public CallbackVector
{
using CallbackType = std::function<void(const Event&)>;
using ContainerElement = std::pair<int, CallbackType>;
using ContainerType = std::vector<ContainerElement>;
ContainerType container;
ContainerType toAdd;
std::vector<int> toRemove;
int inTransaction = 0;
virtual void remove(const int token) override
{
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())
{
container.erase(removeFrom, container.end());
}
}
void add(const int token, const CallbackType& callback)
{
if(inTransaction > 0)
{
toAdd.emplace_back(token, callback);
}
else
{
container.emplace_back(token, callback);
}
}
void beginTransaction()
{
++inTransaction;
}
void commitTransaction()
{
--inTransaction;
if(inTransaction > 0)
{
return;
}
inTransaction = 0;
if(toAdd.empty() == false)
{
container.insert(container.end(), toAdd.begin(), toAdd.end());
toAdd.clear();
}
if(toRemove.empty() == false)
{
for(auto token : toRemove)
{
remove(token);
}
toRemove.clear();
}
}
};
} // namespace dexode::eventbus::internal

View File

@ -5,12 +5,21 @@
namespace dexode::eventbus::internal
{
using event_id_t = std::size_t;
template <typename T>
struct type_id_ptr
{
static const T* const id;
};
template <typename T>
const T* const type_id_ptr<T>::id = nullptr;
using event_id_t = const void*;
template <typename T>
constexpr event_id_t event_id() // Helper for getting "type id"
{
return typeid(T).hash_code();
return &type_id_ptr<T>::id;
}
template <class Event>

View File

@ -0,0 +1,27 @@
//
// Created by gelldur on 22.12.2019.
//
#pragma once
#include <functional>
#include <type_traits>
namespace dexode::eventbus::internal
{
template <typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret (*)(Arg, Rest...));
template <typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret (F::*)(Arg, Rest...));
template <typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret (F::*)(Arg, Rest...) const);
template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);
template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));
} // namespace dexode::eventbus::internal

View File

@ -0,0 +1,17 @@
//
// Created by gelldur on 24.12.2019.
//
#include "PassPerk.hpp"
#include "dexode/eventbus/Bus.hpp"
namespace dexode::eventbus::perk
{
Flag PassEverythingPerk::onPrePostponeEvent(PostponeHelper& postponeCall)
{
postponeCall.postponeCallback(*_passToBus, std::move(postponeCall.event));
return Flag::postpone_cancel;
}
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,32 @@
//
// Created by gelldur on 24.12.2019.
//
#pragma once
#include <memory>
#include "Perk.hpp"
namespace dexode::eventbus
{
class PostponeHelper;
class Bus;
} // namespace dexode::eventbus
namespace dexode::eventbus::perk
{
class PassEverythingPerk : public Perk
{
public:
PassEverythingPerk(std::shared_ptr<dexode::eventbus::Bus> passTo)
: _passToBus{std::move(passTo)}
{}
Flag onPrePostponeEvent(PostponeHelper& postponeCall);
private:
std::shared_ptr<dexode::eventbus::Bus> _passToBus;
};
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,9 @@
//
// Created by gelldur on 23.12.2019.
//
#include "Perk.hpp"
namespace dexode::eventbus::perk
{
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,22 @@
//
// Created by gelldur on 23.12.2019.
//
#pragma once
namespace dexode::eventbus::perk
{
enum class Flag : int
{
nop,
postpone_cancel,
postpone_continue,
};
class Perk
{
public:
virtual ~Perk() = default;
};
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,39 @@
//
// Created by gelldur on 23.12.2019.
//
#include "PerkEventBus.hpp"
namespace dexode::eventbus::perk
{
PerkEventBus::RegisterHelper PerkEventBus::addPerk(std::unique_ptr<Perk> perk)
{
auto* local = perk.get();
_perks.push_back(std::move(perk));
return RegisterHelper(this, local);
}
bool PerkEventBus::postponeEvent(PostponeHelper& postponeCall)
{
for(const auto& onPrePostpone : _onPrePostpone)
{
if(onPrePostpone(postponeCall) == perk::Flag::postpone_cancel)
{
return false;
}
}
if(EventBus::postponeEvent(postponeCall))
{
for(const auto& onPostPostpone : _onPostPostpone)
{
if(onPostPostpone(postponeCall) == perk::Flag::postpone_cancel)
{
break;
}
}
return true;
}
return false;
}
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,74 @@
//
// Created by gelldur on 23.12.2019.
//
#pragma once
#include <memory>
#include <vector>
#include "Perk.hpp"
#include "dexode/EventBus.hpp"
namespace dexode::eventbus::perk
{
class PerkEventBus : public EventBus
{
public:
class RegisterHelper
{
friend PerkEventBus;
public:
template <typename Perk_t>
RegisterHelper& registerPrePostpone(perk::Flag (Perk_t::*method)(PostponeHelper&))
{
_bus->_onPrePostpone.push_back(
std::bind(method, static_cast<Perk_t*>(_perk), std::placeholders::_1));
return *this;
}
template <typename Perk_t>
RegisterHelper& registerPostPostpone(perk::Flag (Perk_t::*method)(PostponeHelper&))
{
_bus->_onPostPostpone.push_back(
std::bind(method, static_cast<Perk_t*>(_perk), std::placeholders::_1));
return *this;
}
private:
PerkEventBus* _bus;
Perk* _perk;
RegisterHelper(PerkEventBus* bus, Perk* perk)
: _bus(bus)
, _perk(perk)
{}
};
RegisterHelper addPerk(std::unique_ptr<Perk> perk);
template <typename T>
T* getPerk()
{
auto found =
std::find_if(_perks.begin(), _perks.end(), [](const std::unique_ptr<Perk>& perk) {
return dynamic_cast<T*>(perk.get()) != nullptr;
});
if(found != _perks.end())
{
return static_cast<T*>(found->get());
}
return nullptr;
}
protected:
bool postponeEvent(PostponeHelper& postponeCall) override;
private:
std::vector<std::unique_ptr<Perk>> _perks;
std::vector<std::function<perk::Flag(PostponeHelper&)>> _onPrePostpone;
std::vector<std::function<perk::Flag(PostponeHelper&)>> _onPostPostpone;
};
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,20 @@
//
// Created by gelldur on 24.12.2019.
//
#include "TagPerk.hpp"
namespace dexode::eventbus::perk
{
Flag TagPerk::onPrePostponeEvent(PostponeHelper& postponeCall)
{
if(auto found = _eventsToWrap.find(postponeCall.eventID); found != _eventsToWrap.end())
{
found->second(postponeCall.event);
return Flag::postpone_cancel;
}
return Flag::postpone_continue;
}
}

View File

@ -0,0 +1,48 @@
//
// Created by gelldur on 24.12.2019.
//
#pragma once
#include <any>
#include <functional>
#include <map>
#include <string>
#include "Perk.hpp"
#include "dexode/eventbus/Bus.hpp"
#include "dexode/eventbus/internal/event_id.hpp"
namespace dexode::eventbus::perk
{
class TagPerk : public Perk
{
public:
TagPerk(std::string tag, dexode::eventbus::Bus* owner)
: _tag{std::move(tag)}
, _ownerBus{owner}
{}
Flag onPrePostponeEvent(PostponeHelper& postponeCall);
template <typename TagEvent>
TagPerk& wrapTag()
{
static_assert(internal::validateEvent<TagEvent>(), "Invalid tag event");
static_assert(internal::validateEvent<typename TagEvent::Event>(), "Invalid event");
constexpr auto eventID = internal::event_id<typename TagEvent::Event>();
_eventsToWrap[eventID] = [this](std::any event) {
TagEvent newEvent{_tag, std::move(std::any_cast<typename TagEvent::Event>(event))};
_ownerBus->postpone<TagEvent>(std::move(newEvent));
};
return *this;
}
private:
std::map<internal::event_id_t, std::function<void(std::any)>> _eventsToWrap;
std::string _tag;
dexode::eventbus::Bus* _ownerBus;
};
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,35 @@
//
// Created by gelldur on 24.12.2019.
//
#include "WaitPerk.hpp"
namespace dexode::eventbus::perk
{
bool WaitPerk::wait()
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(_waitMutex);
_eventWaiting.wait(lock);
return true;
}
bool WaitPerk::waitFor(const std::chrono::milliseconds timeout)
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(_waitMutex);
if(_eventWaiting.wait_for(lock, timeout) == std::cv_status::timeout)
{
return false;
}
return true;
}
Flag WaitPerk::onPostponeEvent(PostponeHelper&)
{
_eventWaiting.notify_one();
return Flag::postpone_continue;
}
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,33 @@
//
// Created by gelldur on 24.12.2019.
//
#pragma once
#include <chrono>
#include <condition_variable>
#include <mutex>
#include "Perk.hpp"
namespace dexode::eventbus
{
class PostponeHelper;
}
namespace dexode::eventbus::perk
{
class WaitPerk : public Perk
{
public:
bool wait();
bool waitFor(std::chrono::milliseconds timeout);
Flag onPostponeEvent(PostponeHelper& postponeCall);
private:
std::condition_variable _eventWaiting;
std::mutex _waitMutex;
};
} // namespace dexode::eventbus::perk

View File

@ -0,0 +1,35 @@
//
// Created by gelldur on 24.12.2019.
//
#pragma once
#include <memory>
#include "dexode/eventbus/Bus.hpp"
namespace dexode::eventbus::permission
{
/**
* Intention of this helper is to hide API of other bus but allow only to postpone events
* So no:
* - listening
* - processing
*/
class PostponeBus
{
public:
PostponeBus(std::shared_ptr<Bus> hideBus)
: _hideBus{std::move(hideBus)}
{}
template <typename Event>
constexpr bool postpone(Event event)
{
return _hideBus->postpone(event);
}
private:
std::shared_ptr<Bus> _hideBus;
};
} // namespace dexode::eventbus::permission

View File

@ -1,71 +0,0 @@
#include "dexode/eventbus/strategy/Protected.hpp"
namespace dexode::eventbus::strategy
{
std::size_t Protected::processLimit(const std::size_t maxCountOfEvents)
{
std::size_t processed = 0;
std::function<void()> postponeEventCallback;
while(processed < maxCountOfEvents)
{
{
std::unique_lock writeLock{_mutex};
if(_eventQueue.empty())
{
break;
}
postponeEventCallback = std::move(_eventQueue.front());
_eventQueue.pop_front();
}
postponeEventCallback();
++processed;
}
return processed;
}
void Protected::unlistenAll(const std::uint32_t listenerID)
{
std::unique_lock writeLock{_mutex}; // TODO locking already locked mutex
for(auto& element : _callbacks)
{
element.second->remove(listenerID);
}
}
bool Protected::wait()
{
// Extra check before wait because maybe we already have something in queue
if(hasEvents())
{
return true;
}
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(_waitMutex);
_eventWaiting.wait(lock);
return hasEvents();
}
bool Protected::waitFor(std::chrono::milliseconds timeout)
{
// Extra check before wait because maybe we already have something in queue
if(hasEvents())
{
return true;
}
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(_waitMutex);
_eventWaiting.wait_for(lock, timeout);
return hasEvents();
}
bool Protected::hasEvents() const
{
std::shared_lock readLock{_mutex};
return not _eventQueue.empty();
}
} // namespace dexode::eventbus::strategy

View File

@ -1,115 +0,0 @@
#pragma once
#include <condition_variable>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <vector>
#include "dexode/EventBus.hpp"
#include "dexode/eventbus/internal/AsyncCallbackVector.h"
namespace dexode::eventbus::strategy
{
class Protected
{
public:
Protected() = default;
~Protected() = default;
Protected(const Protected&) = delete;
Protected(Protected&&) = delete;
Protected& operator=(Protected&&) = delete;
Protected& operator=(const Protected&) = delete;
template <typename Event>
void post(const Event& event)
{
std::shared_lock readLock{_mutex};
using Vector = eventbus::internal::AsyncCallbackVector<Event>;
auto found = _callbacks.find(eventbus::internal::event_id<Event>());
if(found == _callbacks.end())
{
return; // no such notifications
}
std::unique_ptr<eventbus::internal::CallbackVector>& vector = found->second;
assert(dynamic_cast<Vector*>(vector.get()));
auto* callbacks = static_cast<Vector*>(vector.get());
for(const auto& element : callbacks->container)
{
element.second(event);
}
}
template <typename Event>
void postpone(Event&& event)
{
{
std::unique_lock writeLock{_mutex};
_eventQueue.push_back(
[this, event = std::forward<Event>(event)]() { post<Event>(event); });
}
_eventWaiting.notify_one();
}
std::size_t processLimit(std::size_t maxCountOfEvents);
std::size_t getPostponeEventCount() const
{
std::shared_lock lock{_mutex};
return _eventQueue.size();
}
bool wait();
bool waitFor(std::chrono::milliseconds timeout);
template <class Event>
void listen(const std::uint32_t listenerID, std::function<void(const Event&)>&& callback)
{
using Vector = eventbus::internal::AsyncCallbackVector<Event>;
std::unique_lock writeLock{_mutex};
auto eventListeners = _callbacks.find(eventbus::internal::event_id<Event>());
if(eventListeners == _callbacks.cend())
{
eventListeners = _callbacks.emplace_hint(
eventListeners, eventbus::internal::event_id<Event>(), std::make_unique<Vector>());
}
assert(dynamic_cast<Vector*>(eventListeners->second.get()));
auto* vectorImpl = static_cast<Vector*>(eventListeners->second.get());
vectorImpl->add(listenerID, std::forward<std::function<void(const Event&)>>(callback));
}
void unlistenAll(std::uint32_t listenerID);
template <typename Event>
void unlisten(const std::uint32_t listenerID)
{
std::unique_lock writeLock{_mutex}; // TODO locking already locked mutex
auto found = _callbacks.find(eventbus::internal::event_id<Event>());
if(found != _callbacks.end())
{
found->second->remove(listenerID);
}
}
private:
std::map<eventbus::internal::event_id_t, std::unique_ptr<eventbus::internal::CallbackVector>>
_callbacks;
mutable std::shared_mutex _mutex;
std::mutex _waitMutex;
std::condition_variable _eventWaiting;
std::deque<std::function<void()>> _eventQueue;
bool hasEvents() const;
};
} // namespace dexode::eventbus::strategy

View File

@ -1,115 +0,0 @@
#pragma once
#include <deque>
#include <map>
#include <memory>
#include <vector>
#include "dexode/EventBus.hpp"
#include "dexode/eventbus/internal/TransactionCallbackVector.h"
namespace dexode::eventbus::strategy
{
class Transaction
{
public:
Transaction() = default;
~Transaction() = default;
Transaction(const Transaction&) = delete;
Transaction(Transaction&&) = delete;
Transaction& operator=(Transaction&&) = delete;
Transaction& operator=(const Transaction&) = delete;
template <typename Event>
void post(const Event& event)
{
using Vector = eventbus::internal::TransactionCallbackVector<Event>;
auto found = _callbacks.find(eventbus::internal::event_id<Event>());
if(found == _callbacks.end())
{
return; // no such notifications
}
std::unique_ptr<eventbus::internal::CallbackVector>& vector = found->second;
assert(dynamic_cast<Vector*>(vector.get()));
auto* vectorImpl = static_cast<Vector*>(vector.get());
vectorImpl->beginTransaction();
for(const auto& element : vectorImpl->container)
{
element.second(event);
}
vectorImpl->commitTransaction();
}
template <typename Event>
void postpone(Event&& event)
{
_eventQueue.push_back([this, event = std::forward<Event>(event)]() { post<Event>(event); });
}
std::size_t processLimit(const std::size_t maxCountOfEvents)
{
std::size_t processed = 0;
while(processed < maxCountOfEvents && not _eventQueue.empty())
{
auto asyncCallback = std::move(_eventQueue.front());
_eventQueue.pop_front();
// Needs to be done in this way. Think about recursion in this case...
asyncCallback();
++processed;
}
return processed;
}
[[nodiscard]] std::size_t getQueueEventCount() const noexcept
{
return _eventQueue.size();
}
template <class Event>
void listen(const std::uint32_t listenerID, std::function<void(const Event&)>&& callback)
{
using Vector = eventbus::internal::TransactionCallbackVector<Event>;
std::unique_ptr<eventbus::internal::CallbackVector>& vector =
_callbacks[eventbus::internal::event_id<Event>()];
if(vector == nullptr)
{
vector = std::make_unique<Vector>();
}
assert(dynamic_cast<Vector*>(vector.get()));
auto* vectorImpl = static_cast<Vector*>(vector.get());
vectorImpl->add(listenerID, std::forward<std::function<void(const Event&)>>(callback));
}
void unlistenAll(const std::uint32_t listenerID)
{
for(auto& element : _callbacks)
{
element.second->remove(listenerID);
}
}
template <typename Event>
void unlisten(const std::uint32_t listenerID)
{
static_assert(eventbus::internal::validateEvent<Event>(), "Invalid event");
auto found = _callbacks.find(eventbus::internal::event_id<Event>());
if(found != _callbacks.end())
{
found->second->remove(listenerID);
}
}
private:
std::map<eventbus::internal::event_id_t, std::unique_ptr<eventbus::internal::CallbackVector>>
_callbacks;
std::deque<std::function<void()>> _eventQueue;
};
} // namespace dexode::eventbus::strategy

View File

@ -0,0 +1,42 @@
#pragma once
#include <any>
#include <cstdint>
namespace dexode::eventbus::stream
{
class EventStream
{
public:
virtual ~EventStream() = default;
virtual void postpone(std::any event) = 0;
virtual std::size_t process(std::size_t limit) = 0;
virtual bool addListener(std::uint32_t listenerID, std::any callback) = 0;
virtual bool removeListener(std::uint32_t listenerID) = 0;
};
class NoopEventStream : public EventStream
{
public:
void postpone(std::any event) override
{
throw std::runtime_error{"Noop"};
}
size_t process(std::size_t limit) override
{
throw std::runtime_error{"Noop"};
}
bool addListener(std::uint32_t listenerID, std::any callback) override
{
throw std::runtime_error{"Noop"};
}
bool removeListener(std::uint32_t listenerID) override
{
throw std::runtime_error{"Noop"};
}
};
} // namespace dexode::eventbus::stream

View File

@ -0,0 +1,163 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <functional>
#include <shared_mutex>
#include "dexode/eventbus/stream/EventStream.hpp"
namespace dexode::eventbus::stream
{
template <typename Event, typename CallbackReturn = void, typename... ExtraArgTypes>
class ProtectedEventStream : public EventStream
{
using Callback = std::function<CallbackReturn(const Event&, ExtraArgTypes...)>;
public:
void postpone(std::any event) override
{
auto myEvent = std::any_cast<Event>(event);
std::lock_guard writeGuard{_mutexEvent};
_queue.push_back(std::move(myEvent));
}
std::size_t process(const std::size_t limit) override
{
std::vector<Event> processEvents;
{
std::lock_guard writeGuard{_mutexEvent};
if(limit >= _queue.size())
{
processEvents.reserve(_queue.size());
std::swap(processEvents, _queue);
}
else
{
const auto countElements = std::min(limit, _queue.size());
processEvents.reserve(countElements);
std::move(_queue.begin(),
std::next(_queue.begin(), countElements),
std::back_inserter(processEvents));
}
}
for(const auto& event : processEvents)
{
// At this point we need to consider transaction safety as during some notification
// we can add/remove listeners
_isProcessing = true;
for(auto& callback : _callbacks)
{
callback(event);
}
_isProcessing = false;
flushWaitingOnes();
}
return processEvents.size();
}
bool addListener(const std::uint32_t listenerID, std::any callback) override
{
std::lock_guard writeGuard{_mutexCallbacks};
auto myCallback = std::any_cast<Callback>(callback);
if(_isProcessing)
{
_waiting.emplace_back(listenerID, std::move(myCallback));
return true;
}
return rawAddListener(listenerID, std::move(myCallback));
}
bool removeListener(const std::uint32_t listenerID) override
{
std::lock_guard writeGuard{_mutexCallbacks};
if(_isProcessing)
{
_waiting.emplace_back(listenerID, Callback{});
return true;
}
return rawRemoveListener(listenerID);
}
[[nodiscard]] bool hasEvents() const
{
std::shared_lock readGuard{_mutexEvent};
return not _queue.empty();
}
private:
std::vector<std::uint32_t> _listenerIDs;
std::vector<Event> _queue;
std::vector<Callback> _callbacks;
std::atomic<bool> _isProcessing{false};
std::vector<std::pair<std::uint32_t, Callback>> _waiting;
std::shared_mutex _mutexEvent;
std::shared_mutex _mutexCallbacks;
void flushWaitingOnes()
{
assert(_isProcessing == false);
std::lock_guard writeGuard{_mutexCallbacks};
if(_waiting.empty())
{
return;
}
for(auto&& element : _waiting)
{
if(element.second) // if callable it means we want to add
{
rawAddListener(element.first, std::move(element.second));
}
else // if not callable we want to remove
{
rawRemoveListener(element.first);
}
}
_waiting.clear();
}
bool rawAddListener(const std::uint32_t listenerID, Callback&& callback)
{
auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID);
if(found != _listenerIDs.end())
{
/// ###### IMPORTANT ######
/// This exception has some reason.
/// User should use multiple listeners instead of one. Thanks to that it makes
/// it more clear what will happen when call unlisten<Event> with specific Event
throw std::invalid_argument{std::string{"Already added listener for event: "} +
typeid(Event).name()};
}
_callbacks.push_back(std::forward<Callback>(callback));
_listenerIDs.push_back(listenerID);
assert(_listenerIDs.size() == _callbacks.size());
return true;
}
bool rawRemoveListener(const std::uint32_t listenerID)
{
auto found = std::find(_listenerIDs.begin(), _listenerIDs.end(), listenerID);
if(found == _listenerIDs.end())
{
return false;
}
const auto index = std::distance(_listenerIDs.begin(), found);
_listenerIDs.erase(found);
_callbacks.erase(std::next(_callbacks.begin(), index));
assert(_listenerIDs.size() == _callbacks.size());
return true;
}
};
} // namespace dexode::eventbus::stream

View File

@ -1,55 +0,0 @@
#include <eventbus/AsyncEventBus.h>
namespace Dexode
{
std::size_t AsyncEventBus::processCommandsAndGetQueuedEventsCount()
{
std::lock_guard<std::mutex> guard {_eventMutex};
while(_commandsQueue.empty() == false)
{
_commandsQueue
.front()(); //This can't add any extra commands, because in this queue we story only listen/unlisten stuff
_commandsQueue.pop_front();
}
//Yeah we want to return events count. So don't have to call getQueueEventCount
return _eventQueue.size();
}
int AsyncEventBus::consume(int max)
{
int consumed = 0;
std::function<void()> eventCommand;
while(processCommandsAndGetQueuedEventsCount() > 0 && consumed < max) //order is important
{
{
std::lock_guard<std::mutex> guard {_eventMutex};
eventCommand = std::move(_eventQueue.front());
_eventQueue.pop_front();
}
eventCommand();
++consumed;
}
return consumed;
}
bool AsyncEventBus::wait()
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(_waitMutex);
_eventWaiting.wait(lock);
return not _eventQueue.empty();
}
bool AsyncEventBus::waitFor(std::chrono::milliseconds timeout)
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(_waitMutex);
_eventWaiting.wait_for(lock, timeout);
return not _eventQueue.empty();
}
} // namespace Dexode

View File

@ -0,0 +1,54 @@
//
// Created by gelldur on 14.06.19.
//
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <benchmark/benchmark.h>
#include <eventbus/AsyncEventBus.h>
#include <eventbus/TokenHolder.h>
namespace
{
struct SimpleEvent
{
std::int64_t value = 0;
};
Dexode::AsyncEventBus bus;
} // namespace
void checkFor(benchmark::State& state)
{
if(state.thread_index == 0)
{
Dexode::TokenHolder<Dexode::AsyncEventBus> listener {&bus};
std::uint64_t consumed = 0;
listener.listen<SimpleEvent>(
[&consumed](const auto& event) { benchmark::DoNotOptimize(consumed += 1); });
for(auto _ : state)
{
//if(bus.wait())
{
bus.consume();
}
}
state.counters["consumed"] = consumed;
}
else
{
for(auto _ : state)
{
bus.schedule(SimpleEvent {std::chrono::steady_clock::now().time_since_epoch().count()});
}
}
}
BENCHMARK(checkFor)->Threads(2)->MinTime(1)->MeasureProcessCPUTime();
BENCHMARK(checkFor)->Threads(5)->MinTime(1)->MeasureProcessCPUTime();
BENCHMARK(checkFor)->Threads(10)->MinTime(1)->MeasureProcessCPUTime();

View File

@ -1,13 +0,0 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(Sample LANGUAGES CXX)
add_executable(Sample
src/main.cpp
)
if(NOT TARGET Dexode::EventBus)
find_package(EventBus CONFIG REQUIRED)
endif()
target_link_libraries(Sample PRIVATE Dexode::EventBus)

View File

@ -16,12 +16,14 @@ find_package(Threads REQUIRED)
# Target definition
add_executable(EventBusTest
src/AsyncEventBusTest.cpp
src/EventCollectorTest.cpp
src/EventIdTest.cpp
src/NotifierTest.cpp
src/TestTagEventBus.cpp
src/dexode/eventbus/test/event.hpp
src/dexode/eventbus/test/SuiteConcurrentEventBus.cpp
src/dexode/eventbus/test/SuiteEventBus.cpp
src/dexode/eventbus/test/SuiteEventID.cpp
src/dexode/eventbus/test/SuiteListener.cpp
src/main.cpp
)
target_include_directories(EventBusTest PRIVATE src/)
target_compile_options(EventBusTest PUBLIC
-Wall -pedantic

View File

@ -1,146 +0,0 @@
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <catch2/catch.hpp>
#include <dexode/EventBus.hpp>
#include <dexode/eventbus/strategy/Protected.hpp>
using namespace std::chrono;
const auto ns2 = std::chrono::nanoseconds{2}; // GCC 7 has some issues with 2ms :/
const auto ns3 = std::chrono::nanoseconds{3};
using namespace dexode;
using ProtectedEventBus = EventBus<eventbus::strategy::Protected>;
using Listener = ProtectedEventBus::Listener;
TEST_CASE("Should consume events in synchronous way When using AsyncEventBus", "[AsyncEventBus]")
{
struct SimpleEvent
{
std::thread::id id;
};
ProtectedEventBus bus;
auto listener = Listener::createNotOwning(bus);
int counter = 0;
listener.listen<SimpleEvent>([&counter](const SimpleEvent& event) {
std::cout << "Event from: " << event.id << std::endl;
++counter;
});
std::thread worker1{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns3);
}
}};
std::thread worker2{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns2);
}
}};
REQUIRE(counter == 0);
while(counter < 20)
{
static int sumOfConsumed = 0;
REQUIRE(counter == sumOfConsumed);
sumOfConsumed += bus.processAll();
REQUIRE(counter == sumOfConsumed);
}
worker1.join();
worker2.join();
}
TEST_CASE("Should unlisten for event When call unlisten inside Listener", "[AsyncEventBus]")
{
struct SimpleEvent
{
std::thread::id id;
};
ProtectedEventBus bus;
auto listener = Listener::createNotOwning(bus);
int counter = 0;
listener.listen<SimpleEvent>([&counter](const SimpleEvent& event) {
std::cout << "Event from: " << event.id << std::endl;
++counter;
// TODO FIXME
// bus.unlistenAll(myToken); // This doesn't mean that unlisten will be ASAP!
});
REQUIRE(counter == 0);
bus.postpone(SimpleEvent{std::this_thread::get_id()});
// This should consume (listen request), SimpleEvent, ()unlisten request
REQUIRE(bus.processLimit(1) == 1);
for(int i = 0; i < 10; ++i)
{
bus.processAll();
bus.postpone(SimpleEvent{std::this_thread::get_id()});
bus.processAll();
}
REQUIRE(counter == 1); // Should be called only once
}
TEST_CASE("Should listen for only 1 event When call unlisten inside Listener", "[AsyncEventBus]")
{
struct SimpleEvent
{
std::thread::id id;
};
ProtectedEventBus bus;
auto listener = Listener::createNotOwning(bus);
int counter = 0;
listener.listen<SimpleEvent>([&counter](const SimpleEvent& event) {
std::cout << "Event from: " << event.id << std::endl;
++counter;
// TODO FIXME
// bus.unlistenAll(myToken); // This doesn't mean that unlisten will be ASAP!
});
// Workers in this test are only extra stuff
std::thread worker1{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns3);
}
}};
// Workers in this test are only extra stuff
std::thread worker2{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns2);
}
}};
REQUIRE(counter == 0);
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
bus.postpone(SimpleEvent{std::this_thread::get_id()});
bus.processAll();
}
REQUIRE(counter == 1); // Should be called only once
worker1.join();
worker2.join();
}

View File

@ -1,77 +0,0 @@
//
// Created by Dawid Drozd aka Gelldur on 05.08.17.
//
#include <catch2/catch.hpp>
#include <dexode/EventBus.hpp>
#include <dexode/eventbus/strategy/Transaction.hpp>
using namespace dexode;
using TransactionEventBus = EventBus<eventbus::strategy::Transaction>;
using Listener = TransactionEventBus::Listener;
TEST_CASE("eventbus/EventCollector sample", "Simple test for EventCollector")
{
struct SimpleEvent
{
int value;
};
TransactionEventBus bus;
int callCount = 0;
{
auto listener = Listener::createNotOwning(bus);
listener.listen<SimpleEvent>([&](const SimpleEvent& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus.post(SimpleEvent{3});
REQUIRE(callCount == 1);
}
bus.post(SimpleEvent{2});
REQUIRE(callCount == 1);
}
TEST_CASE("eventbus/EventCollector unlistenAll", "EventCollector::unlistenAll")
{
struct SimpleEvent
{
int value;
};
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
int callCount = 0;
listener.listen<SimpleEvent>([&](const SimpleEvent& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus.post(SimpleEvent{3});
listener.unlistenAll();
bus.post(SimpleEvent{2});
REQUIRE(callCount == 1);
}
TEST_CASE("eventbus/EventCollector reset", "EventCollector reset when we reasign")
{
struct SimpleEvent
{
int value;
};
TransactionEventBus bus;
int callCount = 0;
auto listener = Listener::createNotOwning(bus);
listener.listen<SimpleEvent>([&](const SimpleEvent& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus.post(SimpleEvent{3});
REQUIRE(callCount == 1);
listener = Listener{};
bus.post(SimpleEvent{2});
REQUIRE(callCount == 1);
}

View File

@ -1,52 +0,0 @@
#include <set>
#include <catch2/catch.hpp>
#include <dexode/eventbus/internal/common.h>
using namespace dexode;
namespace
{
struct Anonymous
{};
} // namespace
struct TestA
{
int a;
};
namespace Test
{
struct TestA
{
bool b;
};
namespace TestN
{
struct TestA
{
long long c;
};
} // namespace TestN
} // namespace Test
TEST_CASE("Should return unique id for each event When using Internal::event_id<Event>",
"[EventId]")
{
std::set<eventbus::internal::event_id_t> unique;
REQUIRE(unique.insert(eventbus::internal::event_id<Anonymous>()).second);
REQUIRE_FALSE(unique.insert(eventbus::internal::event_id<Anonymous>()).second); // already there
struct TestA
{};
REQUIRE(unique.insert(eventbus::internal::event_id<TestA>()).second);
REQUIRE(unique.insert(eventbus::internal::event_id<::TestA>()).second);
REQUIRE(unique.insert(eventbus::internal::event_id<Test::TestA>()).second);
REQUIRE(unique.insert(eventbus::internal::event_id<Test::TestN::TestA>()).second);
}

View File

@ -1,393 +0,0 @@
//
// Created by Dawid Drozd aka Gelldur on 05.08.17.
//
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include <dexode/EventBus.hpp>
#include <dexode/eventbus/strategy/Transaction.hpp>
using namespace dexode;
using TransactionEventBus = EventBus<eventbus::strategy::Transaction>;
using Listener = TransactionEventBus::Listener;
TEST_CASE("eventbus/Simple test", "Simple test")
{
TransactionEventBus bus;
struct SimpleEvent
{
int value;
};
auto listener = Listener::createNotOwning(bus);
listener.listen<SimpleEvent>([](const SimpleEvent& event) { REQUIRE(event.value == 3); });
bus.post(SimpleEvent{3});
listener.unlistenAll();
bus.post(SimpleEvent{2});
listener.listen<SimpleEvent>([](const SimpleEvent& event) { REQUIRE(event.value == 1); });
bus.post(SimpleEvent{1});
}
TEST_CASE("eventbus/Simple test2", "Simple test")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct SimpleEvent
{
int value;
};
listener.listen<SimpleEvent>([](const SimpleEvent& event) { REQUIRE(event.value == 3); });
bus.post<SimpleEvent>({3});
listener.unlistenAll();
bus.post(SimpleEvent{2});
listener.listen<SimpleEvent>([](const SimpleEvent& event) { REQUIRE(event.value == 1); });
bus.post(SimpleEvent{1});
}
TEST_CASE("eventbus/EventBus listen & post",
"Listen & post without notification object. Using only string")
{
int isCalled = 0;
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct SimpleEvent
{
int value;
};
listener.listen<SimpleEvent>([&](const SimpleEvent& event) {
++isCalled;
REQUIRE(event.value == 3);
});
REQUIRE(isCalled == 0);
bus.post(SimpleEvent{3});
REQUIRE(isCalled == 1);
listener.unlistenAll();
bus.post(SimpleEvent{2});
REQUIRE(isCalled == 1);
listener.listen<SimpleEvent>([&](const SimpleEvent& event) {
++isCalled;
REQUIRE(event.value == 1);
});
bus.post(SimpleEvent{1});
REQUIRE(isCalled == 2);
}
TEST_CASE("eventbus/Different notification", "Valid check notification")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct SimpleEvent1
{
int value;
};
struct SimpleEvent2
{
int value;
};
bool called1 = false;
bool called2 = false;
listener.listen<SimpleEvent1>([&called1](const SimpleEvent1& event) {
called1 = true;
REQUIRE(event.value == 1);
});
listener.listen<SimpleEvent2>([&called2](const SimpleEvent2& event) {
called2 = true;
REQUIRE(event.value == 2);
});
REQUIRE(called1 == false);
bus.post(SimpleEvent1{1});
REQUIRE(called1 == true);
REQUIRE(called2 == false);
called1 = false;
bus.post(SimpleEvent2{2});
REQUIRE(called1 == false);
REQUIRE(called2 == true);
}
namespace Scope1
{
struct SimpleEvent
{
int value;
};
} // namespace Scope1
namespace Scope2
{
struct SimpleEvent
{
int value;
};
} // namespace Scope2
TEST_CASE("eventbus/EventBus different events",
"Events with the same name but different scope should be different")
{
int isCalled = 0;
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
listener.listen<Scope1::SimpleEvent>([&](const Scope1::SimpleEvent& event) {
++isCalled;
REQUIRE(event.value == 1);
});
listener.listen<Scope2::SimpleEvent>([&](const Scope2::SimpleEvent& event) {
++isCalled;
REQUIRE(event.value == 2);
});
REQUIRE(isCalled == 0);
bus.post(Scope1::SimpleEvent{1});
REQUIRE(isCalled == 1);
bus.post(Scope2::SimpleEvent{2});
REQUIRE(isCalled == 2);
}
TEST_CASE("eventbus/EventBus modification during post",
"Remove listener during notification should not malform EventBus")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct TestEvent
{};
// int token1 = 1;
// int token2 = 2;
int calls = 0;
// listener.listen<TestEvent>(token1, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// });
// listener.listen<TestEvent>(token2, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// });
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
}
TEST_CASE("eventbus/EventBus modification during post2",
"Remove listener during notification should not malform EventBus")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct TestEvent
{};
// int token1 = 1;
// int token2 = 2;
// int token3 = 3;
int calls = 0;
// bus.listen<TestEvent>(token1, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
// bus.listen<TestEvent>(token2, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
// bus.listen<TestEvent>(token3, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 3);
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 3);
}
TEST_CASE("eventbus/EventBus modification during post3",
"Remove listener during notification should not malform EventBus")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct TestEvent
{};
// int token1 = 1;
// int token2 = 2;
// int token3 = 3;
int calls = 0;
// bus.listen<TestEvent>(token1, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// });
// bus.listen<TestEvent>(token2, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token3);
// bus.unlistenAll(token2);
// });
// bus.listen<TestEvent>(token3, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 3);
}
TEST_CASE("eventbus/EventBus modification during post4",
"Remove listener during notification should not malform EventBus")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct TestEvent
{};
// int token1 = 1;
// int token2 = 2;
// int token3 = 3;
int calls = 0;
// bus.listen<TestEvent>(token1, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
//
// bus.listen<TestEvent>(token2, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token3);
// bus.unlistenAll(token2);
// });
// });
// bus.listen<TestEvent>(token3, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
}
TEST_CASE("eventbus/EventBus modification during post5",
"Remove listener during notification should not malform EventBus")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct TestEvent
{};
// int token1 = 1;
// int token2 = 2;
// int token3 = 3;
int calls = 0;
// bus.listen<TestEvent>(token1, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// });
// bus.listen<TestEvent>(token2, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
//
// bus.listen<TestEvent>(token3, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
// });
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 3);
}
TEST_CASE("eventbus/EventBus modification during nested post",
"Remove listener during notification should not malform EventBus")
{
TransactionEventBus bus;
auto listener = Listener::createNotOwning(bus);
struct TestEvent
{};
struct TestEvent2
{};
// int token1 = 1;
// int token2 = 2;
// int token3 = 3;
int calls = 0;
// bus.listen<TestEvent>(token1, [&](const TestEvent& event) {
// bus.post(TestEvent2{});
//
// ++calls;
// bus.unlistenAll(token1);
//
// bus.listen<TestEvent>(token2, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token3);
// bus.unlistenAll(token2);
// });
// });
// bus.listen<TestEvent>(token3, [&](const TestEvent& event) {
// ++calls;
// bus.unlistenAll(token1);
// bus.unlistenAll(token2);
// bus.unlistenAll(token3);
// });
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
REQUIRE_NOTHROW(bus.post(TestEvent{}));
REQUIRE(calls == 2);
}

View File

@ -1,67 +0,0 @@
//
// Created by gelldur on 30.10.2019.
//
#include <string>
#include <catch2/catch.hpp>
#include "dexode/eventbus/TagEventBus.hpp"
#include "dexode/eventbus/strategy/Protected.hpp"
using TagEventBus = dexode::eventbus::TagEventBus<dexode::eventbus::strategy::Protected>;
namespace
{
struct EventWithMessage
{
std::string message = "no-msg";
};
} // namespace
TEST_CASE("Should notify only listeners with specific tag When using TagEventBus", "[TagEventBus]")
{
TagEventBus bus{{"gui", "backend"}};
auto listenerGlobal = TagEventBus::ListenerAll::createNotOwning(bus);
int counterGlobalListener = 0;
int counterTagGUIListener = 0;
int counterTagBackendListener = 0;
listenerGlobal.listen<EventWithMessage>([&](const EventWithMessage& event) {
INFO("[Global listener] Received: EventWithMessage:" << event.message);
++counterGlobalListener;
});
TagEventBus::Listener guiListener{bus.get("gui")};
guiListener.listen<EventWithMessage>([&](const EventWithMessage& event) {
INFO("[GUI listener] Received: EventWithMessage:" << event.message);
++counterTagGUIListener;
});
TagEventBus::Listener backendListener{bus.get("backend")};
backendListener.listen<EventWithMessage>([&](const EventWithMessage& event) {
INFO("[Backend listener] Received: EventWithMessage:" << event.message);
++counterTagBackendListener;
});
{
bus.post(EventWithMessage{"everyone should get this message (global listeners included)"});
REQUIRE(counterGlobalListener == 1);
REQUIRE(counterTagGUIListener == 1);
REQUIRE(counterTagBackendListener == 1);
}
{
bus.post("gui", EventWithMessage{"gui + global should get this message"});
REQUIRE(counterGlobalListener == 2);
REQUIRE(counterTagGUIListener == 2);
REQUIRE(counterTagBackendListener == 1);
}
{
bus.post("backend", EventWithMessage{"backend + global should get this message"});
REQUIRE(counterGlobalListener == 3);
REQUIRE(counterTagGUIListener == 2);
REQUIRE(counterTagBackendListener == 2);
}
}

View File

@ -0,0 +1,180 @@
#include <atomic>
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <catch2/catch.hpp>
#include "dexode/EventBus.hpp"
#include "dexode/eventbus/perk/PerkEventBus.hpp"
#include "dexode/eventbus/perk/WaitPerk.hpp"
#include "dexode/eventbus/test/event.hpp"
using namespace std::chrono_literals;
namespace dexode::eventbus::test
{
using Listener = dexode::EventBus::Listener;
struct SimpleEvent
{
std::thread::id id;
};
constexpr auto ns2 = std::chrono::nanoseconds{2}; // GCC 7 has some issues with 2ms :/
constexpr auto ns3 = std::chrono::nanoseconds{3};
TEST_CASE("Should consume events in synchronous way When using worker threads",
"[concurrent][EventBus]")
{
EventBus bus;
auto listener = Listener::createNotOwning(bus);
std::atomic<int> counter = 0;
listener.listen([&counter](const SimpleEvent& event) {
// std::cout << "Event from: " << event.id << std::endl;
++counter;
});
std::thread worker1{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns3);
}
}};
std::thread worker2{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns2);
}
}};
REQUIRE(counter == 0);
int sumOfConsumed = 0;
while(counter < 20)
{
REQUIRE(counter == sumOfConsumed);
sumOfConsumed += bus.process();
REQUIRE(counter == sumOfConsumed);
}
worker1.join();
worker2.join();
}
TEST_CASE("Should listen for only 1 event When call unlisten inside Listener",
"[concurrent][EventBus]")
{
EventBus bus;
auto listener = Listener::createNotOwning(bus);
std::atomic<int> counter = 0;
listener.listen<SimpleEvent>([&counter, &listener](const SimpleEvent& event) {
// std::cout << "Event from: " << event.id << std::endl;
++counter;
listener.unlistenAll();
});
std::thread worker1{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns3);
}
}};
std::thread worker2{[&bus]() {
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
std::this_thread::sleep_for(ns2);
}
}};
std::thread worker3{[&bus, &counter]() {
while(counter == 0)
{
bus.process();
std::this_thread::sleep_for(ns2);
}
for(int i = 0; i < 10; ++i)
{
bus.process();
std::this_thread::sleep_for(ns2);
}
}};
for(int i = 0; i < 10; ++i)
{
bus.postpone(SimpleEvent{std::this_thread::get_id()});
}
worker1.join();
worker2.join();
worker3.join();
REQUIRE(counter == 1); // Should be called only once
}
TEST_CASE("Should wait work", "[concurrent][EventBus]")
{
dexode::eventbus::perk::PerkEventBus bus;
bus.addPerk(std::make_unique<dexode::eventbus::perk::WaitPerk>())
.registerPostPostpone(&dexode::eventbus::perk::WaitPerk::onPostponeEvent);
auto* waitPerk = bus.getPerk<dexode::eventbus::perk::WaitPerk>();
REQUIRE(waitPerk != nullptr);
auto listener = Listener::createNotOwning(bus);
listener.listen(
[](const event::WaitPerk& event) { std::cout << "In WaitPerk event" << std::endl; });
listener.listen([](const event::T1& event) { std::cout << "In T1 event" << std::endl; });
// Worker which will send event every 10 ms
std::atomic<bool> isWorking = true;
std::atomic<int> produced{0};
std::atomic<int> consumed{0};
std::thread producer{[&bus, &isWorking, &produced]() {
while(isWorking)
{
bus.postpone(event::WaitPerk{});
bus.postpone(event::T1{});
++produced;
++produced;
std::this_thread::sleep_for(5ms);
}
}};
for(int i = 0; i < 20; ++i)
{
auto start = std::chrono::steady_clock::now();
if(waitPerk->waitFor(20ms))
{
int beforeConsumed = consumed;
consumed += bus.process();
INFO("If events available then consumed count should change")
CHECK(consumed > beforeConsumed);
}
else
{
// No events waiting for us
}
std::cout << "I was sleeping for: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< " ms, consumed:" << consumed << std::endl;
}
isWorking = false;
producer.join();
REQUIRE(produced >= consumed);
}
} // namespace dexode::eventbus::test

View File

@ -0,0 +1,424 @@
#include <variant>
#include <catch2/catch.hpp>
#include "dexode/EventBus.hpp"
#include "dexode/eventbus/test/event.hpp"
namespace dexode::eventbus::test
{
// class TestProducer
//{
// public:
// using Events = std::variant<event::T1, event::T2>;
//
// void onUpdate(TransactionEventBus& bus)
// {
// bus.postpone(event::T1{});
// }
//
// private:
//};
// TEST_CASE("Should process events independently When postponed multiple event types",
// "[EventBus]")
//{
// EventBus bus;
//
// int event1CallCount = 0;
// int event2CallCount = 0;
//
// auto listener = Listener::createNotOwning(bus);
// listener.listen<event::T1>([&](const event::T1& event) { ++event1CallCount; });
// listener.listen2([&](const event::T2& event) { ++event2CallCount; });
//
// bus.postpone(event::T1{});
// {
// event::T2 localVariable;
// bus.postpone(localVariable);
// }
//
// REQUIRE(event1CallCount == 0);
// REQUIRE(event2CallCount == 0);
// REQUIRE(bus.process<event::T1>() == 1);
// REQUIRE(event1CallCount == 1);
// REQUIRE(event2CallCount == 0);
//
// REQUIRE(bus.process<event::T2>() == 1);
// REQUIRE(event1CallCount == 1);
// REQUIRE(event2CallCount == 1);
//}
TEST_CASE("Should deliver event with desired value When postpone event", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
listener.listen([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
REQUIRE(callCount == 0);
bus.postpone(event::Value{3});
REQUIRE(callCount == 0);
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
}
TEST_CASE("Should be able to replace listener When we do listen -> unlisten -> listen",
"[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
{
// TODO validate other signatures is it possible to make mistake? (maybe fail to build tests
// ?)
listener.listen([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
}
{
listener.unlisten<event::Value>();
bus.postpone(event::Value{2});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
}
{
listener.listen([&](const event::Value& event) {
REQUIRE(event.value == 1);
++callCount;
});
bus.postpone(event::Value{1});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 2);
}
}
TEST_CASE("Should be able to replace listener When we do listen -> unlistenAll -> listen",
"[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
{
// TODO validate other signatures is it possible to make mistake? (maybe fail to build tests
// ?)
listener.listen([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
listener.listen([&](const event::T1& event) { ++callCount; });
bus.postpone(event::Value{3});
bus.postpone(event::T1{});
REQUIRE(bus.process() == 2);
REQUIRE(callCount == 2);
}
{
listener.unlistenAll();
bus.postpone(event::Value{3});
bus.postpone(event::T1{});
REQUIRE(bus.process() == 2);
REQUIRE(callCount == 2); // no new calls
}
{
listener.listen([&](const event::Value& event) {
REQUIRE(event.value == 1);
++callCount;
});
bus.postpone(event::Value{1});
bus.postpone(event::T1{});
REQUIRE(bus.process() == 2);
REQUIRE(callCount == 3);
}
}
TEST_CASE("Should flush events When no one listens", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
}
TEST_CASE("Should be able to unlisten When processing event", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
listener.listen([&](const event::Value& event) {
++callCount;
listener.unlisten<event::Value>();
});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
REQUIRE(callCount == 1);
}
TEST_CASE("Should not fail When unlisten multiple times during processing event", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
listener.listen([&](const event::Value& event) {
REQUIRE_NOTHROW(listener.unlisten<event::Value>());
REQUIRE_NOTHROW(listener.unlisten<event::Value>());
REQUIRE_NOTHROW(listener.unlisten<event::Value>());
++callCount;
});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
REQUIRE(callCount == 1);
}
TEST_CASE("Should fail When try to add not unique listener", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
REQUIRE_NOTHROW(listener.listen([](const event::T1&) {}));
REQUIRE_THROWS(listener.listen([](const event::T1&) {})); // We already added listener
}
TEST_CASE("Should be able to add listener When processing event", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
auto listenerOther = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
int callCountOther = 0;
listener.listen([&](const event::Value& event) {
++callCount;
if(callCount == 1) // remember that we can only add it once!
{
listenerOther.listen(
[&callCountOther](const event::Value& event) { ++callCountOther; });
}
});
{
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
REQUIRE(callCountOther == 0);
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 2);
REQUIRE(callCountOther == 1);
}
{
listenerOther.unlistenAll();
callCount = 0;
callCountOther = 0;
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
REQUIRE(callCount == 3);
REQUIRE(callCountOther == 2);
}
}
TEST_CASE("Should be able to add listener and unlisten When processing event", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
auto listenerOther = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
int callCountOther = 0;
listener.listen([&](const event::Value& event) {
++callCount;
if(callCount == 1) // remember that we can only add it once!
{
listenerOther.listen([&](const event::Value& event) { ++callCountOther; });
}
listener.unlistenAll();
});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3);
REQUIRE(callCount == 1);
REQUIRE(callCountOther == 2);
}
TEST_CASE(
"Should be able to add listener and remove listener in Matryoshka style When processing event",
"[EventBus]")
{
EventBus bus;
auto listener1 = EventBus::Listener::createNotOwning(bus);
auto listener2 = EventBus::Listener::createNotOwning(bus);
auto listener3 = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
listener1.listen([&](const event::Value& event) {
listener1.unlistenAll(); // Level 1
listener2.listen([&](const event::Value& event) {
listener2.unlistenAll(); // Level 2
listener3.listen([&](const event::Value& event) {
listener3.unlistenAll(); // Level 3 (final)
++callCount;
});
++callCount;
});
++callCount;
});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 4);
REQUIRE(callCount == 3);
}
TEST_CASE("Should be chain listen and unlisten When processing event", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
auto listenerOther = EventBus::Listener::createNotOwning(bus);
int callCount = 0;
int callOtherOption = 0;
listener.listen([&](const event::Value& event) {
++callCount;
// remember that we can only add it once!
listenerOther.listen([&](const event::Value& event) { callOtherOption = 1; });
listenerOther.unlisten<event::Value>();
listenerOther.listen([&](const event::Value& event) { callOtherOption = 2; });
listenerOther.unlisten<event::Value>();
listenerOther.listen([&](const event::Value& event) { callOtherOption = 3; });
listenerOther.unlisten<event::Value>();
listenerOther.listen([&](const event::Value& event) { callOtherOption = 4; });
listener.unlistenAll();
});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3);
REQUIRE(callCount == 1);
REQUIRE(callOtherOption == 4);
}
TEST_CASE("Should not process events When no more events", "[EventBus]")
{
EventBus bus;
auto listener = EventBus::Listener::createNotOwning(bus);
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 3); // it shouldn't accumulate events
REQUIRE(bus.process() == 0);
}
TEST_CASE("Should distinguish event producer When", "[EventBus]")
{
// EventBus bus;
//
// // dexode::eventbus::WrapTag<event::T1, Tag, Tag::gui> e;
//
// int counterGui = 0;
// int counterBackend = 0;
// auto listener = EventBus::Listener::createNotOwning(bus);
//
// listener.listen([&](const event::Tag<event::T1>& event) {
// if(event.tag == "gui")
// {
// ++counterGui;
// }
// else if(event.tag == "backend")
// {
// ++counterBackend;
// }
// else
// {
// FAIL();
// }
// });
//
// auto producerGui = [tag = Tag::gui](EventBus& bus) {
// const event::T1 event;
// bus.postpone(event);
// };
//
// auto producerBackend = [](EventBus& bus) {
// const event::T1 event;
// bus.postpone(Tag::backend, event);
// };
//
// auto producerNoTag = [](EventBus& bus) {
// const event::T1 event;
// bus.postpone(event);
// };
//
// auto producerGeneric = [](EventBus& bus, Tag tag) {
// const event::T1 event;
// if(tag == Tag::none)
// {
// bus.postpone(event);
// }
// else
// {
// bus.postpone(tag, event);
// }
// };
//
// producerGui(bus);
// producerGui(bus);
// producerBackend(bus);
// producerGui(bus);
// producerNoTag(bus);
// producerGeneric(bus, Tag::none);
// producerGeneric(bus, Tag::backend);
//
// CHECK(bus.process() == 7);
//
// REQUIRE(counterGui == 3);
// REQUIRE(counterBackend == 2);
}
} // namespace dexode::eventbus::test
// TODO should listen work with bind'ing e.g.
//_listener.listen<dashboard::back::events::LoadOrders>(
// std::bind(&BackDashboard::onLoadOrders, this, std::placeholders::_1));

View File

@ -0,0 +1,57 @@
#include <set>
#include <catch2/catch.hpp>
#include "dexode/eventbus/internal/event_id.hpp"
using namespace dexode;
namespace
{
struct Anonymous
{};
} // namespace
struct TestA
{
int a;
};
namespace Test
{
struct TestA
{
bool b;
};
namespace TestN
{
struct TestA
{
long long c;
};
} // namespace TestN
} // namespace Test
namespace dexode::eventbus::test
{
TEST_CASE("Should return unique id for each event When using event_id<Event>", "[EventID]")
{
std::set<eventbus::internal::event_id_t> unique;
REQUIRE(unique.insert(internal::event_id<Anonymous>()).second);
REQUIRE_FALSE(unique.insert(internal::event_id<Anonymous>()).second); // already there
struct TestA // "name collision" but not quite collision
{};
REQUIRE(unique.insert(internal::event_id<TestA>()).second);
REQUIRE(unique.insert(internal::event_id<::TestA>()).second);
REQUIRE(unique.insert(internal::event_id<Test::TestA>()).second);
REQUIRE(unique.insert(internal::event_id<Test::TestN::TestA>()).second);
}
} // namespace dexode::eventbus::test

View File

@ -0,0 +1,234 @@
#include <catch2/catch.hpp>
#include "dexode/EventBus.hpp"
#include "dexode/eventbus/test/event.hpp"
namespace dexode::eventbus::test
{
using Listener = dexode::EventBus::Listener;
TEST_CASE("Should remove all listeners When use unlistenAll", "[EventBus][Listener]")
{
EventBus bus;
auto listener = Listener::createNotOwning(bus);
int callCount = 0;
listener.listen([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
listener.listen([&](const event::T1& event) { ++callCount; });
bus.postpone(event::Value{3});
bus.postpone(event::T1{});
REQUIRE(bus.process() == 2);
REQUIRE(callCount == 2);
listener.unlistenAll();
bus.postpone(event::Value{2});
bus.postpone(event::T1{});
REQUIRE(bus.process() == 2);
REQUIRE(callCount == 2); // unchanged
}
TEST_CASE("Should unlisten all events When listener instance is overriden", "[EventBus][Listener]")
{
EventBus bus;
int callCount = 0;
auto listener = Listener::createNotOwning(bus);
listener.listen<event::Value>([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
listener = Listener{};
bus.postpone(event::Value{2});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
}
TEST_CASE("Should unlisten all events When listener instance is destroyed", "[EventBus][Listener]")
{
EventBus bus;
int callCount = 0;
{
auto listener = Listener::createNotOwning(bus);
listener.listen<event::Value>([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus.postpone(event::Value{3});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
}
bus.postpone(event::Value{2});
REQUIRE(bus.process() == 1);
REQUIRE(callCount == 1);
}
TEST_CASE("Should keep listeners When listener is moved", "[EventBus][Listener]")
{
auto bus = std::make_shared<EventBus>();
int callCount = 0;
Listener transferOne;
{
Listener listener{bus};
listener.listen<event::Value>([&](const event::Value& event) {
REQUIRE(event.value == 3);
++callCount;
});
bus->postpone(event::Value{3});
REQUIRE(bus->process() == 1);
REQUIRE(callCount == 1);
transferOne = std::move(listener);
}
bus->postpone(event::Value{3});
REQUIRE(bus->process() == 1);
REQUIRE(callCount == 2);
}
TEST_CASE("Should receive event When listener added AFTER event emit but BEFORE event porcess",
"[EventBus][Listener]")
{
auto bus = std::make_shared<EventBus>();
int callCount = 0;
bus->postpone(event::Value{22});
Listener listener{bus};
listener.listen<event::Value>([&](const event::Value& event) {
REQUIRE(event.value == 22);
++callCount;
});
REQUIRE(callCount == 0);
bus->process();
REQUIRE(callCount == 1);
}
TEST_CASE("Should bind listen to class method and unbind When clazz dtor is called",
"[EventBus][Listener]")
{
auto bus = std::make_shared<EventBus>();
struct TestBind
{
int callCount = 0;
Listener listener;
TestBind(const std::shared_ptr<EventBus>& bus)
: listener{bus}
{
listener.listen<event::T1>(std::bind(&TestBind::onEvent, this, std::placeholders::_1));
}
void onEvent(const event::T1& event)
{
++callCount;
}
};
bus->postpone(event::T1{});
{
TestBind bindClazz{bus};
REQUIRE(bindClazz.callCount == 0);
CHECK(bus->process() == 1);
REQUIRE(bindClazz.callCount == 1);
}
bus->postpone(event::T1{});
CHECK(bus->process() == 1);
}
static int globalCallCount{0}; // bad practice but want to just test
void freeFunction(const event::T1& event)
{
++globalCallCount;
}
TEST_CASE("Should compile When listen in different forms", "[EventBus][Listener]")
{
EventBus bus;
int callCount = 0;
// Listen with lambda
auto listener = Listener::createNotOwning(bus);
listener.listen([&](const event::T1& event) { ++callCount; });
// Listen with std::function
auto listener2 = Listener::createNotOwning(bus);
std::function<void(const event::T1&)> callback = [&](const event::T1&) { ++callCount; };
listener2.listen(callback);
// Listen with std::bind
auto listener3 = Listener::createNotOwning(bus);
struct TestClazz
{
int clazzCallCount{0};
void onEvent(const event::T1& event)
{
++clazzCallCount;
}
};
TestClazz clazz;
listener3.listen<event::T1>(std::bind(&TestClazz::onEvent, &clazz, std::placeholders::_1));
// Listen with free function
auto listener4 = Listener::createNotOwning(bus);
listener4.listen(freeFunction);
bus.postpone(event::T1{});
bus.process();
REQUIRE(globalCallCount == 1);
REQUIRE(clazz.clazzCallCount == 1);
REQUIRE(callCount == 2);
}
TEST_CASE("Should NOT be able to add multiple same event callbacks When using same listener",
"[EventBus][Listener]")
{
/// User should use separate Listener instance as it would be unabigious what should happen when
/// call unlisten<Event>
EventBus bus;
auto listener = Listener::createNotOwning(bus);
listener.listen([](const event::Value& event) {});
REQUIRE_THROWS(listener.listen([](const event::Value& event) {}));
}
TEST_CASE("Should compile", "[EventBus][Listener]")
{
// Test case to check for compilation
EventBus bus;
{
auto listener = Listener::createNotOwning(bus);
const auto callback = [](const event::Value& event) {};
listener.listen(callback);
}
{
auto listener = Listener::createNotOwning(bus);
auto callback = [](const event::Value& event) {};
listener.listen(callback);
}
{
auto listener = Listener::createNotOwning(bus);
auto callback = [](const event::Value& event) {};
listener.listen(std::move(callback));
}
{
auto listener = Listener::createNotOwning(bus);
listener.listen([](const event::Value& event) {});
}
}
} // namespace dexode::eventbus::test

View File

@ -0,0 +1,32 @@
//
// Created by gelldur on 24.11.2019.
//
#pragma once
namespace dexode::eventbus::test::event
{
struct T1
{};
struct T2
{};
struct Value
{
int value{-1};
};
template <typename Event>
struct Tag
{
Event data;
std::string tag;
};
struct WaitPerk
{
};
} // namespace dexode::eventbus::test::event

2
test/src/main.cpp Normal file
View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

3
use_case/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
#
add_subdirectory(basic/)
add_subdirectory(tagged_events/)

5
use_case/README.md Normal file
View File

@ -0,0 +1,5 @@
# Use cases
This folder should contain use cases and approaches how to use EventBus.
Most important need for this is only to store needed use cases by other projects.

View File

@ -0,0 +1,6 @@
#
add_executable(UseCase_Basic
src/main.cpp
)
target_link_libraries(UseCase_Basic PRIVATE Dexode::EventBus)

3
use_case/basic/README.md Normal file
View File

@ -0,0 +1,3 @@
# Use case: Basic
Just basic use case of EventBus.

View File

@ -7,16 +7,18 @@
#include <memory>
#include <string>
#include <eventbus/EventBus.h>
#include <eventbus/EventCollector.h>
#include <dexode/EventBus.hpp>
namespace Event // Example namespace for events
using EventBus = dexode::EventBus;
using Listener = dexode::EventBus::Listener;
namespace event // Example namespace for events
{
struct Gold // Event that will be proceed when our gold changes
{
int value = 0;
};
} // namespace Event
} // namespace event
enum class Monster
{
@ -28,8 +30,8 @@ enum class Monster
class Character
{
public:
Character(const std::shared_ptr<Dexode::EventBus>& eventBus)
: _bus {eventBus}
Character(const std::shared_ptr<EventBus>& eventBus)
: _bus{eventBus}
{}
void kill(Monster monsterType)
@ -46,19 +48,19 @@ public:
{
_gold += 25;
}
_bus->notify(Event::Gold {_gold});
_bus->postpone(event::Gold{_gold});
}
private:
int _gold = 0;
std::shared_ptr<Dexode::EventBus> _bus;
std::shared_ptr<EventBus> _bus;
};
class UIWallet
{
public:
UIWallet(const std::shared_ptr<Dexode::EventBus>& eventBus)
: _listener {eventBus}
UIWallet(const std::shared_ptr<EventBus>& eventBus)
: _listener{eventBus}
{}
void onDraw() // Example "draw" of UI
@ -67,9 +69,9 @@ public:
}
void onEnter() // We could also do such things in ctor and dtor but a lot of UI has something
// like this
// like this
{
_listener.listen<Event::Gold>(
_listener.listen<event::Gold>(
[this](const auto& event) { _gold = std::to_string(event.value); });
}
@ -80,18 +82,18 @@ public:
private:
std::string _gold = "0";
Dexode::EventCollector _listener;
Listener _listener;
};
class ShopButton // Shop button is only enabled when we have some gold (odd decision but for sample
// good :P)
// Shop button is only enabled when we have some gold (odd decision but for sample good :P)
class ShopButton
{
public:
ShopButton(const std::shared_ptr<Dexode::EventBus>& eventBus)
: _listener {eventBus}
ShopButton(const std::shared_ptr<EventBus>& eventBus)
: _listener{eventBus}
{
// We can use lambda or bind your choice
_listener.listen<Event::Gold>(
_listener.listen<event::Gold>(
std::bind(&ShopButton::onGoldUpdated, this, std::placeholders::_1));
// Also we use RAII idiom to handle unlisten
}
@ -102,10 +104,10 @@ public:
}
private:
Dexode::EventCollector _listener;
Listener _listener;
bool _isEnabled = false;
void onGoldUpdated(const Event::Gold& event)
void onGoldUpdated(const event::Gold& event)
{
_isEnabled = event.value > 0;
std::cout << "Shop button is:" << _isEnabled << std::endl; // some kind of logs
@ -114,13 +116,13 @@ private:
int main(int argc, char* argv[])
{
std::shared_ptr<Dexode::EventBus> eventBus = std::make_shared<Dexode::EventBus>();
auto eventBus = std::make_shared<EventBus>();
Character 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();
}
@ -128,13 +130,17 @@ int main(int argc, char* argv[])
wallet.onDraw();
{
characterController.kill(Monster::Tux);
eventBus->process();
}
wallet.onDraw();
// It is easy to test UI eg.
eventBus->notify(Event::Gold {1});
eventBus->postpone(event::Gold{1});
eventBus->process();
assert(shopButton.isEnabled() == true);
eventBus->notify(Event::Gold {0});
eventBus->postpone(event::Gold{0});
eventBus->process();
assert(shopButton.isEnabled() == false);
wallet.onExit();

View File

@ -0,0 +1,11 @@
#
add_executable(UseCase_TaggedEvents
src/Character.cpp src/Character.hpp
src/event.hpp
src/EventBus.hpp
src/main.cpp
src/Team.cpp src/Team.hpp
src/Gui.cpp src/Gui.hpp)
target_include_directories(UseCase_TaggedEvents PUBLIC src/)
target_link_libraries(UseCase_TaggedEvents PRIVATE Dexode::EventBus)

View File

@ -0,0 +1,14 @@
# Use case: Tagged events
## Motivation
Sometimes we have event producer but at some point we would like
to reuse event producer for different configuration. In some cases we
could just add some "tag" to event to resolve this issue.
I would like to resolve this in other way as maybe data producer can't
be aware of its "tag". Other issue is spreading "tag" requirement over
whole project and maybe we don't want to modify old code.
It would be nice to just add some abstraction layer which will resolve
this issue.

View File

@ -0,0 +1,25 @@
//
// Created by gelldur on 22.12.2019.
//
#include "Character.hpp"
#include "event.hpp"
Character::Character(std::shared_ptr<EventBus> bus)
: _bus{std::move(bus)}
{
(void)_iq; // probably something should use it ;)
}
void Character::pickGold(int goldCount)
{
// We could store character name as member. Sure this isn't complex example rather simple one.
// Imagine few levels of composition ;)
_sackGold += goldCount;
_bus->postpone(event::GoldUpdate{_sackGold});
}
void Character::damage(int amount)
{
_health -= amount;
}

View File

@ -0,0 +1,23 @@
//
// Created by gelldur on 22.12.2019.
//
#pragma once
#include <memory>
#include <string>
#include "EventBus.hpp"
class Character
{
public:
Character(std::shared_ptr<EventBus> bus);
void pickGold(int goldCount);
void damage(int amount);
private:
std::shared_ptr<EventBus> _bus;
int _sackGold = 0;
int _health = 100;
int _iq = 200;
};

View File

@ -0,0 +1,9 @@
//
// Created by gelldur on 22.12.2019.
//
#pragma once
#include <dexode/EventBus.hpp>
using EventBus = dexode::EventBus;
using Listener = dexode::EventBus::Listener;

View File

@ -0,0 +1,33 @@
//
// Created by gelldur on 22.12.2019.
//
#include "Gui.hpp"
#include <iostream>
#include "event.hpp"
Gui::Gui(const std::shared_ptr<EventBus>& bus)
: _listener{bus}
{
_listener.listen(
[this](const event::NewTeamMember& event) { _sackOfGold.emplace(event.memberName, 0); });
_listener.listen([this](const event::TagEvent<event::GoldUpdate>& event) {
auto found = _sackOfGold.find(event.tag);
if(found != _sackOfGold.end())
{
found->second = event.data.goldCount;
}
});
}
void Gui::draw()
{
std::cout << "-----------------------------\n";
for(const auto& player : _sackOfGold)
{
std::cout << "Name:" << player.first << " - gold: " << player.second << "\n";
}
std::cout << "-----------------------------" << std::endl;
}

View File

@ -0,0 +1,21 @@
//
// Created by gelldur on 22.12.2019.
//
#pragma once
#include <map>
#include <string>
#include "EventBus.hpp"
class Gui
{
public:
Gui(const std::shared_ptr<EventBus>& bus);
void draw();
private:
EventBus::Listener _listener;
std::map<std::string, int> _sackOfGold;
};

View File

@ -0,0 +1,43 @@
//
// Created by gelldur on 22.12.2019.
//
#include "Team.hpp"
#include <dexode/eventbus/perk/PassPerk.hpp>
#include <dexode/eventbus/perk/PerkEventBus.hpp>
#include <dexode/eventbus/perk/TagPerk.hpp>
#include "event.hpp"
Team::Team(std::shared_ptr<EventBus> bus)
: _bus(std::move(bus))
{}
Character& Team::getMember(const std::string& name)
{
auto found = std::find(_names.begin(), _names.end(), name);
if(found == _names.end())
{
throw std::out_of_range("No such team member: " + name);
}
return _squad.at(std::distance(_names.begin(), found));
}
void Team::addPlayer(const std::string& name)
{
auto characterBus = std::make_shared<dexode::eventbus::perk::PerkEventBus>();
{
auto tagPerk = std::make_unique<dexode::eventbus::perk::TagPerk>(name, _bus.get());
tagPerk->wrapTag<event::TagEvent<event::GoldUpdate>>();
characterBus->addPerk(std::move(tagPerk))
.registerPrePostpone(&dexode::eventbus::perk::TagPerk::onPrePostponeEvent);
}
characterBus->addPerk(std::make_unique<dexode::eventbus::perk::PassEverythingPerk>(_bus))
.registerPrePostpone(&dexode::eventbus::perk::PassEverythingPerk::onPrePostponeEvent);
_squad.emplace_back(characterBus);
_names.push_back(name);
_bus->postpone(event::NewTeamMember{name});
}

View File

@ -0,0 +1,24 @@
//
// Created by gelldur on 22.12.2019.
//
#pragma once
#include <string>
#include <vector>
#include "Character.hpp"
class Team
{
public:
Team(std::shared_ptr<EventBus> bus);
void addPlayer(const std::string& name);
Character& getMember(const std::string& name);
private:
std::vector<std::string> _names;
std::vector<Character> _squad;
std::shared_ptr<EventBus> _bus;
};

View File

@ -0,0 +1,27 @@
//
// Created by gelldur on 22.12.2019.
//
#pragma once
namespace event
{
struct GoldUpdate
{
int goldCount;
};
struct NewTeamMember
{
std::string memberName;
};
template <typename T>
struct TagEvent
{
using Event = T;
std::string tag;
Event data;
};
} // namespace event

View File

@ -0,0 +1,42 @@
#include <cassert>
#include <iostream>
#include <memory>
#include <string>
#include "EventBus.hpp"
#include "Gui.hpp"
#include "Team.hpp"
int main(int argc, char* argv[])
{
auto eventBus = std::make_shared<EventBus>();
Gui gui{eventBus};
Team myTeam{eventBus};
auto updateFrame = [&]() {
// single frame update ;)
eventBus->process();
gui.draw();
std::cout << "###################################################\n";
std::cout << "###################################################" << std::endl;
};
{ // single update
myTeam.addPlayer("Gelldur");
updateFrame();
}
{ // single update
myTeam.getMember("Gelldur").pickGold(100);
updateFrame();
}
{ // single update
myTeam.addPlayer("Gosia");
myTeam.addPlayer("Dexter");
updateFrame();
}
return 0;
}