vkmEngine 1.0.0
A C++ game engine · vkmengine.com
Loading...
Searching...
No Matches
bus.h
1#pragma once
2
3#include <cstdint>
4#include <algorithm>
5#include <functional>
6#include <utility>
7#include <vector>
8
9namespace Vkm::Engine {
10
14using ListenerId = uint32_t;
15
19class IBus {
20 public:
21 IBus() = default;
22 virtual ~IBus() = default;
23
24 IBus(const IBus& other) = delete;
25 IBus& operator=(const IBus& other) = delete;
26
27 IBus(IBus && other) = delete;
28 IBus& operator=(IBus && other) = delete;
29
30 public:
38 virtual void takeQueue() = 0;
39
43 virtual void deliver() = 0;
44
52 virtual bool hasListeners() const = 0;
53};
54
60template<typename EventT>
61class Bus : public IBus {
62 public:
63 Bus() = default;
64 ~Bus() override = default;
65
66 Bus(const Bus& other) = delete;
67 Bus& operator=(const Bus& other) = delete;
68
69 Bus(Bus && other) = delete;
70 Bus& operator=(Bus && other) = delete;
71
72 public:
83 ListenerId subscribe(std::function<void(const EventT&)> cb) {
84 const ListenerId id = m_nextId++;
85
86 if (m_flushDepth > 0) m_pending.push_back({id, std::move(cb)});
87 else m_listeners.push_back({id, std::move(cb)});
88 return id;
89 }
90
103 bool remove(ListenerId id) {
104 for (auto it = m_listeners.begin(); it != m_listeners.end(); ++it) {
105 if (it->id != id) continue;
106
107 if (m_flushDepth != 0) it->alive = false;
108 else m_listeners.erase(it);
109 return true;
110 }
111 for (auto it = m_pending.begin(); it != m_pending.end(); ++it) {
112 if (it->id != id) continue;
113
114 m_pending.erase(it);
115 return true;
116 }
117 return false;
118 }
119
125 void emit(const EventT& event) {
126 DispatchScope scope(*this);
127 const size_t n = m_listeners.size();
128 for (size_t i = 0; i < n; ++i) {
129 if (m_listeners[i].alive) m_listeners[i].cb(event);
130 }
131 }
132
138 void enqueue(EventT event) {
139 m_queue.push_back(std::move(event));
140 }
141
147 void takeQueue() override {
148 m_dispatch.clear();
149 m_dispatch.swap(m_queue);
150 }
151
157 void deliver() override {
158 if (m_dispatch.empty()) return;
159
160 DispatchScope scope(*this);
161 const size_t n = m_listeners.size();
162 for (auto& e : m_dispatch) {
163 for (size_t i = 0; i < n; ++i) {
164 if (m_listeners[i].alive) m_listeners[i].cb(e);
165 }
166 }
167 }
168
174 bool hasListeners() const override {
175 for (const Entry& e : m_listeners) {
176 if (e.alive) return true;
177 }
178 return !m_pending.empty();
179 }
180
181 private:
182 struct Entry {
183 ListenerId id;
184 std::function<void(const EventT&)> cb;
185 bool alive = true;
186 };
187
194 class DispatchScope {
195 public:
196 explicit DispatchScope(Bus& bus) : m_bus(bus) { ++m_bus.m_flushDepth; }
197 ~DispatchScope() {
198 --m_bus.m_flushDepth;
199 m_bus.admitPending();
200 }
201
202 DispatchScope(const DispatchScope& other) = delete;
203 DispatchScope& operator=(const DispatchScope& other) = delete;
204
205 DispatchScope(DispatchScope && other) = delete;
206 DispatchScope& operator=(DispatchScope && other) = delete;
207
208 private:
209 Bus& m_bus;
210 };
211
218 void admitPending() {
219 if (m_flushDepth != 0) return;
220
221 const auto dead = [](const Entry& e) { return !e.alive; };
222 m_listeners.erase(
223 std::remove_if(m_listeners.begin(), m_listeners.end(), dead),
224 m_listeners.end()
225 );
226
227 if (m_pending.empty()) return;
228 for (Entry& entry : m_pending) m_listeners.push_back(std::move(entry));
229 m_pending.clear();
230 }
231
232 private:
233 std::vector<Entry> m_listeners;
234 std::vector<Entry> m_pending;
235 std::vector<EventT> m_queue;
236 std::vector<EventT> m_dispatch;
237 ListenerId m_nextId = 1;
238 int m_flushDepth = 0;
239};
240
241} // namespace Vkm::Engine
bool hasListeners() const override
Whether any live listener remains, mid-dispatch removals aside.
Definition bus.h:174
void deliver() override
Deliver the batch takeQueue set aside to every current listener.
Definition bus.h:157
void emit(const EventT &event)
Dispatch event to every current listener synchronously.
Definition bus.h:125
void takeQueue() override
Swap the queue aside so what a listener enqueues cannot join it.
Definition bus.h:147
ListenerId subscribe(std::function< void(const EventT &)> cb)
Append a listener and return its new id.
Definition bus.h:83
bool remove(ListenerId id)
Erase the listener with id.
Definition bus.h:103
void enqueue(EventT event)
Buffer event for delivery on the next flush().
Definition bus.h:138
virtual void takeQueue()=0
Set this bus's queued events aside, ready to be delivered.
virtual bool hasListeners() const =0
Whether anything is still listening on this bus.
virtual void deliver()=0
Deliver what takeQueue set aside, to this bus's listeners.