Skip to content

Commit

Permalink
deque on deconstruct and nits
Browse files Browse the repository at this point in the history
  • Loading branch information
phyBrackets authored and LeonMatthesKDAB committed Dec 18, 2023
1 parent bcbbd34 commit 6a9fef8
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 28 deletions.
6 changes: 6 additions & 0 deletions src/kdbindings/connection_evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ConnectionEvaluator

void enqueueSlotInvocation(const ConnectionHandle &handle, const std::function<void()> &connection)
{
std::lock_guard<std::mutex> lock(m_connectionsMutex);
m_deferredConnections.push_back({ handle, std::move(connection) });
}

Expand All @@ -74,6 +75,11 @@ class ConnectionEvaluator
std::remove_if(m_deferredConnections.begin(), m_deferredConnections.end(), handleMatches),
m_deferredConnections.end());
}
void disconnectAllDeferred()
{
// Clear the vector of deferred connections
m_deferredConnections.clear();
}

std::vector<std::pair<ConnectionHandle, std::function<void()>>> m_deferredConnections;
std::mutex m_connectionsMutex;
Expand Down
4 changes: 2 additions & 2 deletions src/kdbindings/connection_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class SignalImplBase : public std::enable_shared_from_this<SignalImplBase>

virtual ~SignalImplBase() = default;

virtual void disconnect(const GenerationalIndex &id, const ConnectionHandle &handle) = 0;
virtual void disconnect(const ConnectionHandle &handle) = 0;
virtual bool blockConnection(const GenerationalIndex &id, bool blocked) = 0;
virtual bool isConnectionActive(const GenerationalIndex &id) const = 0;
virtual bool isConnectionBlocked(const GenerationalIndex &id) const = 0;
Expand Down Expand Up @@ -86,7 +86,7 @@ class ConnectionHandle
{
if (m_id.has_value()) {
if (auto shared_impl = checkedLock()) {
shared_impl->disconnect(*m_id, *this);
shared_impl->disconnect(*this);
}
}

Expand Down
66 changes: 53 additions & 13 deletions src/kdbindings/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,26 @@ class Signal
}

// Disconnects a previously connected function
void disconnect(const Private::GenerationalIndex &id, const ConnectionHandle &handle) override
void disconnect(const ConnectionHandle &handle) override
{
// If the connection evaluator is still valid, remove any queued up slot invocations
// associated with the given handle to prevent them from being evaluated in the future.
auto connection = m_connections.get(id);
if (connection->slotDeferred) {
if (auto evaluatorPtr = connection->m_connectionEvaluator.lock()) {
auto idOpt = handle.m_id; // Retrieve the connection associated with this id

// Proceed only if the id is valid
if (idOpt.has_value()) {
auto id = idOpt.value();

evaluatorPtr->dequeueSlotInvocation(handle);
// Retrieve the connection associated with this id
auto connection = m_connections.get(id);
if (connection && connection->slotDeferred) {
if (auto evaluatorPtr = connection->m_connectionEvaluator.lock()) {
evaluatorPtr->dequeueSlotInvocation(handle);
}
}

m_connections.erase(id);
}
m_connections.erase(id);
}

// Disconnects all previously connected functions
Expand All @@ -135,6 +143,29 @@ class Signal
m_connections.clear();
}

void disconnectAllConnections()
{
const auto numEntries = m_connections.entriesSize();

for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) {
const auto indexOpt = m_connections.indexAtEntry(i);
if (indexOpt) {
const auto con = m_connections.get(*indexOpt);
if (con) {
if (con->slot) {
m_connections.clear();
}

if (con->slotDeferred && con->m_connectionEvaluator.lock()) {
con->m_connectionEvaluator.lock()->disconnectAllDeferred();
}

m_connections.erase(*indexOpt);
}
}
}
}

bool blockConnection(const Private::GenerationalIndex &id, bool blocked) override
{
Connection *connection = m_connections.get(id);
Expand Down Expand Up @@ -162,7 +193,7 @@ class Signal
}
}

