From: Mikko Rasa Date: Thu, 20 Oct 2022 21:02:25 +0000 (+0300) Subject: Add an event bus for delivering events X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=6a93721ab67315e916f6c649b1f7bc5447d611a4;p=libs%2Fgame.git Add an event bus for delivering events --- diff --git a/source/game/eventbus.cpp b/source/game/eventbus.cpp new file mode 100644 index 0000000..30fc849 --- /dev/null +++ b/source/game/eventbus.cpp @@ -0,0 +1,18 @@ +#include "eventbus.h" + +namespace Msp::Game { + +unsigned EventBus::get_next_id() +{ + static unsigned next_id = 0; + return next_id++; +} + +void EventBus::remove_observer(EventObserver &obs) +{ + for(const Dispatcher &e: dispatchers) + if(e.dispatcher) + e.remover(e.dispatcher, obs); +} + +} // namespace Msp::Game diff --git a/source/game/eventbus.h b/source/game/eventbus.h new file mode 100644 index 0000000..ce9c812 --- /dev/null +++ b/source/game/eventbus.h @@ -0,0 +1,113 @@ +#ifndef MSP_GAME_EVENTBUS_H_ +#define MSP_GAME_EVENTBUS_H_ + +#include +#include + +namespace Msp::Game { + +class EventObserver; + +template +struct EventDispatcher +{ + struct Handler + { + EventObserver *observer; + std::function callback; + }; + + std::vector handlers; + + void add_observer(EventObserver *obs, std::function &&cb) + { handlers.emplace_back(obs, std::move(cb)); } + + void remove_observer(EventObserver *obs) + { std::erase_if(handlers, [obs](const Handler &h){ return h.observer==obs; }); } + + void dispatch(const T &) const; +}; + + +class EventBus +{ +private: + using DeleteFunc = void(void *); + using RemoveFunc = void(void *, EventObserver &); + + struct Dispatcher + { + void *dispatcher = nullptr; + DeleteFunc *deleter = nullptr; + RemoveFunc *remover = nullptr; + }; + + std::vector dispatchers; + + static unsigned get_next_id(); + +public: + template + static unsigned get_event_id(); + +private: + template + EventDispatcher &get_emitter(); + +public: + template + void add_observer(EventObserver &obs, std::function cb) + { get_emitter().add_observer(obs, std::move(cb)); } + + void replace_observer(EventObserver &, EventObserver &); + void remove_observer(EventObserver &); + + template + void dispatch(const T &) const; +}; + + +template +void EventDispatcher::dispatch(const T &event) const +{ + for(const Handler &h: handlers) + h.callback(event); +} + + +template +inline unsigned EventBus::get_event_id() +{ + static unsigned id = get_next_id(); + return id; +} + +template +inline EventDispatcher &EventBus::get_emitter() +{ + unsigned id = get_event_id(); + if(dispatchers.size()<=id) + dispatchers.resize(id+1); + + Dispatcher &event = dispatchers[id]; + if(!event.dispatcher) + { + event.dispatcher = new EventDispatcher; + event.deleter = [](void *p){ delete static_cast *>(p); }; + event.remover = [](void *p, EventObserver &o){ static_cast *>(p)->remove_observer(o); }; + } + + return static_cast *>(event.dispatcher); +} + +template +inline void EventBus::dispatch(const T &event) const +{ + unsigned id = get_event_id(); + if(id *>(dispatchers[id].dispatcher)->dispatch(event); +} + +} // namespace Msp::Game + +#endif diff --git a/source/game/eventobserver.cpp b/source/game/eventobserver.cpp new file mode 100644 index 0000000..440d299 --- /dev/null +++ b/source/game/eventobserver.cpp @@ -0,0 +1,24 @@ +#include "eventobserver.h" +#include "eventsource.h" + +namespace Msp::Game { + +EventObserver::EventObserver(EventBus &b): + bus(b) +{ } + +EventObserver::~EventObserver() +{ + bus.remove_observer(*this); + for(EventSourceBase *s: observed_sources) + s->remove_observer(*this); +} + +void EventObserver::remove_source(EventSourceBase &src) +{ + auto i = lower_bound(observed_sources, &src); + if(i!=observed_sources.end() && *i==&src) + observed_sources.erase(i); +} + +} // namespace Msp::Game diff --git a/source/game/eventobserver.h b/source/game/eventobserver.h new file mode 100644 index 0000000..31735a6 --- /dev/null +++ b/source/game/eventobserver.h @@ -0,0 +1,43 @@ +#ifndef MSP_GAME_EVENTOBSERVER_H_ +#define MSP_GAME_EVENTOBSERVER_H_ + +#include +#include +#include "eventbus.h" + +namespace Msp::Game { + +class EventSourceBase; + +class EventObserver: public NonCopyable +{ +private: + EventBus &bus; + std::vector observed_sources; + +public: + EventObserver(EventBus &); + ~EventObserver(); + + template + void observe(std::function cb) + { bus.add_observer(*this, std::move(cb)); } + + template + void observe(S &, std::function ); + + void remove_source(EventSourceBase &); +}; + +template +void EventObserver::observe(S &src, std::function cb) +{ + src.add_observer(*this, std::move(cb)); + auto i = lower_bound(observed_sources, &src); + if(i==observed_sources.end() || *i!=&src) + observed_sources.insert(i, &src); +} + +} // namespace Msp::Game + +#endif diff --git a/source/game/eventsource.h b/source/game/eventsource.h new file mode 100644 index 0000000..5dacbee --- /dev/null +++ b/source/game/eventsource.h @@ -0,0 +1,66 @@ +#ifndef MSP_GAME_EVENTSOURCE_H_ +#define MSP_GAME_EVENTSOURCE_H_ + +#include "eventbus.h" +#include "eventobserver.h" + +namespace Msp::Game { + +class EventObserver; + +class EventSourceBase +{ +protected: + EventBus &bus; + +public: + EventSourceBase(EventBus &b): bus(b) { } + + virtual void remove_observer(EventObserver &) = 0; +}; + + +template +class EventSource: public EventSourceBase, private EventDispatcher... +{ +public: + EventSource(EventBus &b): EventSourceBase(b) { } + ~EventSource() { (cancel_observation(), ...); } + + template + void add_observer(EventObserver &obs, std::function cb) + { static_cast &>(*this).add_observer(obs, std::move(cb)); } + + void remove_observer(EventObserver &obs) override + { (static_cast &>(*this).remove_observer(&obs), ...); } + +private: + template + void cancel_observation(); + +public: + template + void emit(Args &&...) const; +}; + + +template +template +inline void EventSource::emit(Args &&... args) const +{ + T event(std::forward(args)...); + static_cast &>(*this).dispatch(event); + bus.dispatch(event); +} + +template +template +inline void EventSource::cancel_observation() +{ + for(const auto &h: static_cast &>(*this).handlers) + h.observer->remove_source(*this); +} + +} // namespace Msp::Game + +#endif diff --git a/source/game/stage.h b/source/game/stage.h index 47ca47d..dbffc99 100644 --- a/source/game/stage.h +++ b/source/game/stage.h @@ -3,6 +3,7 @@ #include #include +#include "eventbus.h" #include "handle.h" namespace Msp::Game { @@ -14,6 +15,7 @@ class Stage { private: PoolPool pools; + EventBus event_bus; /* Use unique_ptr because there's only one root per stage so it's pointless to put it in a pool. */ std::unique_ptr root; @@ -24,6 +26,7 @@ public: ~Stage(); PoolPool &get_pools() { return pools; } + EventBus &get_event_bus() { return event_bus; } Handle get_root() { return Handle::from_object(root.get()); } template