Typed ValueTree structure

In an application where the same ValueTree’s are used in many different placed/views/components, how would you recommend using typed “wrappers” of the actual ValueTrees? I watched David Rowland’s great talk about ValueTrees (https://www.youtube.com/watch?v=3IaMjH5lBEY) and am planning to use the “typed wrapper” approach he describes.

For example consider the data model is a list of songs and a volume value and there’s a “CurrentSong” and “SongList” view. I see two ways to deal with multiple components using the same ValueTree

  1. Parse the full model into a typed class that has properties corresponding to the ValueTree’s structure, eg “AppModel” which has a list of Songs and a Volume value. Render the view based on the typed instances, so pass around the typed wrappers instead of value trees. Use a sort of facade pattern on the typed classes that listens to the actual change triggers on the ValueTree.

  2. Render the view from the ValueTree, and let the components themselves create the typed wrappers they need.

Rowland seems to be using 2., but I might very well be mistaken. He also seems to be mixing the view/ui components and typed wrappers into a single entity, there doesn’t seem to be a clear distinction between what is data and what is rendering that data (except of course the data is in the ValueTree).

It seems to me that 1. would be easier to design since there’s a clear distinction between data and view, and the ValueTree would be practically abstracted away from anything but the typed wrappers. There might be downsides to this approach that I’m not aware of.

What approach do you use or recommend?

You may be able to achieve what you’re going for in approach 1 by using CachedValue to store a type-safe reference to a property inside a ValueTree. You can store the ValueTree inside your model class and expose its properties as public CachedValue members.

This approach does have some trade-offs – for example, if you have one or more ValueTree::Listener objects listening to the tree that the CachedValue references, the listener’s valueTreePropertyChanged() callback may occur before the CachedValue has been updated (can be worked around by using forceUpdateOfCachedValue()). There’s some really great information about this approach and some similar alternatives in this thread .

2 Likes

I use option 1. All of my ValueTrees are attached to one of two top level ValueTrees (persistent and runtime). These are passed into just about all of my classes (backend and gui) where the classes use the corresponding wrappers that they are interested in. I have a ValueTreeWrapper class which makes it super easy to spin up new wrappers. Here is the most brief of example to explain some…

MyApp::initiailize()
{
    // other set up stuff
    // 'wrapOrCreate' is used by the owner of the VT
    // it will look for the VT as either the one passed in, or a child of it
    // if not found it will create it
    guiProperties.wrapOrCreate (runtimePropertiesVT);
    layoutProperties.wrapOrCreate (persistentPropertiesVT);

    transport.init (persistentPropertiesVT, runtimePropertiesVT);

    mainWindow = std::make_unique<MyMainWindow> ("", persistentPropertiesVT, runtimePropertiesVT);
}

Transport::init (ValueTree persistentPropertiesVT, ValueTree runtimePropertiesVT)
{
    transportProperties.wrapOrCreate (runtimePropertiesVT);
}

MyMainWindow::MyMainWindow (String title, ValueTree persistentPropertiesVT, ValueTree runtimePropertiesVT)
{
    transportComponent.init (persistentPropertiesVT, runtimePropertiesVT)
}

TransportComponent::init (ValueTree persistentPropertiesVT, ValueTree runtimePropertiesVT)
{
    // `wrap` is used by clients of the data
    // it will look for the VT as either the one passed in, or a child of it
    // if it does not find it, it will return an invalid ValueTree
    transportProperties.wrap (runtimePropertiesVT);
    transportProperties.onStateChange = [this] (TransportState transportState) { updateTransport (transportState); };
    updateTransport (transportProperties.getState ());
}

TransportComponent::stopButtonClicked()
{
    transportProperties.setState (TransportState::stop);
}
2 Likes

That looks nice! Any chance you could share the wrapper class? :innocent:

Do you handle lists through the wrapper as well? What I thought about was using a child VT for each property that is a list and storing the items inside that child, instead of storing all items as direct children and filtering based on type. So eg. for a property like root.songs would be

<root projectName="foobar">
    <songs>
        <song/>
        ... 
    </songs>
   <authors>
       </author>
       ...
   </authors>
</root>

And then I supposed you could have a generic wrapper class that handles the “songs” element and creates it if empty etc, like you described

Great link, thanks! Was planning on using CachedValues, so very nice to know about potential timing issues.

I’d be glad to share the wrapper… there are some changes I want to make to it, but I can’t take the time while I am mid-project. :slight_smile:

For Lists I have the list owner valuetree wrapper, which has API’s to manage children, with old school style getNumChildren (where Children would be context specific, getNumSongs), and getChild(int childIndex), along with more modern style forEachSong(std::function<void(ValueTree song)> songCallback). Note the returned values are the ValueTree’s not the wrappers, as wrappers are not meant to be passed around, ie. they wrap locally.

Thank you so much, this is super useful! Why are the wrappers not meant to be passed around? If you select a song from the song list, and have a component that shows the selected song, would you then store an id of the song somewhere in the VT hierarchy or pass the selected VT of the song around?

Also, any reason for not using CachedValue's?

You can pass them around, it just doesn’t fit my design, ie. that it is a lightweight object that can be used anywhere. There can also be the callback issue, where you inadvertently replace a callback in the wrapper, ie. there are not callback lists, just the simple onThing type callback. But, there are a few times where I pass a reference to one, but usually I leave it to the consumer of the data to just wrap the valuetree.

as for CachedValues, I wouldn’t use them in the wrapper, since it is doing all the same stuff, but you could modify the wrapper to cache the values in the same way. there would be a some work involved in modifying the wrapper to make that easy to setup, but it would certainly be useful in the way CachedValue is, where you don’t incur the cost of ValueTree code to get the value.