summaryrefslogtreecommitdiffstats
path: root/deps/include/entt/signal
diff options
context:
space:
mode:
Diffstat (limited to 'deps/include/entt/signal')
-rw-r--r--deps/include/entt/signal/delegate.hpp323
-rw-r--r--deps/include/entt/signal/dispatcher.hpp397
-rw-r--r--deps/include/entt/signal/emitter.hpp182
-rw-r--r--deps/include/entt/signal/fwd.hpp46
-rw-r--r--deps/include/entt/signal/sigh.hpp471
5 files changed, 1419 insertions, 0 deletions
diff --git a/deps/include/entt/signal/delegate.hpp b/deps/include/entt/signal/delegate.hpp
new file mode 100644
index 0000000..e8b3299
--- /dev/null
+++ b/deps/include/entt/signal/delegate.hpp
@@ -0,0 +1,323 @@
+#ifndef ENTT_SIGNAL_DELEGATE_HPP
+#define ENTT_SIGNAL_DELEGATE_HPP
+
+#include <cstddef>
+#include <functional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "../core/type_traits.hpp"
+#include "fwd.hpp"
+
+namespace entt {
+
+/*! @cond TURN_OFF_DOXYGEN */
+namespace internal {
+
+template<typename Ret, typename... Args>
+constexpr auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...);
+
+template<typename Ret, typename Type, typename... Args, typename Other>
+constexpr auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...);
+
+template<typename Class, typename Ret, typename... Args, typename... Other>
+constexpr auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...);
+
+template<typename Class, typename Ret, typename... Args, typename... Other>
+constexpr auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...);
+
+template<typename Class, typename Type, typename... Other>
+constexpr auto function_pointer(Type Class::*, Other &&...) -> Type (*)();
+
+template<typename... Type>
+using function_pointer_t = decltype(function_pointer(std::declval<Type>()...));
+
+template<typename... Class, typename Ret, typename... Args>
+[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) {
+ return std::index_sequence_for<Class..., Args...>{};
+}
+
+} // namespace internal
+/*! @endcond */
+
+/**
+ * @brief Basic delegate implementation.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error unless the template parameter is a function type.
+ */
+template<typename>
+class delegate;
+
+/**
+ * @brief Utility class to use to send around functions and members.
+ *
+ * Unmanaged delegate for function pointers and members. Users of this class are
+ * in charge of disconnecting instances before deleting them.
+ *
+ * A delegate can be used as a general purpose invoker without memory overhead
+ * for free functions possibly with payloads and bound or unbound members.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename Ret, typename... Args>
+class delegate<Ret(Args...)> {
+ template<auto Candidate, std::size_t... Index>
+ [[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
+ return [](const void *, Args... args) -> Ret {
+ [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
+ [[maybe_unused]] constexpr auto offset = !std::is_invocable_r_v<Ret, decltype(Candidate), type_list_element_t<Index, type_list<Args...>>...> * (sizeof...(Args) - sizeof...(Index));
+ return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
+ };
+ }
+
+ template<auto Candidate, typename Type, std::size_t... Index>
+ [[nodiscard]] auto wrap(Type &, std::index_sequence<Index...>) noexcept {
+ return [](const void *payload, Args... args) -> Ret {
+ Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+ [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
+ [[maybe_unused]] constexpr auto offset = !std::is_invocable_r_v<Ret, decltype(Candidate), Type &, type_list_element_t<Index, type_list<Args...>>...> * (sizeof...(Args) - sizeof...(Index));
+ return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
+ };
+ }
+
+ template<auto Candidate, typename Type, std::size_t... Index>
+ [[nodiscard]] auto wrap(Type *, std::index_sequence<Index...>) noexcept {
+ return [](const void *payload, Args... args) -> Ret {
+ Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+ [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
+ [[maybe_unused]] constexpr auto offset = !std::is_invocable_r_v<Ret, decltype(Candidate), Type *, type_list_element_t<Index, type_list<Args...>>...> * (sizeof...(Args) - sizeof...(Index));
+ return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
+ };
+ }
+
+public:
+ /*! @brief Function type of the contained target. */
+ using function_type = Ret(const void *, Args...);
+ /*! @brief Function type of the delegate. */
+ using type = Ret(Args...);
+ /*! @brief Return type of the delegate. */
+ using result_type = Ret;
+
+ /*! @brief Default constructor. */
+ delegate() noexcept = default;
+
+ /**
+ * @brief Constructs a delegate with a given object or payload, if any.
+ * @tparam Candidate Function or member to connect to the delegate.
+ * @tparam Type Type of class or type of payload, if any.
+ * @param value_or_instance Optional valid object that fits the purpose.
+ */
+ template<auto Candidate, typename... Type>
+ delegate(connect_arg_t<Candidate>, Type &&...value_or_instance) noexcept {
+ connect<Candidate>(std::forward<Type>(value_or_instance)...);
+ }
+
+ /**
+ * @brief Constructs a delegate and connects an user defined function with
+ * optional payload.
+ * @param function Function to connect to the delegate.
+ * @param payload User defined arbitrary data.
+ */
+ delegate(function_type *function, const void *payload = nullptr) noexcept {
+ connect(function, payload);
+ }
+
+ /**
+ * @brief Connects a free function or an unbound member to a delegate.
+ * @tparam Candidate Function or member to connect to the delegate.
+ */
+ template<auto Candidate>
+ void connect() noexcept {
+ instance = nullptr;
+
+ if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
+ fn = [](const void *, Args... args) -> Ret {
+ return Ret(std::invoke(Candidate, std::forward<Args>(args)...));
+ };
+ } else if constexpr(std::is_member_pointer_v<decltype(Candidate)>) {
+ fn = wrap<Candidate>(internal::index_sequence_for<type_list_element_t<0, type_list<Args...>>>(internal::function_pointer_t<decltype(Candidate)>{}));
+ } else {
+ fn = wrap<Candidate>(internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate)>{}));
+ }
+ }
+
+ /**
+ * @brief Connects a free function with payload or a bound member to a
+ * delegate.
+ *
+ * The delegate isn't responsible for the connected object or the payload.
+ * Users must always guarantee that the lifetime of the instance overcomes
+ * the one of the delegate.<br/>
+ * When used to connect a free function with payload, its signature must be
+ * such that the instance is the first argument before the ones used to
+ * define the delegate itself.
+ *
+ * @tparam Candidate Function or member to connect to the delegate.
+ * @tparam Type Type of class or type of payload.
+ * @param value_or_instance A valid reference that fits the purpose.
+ */
+ template<auto Candidate, typename Type>
+ void connect(Type &value_or_instance) noexcept {
+ instance = &value_or_instance;
+
+ if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>) {
+ fn = [](const void *payload, Args... args) -> Ret {
+ Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+ return Ret(std::invoke(Candidate, *curr, std::forward<Args>(args)...));
+ };
+ } else {
+ fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
+ }
+ }
+
+ /**
+ * @brief Connects a free function with payload or a bound member to a
+ * delegate.
+ *
+ * @sa connect(Type &)
+ *
+ * @tparam Candidate Function or member to connect to the delegate.
+ * @tparam Type Type of class or type of payload.
+ * @param value_or_instance A valid pointer that fits the purpose.
+ */
+ template<auto Candidate, typename Type>
+ void connect(Type *value_or_instance) noexcept {
+ instance = value_or_instance;
+
+ if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>) {
+ fn = [](const void *payload, Args... args) -> Ret {
+ Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+ return Ret(std::invoke(Candidate, curr, std::forward<Args>(args)...));
+ };
+ } else {
+ fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
+ }
+ }
+
+ /**
+ * @brief Connects an user defined function with optional payload to a
+ * delegate.
+ *
+ * The delegate isn't responsible for the connected object or the payload.
+ * Users must always guarantee that the lifetime of an instance overcomes
+ * the one of the delegate.<br/>
+ * The payload is returned as the first argument to the target function in
+ * all cases.
+ *
+ * @param function Function to connect to the delegate.
+ * @param payload User defined arbitrary data.
+ */
+ void connect(function_type *function, const void *payload = nullptr) noexcept {
+ ENTT_ASSERT(function != nullptr, "Uninitialized function pointer");
+ instance = payload;
+ fn = function;
+ }
+
+ /**
+ * @brief Resets a delegate.
+ *
+ * After a reset, a delegate cannot be invoked anymore.
+ */
+ void reset() noexcept {
+ instance = nullptr;
+ fn = nullptr;
+ }
+
+ /**
+ * @brief Returns a pointer to the stored callable function target, if any.
+ * @return An opaque pointer to the stored callable function target.
+ */
+ [[nodiscard]] function_type *target() const noexcept {
+ return fn;
+ }
+
+ /**
+ * @brief Returns the instance or the payload linked to a delegate, if any.
+ * @return An opaque pointer to the underlying data.
+ */
+ [[nodiscard]] const void *data() const noexcept {
+ return instance;
+ }
+
+ /**
+ * @brief Triggers a delegate.
+ *
+ * The delegate invokes the underlying function and returns the result.
+ *
+ * @warning
+ * Attempting to trigger an invalid delegate results in undefined
+ * behavior.
+ *
+ * @param args Arguments to use to invoke the underlying function.
+ * @return The value returned by the underlying function.
+ */
+ Ret operator()(Args... args) const {
+ ENTT_ASSERT(static_cast<bool>(*this), "Uninitialized delegate");
+ return fn(instance, std::forward<Args>(args)...);
+ }
+
+ /**
+ * @brief Checks whether a delegate actually stores a listener.
+ * @return False if the delegate is empty, true otherwise.
+ */
+ [[nodiscard]] explicit operator bool() const noexcept {
+ // no need to also test instance
+ return !(fn == nullptr);
+ }
+
+ /**
+ * @brief Compares the contents of two delegates.
+ * @param other Delegate with which to compare.
+ * @return False if the two contents differ, true otherwise.
+ */
+ [[nodiscard]] bool operator==(const delegate<Ret(Args...)> &other) const noexcept {
+ return fn == other.fn && instance == other.instance;
+ }
+
+private:
+ const void *instance{};
+ function_type *fn{};
+};
+
+/**
+ * @brief Compares the contents of two delegates.
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ * @param lhs A valid delegate object.
+ * @param rhs A valid delegate object.
+ * @return True if the two contents differ, false otherwise.
+ */
+template<typename Ret, typename... Args>
+[[nodiscard]] bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) noexcept {
+ return !(lhs == rhs);
+}
+
+/**
+ * @brief Deduction guide.
+ * @tparam Candidate Function or member to connect to the delegate.
+ */
+template<auto Candidate>
+delegate(connect_arg_t<Candidate>) -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Candidate Function or member to connect to the delegate.
+ * @tparam Type Type of class or type of payload.
+ */
+template<auto Candidate, typename Type>
+delegate(connect_arg_t<Candidate>, Type &&) -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename Ret, typename... Args>
+delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate<Ret(Args...)>;
+
+} // namespace entt
+
+#endif
diff --git a/deps/include/entt/signal/dispatcher.hpp b/deps/include/entt/signal/dispatcher.hpp
new file mode 100644
index 0000000..fd0287d
--- /dev/null
+++ b/deps/include/entt/signal/dispatcher.hpp
@@ -0,0 +1,397 @@
+#ifndef ENTT_SIGNAL_DISPATCHER_HPP
+#define ENTT_SIGNAL_DISPATCHER_HPP
+
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../container/dense_map.hpp"
+#include "../core/compressed_pair.hpp"
+#include "../core/fwd.hpp"
+#include "../core/type_info.hpp"
+#include "../core/utility.hpp"
+#include "fwd.hpp"
+#include "sigh.hpp"
+
+namespace entt {
+
+/*! @cond TURN_OFF_DOXYGEN */
+namespace internal {
+
+struct basic_dispatcher_handler {
+ virtual ~basic_dispatcher_handler() noexcept = default;
+ virtual void publish() = 0;
+ virtual void disconnect(void *) = 0;
+ virtual void clear() noexcept = 0;
+ [[nodiscard]] virtual std::size_t size() const noexcept = 0;
+};
+
+template<typename Type, typename Allocator>
+class dispatcher_handler final: public basic_dispatcher_handler {
+ static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Invalid type");
+
+ using alloc_traits = std::allocator_traits<Allocator>;
+ using signal_type = sigh<void(Type &), Allocator>;
+ using container_type = std::vector<Type, typename alloc_traits::template rebind_alloc<Type>>;
+
+public:
+ using allocator_type = Allocator;
+
+ dispatcher_handler(const allocator_type &allocator)
+ : signal{allocator},
+ events{allocator} {}
+
+ void publish() override {
+ const auto length = events.size();
+
+ for(std::size_t pos{}; pos < length; ++pos) {
+ signal.publish(events[pos]);
+ }
+
+ events.erase(events.cbegin(), events.cbegin() + length);
+ }
+
+ void disconnect(void *instance) override {
+ bucket().disconnect(instance);
+ }
+
+ void clear() noexcept override {
+ events.clear();
+ }
+
+ [[nodiscard]] auto bucket() noexcept {
+ return typename signal_type::sink_type{signal};
+ }
+
+ void trigger(Type event) {
+ signal.publish(event);
+ }
+
+ template<typename... Args>
+ void enqueue(Args &&...args) {
+ if constexpr(std::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<Type>)) {
+ events.push_back(Type{std::forward<Args>(args)...});
+ } else {
+ events.emplace_back(std::forward<Args>(args)...);
+ }
+ }
+
+ [[nodiscard]] std::size_t size() const noexcept override {
+ return events.size();
+ }
+
+private:
+ signal_type signal;
+ container_type events;
+};
+
+} // namespace internal
+/*! @endcond */
+
+/**
+ * @brief Basic dispatcher implementation.
+ *
+ * A dispatcher can be used either to trigger an immediate event or to enqueue
+ * events to be published all together once per tick.<br/>
+ * Listeners are provided in the form of member functions. For each event of
+ * type `Type`, listeners are such that they can be invoked with an argument of
+ * type `Type &`, no matter what the return type is.
+ *
+ * The dispatcher creates instances of the `sigh` class internally. Refer to the
+ * documentation of the latter for more details.
+ *
+ * @tparam Allocator Type of allocator used to manage memory and elements.
+ */
+template<typename Allocator>
+class basic_dispatcher {
+ template<typename Type>
+ using handler_type = internal::dispatcher_handler<Type, Allocator>;
+
+ using key_type = id_type;
+ // std::shared_ptr because of its type erased allocator which is useful here
+ using mapped_type = std::shared_ptr<internal::basic_dispatcher_handler>;
+
+ using alloc_traits = std::allocator_traits<Allocator>;
+ using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const key_type, mapped_type>>;
+ using container_type = dense_map<key_type, mapped_type, identity, std::equal_to<>, container_allocator>;
+
+ template<typename Type>
+ [[nodiscard]] handler_type<Type> &assure(const id_type id) {
+ static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
+ auto &&ptr = pools.first()[id];
+
+ if(!ptr) {
+ const auto &allocator = get_allocator();
+ ptr = std::allocate_shared<handler_type<Type>>(allocator, allocator);
+ }
+
+ return static_cast<handler_type<Type> &>(*ptr);
+ }
+
+ template<typename Type>
+ [[nodiscard]] const handler_type<Type> *assure(const id_type id) const {
+ static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
+
+ if(auto it = pools.first().find(id); it != pools.first().cend()) {
+ return static_cast<const handler_type<Type> *>(it->second.get());
+ }
+
+ return nullptr;
+ }
+
+public:
+ /*! @brief Allocator type. */
+ using allocator_type = Allocator;
+ /*! @brief Unsigned integer type. */
+ using size_type = std::size_t;
+
+ /*! @brief Default constructor. */
+ basic_dispatcher()
+ : basic_dispatcher{allocator_type{}} {}
+
+ /**
+ * @brief Constructs a dispatcher with a given allocator.
+ * @param allocator The allocator to use.
+ */
+ explicit basic_dispatcher(const allocator_type &allocator)
+ : pools{allocator, allocator} {}
+
+ /*! @brief Default copy constructor, deleted on purpose. */
+ basic_dispatcher(const basic_dispatcher &) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other The instance to move from.
+ */
+ basic_dispatcher(basic_dispatcher &&other) noexcept
+ : pools{std::move(other.pools)} {}
+
+ /**
+ * @brief Allocator-extended move constructor.
+ * @param other The instance to move from.
+ * @param allocator The allocator to use.
+ */
+ basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator)
+ : pools{container_type{std::move(other.pools.first()), allocator}, allocator} {
+ ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a dispatcher is not allowed");
+ }
+
+ /*! @brief Default destructor. */
+ ~basic_dispatcher() noexcept = default;
+
+ /**
+ * @brief Default copy assignment operator, deleted on purpose.
+ * @return This dispatcher.
+ */
+ basic_dispatcher &operator=(const basic_dispatcher &) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other The instance to move from.
+ * @return This dispatcher.
+ */
+ basic_dispatcher &operator=(basic_dispatcher &&other) noexcept {
+ ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a dispatcher is not allowed");
+ pools = std::move(other.pools);
+ return *this;
+ }
+
+ /**
+ * @brief Exchanges the contents with those of a given dispatcher.
+ * @param other Dispatcher to exchange the content with.
+ */
+ void swap(basic_dispatcher &other) {
+ using std::swap;
+ swap(pools, other.pools);
+ }
+
+ /**
+ * @brief Returns the associated allocator.
+ * @return The associated allocator.
+ */
+ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
+ return pools.second();
+ }
+
+ /**
+ * @brief Returns the number of pending events for a given type.
+ * @tparam Type Type of event for which to return the count.
+ * @param id Name used to map the event queue within the dispatcher.
+ * @return The number of pending events for the given type.
+ */
+ template<typename Type>
+ [[nodiscard]] size_type size(const id_type id = type_hash<Type>::value()) const noexcept {
+ const auto *cpool = assure<std::decay_t<Type>>(id);
+ return cpool ? cpool->size() : 0u;
+ }
+
+ /**
+ * @brief Returns the total number of pending events.
+ * @return The total number of pending events.
+ */
+ [[nodiscard]] size_type size() const noexcept {
+ size_type count{};
+
+ for(auto &&cpool: pools.first()) {
+ count += cpool.second->size();
+ }
+
+ return count;
+ }
+
+ /**
+ * @brief Returns a sink object for the given event and queue.
+ *
+ * A sink is an opaque object used to connect listeners to events.
+ *
+ * The function type for a listener is _compatible_ with:
+ *
+ * @code{.cpp}
+ * void(Type &);
+ * @endcode
+ *
+ * The order of invocation of the listeners isn't guaranteed.
+ *
+ * @sa sink
+ *
+ * @tparam Type Type of event of which to get the sink.
+ * @param id Name used to map the event queue within the dispatcher.
+ * @return A temporary sink object.
+ */
+ template<typename Type>
+ [[nodiscard]] auto sink(const id_type id = type_hash<Type>::value()) {
+ return assure<Type>(id).bucket();
+ }
+
+ /**
+ * @brief Triggers an immediate event of a given type.
+ * @tparam Type Type of event to trigger.
+ * @param value An instance of the given type of event.
+ */
+ template<typename Type>
+ void trigger(Type &&value = {}) {
+ trigger(type_hash<std::decay_t<Type>>::value(), std::forward<Type>(value));
+ }
+
+ /**
+ * @brief Triggers an immediate event on a queue of a given type.
+ * @tparam Type Type of event to trigger.
+ * @param value An instance of the given type of event.
+ * @param id Name used to map the event queue within the dispatcher.
+ */
+ template<typename Type>
+ void trigger(const id_type id, Type &&value = {}) {
+ assure<std::decay_t<Type>>(id).trigger(std::forward<Type>(value));
+ }
+
+ /**
+ * @brief Enqueues an event of the given type.
+ * @tparam Type Type of event to enqueue.
+ * @tparam Args Types of arguments to use to construct the event.
+ * @param args Arguments to use to construct the event.
+ */
+ template<typename Type, typename... Args>
+ void enqueue(Args &&...args) {
+ enqueue_hint<Type>(type_hash<Type>::value(), std::forward<Args>(args)...);
+ }
+
+ /**
+ * @brief Enqueues an event of the given type.
+ * @tparam Type Type of event to enqueue.
+ * @param value An instance of the given type of event.
+ */
+ template<typename Type>
+ void enqueue(Type &&value) {
+ enqueue_hint(type_hash<std::decay_t<Type>>::value(), std::forward<Type>(value));
+ }
+
+ /**
+ * @brief Enqueues an event of the given type.
+ * @tparam Type Type of event to enqueue.
+ * @tparam Args Types of arguments to use to construct the event.
+ * @param id Name used to map the event queue within the dispatcher.
+ * @param args Arguments to use to construct the event.
+ */
+ template<typename Type, typename... Args>
+ void enqueue_hint(const id_type id, Args &&...args) {
+ assure<Type>(id).enqueue(std::forward<Args>(args)...);
+ }
+
+ /**
+ * @brief Enqueues an event of the given type.
+ * @tparam Type Type of event to enqueue.
+ * @param id Name used to map the event queue within the dispatcher.
+ * @param value An instance of the given type of event.
+ */
+ template<typename Type>
+ void enqueue_hint(const id_type id, Type &&value) {
+ assure<std::decay_t<Type>>(id).enqueue(std::forward<Type>(value));
+ }
+
+ /**
+ * @brief Utility function to disconnect everything related to a given value
+ * or instance from a dispatcher.
+ * @tparam Type Type of class or type of payload.
+ * @param value_or_instance A valid object that fits the purpose.
+ */
+ template<typename Type>
+ void disconnect(Type &value_or_instance) {
+ disconnect(&value_or_instance);
+ }
+
+ /**
+ * @brief Utility function to disconnect everything related to a given value
+ * or instance from a dispatcher.
+ * @tparam Type Type of class or type of payload.
+ * @param value_or_instance A valid object that fits the purpose.
+ */
+ template<typename Type>
+ void disconnect(Type *value_or_instance) {
+ for(auto &&cpool: pools.first()) {
+ cpool.second->disconnect(value_or_instance);
+ }
+ }
+
+ /**
+ * @brief Discards all the events stored so far in a given queue.
+ * @tparam Type Type of event to discard.
+ * @param id Name used to map the event queue within the dispatcher.
+ */
+ template<typename Type>
+ void clear(const id_type id = type_hash<Type>::value()) {
+ assure<Type>(id).clear();
+ }
+
+ /*! @brief Discards all the events queued so far. */
+ void clear() noexcept {
+ for(auto &&cpool: pools.first()) {
+ cpool.second->clear();
+ }
+ }
+
+ /**
+ * @brief Delivers all the pending events of a given queue.
+ * @tparam Type Type of event to send.
+ * @param id Name used to map the event queue within the dispatcher.
+ */
+ template<typename Type>
+ void update(const id_type id = type_hash<Type>::value()) {
+ assure<Type>(id).publish();
+ }
+
+ /*! @brief Delivers all the pending events. */
+ void update() const {
+ for(auto &&cpool: pools.first()) {
+ cpool.second->publish();
+ }
+ }
+
+private:
+ compressed_pair<container_type, allocator_type> pools;
+};
+
+} // namespace entt
+
+#endif
diff --git a/deps/include/entt/signal/emitter.hpp b/deps/include/entt/signal/emitter.hpp
new file mode 100644
index 0000000..a35e0d1
--- /dev/null
+++ b/deps/include/entt/signal/emitter.hpp
@@ -0,0 +1,182 @@
+#ifndef ENTT_SIGNAL_EMITTER_HPP
+#define ENTT_SIGNAL_EMITTER_HPP
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+#include "../container/dense_map.hpp"
+#include "../core/compressed_pair.hpp"
+#include "../core/fwd.hpp"
+#include "../core/type_info.hpp"
+#include "../core/utility.hpp"
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @brief General purpose event emitter.
+ *
+ * To create an emitter type, derived classes must inherit from the base as:
+ *
+ * @code{.cpp}
+ * struct my_emitter: emitter<my_emitter> {
+ * // ...
+ * }
+ * @endcode
+ *
+ * Handlers for the different events are created internally on the fly. It's not
+ * required to specify in advance the full list of accepted events.<br/>
+ * Moreover, whenever an event is published, an emitter also passes a reference
+ * to itself to its listeners.
+ *
+ * @tparam Derived Emitter type.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
+ */
+template<typename Derived, typename Allocator>
+class emitter {
+ using key_type = id_type;
+ using mapped_type = std::function<void(void *)>;
+
+ using alloc_traits = std::allocator_traits<Allocator>;
+ using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const key_type, mapped_type>>;
+ using container_type = dense_map<key_type, mapped_type, identity, std::equal_to<>, container_allocator>;
+
+public:
+ /*! @brief Allocator type. */
+ using allocator_type = Allocator;
+ /*! @brief Unsigned integer type. */
+ using size_type = std::size_t;
+
+ /*! @brief Default constructor. */
+ emitter()
+ : emitter{allocator_type{}} {}
+
+ /**
+ * @brief Constructs an emitter with a given allocator.
+ * @param allocator The allocator to use.
+ */
+ explicit emitter(const allocator_type &allocator)
+ : handlers{allocator, allocator} {}
+
+ /*! @brief Default copy constructor, deleted on purpose. */
+ emitter(const emitter &) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other The instance to move from.
+ */
+ emitter(emitter &&other) noexcept
+ : handlers{std::move(other.handlers)} {}
+
+ /**
+ * @brief Allocator-extended move constructor.
+ * @param other The instance to move from.
+ * @param allocator The allocator to use.
+ */
+ emitter(emitter &&other, const allocator_type &allocator)
+ : handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {
+ ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
+ }
+
+ /*! @brief Default destructor. */
+ virtual ~emitter() noexcept {
+ static_assert(std::is_base_of_v<emitter<Derived, Allocator>, Derived>, "Invalid emitter type");
+ }
+
+ /**
+ * @brief Default copy assignment operator, deleted on purpose.
+ * @return This emitter.
+ */
+ emitter &operator=(const emitter &) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other The instance to move from.
+ * @return This emitter.
+ */
+ emitter &operator=(emitter &&other) noexcept {
+ ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
+ handlers = std::move(other.handlers);
+ return *this;
+ }
+
+ /**
+ * @brief Exchanges the contents with those of a given emitter.
+ * @param other Emitter to exchange the content with.
+ */
+ void swap(emitter &other) {
+ using std::swap;
+ swap(handlers, other.handlers);
+ }
+
+ /**
+ * @brief Returns the associated allocator.
+ * @return The associated allocator.
+ */
+ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
+ return handlers.second();
+ }
+
+ /**
+ * @brief Publishes a given event.
+ * @tparam Type Type of event to trigger.
+ * @param value An instance of the given type of event.
+ */
+ template<typename Type>
+ void publish(Type &&value) {
+ if(const auto id = type_id<Type>().hash(); handlers.first().contains(id)) {
+ handlers.first()[id](&value);
+ }
+ }
+
+ /**
+ * @brief Registers a listener with the event emitter.
+ * @tparam Type Type of event to which to connect the listener.
+ * @param func The listener to register.
+ */
+ template<typename Type>
+ void on(std::function<void(Type &, Derived &)> func) {
+ handlers.first().insert_or_assign(type_id<Type>().hash(), [func = std::move(func), this](void *value) {
+ func(*static_cast<Type *>(value), static_cast<Derived &>(*this));
+ });
+ }
+
+ /**
+ * @brief Disconnects a listener from the event emitter.
+ * @tparam Type Type of event of the listener.
+ */
+ template<typename Type>
+ void erase() {
+ handlers.first().erase(type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value());
+ }
+
+ /*! @brief Disconnects all the listeners. */
+ void clear() noexcept {
+ handlers.first().clear();
+ }
+
+ /**
+ * @brief Checks if there are listeners registered for the specific event.
+ * @tparam Type Type of event to test.
+ * @return True if there are no listeners registered, false otherwise.
+ */
+ template<typename Type>
+ [[nodiscard]] bool contains() const {
+ return handlers.first().contains(type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value());
+ }
+
+ /**
+ * @brief Checks if there are listeners registered with the event emitter.
+ * @return True if there are no listeners registered, false otherwise.
+ */
+ [[nodiscard]] bool empty() const noexcept {
+ return handlers.first().empty();
+ }
+
+private:
+ compressed_pair<container_type, allocator_type> handlers;
+};
+
+} // namespace entt
+
+#endif
diff --git a/deps/include/entt/signal/fwd.hpp b/deps/include/entt/signal/fwd.hpp
new file mode 100644
index 0000000..d195312
--- /dev/null
+++ b/deps/include/entt/signal/fwd.hpp
@@ -0,0 +1,46 @@
+#ifndef ENTT_SIGNAL_FWD_HPP
+#define ENTT_SIGNAL_FWD_HPP
+
+#include <memory>
+
+namespace entt {
+
+template<typename>
+class delegate;
+
+template<typename = std::allocator<void>>
+class basic_dispatcher;
+
+template<typename, typename = std::allocator<void>>
+class emitter;
+
+class connection;
+
+struct scoped_connection;
+
+template<typename>
+class sink;
+
+template<typename Type, typename = std::allocator<void>>
+class sigh;
+
+/*! @brief Alias declaration for the most common use case. */
+using dispatcher = basic_dispatcher<>;
+
+/*! @brief Disambiguation tag for constructors and the like. */
+template<auto>
+struct connect_arg_t {
+ /*! @brief Default constructor. */
+ explicit connect_arg_t() = default;
+};
+
+/**
+ * @brief Constant of type connect_arg_t used to disambiguate calls.
+ * @tparam Candidate Element to connect (likely a free or member function).
+ */
+template<auto Candidate>
+inline constexpr connect_arg_t<Candidate> connect_arg{};
+
+} // namespace entt
+
+#endif
diff --git a/deps/include/entt/signal/sigh.hpp b/deps/include/entt/signal/sigh.hpp
new file mode 100644
index 0000000..93c96ea
--- /dev/null
+++ b/deps/include/entt/signal/sigh.hpp
@@ -0,0 +1,471 @@
+#ifndef ENTT_SIGNAL_SIGH_HPP
+#define ENTT_SIGNAL_SIGH_HPP
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "delegate.hpp"
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @brief Sink class.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error unless the template parameter is a function type.
+ *
+ * @tparam Type A valid signal handler type.
+ */
+template<typename Type>
+class sink;
+
+/**
+ * @brief Unmanaged signal handler.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error unless the template parameter is a function type.
+ *
+ * @tparam Type A valid function type.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
+ */
+template<typename Type, typename Allocator>
+class sigh;
+
+/**
+ * @brief Unmanaged signal handler.
+ *
+ * It works directly with references to classes and pointers to member functions
+ * as well as pointers to free functions. Users of this class are in charge of
+ * disconnecting instances before deleting them.
+ *
+ * This class serves mainly two purposes:
+ *
+ * * Creating signals to use later to notify a bunch of listeners.
+ * * Collecting results from a set of functions like in a voting system.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
+ */
+template<typename Ret, typename... Args, typename Allocator>
+class sigh<Ret(Args...), Allocator> {
+ friend class sink<sigh<Ret(Args...), Allocator>>;
+
+ using alloc_traits = std::allocator_traits<Allocator>;
+ using delegate_type = delegate<Ret(Args...)>;
+ using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
+
+public:
+ /*! @brief Allocator type. */
+ using allocator_type = Allocator;
+ /*! @brief Unsigned integer type. */
+ using size_type = std::size_t;
+ /*! @brief Sink type. */
+ using sink_type = sink<sigh<Ret(Args...), Allocator>>;
+
+ /*! @brief Default constructor. */
+ sigh() noexcept(std::is_nothrow_default_constructible_v<allocator_type> && std::is_nothrow_constructible_v<container_type, const allocator_type &>)
+ : sigh{allocator_type{}} {}
+
+ /**
+ * @brief Constructs a signal handler with a given allocator.
+ * @param allocator The allocator to use.
+ */
+ explicit sigh(const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v<container_type, const allocator_type &>)
+ : calls{allocator} {}
+
+ /**
+ * @brief Copy constructor.
+ * @param other The instance to copy from.
+ */
+ sigh(const sigh &other) noexcept(std::is_nothrow_copy_constructible_v<container_type>)
+ : calls{other.calls} {}
+
+ /**
+ * @brief Allocator-extended copy constructor.
+ * @param other The instance to copy from.
+ * @param allocator The allocator to use.
+ */
+ sigh(const sigh &other, const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v<container_type, const container_type &, const allocator_type &>)
+ : calls{other.calls, allocator} {}
+
+ /**
+ * @brief Move constructor.
+ * @param other The instance to move from.
+ */
+ sigh(sigh &&other) noexcept(std::is_nothrow_move_constructible_v<container_type>)
+ : calls{std::move(other.calls)} {}
+
+ /**
+ * @brief Allocator-extended move constructor.
+ * @param other The instance to move from.
+ * @param allocator The allocator to use.
+ */
+ sigh(sigh &&other, const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v<container_type, container_type &&, const allocator_type &>)
+ : calls{std::move(other.calls), allocator} {}
+
+ /*! @brief Default destructor. */
+ ~sigh() noexcept = default;
+
+ /**
+ * @brief Copy assignment operator.
+ * @param other The instance to copy from.
+ * @return This signal handler.
+ */
+ sigh &operator=(const sigh &other) noexcept(std::is_nothrow_copy_assignable_v<container_type>) {
+ calls = other.calls;
+ return *this;
+ }
+
+ /**
+ * @brief Move assignment operator.
+ * @param other The instance to move from.
+ * @return This signal handler.
+ */
+ sigh &operator=(sigh &&other) noexcept(std::is_nothrow_move_assignable_v<container_type>) {
+ calls = std::move(other.calls);
+ return *this;
+ }
+
+ /**
+ * @brief Exchanges the contents with those of a given signal handler.
+ * @param other Signal handler to exchange the content with.
+ */
+ void swap(sigh &other) noexcept(std::is_nothrow_swappable_v<container_type>) {
+ using std::swap;
+ swap(calls, other.calls);
+ }
+
+ /**
+ * @brief Returns the associated allocator.
+ * @return The associated allocator.
+ */
+ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
+ return calls.get_allocator();
+ }
+
+ /**
+ * @brief Number of listeners connected to the signal.
+ * @return Number of listeners currently connected.
+ */
+ [[nodiscard]] size_type size() const noexcept {
+ return calls.size();
+ }
+
+ /**
+ * @brief Returns false if at least a listener is connected to the signal.
+ * @return True if the signal has no listeners connected, false otherwise.
+ */
+ [[nodiscard]] bool empty() const noexcept {
+ return calls.empty();
+ }
+
+ /**
+ * @brief Triggers a signal.
+ *
+ * All the listeners are notified. Order isn't guaranteed.
+ *
+ * @param args Arguments to use to invoke listeners.
+ */
+ void publish(Args... args) const {
+ for(auto pos = calls.size(); pos; --pos) {
+ calls[pos - 1u](args...);
+ }
+ }
+
+ /**
+ * @brief Collects return values from the listeners.
+ *
+ * The collector must expose a call operator with the following properties:
+ *
+ * * The return type is either `void` or such that it's convertible to
+ * `bool`. In the second case, a true value will stop the iteration.
+ * * The list of parameters is empty if `Ret` is `void`, otherwise it
+ * contains a single element such that `Ret` is convertible to it.
+ *
+ * @tparam Func Type of collector to use, if any.
+ * @param func A valid function object.
+ * @param args Arguments to use to invoke listeners.
+ */
+ template<typename Func>
+ void collect(Func func, Args... args) const {
+ for(auto pos = calls.size(); pos; --pos) {
+ if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
+ calls[pos - 1u](args...);
+
+ if constexpr(std::is_invocable_r_v<bool, Func>) {
+ if(func()) {
+ break;
+ }
+ } else {
+ func();
+ }
+ } else {
+ if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
+ if(func(calls[pos - 1u](args...))) {
+ break;
+ }
+ } else {
+ func(calls[pos - 1u](args...));
+ }
+ }
+ }
+ }
+
+private:
+ container_type calls;
+};
+
+/**
+ * @brief Connection class.
+ *
+ * Opaque object the aim of which is to allow users to release an already
+ * estabilished connection without having to keep a reference to the signal or
+ * the sink that generated it.
+ */
+class connection {
+ template<typename>
+ friend class sink;
+
+ connection(delegate<void(void *)> fn, void *ref)
+ : disconnect{fn}, signal{ref} {}
+
+public:
+ /*! @brief Default constructor. */
+ connection()
+ : disconnect{},
+ signal{} {}
+
+ /**
+ * @brief Checks whether a connection is properly initialized.
+ * @return True if the connection is properly initialized, false otherwise.
+ */
+ [[nodiscard]] explicit operator bool() const noexcept {
+ return static_cast<bool>(disconnect);
+ }
+
+ /*! @brief Breaks the connection. */
+ void release() {
+ if(disconnect) {
+ disconnect(signal);
+ disconnect.reset();
+ }
+ }
+
+private:
+ delegate<void(void *)> disconnect;
+ void *signal;
+};
+
+/**
+ * @brief Scoped connection class.
+ *
+ * Opaque object the aim of which is to allow users to release an already
+ * estabilished connection without having to keep a reference to the signal or
+ * the sink that generated it.<br/>
+ * A scoped connection automatically breaks the link between the two objects
+ * when it goes out of scope.
+ */
+struct scoped_connection {
+ /*! @brief Default constructor. */
+ scoped_connection() = default;
+
+ /**
+ * @brief Constructs a scoped connection from a basic connection.
+ * @param other A valid connection object.
+ */
+ scoped_connection(const connection &other)
+ : conn{other} {}
+
+ /*! @brief Default copy constructor, deleted on purpose. */
+ scoped_connection(const scoped_connection &) = delete;
+
+ /**
+ * @brief Move constructor.
+ * @param other The scoped connection to move from.
+ */
+ scoped_connection(scoped_connection &&other) noexcept
+ : conn{std::exchange(other.conn, {})} {}
+
+ /*! @brief Automatically breaks the link on destruction. */
+ ~scoped_connection() noexcept {
+ conn.release();
+ }
+
+ /**
+ * @brief Default copy assignment operator, deleted on purpose.
+ * @return This scoped connection.
+ */
+ scoped_connection &operator=(const scoped_connection &) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other The scoped connection to move from.
+ * @return This scoped connection.
+ */
+ scoped_connection &operator=(scoped_connection &&other) noexcept {
+ conn = std::exchange(other.conn, {});
+ return *this;
+ }
+
+ /**
+ * @brief Acquires a connection.
+ * @param other The connection object to acquire.
+ * @return This scoped connection.
+ */
+ scoped_connection &operator=(connection other) {
+ conn = other;
+ return *this;
+ }
+
+ /**
+ * @brief Checks whether a scoped connection is properly initialized.
+ * @return True if the connection is properly initialized, false otherwise.
+ */
+ [[nodiscard]] explicit operator bool() const noexcept {
+ return static_cast<bool>(conn);
+ }
+
+ /*! @brief Breaks the connection. */
+ void release() {
+ conn.release();
+ }
+
+private:
+ connection conn;
+};
+
+/**
+ * @brief Sink class.
+ *
+ * A sink is used to connect listeners to signals and to disconnect them.<br/>
+ * The function type for a listener is the one of the signal to which it
+ * belongs.
+ *
+ * The clear separation between a signal and a sink permits to store the former
+ * as private data member without exposing the publish functionality to the
+ * users of the class.
+ *
+ * @warning
+ * Lifetime of a sink must not overcome that of the signal to which it refers.
+ * In any other case, attempting to use a sink results in undefined behavior.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
+ */
+template<typename Ret, typename... Args, typename Allocator>
+class sink<sigh<Ret(Args...), Allocator>> {
+ using signal_type = sigh<Ret(Args...), Allocator>;
+ using delegate_type = typename signal_type::delegate_type;
+ using difference_type = typename signal_type::container_type::difference_type;
+
+ template<auto Candidate, typename Type>
+ static void release(Type value_or_instance, void *signal) {
+ sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
+ }
+
+ template<auto Candidate>
+ static void release(void *signal) {
+ sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
+ }
+
+ template<typename Func>
+ void disconnect_if(Func callback) {
+ for(auto pos = signal->calls.size(); pos; --pos) {
+ if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
+ elem = std::move(signal->calls.back());
+ signal->calls.pop_back();
+ }
+ }
+ }
+
+public:
+ /**
+ * @brief Constructs a sink that is allowed to modify a given signal.
+ * @param ref A valid reference to a signal object.
+ */
+ sink(sigh<Ret(Args...), Allocator> &ref) noexcept
+ : signal{&ref} {}
+
+ /**
+ * @brief Returns false if at least a listener is connected to the sink.
+ * @return True if the sink has no listeners connected, false otherwise.
+ */
+ [[nodiscard]] bool empty() const noexcept {
+ return signal->calls.empty();
+ }
+
+ /**
+ * @brief Connects a free function (with or without payload), a bound or an
+ * unbound member to a signal.
+ * @tparam Candidate Function or member to connect to the signal.
+ * @tparam Type Type of class or type of payload, if any.
+ * @param value_or_instance A valid object that fits the purpose, if any.
+ * @return A properly initialized connection object.
+ */
+ template<auto Candidate, typename... Type>
+ connection connect(Type &&...value_or_instance) {
+ disconnect<Candidate>(value_or_instance...);
+
+ delegate_type call{};
+ call.template connect<Candidate>(value_or_instance...);
+ signal->calls.push_back(std::move(call));
+
+ delegate<void(void *)> conn{};
+ conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
+ return {conn, signal};
+ }
+
+ /**
+ * @brief Disconnects a free function (with or without payload), a bound or
+ * an unbound member from a signal.
+ * @tparam Candidate Function or member to disconnect from the signal.
+ * @tparam Type Type of class or type of payload, if any.
+ * @param value_or_instance A valid object that fits the purpose, if any.
+ */
+ template<auto Candidate, typename... Type>
+ void disconnect(Type &&...value_or_instance) {
+ delegate_type call{};
+ call.template connect<Candidate>(value_or_instance...);
+ disconnect_if([&call](const auto &elem) { return elem == call; });
+ }
+
+ /**
+ * @brief Disconnects free functions with payload or bound members from a
+ * signal.
+ * @param value_or_instance A valid object that fits the purpose.
+ */
+ void disconnect(const void *value_or_instance) {
+ ENTT_ASSERT(value_or_instance != nullptr, "Invalid value or instance");
+ disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
+ }
+
+ /*! @brief Disconnects all the listeners from a signal. */
+ void disconnect() {
+ signal->calls.clear();
+ }
+
+private:
+ signal_type *signal;
+};
+
+/**
+ * @brief Deduction guide.
+ *
+ * It allows to deduce the signal handler type of a sink directly from the
+ * signal it refers to.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
+ */
+template<typename Ret, typename... Args, typename Allocator>
+sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
+
+} // namespace entt
+
+#endif