Project

General

Profile

Using Wt::Signal as a simple widget synchronization mechanism between sessions?

Added by Michele Perrone about 1 month ago

Hi all,
I'm playing around and experimenting with some Wt concepts, and I was looking for an "easy" way to synchronize a slider across different sessions.
My idea was to:

  • keep track of the slider value in a global variable
  • have a global signal, to which the slider in each session connects to, and updates the slider value based on the received value
  • when the slider value is changed in a session, the signal is emitted, which should be picked up by all other sessions Unfortunately this doesn't seem to work! Do you have any idea why? I'm attaching the source code. Many thanks in advance! :-)
main.cpp (1.54 KB) main.cpp WtSignalTest

Replies (4)

RE: Using Wt::Signal as a simple widget synchronization mechanism between sessions? - Added by Matthias Van Ceulebroeck about 1 month ago

Hi Michele,

this is certainly possible, and you are close to a working example!
There is only a single thing missing here.

When a signal is listened to, there is no guarantee that the connected function is executed in the same thread as the UI thread, that manages all the W... objects.
So there are two things you need to do.

  1. You need to make the client side of Wt aware that the server is allowed to "force-push" information to it with WApplciation::enableUpdates().
  2. The listening side (of the connection, meaning another WApplication instance) needs to ensure that the UI updates happen in the UI thread. You can do this by taking the WApplication::UpdateLock, performing ths UI changes, and then triggering the updates to the client side with WApplciation::triggerUpdate().

So, I would change:

public:
    MyApplication(const Wt::WEnvironment& env)
      : Wt::WApplication(env)
    {
        enableUpdates(true);

And also:

globalStateChanged.connect([this, slider](int state) {
    Wt::WApplication::UpdateLock lock(this);
    if (lock) {
        slider->setValue(state);
        triggerUpdate();
    }
}

I believe that leads you to a working concept.

P.S. as the documentation of WApplciation::UpdateLock indicates, you may wish to look at WServer::port().

Best,
Matthias

RE: Using Wt::Signal as a simple widget synchronization mechanism between sessions? - Added by Michele Perrone about 1 month ago

Hello Matthias, thank you very much for your prompt reply!
I indeed forgot to take care the UpdateLock... I didn't think about it, because I wasn't starting any background thread on my own like I sometimes do.
Adding that does the trick :-)

I have a doubt about the thread-safety of Wt::Signal::emit() and Wt::Signal::connect(), though. I'm in a scenario where several threads can use both of these methods for a given signal at the same time:

  • I have an std::thread running in the background, calling Wt::Signal::emit() whenever my state is updated
  • then there are the Wt sessions using Wt::Signal::connect() to receive the signal and update their view
  • and finally, the same Wt sessions also use Wt::Signal::emit() whenever the user changes something in the UI, so that the other sessions can update their view based on the new state

Do I need to set up some protection mechanism in order to avoid race conditions?

RE: Using Wt::Signal as a simple widget synchronization mechanism between sessions? - Added by Matthias Van Ceulebroeck about 1 month ago

Hello Michele,

that will indeed be necessary. Since all threads can modify the value, its state ought to be consistent. You'll have to mutex the variable in this design.

Best,
Matthias

RE: Using Wt::Signal as a simple widget synchronization mechanism between sessions? - Added by Michele Perrone 26 days ago

Hello Matthias, thanks for your explanations.

I have an update which might useful for those who stumble upon this topic in the future.

It seems that there was another problem with my code, because globalStateChanged.connect([this, slider](int state) {...} keeps getting called even after the session is destroyed.
This causes a segmentation fault or another fatal error when trying to acquire the lock with Wt::WApplication::UpdateLock lock(this), because this does not reference a valid session anymore.

The first thought was to check whether this was a nullptr, but most C++ compilers discourages this.
So I then went to the Wt::WSignal documentation to see how to disconnect the signal after the session who connected to it was destroyed.
Turns out that it's very easy. I was using this constructor: Wt::Signals::connection Wt::Signal< A >::connect (F function).
However I should have used this one to begin with: Wt::Signals::connection Wt::Signal< A >::connect (T * target, F function), which automatically disconnects on deletion of the target passed as first argument.

So my callback now looks like this:

globalStateChanged.connect(this, [this, slider](int state) {
    Wt::WApplication::UpdateLock lock(this);
    if (lock) {
        slider->setValue(state);
        triggerUpdate();
    }
}

And from my console, I see that it's no longer called once the session is destroyed.

Cheers,
Michele

    (1-4/4)