Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
signal.h
1#pragma once
2#include "gaia/config/config.h"
3
4#include <tuple>
5#include <typeinfo>
6#include <utility>
7
8#include "gaia/cnt/darray.h"
9#include "gaia/core/func.h"
10#include "gaia/core/utility.h"
11
12namespace gaia {
13 namespace util {
15 namespace detail {
16 template <typename Ret, typename... Args>
17 auto func_ptr(Ret (*)(Args...)) -> Ret (*)(Args...);
18
19 template <typename Ret, typename Type, typename... Args, typename Other>
20 auto func_ptr(Ret (*)(Type, Args...), Other&&) -> Ret (*)(Args...);
21
22 template <typename Class, typename Ret, typename... Args, typename... Other>
23 auto func_ptr(Ret (Class::*)(Args...), Other&&...) -> Ret (*)(Args...);
24
25 template <typename Class, typename Ret, typename... Args, typename... Other>
26 auto func_ptr(Ret (Class::*)(Args...) const, Other&&...) -> Ret (*)(Args...);
27
28 template <typename Class, typename Type, typename... Other>
29 auto func_ptr(Type Class::*, Other&&...) -> Type (*)();
30
31 template <typename... Type>
32 using func_ptr_t = decltype(detail::func_ptr(std::declval<Type>()...));
33
34 template <typename... Class, typename Ret, typename... Args>
35 GAIA_NODISCARD constexpr auto index_sequence_for(Ret (*)(Args...)) {
36 return std::index_sequence_for<Class..., Args...>{};
37 }
38
39 // Wraps a function or a member of a specified type
40 template <auto>
41 struct connect_arg_t {};
42 // Function wrapper
43 template <auto Func>
44 inline constexpr connect_arg_t<Func> connect_arg{};
45 } // namespace detail
47
48 template <typename>
49 class delegate;
50 template <typename Function>
51 class signal;
52 template <typename Function>
53 class sink;
54
55 //------------------------------------------------------------------------------
56 // delegate
57 //------------------------------------------------------------------------------
58
66 template <typename Ret, typename... Args>
67 class delegate<Ret(Args...)> {
68 using func_type = Ret(const void*, Args...);
70 template <std::size_t I>
71 using arg_t = std::tuple_element_t<I, std::tuple<Args...>>;
72
73 func_type* m_fnc = nullptr;
74 const void* m_ctx = nullptr;
75
76 public:
77 delegate() noexcept = default;
78
81 template <auto FuncToBind>
82 delegate(detail::connect_arg_t<FuncToBind>) noexcept {
83 bind<FuncToBind>();
84 }
85
90 template <auto FuncToBind, typename Type>
91 delegate(detail::connect_arg_t<FuncToBind>, Type&& value_or_instance) noexcept {
92 bind<FuncToBind>(GAIA_FWD(value_or_instance));
93 }
94
98 delegate(func_type* func, const void* data = nullptr) noexcept {
99 bind(func, data);
100 }
101
104 template <auto FuncToBind>
105 void bind() noexcept {
106 m_ctx = nullptr;
107
108 if constexpr (std::is_invocable_r_v<Ret, decltype(FuncToBind), Args...>) {
109 m_fnc = [](const void*, Args... args) {
110 return Ret(invoke(FuncToBind, GAIA_FWD(args)...));
111 };
112 } else if constexpr (std::is_member_pointer_v<decltype(FuncToBind)>) {
113 m_fnc = wrap<FuncToBind>(detail::index_sequence_for<std::tuple_element_t<0, std::tuple<Args...>>>(
114 detail::func_ptr_t<decltype(FuncToBind)>{}));
115 } else {
116 m_fnc = wrap<FuncToBind>(detail::index_sequence_for(detail::func_ptr_t<decltype(FuncToBind)>{}));
117 }
118 }
119
126 template <auto FuncToBind, typename Type>
127 void bind(Type& value_or_instance) noexcept {
128 m_ctx = &value_or_instance;
129
130 if constexpr (std::is_invocable_r_v<Ret, decltype(FuncToBind), Type&, Args...>) {
131 using const_or_not_type = std::conditional_t<std::is_const_v<Type>, const void*, void*>;
132 m_fnc = [](const void* ctx, Args... args) {
133 auto pType = static_cast<Type*>(const_cast<const_or_not_type>(ctx));
134 return Ret(invoke(FuncToBind, *pType, GAIA_FWD(args)...));
135 };
136 } else {
137 m_fnc = wrap<FuncToBind>(
138 value_or_instance, detail::index_sequence_for(detail::func_ptr_t<decltype(FuncToBind), Type>{}));
139 }
140 }
141
146 template <auto FuncToBind, typename Type>
147 void bind(Type* value_or_instance) noexcept {
148 m_ctx = value_or_instance;
149
150 if constexpr (std::is_invocable_r_v<Ret, decltype(FuncToBind), Type*, Args...>) {
151 using const_or_not_type = std::conditional_t<std::is_const_v<Type>, const void*, void*>;
152 m_fnc = [](const void* ctx, Args... args) {
153 auto pType = static_cast<Type*>(const_cast<const_or_not_type>(ctx));
154 return Ret(invoke(FuncToBind, pType, GAIA_FWD(args)...));
155 };
156 } else {
157 m_fnc = wrap<FuncToBind>(
158 value_or_instance, detail::index_sequence_for(detail::func_ptr_t<decltype(FuncToBind), Type>{}));
159 }
160 }
161
166 void bind(func_type* function, const void* context = nullptr) noexcept {
167 m_fnc = function;
168 m_ctx = context;
169 }
170
172 void reset() noexcept {
173 m_fnc = nullptr;
174 m_ctx = nullptr;
175 }
176
179 GAIA_NODISCARD bool has_func() const noexcept {
180 return m_fnc != nullptr;
181 }
182
185 GAIA_NODISCARD const void* instance() const noexcept {
186 return m_ctx;
187 }
188
192 Ret operator()(Args... args) const {
193 GAIA_ASSERT(m_fnc != nullptr && "Trying to call an unbound delegate!");
194 return m_fnc(m_ctx, GAIA_FWD(args)...);
195 }
196
199 GAIA_NODISCARD explicit operator bool() const noexcept {
200 // There's no way to set just m_ctx so it's enough to test m_fnc
201 return m_fnc != nullptr;
202 }
203
207 GAIA_NODISCARD bool operator==(const delegate<Ret(Args...)>& other) const noexcept {
208 return m_fnc == other.m_fnc && m_ctx == other.m_ctx;
209 }
210
214 GAIA_NODISCARD bool operator!=(const delegate<Ret(Args...)>& other) const noexcept {
215 return !operator==(other);
216 }
217
218 private:
219 template <auto FuncToBind, std::size_t... Index>
220 GAIA_NODISCARD auto wrap(std::index_sequence<Index...>) noexcept {
221 return [](const void*, Args... args) {
222 [[maybe_unused]] const auto argsFwd = std::forward_as_tuple(GAIA_FWD(args)...);
223 return Ret(invoke(FuncToBind, GAIA_FWD_AS(arg_t<Index>, std::get<Index>(argsFwd))...));
224 };
225 }
226
227 template <auto FuncToBind, typename Type, std::size_t... Index>
228 GAIA_NODISCARD auto wrap(Type&, std::index_sequence<Index...>) noexcept {
229 using const_or_not_type = std::conditional_t<std::is_const_v<Type>, const void*, void*>;
230 return [](const void* ctx, Args... args) {
231 [[maybe_unused]] const auto argsFwd = std::forward_as_tuple(GAIA_FWD(args)...);
232 auto pType = static_cast<Type*>(const_cast<const_or_not_type>(ctx));
233 return Ret(invoke(FuncToBind, *pType, GAIA_FWD_AS(arg_t<Index>, std::get<Index>(argsFwd))...));
234 };
235 }
236
237 template <auto FuncToBind, typename Type, std::size_t... Index>
238 GAIA_NODISCARD auto wrap(Type*, std::index_sequence<Index...>) noexcept {
239 using const_or_not_type = std::conditional_t<std::is_const_v<Type>, const void*, void*>;
240 return [](const void* ctx, Args... args) {
241 [[maybe_unused]] const auto argsFwd = std::forward_as_tuple(GAIA_FWD(args)...);
242 auto pType = static_cast<Type*>(const_cast<const_or_not_type>(ctx));
243 return Ret(invoke(FuncToBind, pType, GAIA_FWD_AS(arg_t<Index>, std::get<Index>(argsFwd))...));
244 };
245 }
246 };
247
254 template <typename Ret, typename... Args>
255 GAIA_NODISCARD bool operator==(const delegate<Ret(Args...)>& lhs, const delegate<Ret(Args...)>& rhs) noexcept {
256 return lhs == rhs;
257 }
258
265 template <typename Ret, typename... Args>
266 GAIA_NODISCARD bool operator!=(const delegate<Ret(Args...)>& lhs, const delegate<Ret(Args...)>& rhs) noexcept {
267 return lhs != rhs;
268 }
269
273 template <auto Func>
274 delegate(detail::connect_arg_t<Func> tag) noexcept
275 -> delegate<std::remove_pointer_t<detail::func_ptr_t<decltype(Func)>>>;
276
282 template <auto Func, typename Type>
283 delegate(detail::connect_arg_t<Func> tag, Type&& value_or_instance) noexcept
284 -> delegate<std::remove_pointer_t<detail::func_ptr_t<decltype(Func), Type>>>;
285
291 template <typename Ret, typename... Args>
292 delegate(Ret (*func)(const void*, Args...), const void* data = nullptr) noexcept -> delegate<Ret(Args...)>;
293
294 //------------------------------------------------------------------------------
295 // signal
296 //------------------------------------------------------------------------------
297
298 template <typename Ret, typename... Args>
299 class signal<Ret(Args...)>;
300
302 namespace detail {
303 template <typename Ret, typename... Args>
304 using container = cnt::darray<delegate<Ret(Args...)>>;
305 } // namespace detail
307
312 template <typename Ret, typename... Args>
313 class signal<Ret(Args...)> {
314 friend class sink<Ret(Args...)>;
315
316 private:
318 detail::container<Ret, Args...> m_listeners;
319
320 public:
322 using size_type = typename detail::container<Ret, Args...>::size_type;
324 using sink_type = sink<Ret(Args...)>;
325
328 GAIA_NODISCARD size_type size() const noexcept {
329 return m_listeners.size();
330 }
331
334 GAIA_NODISCARD bool empty() const noexcept {
335 return m_listeners.empty();
336 }
337
340 void emit(Args... args) {
341 for (auto&& call: std::as_const(m_listeners))
342 call(args...);
343 }
344 };
345
346 //------------------------------------------------------------------------------
347 // Sink
348 //------------------------------------------------------------------------------
349
357 template <typename Ret, typename... Args>
358 class sink<Ret(Args...)> {
359 using signal_type = signal<Ret(Args...)>;
360 using func_type = Ret(const void*, Args...);
361
362 signal_type* m_s;
363
364 public:
367 sink(signal<Ret(Args...)>& ref) noexcept: m_s{&ref} {}
368
372 void move_from(sink& other) {
373 m_s->m_listeners.reserve(m_s->m_listeners.size() + other.m_s->m_listeners.size());
374 for (auto&& listener: other.m_s->m_listeners)
375 m_s->m_listeners.push_back(GAIA_MOV(listener));
376
377 other.reset();
378 }
379
383 template <auto FuncToBind>
384 void bind() {
385 delegate<Ret(Args...)> call{};
386 call.template bind<FuncToBind>();
387 bind_internal(call);
388 }
389
396 template <auto FuncToBind, typename Type>
397 void bind(Type& value_or_instance) {
398 delegate<Ret(Args...)> call{};
399 call.template bind<FuncToBind>(value_or_instance);
400 bind_internal(call);
401 }
402
407 void bind(func_type* func, const void* data = nullptr) {
408 if (func == nullptr && data == nullptr)
409 return;
410
411 delegate<Ret(Args...)> call{};
412 call.bind(func, data);
413 bind_internal(call);
414 }
415
418 template <auto FuncToUnbind>
419 void unbind() {
420 delegate<Ret(Args...)> call{};
421 call.template bind<FuncToUnbind>();
422
423 m_s->m_listeners.retain([&](const auto& l) {
424 return l != call;
425 });
426 }
427
432 template <auto FuncToUnbind, typename Type>
433 void unbind(Type& value_or_instance) {
434 delegate<Ret(Args...)> call{};
435 call.template bind<FuncToUnbind>(value_or_instance);
436
437 auto& listeners = m_s->m_listeners;
438 for (uint32_t i = 0; i < listeners.size();) {
439 if (listeners[i] != call) {
440 ++i;
441 continue;
442 }
443
444 core::swap_erase_unsafe(listeners, i);
445 }
446 }
447
451 template <typename Type>
452 void unbind(Type* value_or_instance) {
453 if (!value_or_instance)
454 return;
455
456 auto& listeners = m_s->m_listeners;
457 for (uint32_t i = 0; i < listeners.size();) {
458 if (listeners[i].instance() != value_or_instance) {
459 ++i;
460 continue;
461 }
462
463 core::swap_erase_unsafe(listeners, i);
464 }
465 }
466
470 template <typename Type>
471 void unbind(Type& value_or_instance) {
472 unbind(&value_or_instance);
473 }
474
476 void reset() {
477 m_s->m_listeners.clear();
478 }
479
480 private:
481 void bind_internal(const delegate<Ret(Args...)>& call) {
482 if (!core::has(m_s->m_listeners, call))
483 m_s->m_listeners.push_back(GAIA_MOV(call));
484 }
485 };
486
491 template <typename Ret, typename... Args>
492 sink(signal<Ret(Args...)>& ref) noexcept -> sink<Ret(Args...)>;
493 } // namespace util
494} // namespace gaia
delegate(detail::connect_arg_t< FuncToBind >, Type &&value_or_instance) noexcept
Constructs a delegate by binding a free function with context or a bound member to it.
Definition signal.h:91
void bind(func_type *function, const void *context=nullptr) noexcept
Binds an user defined function with optional context to a delegate. The context is returned as the fi...
Definition signal.h:166
void reset() noexcept
Resets a delegate. After a reset, a delegate cannot be invoked anymore.
Definition signal.h:172
GAIA_NODISCARD bool has_func() const noexcept
Returns the functor pointer linked to a delegate, if any.
Definition signal.h:179
void bind() noexcept
Binds a free function or an unbound member to a delegate.
Definition signal.h:105
void bind(Type &value_or_instance) noexcept
Binds a free function with context or a bound member to a delegate. When used to bind a ree function ...
Definition signal.h:127
Ret operator()(Args... args) const
The delegate invokes the underlying function and returns the result.
Definition signal.h:192
delegate(func_type *func, const void *data=nullptr) noexcept
Constructs a delegate by binding a function with optional context to it.
Definition signal.h:98
GAIA_NODISCARD bool operator!=(const delegate< Ret(Args...)> &other) const noexcept
Compares the contents of two delegates.
Definition signal.h:214
GAIA_NODISCARD bool operator==(const delegate< Ret(Args...)> &other) const noexcept
Compares the contents of two delegates.
Definition signal.h:207
GAIA_NODISCARD const void * instance() const noexcept
Returns the instance or the context linked to a delegate, if any.
Definition signal.h:185
delegate(detail::connect_arg_t< FuncToBind >) noexcept
Constructs a delegate by binding a free function or an unbound member to it.
Definition signal.h:82
void bind(Type *value_or_instance) noexcept
Binds a free function with context or a bound member to a delegate.
Definition signal.h:147
Definition signal.h:49
Signal is a container of listener which it can notify. It works directly with references to classes a...
Definition signal.h:313
GAIA_NODISCARD size_type size() const noexcept
Number of listeners connected to the signal.
Definition signal.h:328
typename detail::container< Ret, Args... >::size_type size_type
Type used to represent listener counts.
Definition signal.h:322
GAIA_NODISCARD bool empty() const noexcept
Check is there is any listener bound to the signal.
Definition signal.h:334
void emit(Args... args)
Signals all listeners.
Definition signal.h:340
Definition signal.h:51
Sink is a helper class used to bind listeners to signals. The separation between signal and sink make...
Definition signal.h:358
sink(signal< Ret(Args...)> &ref) noexcept
Constructs a sink that is allowed to modify a given signal.
Definition signal.h:367
void move_from(sink &other)
Moves signals from another sink to this one. Result is stored in this object. The sink we merge from ...
Definition signal.h:372
void bind()
Binds a free function or an unbound member to a signal. The signal handler performs checks to avoid m...
Definition signal.h:384
void bind(Type &value_or_instance)
Binds a free function with context or a bound member to a signal. When used to bind a free function w...
Definition signal.h:397
void unbind(Type &value_or_instance)
Unbinds a free function with context or bound members from a signal.
Definition signal.h:471
void unbind(Type *value_or_instance)
Unbinds a free function with context or bound members from a signal.
Definition signal.h:452
void unbind(Type &value_or_instance)
Unbinds a free function with context or a bound member from a signal.
Definition signal.h:433
void bind(func_type *func, const void *data=nullptr)
Binds an user defined function with optional context to a signal. The context is returned as the firs...
Definition signal.h:407
void reset()
Unbinds all listeners from a signal.
Definition signal.h:476
void unbind()
Unbinds a free function or an unbound member from a signal.
Definition signal.h:419
Definition signal.h:53