So, I have been using callAsync in most situations where something has to be done on the message thread instead of the audio thread, eg when parameterChanged is called from the audio thread.
I have had issue so far and I find this method much simpler and practical (although it does not coalesce) than setting an atomic bool and polling it with a timer, or using AsyncUpdater which is not real-time safe anyway, it appears).
It is safe (enough) for real-time use though?
Should I replace it with real-time safe variants of AsyncUpdater as proposed by dave96 in this 2020 speech?
This is also much more scalable than a callAsync because it lets you run multiple actions based on the same parameter which I think would work better for UIs, and also it lets you completely destroy the listener when the editor is closed and do nothing in that case.
(In my real world code there are a few other micro optimizations like sharing the timer for all listeners but this is the pattern generally).
Beauty of this is, you can adjust the poll-speed also dynamically.
For example if you notice a change, you can start polling twice as fast for smoother callbacks in the UI.
The main thing to bear in mind is that anything that interacts with or sends a message to the message queue isn’t realtime safe. It’s not just than locks might be involved somewhere down the line, it’s that there could be an inversion of priority which would really screw things up.
One way of handling is the method mentioned else where here. Use a timer and poll. However, you might want to specifically adapt this somewhat if you’ve got hundreds or thousands of parameters.
In my tests even 10,000 such timers checking an atomic at 60fps is not a performance cost - especially if you destroy them when the editor or parts of the editor that aren’t shown are closed. I’ve tried running many such timers in production code and it’s never caused an issue.
Having that said, in my ‘real’ code I actually don’t have 10,000 timer instances and instead I have one static timer that ‘listeners’ subscribe to with a broadcaster/listener mechanism.
The biggest advantage of that other a minor performance optimization is that you get all timer callbacks in the same rate/order and it’s easier to keep track of side effects happening as a result of a single parameter change.
I like the approach of getting rid of the listener altogether, thanks for sharing this!
I wonder if callAsync is better of worse than AsyncUpdater in terms of RT safety, as AsyncUpdater is used in a bunch of plugins on the field: if there was notable problems associated with it then surely it would have been replaced and would not be the default for parameter attachment in Juce anymore (or would it?).
I have used parameter attachments in plugins without issues. The AsyncUpdater will be called from the audio thread if:
parameterChanged is called from audio thread (e.g., via automation)
and the AsyncUpdater is alive (i.e., the editor is open)
I would guess that this scenario may be not that common? Anyway, I will definitely avoid parameter attachments in current and future plugins for safety. My current idea is to set a bool atomic for several sliders together and use parameterChanged to update the atomic. Then I can check the atomic in a timer.
AsyncUpdater and callAsync are totally fine to use, just not for the audio thread.
And yes, having those as a part of JUCE’s ParameterAttachment is a bug in JUCE. I would expect plugins that use those all over the place to run into severe performance problems if a lot of parameters are automated and the editor is open - fortunately this isn’t the case most of the time in most user sessions, but under the right user situation I expect you would be able to observe issues with that solution.
The ParameterAttachment async call is going to be very dependent on the state of the message queue and whatever else is going on. In theory it should be just posting a message which most of the time will be ok. It’s just that you don’t have RT guarantees.