void emit(Args... p) /*const*/
void emit(Args... p)
{

const auto numEntries = m_connections.entriesSize();
Expand Down Expand Up @@ -195,7 +226,7 @@ class Signal
struct Connection {
std::function<void(Args...)> slot;
std::function<void(const ConnectionHandle &, Args...)> slotDeferred;
std::weak_ptr<ConnectionEvaluator> m_connectionEvaluator; // Use shared_ptr for stronger ownership.
std::weak_ptr<ConnectionEvaluator> m_connectionEvaluator;
bool blocked{ false };
};

Expand Down Expand Up @@ -224,7 +255,11 @@ class Signal
*/
~Signal()
{
disconnectAll();
if (m_impl) {
m_impl->disconnectAllConnections();
} else {
disconnectAll();
}
}

/**
Expand Down Expand Up @@ -259,13 +294,18 @@ class Signal
* The `KDBindings::Signal` class itself is not thread-safe. While the `ConnectionEvaluator` is inherently
* thread-safe, ensure that any concurrent access to this Signal is protected externally to maintain thread safety.
*/
ConnectionHandle connectDeferred(const std::shared_ptr<ConnectionEvaluator> &evaluator, std::function<void(const ConnectionHandle &, Args...)> const &slot)
ConnectionHandle connectDeferred(const std::shared_ptr<ConnectionEvaluator> &evaluator, std::function<void(Args...)> const &slot)
{
ensureImpl();

// Create a new ConnectionHandle instance for this connection
// Create a wrapper function that includes the ConnectionHandle
auto wrapper = [slot](const ConnectionHandle &handle, Args... args) {
slot(args...);
};

// Call connectDeferred on the implementation with the wrapper function
ConnectionHandle handle(m_impl, {});
handle.setId(m_impl->connectDeferred(evaluator, slot));
handle.setId(m_impl->connectDeferred(evaluator, wrapper));
return handle;
}

Expand Down Expand Up @@ -322,7 +362,7 @@ class Signal
void disconnect(const ConnectionHandle &handle)
{
if (m_impl && handle.belongsTo(*this) && handle.m_id.has_value()) {
m_impl->disconnect(*handle.m_id, handle);
m_impl->disconnect(handle);
// TODO check if Impl is now empty and reset
} else {
throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
Expand Down
42 changes: 29 additions & 13 deletions tests/signal/tst_signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@ TEST_CASE("Signal connections")
int val = 4;
auto evaluator = std::make_shared<ConnectionEvaluator>();

auto connection1 = signal1.connectDeferred(evaluator, [&val](const ConnectionHandle& handle, int value) {
val += value;
});
auto connection1 = signal1.connectDeferred(evaluator, [&val](int value) {
val += value;
});

auto connection2 = signal2.connectDeferred(evaluator, [&val](const ConnectionHandle& handle, int value1, int value2) {
val += value1;
val += value2;
});
auto connection2 = signal2.connectDeferred(evaluator, [&val](int value1, int value2) {
val += value1;
val += value2;
});

REQUIRE(connection1.isActive());

Expand All @@ -134,13 +134,13 @@ TEST_CASE("Signal connections")
auto evaluator = std::make_shared<ConnectionEvaluator>();

std::thread thread1([&] {
signal1.connectDeferred(evaluator, [&val](const ConnectionHandle& handle, int value) {
signal1.connectDeferred(evaluator, [&val](int value) {
val += value;
});
});

std::thread thread2([&] {
signal2.connectDeferred(evaluator, [&val](const ConnectionHandle& handle, int value) {
signal2.connectDeferred(evaluator, [&val](int value) {
val += value;
});
});
Expand All @@ -165,11 +165,11 @@ TEST_CASE("Signal connections")
int val2 = 4;
auto evaluator = std::make_shared<ConnectionEvaluator>();

signal1.connectDeferred(evaluator, [&val1](const ConnectionHandle& handle, int value) {
signal1.connectDeferred(evaluator, [&val1](int value) {
val1 += value;
});

signal2.connectDeferred(evaluator, [&val2](const ConnectionHandle& handle, int value) {
signal2.connectDeferred(evaluator, [&val2](int value) {
val2 += value;
});

Expand Down Expand Up @@ -199,7 +199,7 @@ TEST_CASE("Signal connections")
int val = 4;
auto evaluator = std::make_shared<ConnectionEvaluator>();

auto connection = signal.connectDeferred(evaluator, [&val](const ConnectionHandle& handle, int value) {
auto connection = signal.connectDeferred(evaluator, [&val](int value) {
val += value;
});

Expand All @@ -220,7 +220,7 @@ TEST_CASE("Signal connections")
int val = 4;
auto evaluator = std::make_shared<ConnectionEvaluator>();

signal.connectDeferred(evaluator, [&val](const ConnectionHandle& handle, int value) {
signal.connectDeferred(evaluator, [&val](int value) {
val += value;
});

Expand All @@ -233,6 +233,22 @@ TEST_CASE("Signal connections")
REQUIRE(val == 6);
}

SUBCASE("Disconnect deferred connection from deleted signal")
{
auto signal = new Signal<>();
auto evaluator = std::make_shared<ConnectionEvaluator>();
bool called = false;

auto handle = signal->connectDeferred(evaluator, [&called]() { called = true; });
signal->emit();
delete signal;

handle.disconnect();
REQUIRE(called == false);
evaluator->evaluateDeferredConnections();
REQUIRE(called == false);
}

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

0 comments on commit 6a9fef8

Please sign in to comment.