We’re looking at switching some parts of the code to using ValueTrees.
A specific issue that I don’t have a clear answer to is how to handle xml preset reads that are done on a separate thread to the message thead.
User clicks a preset change interface component and a preset load job is given to a thread pool.
The preset load job returns with a new value tree, which it then copies into an existing tree, which we will call the preset tree. This preset tree is what we want the UI to update from when we load a preset. However updating on the return of the thread pool job is not possible as it is not the message thread. So we can’t use any of the normal value tree callbacks.
The preset tree now has new values that we’d like the UI to update to and we need to do this async.
I spent years trying to find a way to lock ValueTrees reliably. I finally gave up and went with the principle of “do everything on the message thread”. I made a thread safe equivalent to CachedValue which you might find useful though. It lets you get and set properties in a thread safe way. The cached value itself is locked, while the ValueTree uses MessageManager::callAsync for syncing. It works very well for my use case, and might be helpful for yours too. You do have to be explicit about calling the right set function though (i.e. setFromGUIThread() vs setFromOtherThread()).
template <typename T>
class ThreadSafeCachedValue : public juce::ValueTree::Listener
{
public:
ThreadSafeCachedValue(const juce::ValueTree& vt, const juce::Identifier type, juce::UndoManager* um, const T& d = {}) :
myVT(vt), myID(type), undoManager(um), def(d)
{
JUCE_ASSERT_MESSAGE_THREAD;
myVT.addListener(this);
val = myVT.getProperty(myID, def);
}
T getValueFromAnyThread() const
{
const auto l = getLock();
return val;
}
void setFromGUIThread(const T& t)
{
JUCE_ASSERT_MESSAGE_THREAD;
{
const auto l = getLock(); // if we don't update the value here, then other listeners could be notified before this value has updated.
val = t;
}
myVT.setProperty(myID, t, undoManager);
}
void setFromGUIThreadExcludingListener(const T& t, juce::ValueTree::Listener* l)
{
JUCE_ASSERT_MESSAGE_THREAD;
{
const auto l = getLock();
val = t;
}
myVT.setPropertyExcludingListener(l, myID, t, undoManager);
}
// There is a brief window here when the cached value has the new value but the ValueTree doesn't.
void setFromOtherThread(const T& t)
{
juce::MessageManager::callAsync([t, vt = myVT, um = undoManager, id = myID, this]() // copy everything. Don't assume "this" is still around.
{
auto nonConstVT = vt;
nonConstVT.setPropertyExcludingListener(this, id, t, um);
});
const auto l = getLock();
val = t;
}
private:
void valueTreePropertyChanged(ValueTree& vt, const Identifier& Id) override
{
JUCE_ASSERT_MESSAGE_THREAD;
if (vt == myVT && Id == myID)
{
const auto l = getLock();
val = vt.getProperty(myID, def);
}
}
juce::ScopedLock getLock() const { return juce::ScopedLock(valueCS); }
T val, def;
juce::ValueTree myVT;
juce::Identifier myID;
juce::UndoManager* undoManager;
juce::CriticalSection valueCS;
};
If you’re new to ValueTrees, another tip is to build wrapper classes around ValueTrees, I.e. you have a class which contains a single node of a ValueTree, and then x number of ThreadSafeCachedValue per relevant property. The wrapper class is then owned by shared_ptr, so that every pointer shares the same lock. Without this, the ThreadSafeCachedValue class I’ve shared probably won’t be useful.
I think the typical way to do this would be to have the preset load job trigger an AsyncUpdater when it’s finished. Then do the ValueTree flipping in the updater’s handleAsyncUpdate().
I’m not sure how to exactly pass the new tree created by the preset load thread job over to the message thread so that when handleASyncUpdate() is called, that you finally make the assignment oldPresettTree=newPresetTree on the message thread and listeners are updated.
You could use a member ValueTree called newPresetTree and store it there while waiting for handleAsyncUpdate() to be called, but that is like messaging through a class member which is a bit ugly.
Yep, an AsyncUpdater is probably not the best way here, I think I wasn’t firing on all cylinders this morning.
Are you using an AudioProcessorValueTreeState? Then you can just call replaceState() and it will do its magic on the message thread anyway, as the doc states. So in that case you don’t need to worry about how to safely pass the data, it does this for you.
If you’re working with plain ValueTrees, apart from implementing an AsyncUpdater the more convenient and appropriate option would probably be to use MessageManager::callAsync() with a lambda that captures the new ValueTree (by value) and and the old one by reference (or maybe better call a function on whatever object owns the ValueTree).
The scenario is where you have a tree in a preset that contains values that are not parameters, say global settings etc
So, I just use callAsync from preset load thread job passing the new tree and then assign the newTree to the oldTree in MessageManager::callAsync…since assigning one tree to another is just copying the listeners from the old tree to the new tree and then swapping a shared object pointer, the old tree will be released and the new tree, now containing the listeners will trigger an update, all happening on the message thread.