WeakReference to a ValueTree

While trying to instantiate an object of type juce::WeakReference<juce::ValueTree> I get the following compilation error: “masterReference: is not a member of juce::ValueTree”.
To my understanding, this means that ValueTree simply doesn’t support being stored in a WeakRerence.
if so, why is that? and is there a workaround?

You should not be dealing with pointers and ValueTree’s. Per the docs: as they're simply a lightweight reference to a shared data container. Creating a copy of another ValueTree simply creates a new reference to the same underlying object - to make a separate, deep copy of a tree you should explicitly call createCopy(). If you have a ValueTree, you have a reference to the underlying data. if you copy it (without the createCopy() commed), you get another lightweight container which references the same underlying data. If the first one goes out of scope, the second one still contains the reference to the underlying data. rinse and repeat. :slight_smile:

I’m aware that ValueTree is essentially a lightweight reference to a shared data container. But a case where it’s essential to pass it by reference/ptr, is when you want to register/unregister listeners to the ValueTree. Because listeners listen to the ValueTree instance, not to the container it refers to.

I don’t think weak reference is the right approach in that case.

Each object you have that needs to listen to changes in the tree should listen to their own ValueTree object that you would point to refer to the correct shared object. That way the listener will never outlive the tree it’s listening to.

ValueTree::Listener is a bit hard to wrap the head around:

ValueTree contains a reference counted SharedObject, as others pointed out.
The listeners are stored in the actual ValueTree, not the SharedObject.

If you assign a ValueTree, it will reference the SharedValueTree. But if the original ValueTree goes out of scope, the listenerList is gone too.

If you pass it as reference, all warnings about lifetime apply. But that is usually not an issue, because the when the listenerList is gone, nothing is called any more.

ValueTree origin { "tree" };
ValueTree copy = origin;  // points now to the same data

Pass the ValueTree in by simple reference instead of WeakReference:

void addThisListener(ValueTree& vt)
{
     vt.addListener(this);
}

The ValueTree will stay alive in the caller’s scope, so nothing can go wrong (unless you’re accessing it from different threads at once).

I’ll try to explain why I want to use WeakReference<ValueTree>. it’s a long explanation, but please bear with me.

I’m building my own DAW, which has projects, the projects have tracks, the tracks have clips, and so on…
those data entities are represented as classes, which are responsible for keeping the DB, audio graph, and UI up to date. They do that by owning a ValueTree which holds the data (state) of the entity, and broadcast changes to all listeners.

UI components can manipulate those classes by calling one of their methods async (for example, void Track::setTrackName(const std::string& newName)), and all UI components that listen to the class will get notified of the change and will update themself async back on the message thread accordingly.

UI component can register themself as listeners to those classes by calling their juce::ValueTree& addListener(juce::ValueTree::Listener* listener), and unregister by calling void removeListener(juce::ValueTree::Listener* listener) on the ValueTree& returned from the addListener method.

Here’s Track.h for example

class Track : private juce::ValueTree::Listener

{
public:
Track(juce::ValueTree& state);
~Track();

juce::ValueTree& addListener(juce::ValueTree::Listener* listener);

int getId() const;
std::string getName() const;
std::string getColor() const;
float getLength() const;
float getFader() const;
float getPanner() const;
bool getMute() const;

void createClip(Models::Clip& clipData);
void deleteClip(const int id);

std::vector<Models::Clip> retrieveClips() const;

void setFader(const float newFader);
void setPanner(const float newPanner);
void setMute(const bool newMute);
void setName(const std::string name);
void setColor(const std::string color);

private:
void valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged, const juce::Identifier& property) override;

bool doesClipExist(const int id) const;

void loadDataFromDb();

private:
juce::ValueTree _state;
juce::CachedValue _id;
juce::CachedValuejuce::String _name;
juce::CachedValuejuce::String _color;
juce::CachedValue _length;
juce::CachedValue _fader;
juce::CachedValue _panner;
juce::CachedValue _mute;
juce::OwnedArray<Clip> _clips;
};

Generally, all good, with one exception - when a UI component decides to delete a Clip, it calls async the void Track::deleteClip(const int id), the method will notify all listening components that the clip was deleted. The components will update themself async back on the message thread, so the deleteClip method won’t wait for each component to update before proceeding. After that, In the deleteClip method, the matching Clip object gets deleted, and it’s ValueTree with it. Right after that, on the message thread, one of the listening components removes a child component which represents the just deleted clip. The removed object tries to unregister itself from its ValueTree before deconstruction, but because the value tree was already deleted before, an error accrues.
The same applies when trying to delete a track and a project

With WeakReference<ValueTree> the component could simply check if the ValueTree was deleted, and act accordingly.

TL;DR… :slight_smile: if you make the clips into children, then management could be done with child add/remove callbacks. Maybe you are already doing this, and I missed the detail (in the explanation I did not read)… :slight_smile: I suspect there is a better way to achieve what you want, without fiddling with this level of stuff.

In which regard you mean that I “make the clips into children”, children in the ValueTree? or children of a component?

And which add/remove child callbacks are you referring to, ValueTree::Listener::valueTreeChildAdded() and ValueTree::Listener::valueTreeChildremoved()? or something else?

Here are two diagrams that might make the situation described clearer (the text might too small to read, so feel free to zoom in) (notice the chronologic numbering of the actions):

This shouldn’t happen if the removed object keeps its own copy of the ValueTree as a member variable. ValueTree is reference counted, so the underlying object will stay in scope for as long as it is needed. Furthermore, if you have multiple copies of a ValueTree all pointing towards the same underlying data, they will all receive the same listener callbacks.

By the way, you definitely won’t be able to use WeakRefrence. As explained in the documentation, WeakReference is intrusive, meaning that you have to modify the class with the JUCE_DECLARE_WEAK_REFERENCEABLE macro in order for it to work.

1 Like