153 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
#pragma once
 | 
						|
 | 
						|
#if !defined(MIJIN_ASYNC_SIGNAL_HPP_INCLUDED)
 | 
						|
#define MIJIN_ASYNC_SIGNAL_HPP_INCLUDED 1
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <cstdint>
 | 
						|
#include <functional>
 | 
						|
#include <memory>
 | 
						|
#include <mutex>
 | 
						|
#include <vector>
 | 
						|
#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<signal_token_t>::max();
 | 
						|
 | 
						|
//
 | 
						|
// public types
 | 
						|
//
 | 
						|
 | 
						|
MIJIN_DEFINE_FLAG(Oneshot);
 | 
						|
 | 
						|
template<typename... TArgs>
 | 
						|
class Signal
 | 
						|
{
 | 
						|
public:
 | 
						|
    using handler_t = std::function<void(TArgs...)>;
 | 
						|
    using token_t = signal_token_t;
 | 
						|
private:
 | 
						|
    struct RegisteredHandler
 | 
						|
    {
 | 
						|
        handler_t callable;
 | 
						|
        std::weak_ptr<void> referenced;
 | 
						|
        token_t token;
 | 
						|
        Oneshot oneshot = Oneshot::NO;
 | 
						|
    };
 | 
						|
    using handler_vector_t = std::vector<RegisteredHandler>;
 | 
						|
private:
 | 
						|
    handler_vector_t handlers_;
 | 
						|
    token_t nextToken = 1;
 | 
						|
    std::mutex handlersMutex_;
 | 
						|
public:
 | 
						|
    Signal() = default;
 | 
						|
    Signal(const Signal&) = delete;
 | 
						|
    Signal(Signal&&) noexcept = default;
 | 
						|
public:
 | 
						|
    Signal& operator=(const Signal&) = delete;
 | 
						|
    Signal& operator=(Signal&&) noexcept = default;
 | 
						|
public:
 | 
						|
    template<typename THandler, typename TWeak = void>
 | 
						|
    inline token_t connect(THandler handler, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) noexcept;
 | 
						|
    template<typename TObject, typename TWeak = void>
 | 
						|
    inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) noexcept;
 | 
						|
    inline void disconnect(token_t token) noexcept;
 | 
						|
    inline void emit(TArgs&&... args) noexcept;
 | 
						|
};
 | 
						|
 | 
						|
//
 | 
						|
// public functions
 | 
						|
//
 | 
						|
 | 
						|
template<typename... TArgs>
 | 
						|
template<typename THandler, typename TWeak>
 | 
						|
inline auto Signal<TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> referenced) 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... TArgs>
 | 
						|
template<typename TObject, typename TWeak>
 | 
						|
inline auto Signal<TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) noexcept -> token_t
 | 
						|
{
 | 
						|
    std::lock_guard lock(handlersMutex_);
 | 
						|
 | 
						|
    auto callable = [object = &object, handler](TArgs... args)
 | 
						|
    {
 | 
						|
        std::invoke(handler, object, std::forward<TArgs>(args)...);
 | 
						|
    };
 | 
						|
    handlers_.push_back({
 | 
						|
        .callable = std::move(callable),
 | 
						|
        .referenced = std::move(referenced),
 | 
						|
        .token = nextToken,
 | 
						|
        .oneshot = oneshot
 | 
						|
    });
 | 
						|
 | 
						|
    return nextToken++;
 | 
						|
}
 | 
						|
 | 
						|
template<typename... TArgs>
 | 
						|
inline void Signal<TArgs...>::disconnect(token_t token) 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... TArgs>
 | 
						|
inline void Signal<TArgs...>::emit(TArgs&&... args) 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<TArgs>(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)
 |