]> git.tdb.fi Git - libs/game.git/commitdiff
Add an event bus for delivering events
authorMikko Rasa <tdb@tdb.fi>
Thu, 20 Oct 2022 21:02:25 +0000 (00:02 +0300)
committerMikko Rasa <tdb@tdb.fi>
Thu, 20 Oct 2022 21:34:26 +0000 (00:34 +0300)
source/game/eventbus.cpp [new file with mode: 0644]
source/game/eventbus.h [new file with mode: 0644]
source/game/eventobserver.cpp [new file with mode: 0644]
source/game/eventobserver.h [new file with mode: 0644]
source/game/eventsource.h [new file with mode: 0644]
source/game/stage.h

diff --git a/source/game/eventbus.cpp b/source/game/eventbus.cpp
new file mode 100644 (file)
index 0000000..30fc849
--- /dev/null
@@ -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 (file)
index 0000000..ce9c812
--- /dev/null
@@ -0,0 +1,113 @@
+#ifndef MSP_GAME_EVENTBUS_H_
+#define MSP_GAME_EVENTBUS_H_
+
+#include <functional>
+#include <vector>
+
+namespace Msp::Game {
+
+class EventObserver;
+
+template<typename T>
+struct EventDispatcher
+{
+       struct Handler
+       {
+               EventObserver *observer;
+               std::function<void(const T &)> callback;
+       };
+
+       std::vector<Handler> handlers;
+
+       void add_observer(EventObserver *obs, std::function<void(const T &)> &&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<Dispatcher> dispatchers;
+
+       static unsigned get_next_id();
+
+public:
+       template<typename T>
+       static unsigned get_event_id();
+
+private:
+       template<typename T>
+       EventDispatcher<T> &get_emitter();
+
+public:
+       template<typename T>
+       void add_observer(EventObserver &obs, std::function<void(const T &)> cb)
+       { get_emitter<T>().add_observer(obs, std::move(cb)); }
+
+       void replace_observer(EventObserver &, EventObserver &);
+       void remove_observer(EventObserver &);
+
+       template<typename T>
+       void dispatch(const T &) const;
+};
+
+
+template<typename T>
+void EventDispatcher<T>::dispatch(const T &event) const
+{
+       for(const Handler &h: handlers)
+               h.callback(event);
+}
+
+
+template<typename T>
+inline unsigned EventBus::get_event_id()
+{
+       static unsigned id = get_next_id();
+       return id;
+}
+
+template<typename T>
+inline EventDispatcher<T> &EventBus::get_emitter()
+{
+       unsigned id = get_event_id<T>();
+       if(dispatchers.size()<=id)
+               dispatchers.resize(id+1);
+
+       Dispatcher &event = dispatchers[id];
+       if(!event.dispatcher)
+       {
+               event.dispatcher = new EventDispatcher<T>;
+               event.deleter = [](void *p){ delete static_cast<EventDispatcher<T> *>(p); };
+               event.remover = [](void *p, EventObserver &o){ static_cast<EventDispatcher<T> *>(p)->remove_observer(o); };
+       }
+
+       return static_cast<EventDispatcher<T> *>(event.dispatcher);
+}
+
+template<typename T>
+inline void EventBus::dispatch(const T &event) const
+{
+       unsigned id = get_event_id<T>();
+       if(id<dispatchers.size() && dispatchers[id].dispatcher)
+               static_cast<EventDispatcher<T> *>(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 (file)
index 0000000..440d299
--- /dev/null
@@ -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 (file)
index 0000000..31735a6
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef MSP_GAME_EVENTOBSERVER_H_
+#define MSP_GAME_EVENTOBSERVER_H_
+
+#include <msp/core/algorithm.h>
+#include <msp/core/noncopyable.h>
+#include "eventbus.h"
+
+namespace Msp::Game {
+
+class EventSourceBase;
+
+class EventObserver: public NonCopyable
+{
+private:
+       EventBus &bus;
+       std::vector<EventSourceBase *> observed_sources;
+
+public:
+       EventObserver(EventBus &);
+       ~EventObserver();
+
+       template<typename T>
+       void observe(std::function<void(const T &)> cb)
+       { bus.add_observer<T>(*this, std::move(cb)); }
+
+       template<typename T, typename S>
+       void observe(S &, std::function<void(const T &)> );
+
+       void remove_source(EventSourceBase &);
+};
+
+template<typename T, typename S>
+void EventObserver::observe(S &src, std::function<void(const T &)> 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 (file)
index 0000000..5dacbee
--- /dev/null
@@ -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<typename... E>
+class EventSource: public EventSourceBase, private EventDispatcher<E>...
+{
+public:
+       EventSource(EventBus &b): EventSourceBase(b) { }
+       ~EventSource() { (cancel_observation<E>(), ...); }
+
+       template<typename T>
+       void add_observer(EventObserver &obs, std::function<void(const T &)> cb)
+       { static_cast<EventDispatcher<T> &>(*this).add_observer(obs, std::move(cb)); }
+
+       void remove_observer(EventObserver &obs) override
+       { (static_cast<EventDispatcher<E> &>(*this).remove_observer(&obs), ...); }
+
+private:
+       template<typename T>
+       void cancel_observation();
+
+public:
+       template<typename T, typename... Args>
+       void emit(Args &&...) const;
+};
+
+
+template<typename... E>
+template<typename T, typename... Args>
+inline void EventSource<E...>::emit(Args &&... args) const
+{
+       T event(std::forward<Args>(args)...);
+       static_cast<const EventDispatcher<T> &>(*this).dispatch(event);
+       bus.dispatch(event);
+}
+
+template<typename... E>
+template<typename T>
+inline void EventSource<E...>::cancel_observation()
+{
+       for(const auto &h: static_cast<EventDispatcher<T> &>(*this).handlers)
+               h.observer->remove_source(*this);
+}
+
+} // namespace Msp::Game
+
+#endif
index 47ca47d9a572b6c5742e5a5279877d5af983584c..dbffc9960bedb4fd89c55a232dd6dd91f0fb68c5 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <memory>
 #include <msp/time/timedelta.h>
+#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> root;
@@ -24,6 +26,7 @@ public:
        ~Stage();
 
        PoolPool &get_pools() { return pools; }
+       EventBus &get_event_bus() { return event_bus; }
        Handle<Root> get_root() { return Handle<Root>::from_object(root.get()); }
 
        template<typename T, typename F>