#pragma once #if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED) #define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1 #include #include #include #include #include #include #include "../internal/common.hpp" #include "../util/flag.hpp" namespace mijin { // // public defines // // // public constants // using signal_token_t = std::uint32_t; inline constexpr signal_token_t INVALID_SIGNAL_TOKEN = std::numeric_limits::max(); // // public types // MIJIN_DEFINE_FLAG(Oneshot); template typename TAllocator, typename... TArgs> class BaseSignal { public: using handler_t = std::function; // TODO: write a custom function wrapper with allocator support using token_t = signal_token_t; private: struct RegisteredHandler { handler_t callable; std::weak_ptr referenced; token_t token; Oneshot oneshot = Oneshot::NO; }; using handler_vector_t = std::vector>; private: handler_vector_t handlers_; token_t nextToken = 1; std::mutex handlersMutex_; public: explicit BaseSignal(TAllocator allocator = {}) : handlers_(TAllocator(std::move(allocator))) {} BaseSignal(const BaseSignal&) = delete; BaseSignal(BaseSignal&&) MIJIN_NOEXCEPT = default; void reinit(TAllocator allocator = {}) { MIJIN_ASSERT(handlers_.empty(), "Attempting to re-initialize a signal that already has handlers."); handlers_ = handler_vector_t(TAllocator(std::move(allocator))); } public: BaseSignal& operator=(const BaseSignal&) = delete; BaseSignal& operator=(BaseSignal&&) MIJIN_NOEXCEPT = default; public: template inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr referenced = std::weak_ptr()) MIJIN_NOEXCEPT; template inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO, std::weak_ptr referenced = std::weak_ptr()) MIJIN_NOEXCEPT; inline void disconnect(token_t token) MIJIN_NOEXCEPT; template inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT; }; template using Signal = BaseSignal; // // public functions // template typename TAllocator, typename... TArgs> template inline auto BaseSignal::connect(THandler handler, Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t { std::lock_guard lock(handlersMutex_); auto callable = handler_t(handler); handlers_.push_back({ .callable = std::move(callable), .referenced = std::move(referenced), .token = nextToken, .oneshot = oneshot }); return nextToken++; } template typename TAllocator, typename... TArgs> template inline auto BaseSignal::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr referenced) MIJIN_NOEXCEPT -> token_t { std::lock_guard lock(handlersMutex_); auto callable = [object = &object, handler](TArgs... args) { std::invoke(handler, object, std::forward(args)...); }; handlers_.push_back({ .callable = std::move(callable), .referenced = std::move(referenced), .token = nextToken, .oneshot = oneshot }); return nextToken++; } template typename TAllocator, typename... TArgs> inline void BaseSignal::disconnect(token_t token) MIJIN_NOEXCEPT { std::lock_guard lock(handlersMutex_); auto it = std::remove_if(handlers_.begin(), handlers_.end(), [token](const RegisteredHandler& handler) { return handler.token == token; }); handlers_.erase(it, handlers_.end()); } template typename TAllocator, typename... TArgs> template inline void BaseSignal::emit(TArgs2&&... args) MIJIN_NOEXCEPT { std::lock_guard lock(handlersMutex_); // first erase any handlers with expired references // auto it = std::remove_if(handlers_.begin(), handlers_.end(), [](const RegisteredHandler& handler) // { // return handler.referenced.expired(); // }); // handlers_.erase(it, handlers_.end()); // TODO: this doesn't really work since expired() also returns true if the pointer was never set // invoke all handlers for (RegisteredHandler& handler : handlers_) { handler.callable(std::forward(args)...); } // remove any oneshot auto it = std::remove_if(handlers_.begin(), handlers_.end(), [](const RegisteredHandler& handler) { return handler.oneshot; }); handlers_.erase(it, handlers_.end()); } } // namespace mijin #endif // !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)