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

implementing single-shot connection mechanism #68

Closed
wants to merge 3 commits into from

Conversation

phyBrackets
Copy link
Contributor

Implementing #21

The idea behind connectReflective is to allow a slot to receive its ConnectionHandle as a parameter, enabling it to manage its own connection state, such as disconnecting after execution for a single-shot behavior. To safely manage lifetimes and avoid the lambda being deleted while in use, we used smart pointers for now, but i think we still need to go through the design and safety consideration carefully.

Copy link
Contributor

@seanharmer seanharmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting approach. Thanks for the patch.

src/kdbindings/connection_handle.h Outdated Show resolved Hide resolved
Copy link
Contributor

@LeonMatthesKDAB LeonMatthesKDAB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be implemented easier by reusing the existing mechanism from connectDeferred.

* @param slot A std::function that takes a shared_ptr<ConnectionHandle> 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<ConnectionHandle> connectReflective(std::function<void(std::shared_ptr<ConnectionHandle>, Args...)> const &slot)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the shared_ptr here.

The connectDeferred method already pretty much does reflective connection.
Why can't we use the same mechanism here?

Basically just rename Connection::slotDeferred to Connection::slotReflective.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but what's the need for Connection::SlotReflective ? Currently we are using connect to call with the wrappedSlot , is there any deal with ConnectionEvaluator for the single-shot mechanism? If not I don't think if we want it through the connectDeferred , and I think we can remove the shared_ptr from here,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm trying to say is that both single-shot connections and connectDeferred both end up using a reflective connection.

This is already the case with our current connectDeferred implementation. It is already reflective.

When you call connectDeferred, what ends up being connected to the signal is a function that takes its own ConnectionHandle as argument. That is basically a reflective connection.
So we should be able to use this same, existing mechanism, instead of having to reinvent it.

See:

auto deferredSlot = [weakEvaluator = std::move(weakEvaluator), slot](const ConnectionHandle &handle, Args... args) {

The lambda that's created there takes:

  1. A ConnectionHandle reference
  2. The arguments emitted by the Signal

That is already a reflective connection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry If I'm still misunderstanding it, So do we want to have an another method connectReflective or not? I think we need it for accepting the slot with ConnectionHandle parameter, and then we want to wrap that slot and pass it to the connectDeferred method, right?

But in case using the connectDeferred directly for single-shot, still for that we have to change the definition of connectDeferred to accept slot of type std::function<void(ConnectionHandle&, Args...)> slot , I'm not sure if it's worth to change the definition as we have to change the tests as well and even this method is not heavily used but still it would be 'breaking' change. We might wanna use type_traits for the connectDeferred method to accept slots both with and without a ConnectionHandle parameter for keeping the backward compatibility. Also do we want to make the ConnectionEvaluator optional, if we want to update the existing mechanism for single-shot?

Basically from the user perspective this should be the case right for the above case?

    auto handle = mySignal.connectDeferred(evaluator may be optional, [&val](ConnectionHandle& selfHandle, int value) {
            val += value;

            selfHandle.disconnect();
        });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's not what I meant!

Let me try explaining it differently :)

I'm totally onboard with adding connectReflective as public API.
What I just mean is that connectDeferred internally already uses this!

Basically this code that we already have:

auto weakEvaluator = std::weak_ptr<ConnectionEvaluator>(evaluator);

//                               This is a reflective connection
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
auto deferredSlot = [weakEvaluator = std::move(weakEvaluator), slot](const ConnectionHandle &handle, Args... args) {
    if (auto evaluatorPtr = weakEvaluator.lock()) {
        auto lambda = [slot, args...]() {
            slot(args...);
        };
        evaluatorPtr->enqueueSlotInvocation(handle, lambda);
    } else {
        throw std::runtime_error("ConnectionEvaluator is no longer alive");
    }
};
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Connection newConnection;
newConnection.m_connectionEvaluator = evaluator;
newConnection.slotDeferred = deferredSlot;
//            ^^^^^^^^^^^^ this could just be called slotReflective - that's basically what it is already!

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

Is already doing a reflective connection.

The deferredSlot lambda is exactly the signature that we expect from a reflective connection:
(const ConnectionHandle &handle, Args... args)

So we clearly already have reflective connections internally.
Why not reuse that same mechanism?

The slotDeferred method therefore simply uses the reflective connections internally.
That's something the user doesn't and shouldn't need to know about, but it's still something we can reuse internally.

We'll still definitely want to provide a separate connectReflective method as public API, but why does that new method have to reinvent the wheel? We already have the mechanism, let's just expose it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes that's I understand, Thanks for explaining!

The deferredSlot lambda is exactly the signature that we expect from a reflective connection:
(const ConnectionHandle &handle

I doubt that it should not be a const, as selfHandle cannot call the non-const method disconnect() on it. Since disconnect modifies the ConnectionHandle object, it cannot be called on a const instance.

We need to ensure that the slot has a modifiable (non-const) reference to its ConnectionHandle. User surely does not want to wrap the selfHandle with const_cast<ConnectionHandle&> while calling disconnect() on selfHandle.

What do you think, any better idea?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, of course, it needs to be given a non-const reference, but that should be easy enough to do :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phyBrackets any news on this?

I'll not be available for the next two weeks, so if you want to get another round of reviews in, your changes need to be uploaded before Friday of this week.

Copy link
Contributor

@LeonMatthesKDAB LeonMatthesKDAB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks a lot better.

We don't need the weak_ptr in Connectionhandle now I think?

I still don't understand why we shouldn't just reuse the mechanism for slotDeferred that already passes a reflective handle to the connected function.
But I guess the shared_ptr way also works.

@@ -181,6 +181,8 @@ class ConnectionHandle
template<typename...>
friend class Signal;

std::weak_ptr<ConnectionHandle> self; // Allows for safe self-reference
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not needed anymore?

@LeonMatthesKDAB
Copy link
Contributor

I've created a follow-up PR for this in #70 that refactors this to reuse the mechanism from connectDeferred.

@phyBrackets Let me know if there's any reason why we shouldn't go that route.

@LeonMatthesKDAB
Copy link
Contributor

Superceded by #70

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants