417 lines
11 KiB
C++
417 lines
11 KiB
C++
/*
|
|
* Copyright 2015 - 2025 gary@drinkingtea.net
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <ox/std/assert.hpp>
|
|
#include <ox/std/def.hpp>
|
|
#include <ox/std/defines.hpp>
|
|
#include <ox/std/error.hpp>
|
|
#include <ox/std/memory.hpp>
|
|
#include <ox/std/trace.hpp>
|
|
#include <ox/std/vector.hpp>
|
|
|
|
namespace ox {
|
|
|
|
class SignalHandler;
|
|
|
|
#ifndef OX_OS_BareMetal
|
|
|
|
namespace detail {
|
|
|
|
template<typename T>
|
|
struct isError {
|
|
static constexpr bool value = false;
|
|
};
|
|
|
|
template<>
|
|
struct isError<Error> {
|
|
static constexpr bool value = true;
|
|
};
|
|
|
|
}
|
|
|
|
template<class... Args>
|
|
class Signal {
|
|
protected:
|
|
struct BaseSlot {
|
|
virtual ~BaseSlot() = default;
|
|
virtual void call(Args...) = 0;
|
|
virtual void cleanup(Signal*) noexcept {}
|
|
[[nodiscard]]
|
|
virtual const void *receiver() const noexcept { return nullptr; }
|
|
};
|
|
|
|
template<typename F>
|
|
struct FunctionSlot: public BaseSlot {
|
|
F f;
|
|
|
|
explicit FunctionSlot(F f) {
|
|
this->f = f;
|
|
}
|
|
|
|
void call(Args... args) final {
|
|
if constexpr(detail::isError<decltype(f(args...))>::value) {
|
|
OX_THROW_ERROR(f(args...));
|
|
} else {
|
|
f(args...);
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename T, typename Method>
|
|
struct MethodSlot: public BaseSlot {
|
|
T m_receiver = nullptr;
|
|
Method m_methodPtr;
|
|
|
|
MethodSlot(T receiver, Method methodPtr) {
|
|
m_receiver = receiver;
|
|
m_methodPtr = methodPtr;
|
|
}
|
|
|
|
void call(Args... args) final {
|
|
if constexpr(detail::isError<decltype((m_receiver->*(m_methodPtr))(args...))>::value) {
|
|
OX_THROW_ERROR((m_receiver->*(m_methodPtr))(args...));
|
|
} else {
|
|
f(args...);
|
|
}
|
|
}
|
|
|
|
void cleanup(Signal *signal) noexcept final {
|
|
std::ignore = m_receiver->destruction.disconnectSignal(signal);
|
|
//if (err) {
|
|
// oxErrorf("Signal could not notify receiver that it is being destroyed. Destruction of receiver will cause use-after-free. ({})", toStr(err));
|
|
//}
|
|
}
|
|
|
|
[[nodiscard]]
|
|
const void *receiver() const noexcept final {
|
|
return m_receiver;
|
|
}
|
|
};
|
|
|
|
template<typename SignalT, typename Method>
|
|
struct SignalMethodSlot: public BaseSlot {
|
|
const SignalT *m_receiver = nullptr;
|
|
Method m_methodPtr;
|
|
|
|
SignalMethodSlot(const SignalT *receiver, Method methodPtr) {
|
|
m_receiver = receiver;
|
|
m_methodPtr = methodPtr;
|
|
}
|
|
|
|
void call(Args... args) final {
|
|
if constexpr(detail::isError<decltype((m_receiver->*(m_methodPtr))(args...))>::value) {
|
|
OX_THROW_ERROR((m_receiver->*(m_methodPtr))(args...));
|
|
} else {
|
|
(m_receiver->*(m_methodPtr))(args...);
|
|
}
|
|
}
|
|
|
|
void cleanup(Signal*) noexcept final {
|
|
}
|
|
|
|
[[nodiscard]]
|
|
const void *receiver() const noexcept final {
|
|
return m_receiver;
|
|
}
|
|
};
|
|
|
|
mutable Vector<UniquePtr<BaseSlot>> m_slots;
|
|
|
|
public:
|
|
~Signal() noexcept;
|
|
|
|
void connect(Error(*f)(Args...)) const noexcept;
|
|
|
|
template<typename T, typename Method>
|
|
void connect(const T *receiver, Method methodPtr) const noexcept;
|
|
|
|
template<typename T, typename Method>
|
|
void connect(T *receiver, Method methodPtr) const noexcept;
|
|
|
|
template<class... SubArgs, typename Method>
|
|
void connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept;
|
|
|
|
template<class... SubArgs>
|
|
Error disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept;
|
|
|
|
Error disconnectObject(const void *receiver) const noexcept;
|
|
|
|
[[nodiscard]]
|
|
size_t connectionCnt() const noexcept {
|
|
return m_slots.size();
|
|
}
|
|
|
|
void emit(Args... args) const;
|
|
|
|
Error emitCheckError(Args... args) const noexcept;
|
|
};
|
|
|
|
extern template class Signal<const SignalHandler*>;
|
|
|
|
template<class... Args>
|
|
Signal<Args...>::~Signal() noexcept {
|
|
for (auto &slot : m_slots) {
|
|
slot->cleanup(this);
|
|
}
|
|
}
|
|
|
|
template<class... Args>
|
|
void Signal<Args...>::connect(Error(*f)(Args...)) const noexcept {
|
|
m_slots.emplace_back(new FunctionSlot<decltype(f)>(f));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<typename T, typename Method>
|
|
void Signal<Args...>::connect(const T *receiver, Method methodPtr) const noexcept {
|
|
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
|
|
m_slots.emplace_back(new MethodSlot<const T*, Method>(receiver, methodPtr));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<typename T, typename Method>
|
|
void Signal<Args...>::connect(T *receiver, Method methodPtr) const noexcept {
|
|
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
|
|
m_slots.emplace_back(new MethodSlot<T*, Method>(receiver, methodPtr));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<class... SubArgs, typename Method>
|
|
void Signal<Args...>::connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept {
|
|
m_slots.emplace_back(new SignalMethodSlot<Signal<SubArgs...>, Method>(receiver, methodPtr));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<class... SubArgs>
|
|
Error Signal<Args...>::disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept {
|
|
return disconnectObject(receiver);
|
|
}
|
|
|
|
template<class... Args>
|
|
Error Signal<Args...>::disconnectObject(const void *receiver) const noexcept {
|
|
for (auto i = 0u; i < m_slots.size(); ++i) {
|
|
const auto &slot = m_slots[i];
|
|
if (slot->receiver() == receiver) {
|
|
OX_RETURN_ERROR(m_slots.erase(i));
|
|
--i;
|
|
}
|
|
}
|
|
return ox::Error(1, "Signal::disconnectObject: Receiver was not found among this Signal's slots");
|
|
}
|
|
|
|
template<class... Args>
|
|
void Signal<Args...>::emit(Args... args) const {
|
|
for (auto &f : m_slots) {
|
|
f->call(args...);
|
|
}
|
|
}
|
|
|
|
template<class... Args>
|
|
Error Signal<Args...>::emitCheckError(Args... args) const noexcept {
|
|
try {
|
|
for (auto &f : m_slots) {
|
|
f->call(args...);
|
|
}
|
|
return ox::Error(0);
|
|
} catch (const ox::Exception &ex) {
|
|
return ox::Error(ex.errCode, ex.msg, ex.src);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
template<typename T>
|
|
class Signal;
|
|
|
|
#endif
|
|
|
|
template<class... Args>
|
|
class Signal<Error(Args...)> {
|
|
protected:
|
|
struct BaseSlot {
|
|
virtual ~BaseSlot() = default;
|
|
virtual Error call(Args...) noexcept = 0;
|
|
virtual void cleanup(Signal*) noexcept {}
|
|
[[nodiscard]]
|
|
virtual const void *receiver() const noexcept { return nullptr; }
|
|
};
|
|
|
|
struct FunctionSlot: public BaseSlot {
|
|
Error (*f)(Args...);
|
|
|
|
explicit FunctionSlot(Error (*f)(Args...)) noexcept {
|
|
this->f = f;
|
|
}
|
|
|
|
Error call(Args... args) noexcept final {
|
|
return f(ox::forward<Args>(args)...);
|
|
}
|
|
};
|
|
|
|
template<typename T, typename Method>
|
|
struct MethodSlot: public BaseSlot {
|
|
T m_receiver = nullptr;
|
|
Method m_methodPtr;
|
|
|
|
MethodSlot(T receiver, Method methodPtr) {
|
|
m_receiver = receiver;
|
|
m_methodPtr = methodPtr;
|
|
}
|
|
|
|
Error call(Args... args) noexcept final {
|
|
return (m_receiver->*(m_methodPtr))(ox::forward<Args>(args)...);
|
|
}
|
|
|
|
void cleanup(Signal *signal) noexcept final {
|
|
std::ignore = m_receiver->destruction.disconnectSignal(signal);
|
|
//oxErrorf("{}", toStr(err));
|
|
//oxAssert(err, "Signal could not notify receiver that it is being destroyed. Destruction of receiver will cause use-after-free.");
|
|
}
|
|
|
|
[[nodiscard]]
|
|
const void *receiver() const noexcept final {
|
|
return m_receiver;
|
|
}
|
|
};
|
|
|
|
template<typename SignalT, typename Method>
|
|
struct SignalMethodSlot: public BaseSlot {
|
|
const SignalT *m_receiver = nullptr;
|
|
Method m_methodPtr;
|
|
|
|
SignalMethodSlot(const SignalT *receiver, Method methodPtr) {
|
|
m_receiver = receiver;
|
|
m_methodPtr = methodPtr;
|
|
}
|
|
|
|
Error call(Args... args) noexcept final {
|
|
return (m_receiver->*(m_methodPtr))(ox::forward<Args>(args)...);
|
|
}
|
|
|
|
void cleanup(Signal*) noexcept final {
|
|
}
|
|
|
|
[[nodiscard]]
|
|
const void *receiver() const noexcept final {
|
|
return m_receiver;
|
|
}
|
|
};
|
|
|
|
mutable Vector<UniquePtr<BaseSlot>> m_slots;
|
|
|
|
public:
|
|
~Signal() noexcept;
|
|
|
|
void connect(Error(*f)(Args...)) const noexcept;
|
|
|
|
template<typename T, typename Method>
|
|
void connect(const T *receiver, Method methodPtr) const noexcept;
|
|
|
|
template<typename T, typename Method>
|
|
void connect(T *receiver, Method methodPtr) const noexcept;
|
|
|
|
template<class... SubArgs, typename Method>
|
|
void connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept;
|
|
|
|
template<class... SubArgs>
|
|
Error disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept;
|
|
|
|
Error disconnectObject(const void *receiver) const noexcept;
|
|
|
|
[[nodiscard]]
|
|
size_t connectionCnt() const noexcept {
|
|
return m_slots.size();
|
|
}
|
|
|
|
void emit(Args... args) const noexcept;
|
|
|
|
Error emitCheckError(Args... args) const noexcept;
|
|
};
|
|
|
|
extern template class Signal<Error(const SignalHandler*)>;
|
|
|
|
class SignalHandler {
|
|
public:
|
|
Signal<Error(const SignalHandler*)> destruction;
|
|
|
|
constexpr SignalHandler() noexcept = default;
|
|
SignalHandler(const SignalHandler&) = delete;
|
|
SignalHandler(SignalHandler&) = delete;
|
|
SignalHandler(SignalHandler&&) = delete;
|
|
|
|
virtual ~SignalHandler() noexcept;
|
|
};
|
|
|
|
template<class... Args>
|
|
Signal<Error(Args...)>::~Signal() noexcept {
|
|
for (auto &slot : m_slots) {
|
|
slot->cleanup(this);
|
|
}
|
|
}
|
|
|
|
template<class... Args>
|
|
void Signal<Error(Args...)>::connect(Error(*f)(Args...)) const noexcept {
|
|
m_slots.emplace_back(new FunctionSlot(f));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<typename T, typename Method>
|
|
void Signal<Error(Args...)>::connect(const T *receiver, Method methodPtr) const noexcept {
|
|
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
|
|
m_slots.emplace_back(new MethodSlot<const T*, Method>(receiver, methodPtr));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<typename T, typename Method>
|
|
void Signal<Error(Args...)>::connect(T *receiver, Method methodPtr) const noexcept {
|
|
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
|
|
m_slots.emplace_back(new MethodSlot<T*, Method>(receiver, methodPtr));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<class... SubArgs, typename Method>
|
|
void Signal<Error(Args...)>::connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept {
|
|
m_slots.emplace_back(new SignalMethodSlot<Signal<SubArgs...>, Method>(receiver, methodPtr));
|
|
}
|
|
|
|
template<class... Args>
|
|
template<class... SubArgs>
|
|
Error Signal<Error(Args...)>::disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept {
|
|
return disconnectObject(receiver);
|
|
}
|
|
|
|
template<class... Args>
|
|
Error Signal<Error(Args...)>::disconnectObject(const void *receiver) const noexcept {
|
|
for (auto i = 0u; i < m_slots.size(); ++i) {
|
|
const auto &slot = m_slots[i];
|
|
if (slot->receiver() == receiver) {
|
|
OX_RETURN_ERROR(m_slots.erase(i));
|
|
--i;
|
|
}
|
|
}
|
|
return ox::Error(1, "Signal::disconnectObject: Receiver was not found among this Signal's slots");
|
|
}
|
|
|
|
template<class... Args>
|
|
void Signal<Error(Args...)>::emit(Args... args) const noexcept {
|
|
for (auto &f : m_slots) {
|
|
std::ignore = f->call(ox::forward<Args>(args)...);
|
|
}
|
|
}
|
|
|
|
template<class... Args>
|
|
Error Signal<Error(Args...)>::emitCheckError(Args... args) const noexcept {
|
|
for (auto &f : m_slots) {
|
|
OX_RETURN_ERROR(f->call(ox::forward<Args>(args)...));
|
|
}
|
|
return ox::Error(0);
|
|
}
|
|
|
|
}
|