Getting components to redraw while the event loop is blocked


#1

I know it is stupid and evil to want to do this, but is there some even close to a sane way of updating a Component's visuals while in a function called by the GUI event loop? Currently I am doing something like this :

void myclass::called_in_response_to_user_interaction() 
{
  ...some slow operation...
  m_label->setText("step 1 finished",dontSendNotification);
  MessageManager::getInstance()->runDispatchLoopUntil(50);
  ...another slow operation...
  m_label->setText("step 2 finished",dontSendNotification);
  MessageManager::getInstance()->runDispatchLoopUntil(50);
  ...yet another slow operation...
  m_label->setText("step 3 finished",dontSendNotification);
}

While this appears to work, it seems very hacky and I believe this wouldn't even compile on OS-X...? (due to the 
#if JUCE_MODAL_LOOPS_PERMITTED && ! (JUCE_MAC || JUCE_IOS) in juce_MessageManager.cpp)

I know I could do something incredibly tedious like split the processing steps in some way so that the control returns to the message loop or implement the processings in a separate thread but that would make already questionable code even worse. (The actual code is far more complicated than the skeleton example above.) As a temporary band-aid, I just want to know if there's some other way than using runDispatchLoopUntil(some_probably_wrong_time_value_here)...

 

 


#2

Use a thread! The thread can send messages to your UI component to tell it to update its message when needed.


#3

Sigh...I guess I will have to again look into running the function in a thread then. (If it was easy and clean to do, I would have obviously already committed to using the additional thread...)

Is there nice way of passing a notification/result from another thread to the GUI thread that a thread has finished its work? I tried searching the forums for that and it looked like there isn't a straightforward solution. It appeared the recommended way would be to have a flag variable which is polled in a timer callback in the GUI thread...? Really, there's nothing else? :-/


#4

It appeared the recommended way would be to have a flag variable which is polled in a timer callback in the GUI thread...? Really, there's nothing else? :-/

That's only really needed for non-blocking realtime performance. I normally use an AsyncUpdater, CallbackMessage, Timer, etc. to do this. There are plenty of ways to send a message.


#5

I stumbled on exact same App pattern and felt a little short with the terse replies given my still novice expertise in JUCE. So here are the practical details on one method for the folks like me… (once you understand, you will discover additional ones, JUCE is magic!)

Context: I have my App logic implemented in a separate AppController class (not inheriting from any GUI Component) that implements just the ActionListener. The AppController gets actions (asynchronously) from GUI components elsewhere. At some point, my AppController gets an action that does a lot of things, taking many seconds and I wanted to update a TextEditor component with a kind of progress log. So we have a long action like:
… execute some logic … update GUI component … logic … update GUI … logic … update GUI… etc.
I discovered like Xenakios that the logic and text updates are executed, but the thread is busy in my AppController and JUCE main message loop never gets a chance to handle the queued Text GUI updates. All updates are cummulated and would only display in block when returning from the actionListenerCallback(). Fact is, the actionListenerCallback() actually runs within the main message thread, thus blocking it from propagating the requested GUI component updates to the display. And forget about calling repaint() directly, that only gets queued too.
My approach was then to invoke, after each GUI component update:
MessageManager::getInstance()->runDispatchLoopUntil(200);
… which worked well; yet this is not recommended and the code gets blotted with plenty such artificial calls that definitely smell like old sequential programming in an App environment that is designed for being fully event driven…

So I did, as recommended by Jules: “use a thread”… this translates to:

void ProcessController::actionListenerCallback(const String& message)
{ 
    // test which action message...
    if ( thisIsMyLongAction(message) )
         juce::Thread::launch([this] { doLongAction(); });
    else // another action ...
}

and bang! it fails because we cannot invoke any GUI component updates in a “foreign” thread, i.e. outside the main message thread. This is indeed quite logic to maintain thread safety in GUI operations. So what?
The solution is then to still launch a separate thread but wrap any function that would invoke directly or indirectly a GUI component update inside a method like

void ProcessView::appendLog(String logLine)
{
    const MessageManagerLock mmlck; // the MAGIC! just declare it, nothing more...
    logText->setText(logText->getText() + logLine + "\n"); // update a GUI component
}

and now we have: … long logic… appendLog(“done this”); … long logic … appendLog(“done that”); …

and the App is live


Calling Component::repaint() in a process thread
#6

