signals: add typed signals with CSignalT<> (#58)

Also more tests
This commit is contained in:
outfoxxed 2025-06-23 13:51:38 -07:00 committed by GitHub
parent d46bd32da5
commit 1b8090e5d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 148 additions and 52 deletions

View file

@ -6,39 +6,27 @@
namespace Hyprutils { namespace Hyprutils {
namespace Signal { namespace Signal {
class CSignal; class CUntypedSignal;
class CSignalListener { class CSignalListener {
public: public:
CSignalListener(std::function<void(std::any)> handler);
CSignalListener(CSignalListener&&) = delete; CSignalListener(CSignalListener&&) = delete;
CSignalListener(CSignalListener&) = delete; CSignalListener(CSignalListener&) = delete;
CSignalListener(const CSignalListener&) = delete; CSignalListener(const CSignalListener&) = delete;
CSignalListener(const CSignalListener&&) = delete; CSignalListener(const CSignalListener&&) = delete;
void emit(std::any data); [[deprecated("Relic of the legacy untyped signal API. Using this with CSignalT is undefined behavior.")]] void emit(std::any data);
private: private:
std::function<void(std::any)> m_fHandler; CSignalListener(std::function<void(void*)> handler);
void emitInternal(void* args);
std::function<void(void*)> m_fHandler;
friend class CUntypedSignal;
}; };
typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener; typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener;
class CStaticSignalListener {
public:
CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner);
CStaticSignalListener(CStaticSignalListener&&) = delete;
CStaticSignalListener(CStaticSignalListener&) = delete;
CStaticSignalListener(const CStaticSignalListener&) = delete;
CStaticSignalListener(const CStaticSignalListener&&) = delete;
void emit(std::any data);
private:
void* m_pOwner = nullptr;
std::function<void(void*, std::any)> m_fHandler;
};
} }
} }

View file

@ -4,25 +4,51 @@
#include <any> #include <any>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <tuple>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
#include "./Listener.hpp" #include "./Listener.hpp"
namespace Hyprutils { namespace Hyprutils {
namespace Signal { namespace Signal {
class CSignal { class CUntypedSignal {
public: protected:
void emit(std::any data = {}); CHyprSignalListener registerListenerInternal(std::function<void(void*)> handler);
void registerStaticListenerInternal(std::function<void(void*)> handler);
void emitInternal(void* args);
// std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler); std::vector<std::unique_ptr<CSignalListener>> m_vStaticListeners;
};
template <typename... Args>
class CSignalT : public CUntypedSignal {
public:
void emit(Args... args) {
auto argsTuple = std::make_tuple(args...);
emitInternal(&argsTuple);
}
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(Args...)> handler) {
return registerListenerInternal([handler](void* argsPtr) { std::apply(handler, *static_cast<std::tuple<Args...>*>(argsPtr)); });
}
// this is for static listeners. They die with this signal. // this is for static listeners. They die with this signal.
// TODO: can we somehow rid of the void* data and make it a custom this? void registerStaticListener(std::function<void(Args...)> handler) {
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner); registerStaticListenerInternal([handler](void* argsPtr) { std::apply(handler, *static_cast<std::tuple<Args...>*>(argsPtr)); });
}
private: template <typename Owner>
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners; void registerStaticListener(std::function<void(Owner*, Args...)> handler, Owner* owner) {
std::vector<std::unique_ptr<CStaticSignalListener>> m_vStaticListeners; registerStaticListener([owner, handler](Args... args) { handler(owner, args...); });
}
};
// compat
class CSignal : public CSignalT<std::any> {
public:
void emit(std::any data = {});
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler);
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner);
}; };
} }
} }

View file

@ -1,22 +1,20 @@
#include <hyprutils/signal/Listener.hpp> #include <hyprutils/signal/Listener.hpp>
#include <tuple>
using namespace Hyprutils::Signal; using namespace Hyprutils::Signal;
Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(std::any)> handler) : m_fHandler(handler) { Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(void*)> handler) : m_fHandler(handler) {
; ;
} }
void Hyprutils::Signal::CSignalListener::emit(std::any data) { void Hyprutils::Signal::CSignalListener::emitInternal(void* data) {
if (!m_fHandler) if (!m_fHandler)
return; return;
m_fHandler(data); m_fHandler(data);
} }
Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner) : m_pOwner(owner), m_fHandler(handler) { void Hyprutils::Signal::CSignalListener::emit(std::any data) {
; auto dataTuple = std::tuple<std::any>(data);
} emitInternal(static_cast<void*>(&dataTuple));
void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) {
m_fHandler(m_pOwner, data);
} }

View file

