Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect reflective: Allow slots to access their own ConnectionHandle #70

Merged
merged 6 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions src/kdbindings/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class Signal
{
auto weakEvaluator = std::weak_ptr<ConnectionEvaluator>(evaluator);

auto deferredSlot = [weakEvaluator = std::move(weakEvaluator), slot](const ConnectionHandle &handle, Args... args) {
auto deferredSlot = [weakEvaluator = std::move(weakEvaluator), slot](ConnectionHandle &handle, Args... args) {
if (auto evaluatorPtr = weakEvaluator.lock()) {
auto lambda = [slot, args...]() {
slot(args...);
Expand All @@ -120,7 +120,15 @@ class Signal

Connection newConnection;
newConnection.m_connectionEvaluator = evaluator;
newConnection.slotDeferred = deferredSlot;
newConnection.slotReflective = deferredSlot;

return m_connections.insert(std::move(newConnection));
}

Private::GenerationalIndex connectReflective(std::function<void(ConnectionHandle &handle, Args...)> const &slot)
{
Connection newConnection;
newConnection.slotReflective = slot;

return m_connections.insert(std::move(newConnection));
}
Expand All @@ -138,7 +146,7 @@ class Signal

// Retrieve the connection associated with this id
auto connection = m_connections.get(id);
if (connection && connection->slotDeferred) {
if (connection && connection->m_connectionEvaluator.lock()) {
if (auto evaluatorPtr = connection->m_connectionEvaluator.lock()) {
evaluatorPtr->dequeueSlotInvocation(handle);
}
Expand Down Expand Up @@ -205,10 +213,10 @@ class Signal
const auto con = m_connections.get(*index);

if (!con->blocked) {
if (con->slotDeferred) {
if (con->slotReflective) {
if (auto sharedThis = shared_from_this(); sharedThis) {
ConnectionHandle handle(sharedThis, *index);
con->slotDeferred(handle, p...);
con->slotReflective(handle, p...);
}
} else if (con->slot) {
con->slot(p...);
Expand All @@ -222,7 +230,7 @@ class Signal
friend class Signal;
struct Connection {
std::function<void(Args...)> slot;
std::function<void(const ConnectionHandle &, Args...)> slotDeferred;
std::function<void(ConnectionHandle &, Args...)> slotReflective;
std::weak_ptr<ConnectionEvaluator> m_connectionEvaluator;
bool blocked{ false };
};
Expand Down Expand Up @@ -271,6 +279,23 @@ 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<void(ConnectionHandle &, Args...)> const &slot)
{
ensureImpl();

return ConnectionHandle{ m_impl, m_impl->connectReflective(slot) };
}

/**
* @brief Establishes a deferred connection between the provided evaluator and slot.
*
Expand Down
46 changes: 46 additions & 0 deletions tests/signal/tst_signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,52 @@ TEST_CASE("Signal connections")
REQUIRE(anotherCalled);
}

SUBCASE("Single Shot Connection")
{
Signal<int> 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("Self-blocking connection")
{
Signal<int> mySignal;
int val = 5;

auto handle = mySignal.connectReflective([&val](ConnectionHandle &self, int value) {
val += value;
self.block(true);
});

REQUIRE_FALSE(handle.isBlocked());
mySignal.emit(5);
REQUIRE(val == 10);
REQUIRE(handle.isBlocked());

mySignal.emit(5);
REQUIRE(val == 10);

handle.block(false);
mySignal.emit(5);
REQUIRE(val == 15);
}

SUBCASE("A signal with arguments can be connected to a lambda and invoked with l-value args")
{
Signal<std::string, int> signal;
Expand Down