diff --git a/include/hyprutils/signal/Listener.hpp b/include/hyprutils/signal/Listener.hpp index a78ffc7..4453896 100644 --- a/include/hyprutils/signal/Listener.hpp +++ b/include/hyprutils/signal/Listener.hpp @@ -6,39 +6,27 @@ namespace Hyprutils { namespace Signal { - class CSignal; + class CUntypedSignal; class CSignalListener { public: - CSignalListener(std::function handler); - CSignalListener(CSignalListener&&) = delete; CSignalListener(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: - std::function m_fHandler; + CSignalListener(std::function handler); + + void emitInternal(void* args); + + std::function m_fHandler; + + friend class CUntypedSignal; }; typedef Hyprutils::Memory::CSharedPointer CHyprSignalListener; - - class CStaticSignalListener { - public: - CStaticSignalListener(std::function 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 m_fHandler; - }; } } diff --git a/include/hyprutils/signal/Signal.hpp b/include/hyprutils/signal/Signal.hpp index f59b9df..e78b48a 100644 --- a/include/hyprutils/signal/Signal.hpp +++ b/include/hyprutils/signal/Signal.hpp @@ -4,25 +4,51 @@ #include #include #include +#include #include #include "./Listener.hpp" namespace Hyprutils { namespace Signal { - class CSignal { - public: - void emit(std::any data = {}); + class CUntypedSignal { + protected: + CHyprSignalListener registerListenerInternal(std::function handler); + void registerStaticListenerInternal(std::function handler); + void emitInternal(void* args); - // - [[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function handler); + std::vector> m_vListeners; + std::vector> m_vStaticListeners; + }; + + template + 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 handler) { + return registerListenerInternal([handler](void* argsPtr) { std::apply(handler, *static_cast*>(argsPtr)); }); + } // 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 handler, void* owner); + void registerStaticListener(std::function handler) { + registerStaticListenerInternal([handler](void* argsPtr) { std::apply(handler, *static_cast*>(argsPtr)); }); + } - private: - std::vector> m_vListeners; - std::vector> m_vStaticListeners; + template + void registerStaticListener(std::function handler, Owner* owner) { + registerStaticListener([owner, handler](Args... args) { handler(owner, args...); }); + } + }; + + // compat + class CSignal : public CSignalT { + public: + void emit(std::any data = {}); + [[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function handler); + void registerStaticListener(std::function handler, void* owner); }; } -} \ No newline at end of file +} diff --git a/src/signal/Listener.cpp b/src/signal/Listener.cpp index 884af5a..da49943 100644 --- a/src/signal/Listener.cpp +++ b/src/signal/Listener.cpp @@ -1,22 +1,20 @@ #include +#include using namespace Hyprutils::Signal; -Hyprutils::Signal::CSignalListener::CSignalListener(std::function handler) : m_fHandler(handler) { +Hyprutils::Signal::CSignalListener::CSignalListener(std::function handler) : m_fHandler(handler) { ; } -void Hyprutils::Signal::CSignalListener::emit(std::any data) { +void Hyprutils::Signal::CSignalListener::emitInternal(void* data) { if (!m_fHandler) return; m_fHandler(data); } -Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function handler, void* owner) : m_pOwner(owner), m_fHandler(handler) { - ; -} - -void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) { - m_fHandler(m_pOwner, data); +void Hyprutils::Signal::CSignalListener::emit(std::any data) { + auto dataTuple = std::tuple(data); + emitInternal(static_cast(&dataTuple)); } diff --git a/src/signal/Signal.cpp b/src/signal/Signal.cpp index 75417d9..e9bfea9 100644 --- a/src/signal/Signal.cpp +++ b/src/signal/Signal.cpp @@ -8,7 +8,7 @@ using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer -void Hyprutils::Signal::CSignal::emit(std::any data) { +void Hyprutils::Signal::CUntypedSignal::emitInternal(void* args) { std::vector> listeners; for (auto& l : m_vListeners) { if (l.expired()) @@ -17,7 +17,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { listeners.emplace_back(l.lock()); } - std::vector statics; + std::vector statics; statics.reserve(m_vStaticListeners.size()); for (auto& l : m_vStaticListeners) { statics.emplace_back(l.get()); @@ -29,11 +29,11 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { if (l.strongRef() == 1) continue; - l->emit(data); + l->emitInternal(args); } for (auto& l : statics) { - l->emit(data); + l->emitInternal(args); } // release SPs @@ -43,8 +43,8 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { // as such we'd be doing a UAF } -CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler) { - CHyprSignalListener listener = makeShared(handler); +CHyprSignalListener Hyprutils::Signal::CUntypedSignal::registerListenerInternal(std::function handler) { + CHyprSignalListener listener = SP(new CSignalListener(handler)); m_vListeners.emplace_back(listener); // housekeeping: remove any stale listeners @@ -53,6 +53,18 @@ CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler, void* owner) { - m_vStaticListeners.emplace_back(std::make_unique(handler, owner)); +void Hyprutils::Signal::CUntypedSignal::registerStaticListenerInternal(std::function handler) { + m_vStaticListeners.emplace_back(std::unique_ptr(new CSignalListener(handler))); +} + +void Hyprutils::Signal::CSignal::emit(std::any data) { + CSignalT::emit(data); +} + +CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler) { + return CSignalT::registerListener(handler); +} + +void Hyprutils::Signal::CSignal::registerStaticListener(std::function handler, void* owner) { + CSignalT::registerStaticListener(handler, owner); } diff --git a/tests/signal.cpp b/tests/signal.cpp index 3d42588..df8177b 100644 --- a/tests/signal.cpp +++ b/tests/signal.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "shared.hpp" @@ -5,9 +6,7 @@ using namespace Hyprutils::Signal; using namespace Hyprutils::Memory; -int main(int argc, char** argv, char** envp) { - int ret = 0; - +void legacy(int& ret) { CSignal signal; int data = 0; 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(); EXPECT(data, 0); +} +void legacyListenerEmit(int& ret) { + int data = 0; + CSignal signal; + auto listener = signal.registerListener([&](std::any d) { data = std::any_cast(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 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 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 signal; + signal.registerStaticListener([&](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; -} \ No newline at end of file +}