if you need to update your GUI thread from the Processor, a simple out-of-the-box solution is to make your processor a changeBroadcaster, and your UI components a changeListener to the processor. This class utilizes JUCE’s built-in AsyncUpdater class to handle what you’re trying to do.

If this doesn’t work, then the approach to what you’re trying to accomplish may be faulty altogether.


#7

See this thread for why you should not use ChangeBroadcaster or AsyncUpdater from your ProcessBlock:


#8

Considering @TheVinn 's comment is five years old, does it still apply? I’ve never experienced any issue updating my UI thread from parameter updates in the processor via AsyncUpdater.

If anyone reading this can chime in on this topic, please do. I am curious.


#9

at the very least, borrowing @TheVinn’s idea for using a lockfree queue running on its own thread to do the triggerAsyncUpdate() is worth looking at.


#10

Context reminder: a “process” or “controller” class (call it as you want) implements quite heavy app logic, execution of which can last many seconds during which we want to inform the user on progress with suitable graphical updates.

What I tried (and understood - if I’m right):
(a) From GUI to the Controller: main GUI components use sendActionMessages (they implement ActionBroadcaster) and the controller implements ActionListener. By comparison the changeBroadcaster would only propagate a generic change event and coalescing applies. Based on the action that is posted, the controller may enter a long lasting processing job. Fact is, action messages are handled asynchronously by the main message thread. When this one decides to invoke the controller’s actionListenerCallback(msg) it does it on the main message thread.

(b.1.) from the Controller to GUI: I have added public methods on various GUI Components that invoke sub-component GUI updates to reflect Controller’s progress. The Controller can invoke these methods, and all updates gets queued (if I am right…). My Controller being busy on the main message thread, all updates it trigerred are not displayed until the Controller returns from actionListenerCallback(msg) [my observation].

(b.2 - alternate tentative) I wrapped my long lasting process into an AsyncUpdate callback, letting actionListenerCallback(msg) return immediately. But again, when handleAsyncUpdate() is executed, that’s under the main message thread and same outcome as (b.1)

(b.3) I fork a Thread from actionListenerCallback(msg) to let the callback return immediately, and the thread executes my long lasting logic but can no longer invoke the methods I exposed that perform GUI component updates because only the main message thread can do. I thought about calling indirectly through AsyncUpdater (now in the direction Controller - > GUI compo), wrapping the GUI update methods inside handleAsyncUpdate() callbacks… that shall work indeed, but I cannot convey any arguments through triggerAsynchUpdate() to pass data for these updates. I can think too about creating a reverse ActionBroadcaster/Listner channel… shall work too, but again the code will get bloated by serializing/deserializing data for updates (fan-IN through sendActionMessage(msg) + Fan-out from actionListenerCallback(msg) )

(b.4) finally, still forking a thread for the long running Controller tasks and just adding one single line of code:
const MessageManagerLock mmlck;
at the start of all methods invoked by my foreign thread (those methods exposed by top GUI components/containers and that perform GUI updates) was simple and elegant. And I can’t forsee any nasty effect of doing that. Locks are very short runs and only invoke basic text, graphic, and button updates. My app now starts the long running task in backgound an I can follow its progress in real time [my observation]

Note that in a “normal” app, all Controller-side tasks (also case for most other actions performed by my controller) are quite fast (far below 1 sec) and so there’s no need to fork a thread and lock the message loop time to post “intermediate” GUI updates. All updates even only visibly taking place after actionListenerCallback(msg) returns become instantaneous for the end user.
So far so nice.


#11

Is it right time to use std:: condition_variable?

http://en.cppreference.com/w/cpp/thread/condition_variable


#12

I would strictly stick to the published JUCE API and use the lock mechanism supplied; here, just a declaration of a MessageManagerLock that automatically releases the lock when going out of scope. And locking GUI component actions versus the Controller thread won’t help. On the contrary, we need to let them run in // at all times.


#13

how about calling the change message asynchronously via juce::MessageManager on the message thread? According to my stack trace in testing this, on MacOS the change message is being called asynchronously from the message queue anyway – however, we know that this’ll behave differently on different operating systems, so we can wrap it in a lambda and call the function from the message thread explicitly.

MyCoolAudioProcessor::setValue(int inIndex, float inValue){
AudioProcessor::setValue(inIndex, inValue);
std::function<void(void)> changeLambda =
 [this]() { this->sendChangeMessage(); };
juce::MessageManager::callAsync(changeLambda);
};