From ac4f32183afedf21e0242204f399124ed0165629 Mon Sep 17 00:00:00 2001 From: Shivam Kunwar Date: Tue, 20 Feb 2024 20:27:50 +0530 Subject: [PATCH 1/3] implementing single-shot connection mechanism --- src/kdbindings/connection_handle.h | 9 +++++++++ src/kdbindings/signal.h | 31 ++++++++++++++++++++++++++++++ tests/signal/tst_signal.cpp | 21 ++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/src/kdbindings/connection_handle.h b/src/kdbindings/connection_handle.h index 81eb447..3a81dd4 100644 --- a/src/kdbindings/connection_handle.h +++ b/src/kdbindings/connection_handle.h @@ -177,10 +177,19 @@ class ConnectionHandle return false; } + // Factory method to create a ConnectionHandle + static std::shared_ptr create(const std::weak_ptr& signalImpl, std::optional id) { + auto handle = std::shared_ptr(new ConnectionHandle(signalImpl, id)); + handle->self = handle; // Keep a weak reference to self + return handle; + } + private: template friend class Signal; + std::weak_ptr self; // Allows for safe self-reference + std::weak_ptr m_signalImpl; std::optional m_id; diff --git a/src/kdbindings/signal.h b/src/kdbindings/signal.h index 3b692d2..b19d14e 100644 --- a/src/kdbindings/signal.h +++ b/src/kdbindings/signal.h @@ -260,6 +260,37 @@ class Signal return ConnectionHandle{ m_impl, m_impl->connect(slot) }; } + /** + * Establishes a connection between a signal and a slot, allowing the slot to access its own connection handle. + * This method is particularly useful for creating connections that can manage themselves, such as disconnecting + * after being triggered a certain number of times or under specific conditions. It wraps the given slot function + * to include a shared pointer to the ConnectionHandle as the first parameter, enabling the slot to interact with + * its own connection state directly. + * + * @param slot A std::function that takes a shared_ptr followed by the signal's parameter types. + * @return A shared_ptr to the ConnectionHandle associated with this connection, allowing for advanced connection management. + */ + std::shared_ptr connectReflective(std::function, Args...)> const &slot) + { + ensureImpl(); + + // Create a placeholder handle (with no ID yet) + auto handle = ConnectionHandle::create(m_impl, std::nullopt); + + auto wrappedSlot = [this, slot, weakHandle = std::weak_ptr(handle)](Args... args) { + if (auto handle = weakHandle.lock()) { // Ensure the handle is still valid + slot(handle, args...); + } + }; + + // Connect the wrapped slot + auto id = m_impl->connect(wrappedSlot); + + // Assign the ID to the handle now that it's known + handle->setId(id); + return handle; + } + /** * @brief Establishes a deferred connection between the provided evaluator and slot. * diff --git a/tests/signal/tst_signal.cpp b/tests/signal/tst_signal.cpp index f0b5a2d..0670491 100644 --- a/tests/signal/tst_signal.cpp +++ b/tests/signal/tst_signal.cpp @@ -257,6 +257,27 @@ TEST_CASE("Signal connections") REQUIRE(anotherCalled); } + SUBCASE("Single Shot Connection") + { + Signal mySignal; + int val = 5; + + // Connect a reflective slot to the signal + auto handle = mySignal.connectReflective([&val](std::shared_ptr selfHandle, int value) { + val += value; + + // Disconnect after handling the signal once + selfHandle->disconnect(); + }); + + mySignal.emit(5); // This should trigger the slot and then disconnect it + REQUIRE(!handle->isActive()); + + mySignal.emit(5); // Signal Already disconnected + + REQUIRE(val == 10); + } + SUBCASE("A signal with arguments can be connected to a lambda and invoked with l-value args") { Signal signal; From bb87581af9d1d6dd3d055d985c042b43037a73e0 Mon Sep 17 00:00:00 2001 From: Shivam Kunwar Date: Wed, 21 Feb 2024 14:21:17 +0530 Subject: [PATCH 2/3] make ConnectionHandle::create a private method --- src/kdbindings/connection_handle.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/kdbindings/connection_handle.h b/src/kdbindings/connection_handle.h index 3a81dd4..128b2dd 100644 --- a/src/kdbindings/connection_handle.h +++ b/src/kdbindings/connection_handle.h @@ -177,13 +177,6 @@ class ConnectionHandle return false; } - // Factory method to create a ConnectionHandle - static std::shared_ptr create(const std::weak_ptr& signalImpl, std::optional id) { - auto handle = std::shared_ptr(new ConnectionHandle(signalImpl, id)); - handle->self = handle; // Keep a weak reference to self - return handle; - } - private: template friend class Signal; @@ -215,6 +208,14 @@ class ConnectionHandle } return nullptr; } + + // Factory method to create a ConnectionHandle + static std::shared_ptr create(const std::weak_ptr &signalImpl, std::optional id) + { + auto handle = std::shared_ptr(new ConnectionHandle(signalImpl, id)); + handle->self = handle; // Keep a weak reference to self + return handle; + } }; /** From 9b2e3229585cc568f25cc8a0c6c3608e97fc51d6 Mon Sep 17 00:00:00 2001 From: Shivam Kunwar Date: Sun, 28 Apr 2024 12:17:31 +0530 Subject: [PATCH 3/3] directly uses ConnectionHandle& to enable more direct and efficient management of connections within slots --- src/kdbindings/signal.h | 32 ++++++++++++++++---------------- tests/signal/tst_signal.cpp | 12 +++++++----- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/kdbindings/signal.h b/src/kdbindings/signal.h index b19d14e..827bc09 100644 --- a/src/kdbindings/signal.h +++ b/src/kdbindings/signal.h @@ -261,34 +261,34 @@ class Signal } /** - * Establishes a connection between a signal and a slot, allowing the slot to access its own connection handle. - * This method is particularly useful for creating connections that can manage themselves, such as disconnecting + * Establishes a connection between a signal and a slot, allowing the slot to access and manage its own connection handle. + * This method is particularly useful for creating connections that can autonomously manage themselves, such as disconnecting * after being triggered a certain number of times or under specific conditions. It wraps the given slot function - * to include a shared pointer to the ConnectionHandle as the first parameter, enabling the slot to interact with + * to include a reference to the ConnectionHandle as the first parameter, enabling the slot to interact with * its own connection state directly. * - * @param slot A std::function that takes a shared_ptr followed by the signal's parameter types. - * @return A shared_ptr to the ConnectionHandle associated with this connection, allowing for advanced connection management. + * @param slot A std::function that takes a ConnectionHandle reference followed by the signal's parameter types. + * @return A ConnectionHandle to the newly established connection, allowing for advanced connection management. */ - std::shared_ptr connectReflective(std::function, Args...)> const &slot) + ConnectionHandle connectReflective(std::function const &slot) { ensureImpl(); - // Create a placeholder handle (with no ID yet) + // Create a new ConnectionHandle with no ID initially. This handle will be used to manage the connection lifecycle. auto handle = ConnectionHandle::create(m_impl, std::nullopt); - auto wrappedSlot = [this, slot, weakHandle = std::weak_ptr(handle)](Args... args) { - if (auto handle = weakHandle.lock()) { // Ensure the handle is still valid - slot(handle, args...); - } + // Prepare the lambda that matches the signature expected by m_impl->connect + auto wrappedSlot = [this, handle, slot](Args... args) mutable { + // Directly invoke the user-provided slot with the handle and args + slot(*handle, args...); }; - // Connect the wrapped slot - auto id = m_impl->connect(wrappedSlot); + // Connect the wrapped slot to the signal implementation. + // The handle ID is set after successful connection to manage this connection specifically. + handle->setId(m_impl->connect(wrappedSlot)); - // Assign the ID to the handle now that it's known - handle->setId(id); - return handle; + // Return the ConnectionHandle, allowing the caller to manage the connection directly. + return *handle; } /** diff --git a/tests/signal/tst_signal.cpp b/tests/signal/tst_signal.cpp index 0670491..08706c5 100644 --- a/tests/signal/tst_signal.cpp +++ b/tests/signal/tst_signal.cpp @@ -263,19 +263,21 @@ TEST_CASE("Signal connections") int val = 5; // Connect a reflective slot to the signal - auto handle = mySignal.connectReflective([&val](std::shared_ptr selfHandle, int value) { + auto handle = mySignal.connectReflective([&val](ConnectionHandle &selfHandle, int value) { val += value; // Disconnect after handling the signal once - selfHandle->disconnect(); + selfHandle.disconnect(); }); mySignal.emit(5); // This should trigger the slot and then disconnect it - REQUIRE(!handle->isActive()); - mySignal.emit(5); // Signal Already disconnected + REQUIRE(!handle.isActive()); - REQUIRE(val == 10); + mySignal.emit(5); // Since the slot is disconnected, this should not affect 'val' + + // Check if the value remains unchanged after the second emit + REQUIRE(val == 10); // 'val' was incremented once to 10 by the first emit and should remain at 10 } SUBCASE("A signal with arguments can be connected to a lambda and invoked with l-value args")