Loading...
Searching...
No Matches
MessageBus.hpp
1#pragma once
2#include <Process/ExecutionContext.hpp>
3#include <Process/ExecutionTransaction.hpp>
4
6
7#include <boost/pfr.hpp>
8
9#include <avnd/common/function_reflection.hpp>
10#include <avnd/common/tag.hpp>
11#include <avnd/concepts/message_bus.hpp>
12
13#include <array>
14#include <atomic>
15#include <cstddef>
16#include <functional>
17#include <memory>
18#include <type_traits>
19#include <utility>
20#include <vector>
21
22namespace oscr
23{
25template <typename F>
27template <typename R, typename Arg>
28struct function_argument<std::function<R(Arg)>>
29{
30 using type = Arg;
31};
32
44{
45 bool request() noexcept { return !m_pending.exchange(true, std::memory_order_acq_rel); }
46 void cancel() noexcept { m_pending.store(false, std::memory_order_release); }
47 void clear() noexcept { m_pending.store(false, std::memory_order_release); }
48
50 void park() noexcept
51 {
52 cancel();
53 m_parked.store(true, std::memory_order_relaxed);
54 }
56 bool unpark() noexcept { return m_parked.exchange(false, std::memory_order_relaxed); }
57
58private:
59 std::atomic_bool m_pending{false};
60 std::atomic_bool m_parked{false};
61};
62
73{
75 void add(std::function<bool()> f) { m_retries.push_back(std::move(f)); }
76
78 void park() noexcept { m_parked = true; }
79
81 void retry() noexcept
82 {
83 if(!std::exchange(m_parked, false))
84 return;
85 for(auto& f : m_retries)
86 if(!f())
87 m_parked = true;
88 }
89
90private:
91 std::vector<std::function<bool()>> m_retries;
92 bool m_parked{};
93};
94
104template <typename T, std::size_t N = 16>
106{
107 static_assert(N > 0 && (N & (N - 1)) == 0);
108
109 bool push(T&& msg) noexcept(std::is_nothrow_move_assignable_v<T>)
110 {
111 const auto w = m_write.load(std::memory_order_relaxed);
112 if(w - m_read.load(std::memory_order_acquire) >= N)
113 {
114 m_dropped.fetch_add(1, std::memory_order_relaxed);
115 return false;
116 }
117 m_slots[w % N] = std::move(msg);
118 m_write.store(w + 1, std::memory_order_release);
119 return true;
120 }
121
123 template <typename F>
124 int drain(F&& f)
125 {
126 this->clear();
127 auto r = m_read.load(std::memory_order_relaxed);
128 const auto w = m_write.load(std::memory_order_acquire);
129 int n = 0;
130 for(; r != w; ++r, ++n)
131 {
132 T msg = std::move(m_slots[r % N]);
133 m_read.store(r + 1, std::memory_order_release);
134 f(std::move(msg));
135 }
136 return n;
137 }
138
139 std::size_t dropped() const noexcept
140 {
141 return m_dropped.load(std::memory_order_relaxed);
142 }
143
144private:
145 std::array<T, N> m_slots{};
146 std::atomic<std::size_t> m_write{0};
147 std::atomic<std::size_t> m_read{0};
148 std::atomic<std::size_t> m_dropped{0};
149};
150
160template <typename T>
162{
163 template <typename F>
164 void for_each_slot(F&& f)
165 {
166 for(auto& s : m_slots)
167 f(s);
168 }
169
170 T& back() noexcept { return m_slots[m_back]; }
171
172 void publish() noexcept
173 {
174 m_back = m_middle.exchange(m_back | fresh, std::memory_order_acq_rel) & index_mask;
175 }
176
179 T* take() noexcept
180 {
181 this->clear();
182 if(!(m_middle.load(std::memory_order_acquire) & fresh))
183 return nullptr;
184 m_front = m_middle.exchange(m_front, std::memory_order_acq_rel) & index_mask;
185 return &m_slots[m_front];
186 }
187
188private:
189 static constexpr int fresh = 4;
190 static constexpr int index_mask = 3;
191 std::array<T, 3> m_slots{};
192 std::atomic<int> m_middle{1};
193 int m_back{0};
194 int m_front{2};
195};
196
198{
200
201 template <typename F>
202 void operator()(const F& f)
203 {
204 if constexpr(std::is_arithmetic_v<F>)
205 r.stream().stream << f;
206 else if constexpr(std::is_aggregate_v<F>)
207 avnd::pfr::for_each_field(f, *this);
208 else if constexpr(avnd::list_ish<F>)
209 {
210 r.stream().stream << (int64_t)std::ssize(f);
211 for(const auto& val : f)
212 {
213 (*this)(val);
214 }
215 }
216 else
217 r.stream() << f;
218 }
219
220 template <typename... Args>
221 void operator()(const std::variant<Args...>& f)
222 {
223 r.stream().stream << (int)f.index();
224 std::visit(*this, f);
225 }
226};
227
229{
230 std::function<void(QByteArray)>& bus;
231
232 // Case for zero-argument message bus (e.g. just a ping)
233 void operator()() { this->bus(QByteArray{}); }
234
235 template <typename T>
236 requires std::is_trivial_v<T>
237 void operator()(const T& msg)
238 {
239 // Here we can just do a memcpy
240 this->bus(QByteArray((const char*)&msg, sizeof(msg)));
241 }
242
243 template <typename T>
244 requires(
245 !std::is_trivial_v<T> && avnd::relocatable<T>
246 && alignof(T) <= alignof(max_align_t))
247 void operator()(const T& msg)
248 {
249 QByteArray b(sizeof(msg), Qt::Uninitialized);
250 auto dst = reinterpret_cast<T*>(b.data());
251 new(dst) T(msg);
252
253 this->bus(std::move(b));
254 }
255
256 template <typename T>
257 requires(
258 !std::is_trivial_v<T> && avnd::relocatable<T>
259 && alignof(T) <= alignof(max_align_t))
260 void operator()(T&& msg)
261 {
262 QByteArray b(sizeof(msg), Qt::Uninitialized);
263 auto dst = reinterpret_cast<T*>(b.data());
264 std::construct_at(dst, std::move(msg));
265
266 this->bus(std::move(b));
267 }
268
269 template <typename T>
270 requires(
271 !std::is_trivial_v<T>
272 && (!avnd::relocatable<T> || (alignof(T) > alignof(max_align_t))))
273 void operator()(const T& msg)
274 {
275 // Here we gotta serialize... :D
276 QByteArray buf;
277
278 DataStreamReader str{&buf};
279 Serializer{str}(msg);
280
281 this->bus(std::move(buf));
282 }
283
284 template <typename T>
285 void operator()(const std::shared_ptr<T>& msg)
286 {
287 SCORE_ASSERT(msg);
288 return (*this)(std::move(*msg));
289 }
290 template <typename T>
291 void operator()(std::unique_ptr<T> msg)
292 {
293 SCORE_ASSERT(msg);
294 return (*this)(std::move(*msg));
295 }
296};
297
299{
301
302 template <typename F>
303 requires std::is_aggregate_v<F>
304 void operator()(F& f)
305 {
306 avnd::pfr::for_each_field(f, *this);
307 }
308
309 template <typename F>
310 requires(std::is_arithmetic_v<F>)
311 void operator()(F& f)
312 {
313 r.stream().stream >> f;
314 }
315
316 template <typename F>
317 requires avnd::list_ish<F>
318 void operator()(F& f)
319 {
320 int64_t sz;
321 r.stream().stream >> sz;
322 SCORE_ASSERT(sz >= 0);
323 for(int64_t i = 0; i < sz; i++)
324 {
325 using type = typename F::value_type;
326 type val;
327 (*this)(val);
328 f.push_back(std::move(val));
329 }
330 }
331
332 template <std::size_t I, typename... Args>
333 bool write_variant(std::variant<Args...>& f)
334 {
335 auto& elt = f.template emplace<I>();
336 (*this)(elt);
337 return true;
338 }
339
340 template <typename... Args>
341 void operator()(std::variant<Args...>& f)
342 {
343 int index{};
344 r.stream().stream >> index;
345
346 SCORE_ASSERT(index >= 0);
347 SCORE_ASSERT(index < sizeof...(Args));
348 [&]<std::size_t... I>(std::index_sequence<I...>)
349 {
350 (((index == I) && write_variant<I>(f)) || ...);
351 }
352 (std::make_index_sequence<sizeof...(Args)>{});
353 }
354
355 void operator()(auto& f) { r.stream() >> f; }
356};
357
359{
360 QByteArray& mess;
361
362 // Case for zero-argument message bus (e.g. just a ping)
363 void operator()() { }
364
365 template <typename T>
366 requires std::is_trivial_v<T>
367 void operator()(T& msg)
368 {
369 // Here we can just do a memcpy
370 memcpy(&msg, mess.data(), mess.size());
371 }
372 template <typename T>
373 requires(
374 !std::is_trivial_v<T> && avnd::relocatable<T>
375 && alignof(T) <= alignof(max_align_t))
376 void operator()(T& msg)
377 {
378 auto src = reinterpret_cast<T*>(mess.data());
379 msg = std::move(*src);
380 std::destroy_at(src);
381 }
382
383 template <typename T>
384 requires(
385 !std::is_trivial_v<T>
386 && (!avnd::relocatable<T> || (alignof(T) > alignof(max_align_t))))
387 void operator()(T& msg)
388 {
389 // Deserialize... :D
390
391 DataStreamWriter str{mess};
392 Deserializer{str}(msg);
393 }
394};
395
401template <typename... T>
402void free_on_main_thread(const std::weak_ptr<Execution::GCCommandQueue>& gcq, T&... t)
403{
404 if(auto q = gcq.lock())
405 q->enqueue(Execution::gc(std::move(t)...));
406}
407
418template <typename Node>
419std::function<void(QByteArray)> ui_to_processor_bus(
420 Node& eff, std::weak_ptr<Execution::ExecutionCommandQueue> qex_ptr,
421 std::weak_ptr<Execution::GCCommandQueue> gcq)
422{
423 return [qex_ptr = std::move(qex_ptr), gcq = std::move(gcq), &eff](QByteArray b) {
424 auto qex = qex_ptr.lock();
425 if(!qex)
426 return;
427
428 using refl = avnd::function_reflection<&Node::process_message>;
429 static_assert(refl::count <= 1);
430
431 if constexpr(refl::count == 0)
432 {
433 qex->enqueue([&eff] { eff.process_message(); });
434 }
435 else
436 {
437 using arg_type = avnd::first_argument<&Node::process_message>;
438 using message_type = std::decay_t<arg_type>;
439 static constexpr auto deliver = [](Node& eff, message_type& m) {
440 if constexpr(std::is_lvalue_reference_v<arg_type>)
441 eff.process_message(m);
442 else
443 eff.process_message(std::move(m));
444 };
445
446 if constexpr(
447 std::is_trivially_destructible_v<message_type>
448 && sizeof(message_type) + sizeof(Node*)
449 < Execution::ExecutionCommand::max_storage)
450 {
451 message_type msg{};
452 MessageBusReader{b}(msg);
453 qex->enqueue([msg, &eff]() mutable { deliver(eff, msg); });
454 }
455 else
456 {
457 auto msg = std::make_unique<message_type>();
458 MessageBusReader{b}(*msg);
459 qex->enqueue([msg = std::move(msg), gcq, &eff]() mutable {
460 deliver(eff, *msg);
461 free_on_main_thread(gcq, msg);
462 });
463 }
464 }
465 };
466}
467}
Definition DataStreamVisitor.hpp:27
Definition DataStreamVisitor.hpp:202
Definition Controls.hpp:30
std::function< void(QByteArray)> ui_to_processor_bus(Node &eff, std::weak_ptr< Execution::ExecutionCommandQueue > qex_ptr, std::weak_ptr< Execution::GCCommandQueue > gcq)
Definition MessageBus.hpp:419
void free_on_main_thread(const std::weak_ptr< Execution::GCCommandQueue > &gcq, T &... t)
Definition MessageBus.hpp:402
STL namespace.
Definition MessageBus.hpp:299
Definition MessageBus.hpp:359
Definition MessageBus.hpp:229
Definition MessageBus.hpp:198
Definition MessageBus.hpp:44
void park() noexcept
Writer's thread: the wake-up could not be sent.
Definition MessageBus.hpp:50
bool unpark() noexcept
Writer's thread: whether a wake-up was parked since the last call.
Definition MessageBus.hpp:56
The argument type of a std::function<void(Arg)>.
Definition MessageBus.hpp:26
Definition MessageBus.hpp:162
T * take() noexcept
Definition MessageBus.hpp:179
Definition MessageBus.hpp:106
int drain(F &&f)
UI thread: calls f with every message written since the last call.
Definition MessageBus.hpp:124
Definition MessageBus.hpp:73
void add(std::function< bool()> f)
UI thread, before the node runs. f returns false when it is still parked.
Definition MessageBus.hpp:75
void retry() noexcept
Writer's thread, at each tick.
Definition MessageBus.hpp:81
void park() noexcept
Writer's thread: a channel parked its wake-up.
Definition MessageBus.hpp:78