My attempt at a ValueTree thread synchronizer

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;
};

Can you describe a simple use case where this would be needed? Not in broad terms of “accessible by any thread”, but with a realistic host-plugin situation?

How / when did you run into situations requiring adding extra safety nets?

In my plugins, with basic/standard parameter usage and state exchanges with the host, I don’t any additional measures. Unless I am missing stuff that I actually implemented in my own abstraction layers.

In the other post that I linked to, it was discussed how some hosts call getStateInformation() from their own thread (not the GUI thread). If you’re using ValueTrees to store the state, then the only option seems to be to lock the ValueTree somehow. This is the use case I had in mind.

I think you are going about this the wrong way. It looks like you are introducing a lot of sync mechanisms during the entire run of your application for a few times of loading and storing data.

Why don’t you lock the MessageManager while in get and setStateInformation?

Lock mechanisms yes, but the will only be contested when the other thread is asking for the ValueTree, which I’m assuming doesn’t happen very often. Other than that, this solution is just about as heavy as maintaining the original ValueTree, with no more hidden allocation than a ValueTree normally takes. MessageManagerLock is another option, but the risk of deadlocking is high.

I built this after reading this line from the other thread:

I was thinking of how to maintain a cached version of a ValueTree so that it could be ready by another thread, and came up with this solution. Happy to be proven wrong if I’ve misunderstood something though.

I think there’s a much simpler way to do that.
Your plugin calls updateHostDisplay() when it’s state has changed to tell the processor it did.

You can use that change (that can only happen in the message thread) to cache the ValueTree, by attaching an AudioProcessorListener to your own processor.

Also: I would use the message thread change to already store the VT as a MemoryBlock/XML string/JSON/etc in the exact format where you give it to the host. If you let the other/host thread touch the VT, weird things can happen due to the shared_ptr nature of VT.

Good to know this trick. I suppose this tool isn’t very useful then!

FYI the ValueTree that is returned is a copy, so there is no risk of interfering with the main state. But it looks like this is a moot point anyway.