AsyncUpdater with a payload

Hello JUCE devs,

Is it possible to extend AsyncUpdater functionality so that to let triggerAsyncUpdate to take an input parameter and send it as a payload to the handleAsyncUpdate method?
This will let us put some logic into handleAsyncUpdate and decide what kind of updates the current component really needs.

Thanks,
Arlen.

Oh, by the way, currently I have the following implementation for your consideration. I’m not sure is it optimal or not. I’m sure you can come up with a better solution.

template<typename PayloadType>
class AsyncUpdaterWithPayload : public AsyncUpdater
{
public:
	auto triggerAsyncUpdate(PayloadType &&payload)
	{
		_payloads.add(std::forward<PayloadType>(payload));

		AsyncUpdater::triggerAsyncUpdate();
	}

	auto triggerAsyncUpdate(const PayloadType &payload)
	{
		_payloads.add(payload);

		AsyncUpdater::triggerAsyncUpdate();
	}

	auto getNextPayload()
	{
		return _payloads.removeAndReturn(0);
	}

	auto numberOfPendingPayloads() const
	{
		return _payloads.size();
	}

protected:
	Array<PayloadType> _payloads;
};

Would this be a case for the Message and MessageListener?

Just curious…

From the docs for AsyncUpdater::triggerAsyncUpdate:

Causes the callback to be triggered at a later time.

This method returns immediately, after which a callback to the handleAsyncUpdate() method will be made by the message thread as soon as possible.

If an update callback is already pending but hasn't happened yet, calling this method will have no effect. 

This means that not every call to triggerAsyncUpdate will lead to a corresponding call to handleAsyncUpdate. So your idea is that you would have to loop over the pending payload elements in handleAsyncUpdate? If you would want to go that route, why don’t you implement this piece of work in your class too, like:

template<typename PayloadType>
class AsyncUpdaterWithPayload : private AsyncUpdater
{
public:
	void triggerAsyncUpdateWithPayload(PayloadType &&payload)
	{
		payloads.add(std::forward<PayloadType>(payload));

		triggerAsyncUpdate();
	}

	void triggerAsyncUpdateWithPayload(const PayloadType &payload)
	{
		payloads.add(payload);

		triggerAsyncUpdate();
	}

	virtual void handleAsyncUpdateWithPayload (const Payload& payload) = 0;

private:
    Array<PayloadType> payloads;
    void handleAsyncUpdate() override
    {
        for (auto& p : payloads)
            handleAsyncUpdateWithPayload (p);

        payloads.clear();
    }

};

The whole point of AsyncUpdater is that it coalesces events into a single callback, so it’s probably the least appropriate class to use if you need to know about each individual event!

Like Daniel said, that’s exactly what Message/MessageListener already does, or MessageManager::callAsync.

We use this all the time. It’s pretty close to what you want.

1 Like

Worth pointing out that this isn’t threadsafe. The accesses to the _payloads array would need to be synchronised.

1 Like

It IS thread safe. Look into the source code of juce_Array:

    void add (const ElementType& newElement)
    {
        const ScopedLockType lock (getLock());
        values.add (newElement);
    }

Yeah. It really seems the case :slight_smile: Thanks!

Looks interesting. Thanks for the advice :slight_smile:

Yep. I see now. Thanks for the clarification :slight_smile:

Thanks. I see now :slight_smile:

Well if you look into the code then you should look at the whole code :wink:
The array has a second template parameter, the type of critical section to use. Per default this is a DummyCriticalSection object, which will lead to all those locks you see in the Array code to do nothing and be optimized away by the compiler. Only if you pass in a CriticalSection as second parameter, the Array becomes really thread safe. By the way the Array docs also say

To make all the array's methods thread-safe, pass in "CriticalSection" as the templated TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection.

So you won’t even need to read the source code to get this information in this case. By the way, most of the juce classes are really well documented, so I would always study the docs first. Finally, this means for a thread safe array, you need to declare it like this: Array<PayloadType, CriticalSection> _payloads

2 Likes

Oops :slight_smile: You right. My bad.

Also, if the payload you intend to send can be efficiently conveyed with a String, check out also ActionBroadcaster / ActionListener

Don’t strings allocate? that makes this not usable on the Audio Thread if that’s the case…

The Array used in the code snippet of the opening post allocates too, so if an allocation-free solution to be called the audio thread is needed, none of the solutions proposed in this thread should work…

That depends, what kind of string we are talking about, there are plenty…

  • juce::String uses internally a ReferenceCountedObject to hold the data, so it allocates
  • std::string is just a container of char, so it can be put on stack (not allocating) or heap (allocating) or moved…

The same rules like std::vector apply. I wonder though, what happens, if you assign a longer std::string to an existing std::string in stack memory… would it move on top of the stack, leaving the previous version empty?

Constructing a fresh new String will allocate, but copy-constructing will just increment the refcount. This is different to the behaviour of std::string, which is not allowed to be implemented using refcounting.

I don’t think that’s necessarily true. Most string implementations will do the ‘small string optimisation’, so strings < 24 chars (normally) will live on the stack rather than the heap, but implementations aren’t required to do that. In every implementation I’m aware of, longer strings will always live on the heap.

If you assign a long string to a std::string that was previously holding a string short enough to enable SSO, then some heap memory will be allocated to hold the new long string.

Thank you for correcting that. TBH, I got doubts after posting, but couldn’t find a good explanation.