@ -8,7 +8,7 @@ using namespace Hyprutils::Memory;
#define SP CSharedPointer #define SP CSharedPointer
#define WP CWeakPointer #define WP CWeakPointer
void Hyprutils::Signal::CSignal::emit(std::any data) { void Hyprutils::Signal::CUntypedSignal::emitInternal(void* args) {
std::vector<SP<CSignalListener>> listeners; std::vector<SP<CSignalListener>> listeners;
for (auto& l : m_vListeners) { for (auto& l : m_vListeners) {
if (l.expired()) if (l.expired())
@ -17,7 +17,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
listeners.emplace_back(l.lock()); listeners.emplace_back(l.lock());
} }
std::vector<CStaticSignalListener*> statics; std::vector<CSignalListener*> statics;
statics.reserve(m_vStaticListeners.size()); statics.reserve(m_vStaticListeners.size());
for (auto& l : m_vStaticListeners) { for (auto& l : m_vStaticListeners) {
statics.emplace_back(l.get()); statics.emplace_back(l.get());
@ -29,11 +29,11 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
if (l.strongRef() == 1) if (l.strongRef() == 1)
continue; continue;
l->emit(data); l->emitInternal(args);
} }
for (auto& l : statics) { for (auto& l : statics) {
l->emit(data); l->emitInternal(args);
} }
// release SPs // release SPs
@ -43,8 +43,8 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
// as such we'd be doing a UAF // as such we'd be doing a UAF
} }
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) { CHyprSignalListener Hyprutils::Signal::CUntypedSignal::registerListenerInternal(std::function<void(void*)> handler) {
CHyprSignalListener listener = makeShared<CSignalListener>(handler); CHyprSignalListener listener = SP<CSignalListener>(new CSignalListener(handler));
m_vListeners.emplace_back(listener); m_vListeners.emplace_back(listener);
// housekeeping: remove any stale listeners // housekeeping: remove any stale listeners
@ -53,6 +53,18 @@ CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<v
return listener; return listener;
} }
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) { void Hyprutils::Signal::CUntypedSignal::registerStaticListenerInternal(std::function<void(void*)> handler) {
m_vStaticListeners.emplace_back(std::make_unique<CStaticSignalListener>(handler, owner)); m_vStaticListeners.emplace_back(std::unique_ptr<CSignalListener>(new CSignalListener(handler)));
}
void Hyprutils::Signal::CSignal::emit(std::any data) {
CSignalT::emit(data);
}
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) {
return CSignalT::registerListener(handler);
}
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
CSignalT<std::any>::registerStaticListener<void>(handler, owner);
} }

View file

@ -1,3 +1,4 @@
#include <any>
#include <hyprutils/signal/Signal.hpp> #include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp" #include "shared.hpp"
@ -5,9 +6,7 @@
using namespace Hyprutils::Signal; using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory; using namespace Hyprutils::Memory;
int main(int argc, char** argv, char** envp) { void legacy(int& ret) {
int ret = 0;
CSignal signal; CSignal signal;
int data = 0; int data = 0;
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; }); auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
@ -23,6 +22,79 @@ int main(int argc, char** argv, char** envp) {
signal.emit(); signal.emit();
EXPECT(data, 0); EXPECT(data, 0);
}
void legacyListenerEmit(int& ret) {
int data = 0;
CSignal signal;
auto listener = signal.registerListener([&](std::any d) { data = std::any_cast<int>(d); });
listener->emit(1); // not a typo
EXPECT(data, 1);
}
void empty(int& ret) {
int data = 0;
CSignalT<> signal;
auto listener = signal.registerListener([&] { data = 1; });
signal.emit();
EXPECT(data, 1);
data = 0;
listener.reset();
signal.emit();
EXPECT(data, 0);
}
void typed(int& ret) {
int data = 0;
CSignalT<int> signal;
auto listener = signal.registerListener([&](int newData) { data = newData; });
signal.emit(1);
EXPECT(data, 1);
}
void typedMany(int& ret) {
int data1 = 0;
int data2 = 0;
int data3 = 0;
CSignalT<int, int, int> signal;
auto listener = signal.registerListener([&](int d1, int d2, int d3) {
data1 = d1;
data2 = d2;
data3 = d3;
});
signal.emit(1, 2, 3);
EXPECT(data1, 1);
EXPECT(data2, 2);
EXPECT(data3, 3);
}
void staticListener(int& ret) {
struct STestOwner {
int data = 0;
} owner;
CSignalT<int> signal;
signal.registerStaticListener<STestOwner>([&](STestOwner* owner, int newData) { owner->data = newData; }, &owner);
signal.emit(1);
EXPECT(owner.data, 1);
}
int main(int argc, char** argv, char** envp) {
int ret = 0;
legacy(ret);
legacyListenerEmit(ret);
empty(ret);
typed(ret);
typedMany(ret);
staticListener(ret);
return ret; return ret;
} }