diff --git a/src/kdbindings/connection_handle.h b/src/kdbindings/connection_handle.h index 81eb447..128b2dd 100644 --- a/src/kdbindings/connection_handle.h +++ b/src/kdbindings/connection_handle.h @@ -181,6 +181,8 @@ class ConnectionHandle template friend class Signal; + std::weak_ptr self; // Allows for safe self-reference + std::weak_ptr m_signalImpl; std::optional m_id; @@ -206,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; + } }; /** diff --git a/src/kdbindings/signal.h b/src/kdbindings/signal.h index 3b692d2..827bc09 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 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 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 ConnectionHandle reference followed by the signal's parameter types. + * @return A ConnectionHandle to the newly established connection, allowing for advanced connection management. + */ + ConnectionHandle connectReflective(std::function const &slot) + { + ensureImpl(); + + // 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); + + // 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 to the signal implementation. + // The handle ID is set after successful connection to manage this connection specifically. + handle->setId(m_impl->connect(wrappedSlot)); + + // Return the ConnectionHandle, allowing the caller to manage the connection directly. + 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..08706c5 100644 --- a/tests/signal/tst_signal.cpp +++ b/tests/signal/tst_signal.cpp @@ -257,6 +257,29 @@ 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](ConnectionHandle &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); // 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") { Signal signal;