mirror of
https://github.com/gelldur/EventBus.git
synced 2024-12-25 18:00:48 +08:00
New EventBus 3.0
This commit is contained in:
parent
6baa41917e
commit
2bc2858a8a
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
130
lib/src/dexode/EventBus.cpp
Normal 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
|
@ -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
|
||||
|
126
lib/src/dexode/eventbus/Bus.hpp
Normal file
126
lib/src/dexode/eventbus/Bus.hpp
Normal 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
|
@ -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:
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
@ -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>
|
27
lib/src/dexode/eventbus/internal/listener_traits.hpp
Normal file
27
lib/src/dexode/eventbus/internal/listener_traits.hpp
Normal 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
|
17
lib/src/dexode/eventbus/perk/PassPerk.cpp
Normal file
17
lib/src/dexode/eventbus/perk/PassPerk.cpp
Normal 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
|
32
lib/src/dexode/eventbus/perk/PassPerk.hpp
Normal file
32
lib/src/dexode/eventbus/perk/PassPerk.hpp
Normal 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
|
9
lib/src/dexode/eventbus/perk/Perk.cpp
Normal file
9
lib/src/dexode/eventbus/perk/Perk.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by gelldur on 23.12.2019.
|
||||
//
|
||||
#include "Perk.hpp"
|
||||
|
||||
namespace dexode::eventbus::perk
|
||||
{
|
||||
|
||||
} // namespace dexode::eventbus::perk
|
22
lib/src/dexode/eventbus/perk/Perk.hpp
Normal file
22
lib/src/dexode/eventbus/perk/Perk.hpp
Normal 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
|
39
lib/src/dexode/eventbus/perk/PerkEventBus.cpp
Normal file
39
lib/src/dexode/eventbus/perk/PerkEventBus.cpp
Normal 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
|
74
lib/src/dexode/eventbus/perk/PerkEventBus.hpp
Normal file
74
lib/src/dexode/eventbus/perk/PerkEventBus.hpp
Normal 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
|
20
lib/src/dexode/eventbus/perk/TagPerk.cpp
Normal file
20
lib/src/dexode/eventbus/perk/TagPerk.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
48
lib/src/dexode/eventbus/perk/TagPerk.hpp
Normal file
48
lib/src/dexode/eventbus/perk/TagPerk.hpp
Normal 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
|
35
lib/src/dexode/eventbus/perk/WaitPerk.cpp
Normal file
35
lib/src/dexode/eventbus/perk/WaitPerk.cpp
Normal 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
|
33
lib/src/dexode/eventbus/perk/WaitPerk.hpp
Normal file
33
lib/src/dexode/eventbus/perk/WaitPerk.hpp
Normal 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
|
35
lib/src/dexode/eventbus/permission/PostponeBus.hpp
Normal file
35
lib/src/dexode/eventbus/permission/PostponeBus.hpp
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
42
lib/src/dexode/eventbus/stream/EventStream.hpp
Normal file
42
lib/src/dexode/eventbus/stream/EventStream.hpp
Normal 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
|
163
lib/src/dexode/eventbus/stream/ProtectedEventStream.hpp
Normal file
163
lib/src/dexode/eventbus/stream/ProtectedEventStream.hpp
Normal 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
|
@ -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
|
54
performance/src/AsyncEventBusPerformance.cpp
Normal file
54
performance/src/AsyncEventBusPerformance.cpp
Normal 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();
|
@ -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)
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
180
test/src/dexode/eventbus/test/SuiteConcurrentEventBus.cpp
Normal file
180
test/src/dexode/eventbus/test/SuiteConcurrentEventBus.cpp
Normal 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
|
424
test/src/dexode/eventbus/test/SuiteEventBus.cpp
Normal file
424
test/src/dexode/eventbus/test/SuiteEventBus.cpp
Normal 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));
|
57
test/src/dexode/eventbus/test/SuiteEventID.cpp
Normal file
57
test/src/dexode/eventbus/test/SuiteEventID.cpp
Normal 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
|
234
test/src/dexode/eventbus/test/SuiteListener.cpp
Normal file
234
test/src/dexode/eventbus/test/SuiteListener.cpp
Normal 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
|
32
test/src/dexode/eventbus/test/event.hpp
Normal file
32
test/src/dexode/eventbus/test/event.hpp
Normal 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
2
test/src/main.cpp
Normal file
@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
3
use_case/CMakeLists.txt
Normal file
3
use_case/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
#
|
||||
add_subdirectory(basic/)
|
||||
add_subdirectory(tagged_events/)
|
5
use_case/README.md
Normal file
5
use_case/README.md
Normal 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.
|
6
use_case/basic/CMakeLists.txt
Normal file
6
use_case/basic/CMakeLists.txt
Normal 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
3
use_case/basic/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Use case: Basic
|
||||
|
||||
Just basic use case of EventBus.
|
@ -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();
|
11
use_case/tagged_events/CMakeLists.txt
Normal file
11
use_case/tagged_events/CMakeLists.txt
Normal 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)
|
14
use_case/tagged_events/README.md
Normal file
14
use_case/tagged_events/README.md
Normal 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.
|
||||
|
25
use_case/tagged_events/src/Character.cpp
Normal file
25
use_case/tagged_events/src/Character.cpp
Normal 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;
|
||||
}
|
23
use_case/tagged_events/src/Character.hpp
Normal file
23
use_case/tagged_events/src/Character.hpp
Normal 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;
|
||||
};
|
9
use_case/tagged_events/src/EventBus.hpp
Normal file
9
use_case/tagged_events/src/EventBus.hpp
Normal 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;
|
33
use_case/tagged_events/src/Gui.cpp
Normal file
33
use_case/tagged_events/src/Gui.cpp
Normal 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;
|
||||
}
|
21
use_case/tagged_events/src/Gui.hpp
Normal file
21
use_case/tagged_events/src/Gui.hpp
Normal 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;
|
||||
};
|
43
use_case/tagged_events/src/Team.cpp
Normal file
43
use_case/tagged_events/src/Team.cpp
Normal 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});
|
||||
}
|
24
use_case/tagged_events/src/Team.hpp
Normal file
24
use_case/tagged_events/src/Team.hpp
Normal 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;
|
||||
};
|
27
use_case/tagged_events/src/event.hpp
Normal file
27
use_case/tagged_events/src/event.hpp
Normal 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
|
42
use_case/tagged_events/src/main.cpp
Normal file
42
use_case/tagged_events/src/main.cpp
Normal 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user