I’ve been trying to come up with a solution for synchronizing ValueTrees
. In general with ValueTree
it’s best to just stick to a single thread, but as this post points out, sometimes you really do need to access one from multiple threads, i.e. if your hosts calls getStateInformation()
from a different thread.
I came up with this solution which I think is quite simple and robust. The idea is to keep an updated copy of the ValueTree
, which can be retrieved in a thread safe way. It’s a one-way process, and doesn’t solve all threading issues. The idea is that you should still stick to a single thread for normal ValueTree
work, but then you can retrieve a copy of that ValueTree
from any thread.
I’ve run tests on it, but haven’t really used it much yet. Please let me know if you spot any errors. Feel free to copy this code if it’s useful.
class VTThreadSynchroniser : public juce::ValueTree::Listener
{
public:
VTThreadSynchroniser(const juce::ValueTree& vt) :
originalVT(vt), coppiedVT(vt.createCopy())
{
originalVT.addListener(this);
}
juce::ValueTree getThreatSafeVTCopy() const
{
juce::ScopedLock l(cs);
return coppiedVT.createCopy(); // copy again for thread safety
}
juce::ValueTree getOriginalVT() const
{
return originalVT;
}
private:
static juce::ValueTree findEquivalentVT(const juce::ValueTree& in, const juce::ValueTree& to)
{
if (!to.getParent().isValid())
return in;
else
{
const auto toParent = to.getParent();
return findEquivalentVT(in, toParent).getChild(toParent.indexOf(to));
}
}
void applyToVTCopy(const juce::ValueTree& vt, const auto& call)
{
juce::ScopedLock l(cs);
std::invoke(call, findEquivalentVT(coppiedVT, vt));
}
void valueTreePropertyChanged(juce::ValueTree& tree, const juce::Identifier& prop) override
{
applyToVTCopy(tree, [tree, prop](juce::ValueTree vt)
{ vt.setProperty(prop, tree[prop], nullptr); });
}
void valueTreeChildAdded(juce::ValueTree& parent, juce::ValueTree& child) override
{
applyToVTCopy(parent, [parent, child](juce::ValueTree vt)
{vt.addChild(child.createCopy(), parent.indexOf(child), nullptr); });
}
void valueTreeChildRemoved(juce::ValueTree& parent, juce::ValueTree& child, int index) override
{
applyToVTCopy(parent, [index](juce::ValueTree vt)
{ vt.removeChild(index, nullptr); });
}
void valueTreeChildOrderChanged(juce::ValueTree& parent, int oldIndex, int newIndex) override
{
applyToVTCopy(parent, [oldIndex, newIndex](juce::ValueTree vt)
{ vt.moveChild(oldIndex, newIndex, nullptr); });
}
juce::ValueTree originalVT, coppiedVT;
juce::CriticalSection cs;
};