242 lines
7.8 KiB
C++
242 lines
7.8 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 "../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<signal_token_t>::max();
|
|
|
|
//
|
|
// public types
|
|
//
|
|
|
|
MIJIN_DEFINE_FLAG(Oneshot);
|
|
|
|
template<template<typename> typename TAllocator, typename... TArgs>
|
|
class BaseSignal
|
|
{
|
|
public:
|
|
using handler_t = std::function<void(TArgs...)>; // TODO: write a custom function wrapper with allocator support
|
|
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, TAllocator<RegisteredHandler>>;
|
|
private:
|
|
handler_vector_t handlers_;
|
|
token_t nextToken = 1;
|
|
std::mutex handlersMutex_;
|
|
public:
|
|
explicit BaseSignal(TAllocator<void> allocator = {}) : handlers_(TAllocator<RegisteredHandler>(std::move(allocator))) {}
|
|
BaseSignal(const BaseSignal&) = delete;
|
|
BaseSignal(BaseSignal&&) MIJIN_NOEXCEPT = default;
|
|
public:
|
|
BaseSignal& operator=(const BaseSignal&) = delete;
|
|
BaseSignal& operator=(BaseSignal&&) MIJIN_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>()) MIJIN_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>()) MIJIN_NOEXCEPT;
|
|
template<typename TObject, typename TWeak = void>
|
|
inline token_t connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot = Oneshot::NO, std::weak_ptr<TWeak> referenced = std::weak_ptr<TWeak>()) MIJIN_NOEXCEPT;
|
|
inline void disconnect(token_t token) MIJIN_NOEXCEPT;
|
|
|
|
template<typename... TArgs2>
|
|
inline void emit(TArgs2&&... args) MIJIN_NOEXCEPT;
|
|
};
|
|
|
|
template<typename... TArgs>
|
|
using Signal = BaseSignal<MIJIN_DEFAULT_ALLOCATOR, TArgs...>;
|
|
|
|
template<template<typename> typename TAllocator, typename... TArgs>
|
|
class SignalAutoToken
|
|
{
|
|
public:
|
|
using signal_t = BaseSignal<TAllocator, TArgs...>;
|
|
private:
|
|
signal_t* signal_ = nullptr;
|
|
signal_token_t token_ = INVALID_SIGNAL_TOKEN;
|
|
public:
|
|
SignalAutoToken() = default;
|
|
SignalAutoToken(const SignalAutoToken&) = delete;
|
|
SignalAutoToken(SignalAutoToken&& other) MIJIN_NOEXCEPT
|
|
: signal_(std::exchange(other.signal_, nullptr)), token_(std::exchange(other.token_, INVALID_SIGNAL_TOKEN)) {}
|
|
|
|
template<typename THandler>
|
|
SignalAutoToken(signal_t& signal, THandler&& handler, Oneshot oneshot = Oneshot::NO) MIJIN_NOEXCEPT
|
|
: signal_(&signal), token_(signal.connect(std::forward<THandler>(handler), oneshot))
|
|
{}
|
|
|
|
template<typename TObject>
|
|
SignalAutoToken(signal_t& signal, TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot = Oneshot::NO) MIJIN_NOEXCEPT
|
|
: signal_(&signal), token_(signal.connect(object, handler, oneshot)) {}
|
|
|
|
template<typename TObject>
|
|
SignalAutoToken(signal_t& signal, TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot = Oneshot::NO) MIJIN_NOEXCEPT
|
|
: signal_(&signal), token_(signal.connect(object, handler, oneshot)) {}
|
|
|
|
~SignalAutoToken() noexcept
|
|
{
|
|
reset();
|
|
}
|
|
|
|
SignalAutoToken& operator=(const SignalAutoToken&) = delete;
|
|
SignalAutoToken& operator=(SignalAutoToken&& other) MIJIN_NOEXCEPT
|
|
{
|
|
if (this != &other)
|
|
{
|
|
reset();
|
|
signal_ = std::exchange(other.signal_, nullptr);
|
|
token_ = std::exchange(other.token_, INVALID_SIGNAL_TOKEN);
|
|
}
|
|
return *this;
|
|
}
|
|
private:
|
|
void reset() MIJIN_NOEXCEPT;
|
|
|
|
friend signal_t;
|
|
};
|
|
|
|
//
|
|
// public functions
|
|
//
|
|
|
|
template<template<typename> typename TAllocator, typename... TArgs>
|
|
void SignalAutoToken<TAllocator, TArgs...>::reset() MIJIN_NOEXCEPT
|
|
{
|
|
if (signal_ != nullptr && token_ != INVALID_SIGNAL_TOKEN)
|
|
{
|
|
signal_->disconnect(token_);
|
|
signal_ = nullptr;
|
|
token_ = INVALID_SIGNAL_TOKEN;
|
|
}
|
|
}
|
|
|
|
template<template<typename> typename TAllocator, typename... TArgs>
|
|
template<typename THandler, typename TWeak>
|
|
inline auto BaseSignal<TAllocator, TArgs...>::connect(THandler handler, Oneshot oneshot, std::weak_ptr<TWeak> 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<template<typename> typename TAllocator, typename... TArgs>
|
|
template<typename TObject, typename TWeak>
|
|
inline auto BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...), Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_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<template<typename> typename TAllocator, typename... TArgs>
|
|
template<typename TObject, typename TWeak>
|
|
inline auto BaseSignal<TAllocator, TArgs...>::connect(TObject& object, void (TObject::* handler)(TArgs...) const, Oneshot oneshot, std::weak_ptr<TWeak> referenced) MIJIN_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<template<typename> typename TAllocator, typename... TArgs>
|
|
inline void BaseSignal<TAllocator, TArgs...>::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<template<typename> typename TAllocator, typename... TArgs>
|
|
template<typename... TArgs2>
|
|
inline void BaseSignal<TAllocator, TArgs...>::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<TArgs2>(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)
|