The ValueTree is JUCE’s secret weapon.”
– (from the ValueTree tutorial on juce.com)

The juce::ValueTree class is a super-flexible data structure that can hold a list of (key, value) pairs (referred to as properties), where:

  • key is of type juce::Identifier, which you can think of as a string, but brings along some other capabilities and constraints that make it better for this use case.
  • value can be of (more or less) any data type, stored internally as a variant type juce::var that’s part of JUCE.
  • There’s no compile-time schema for a ValueTree, properties and children can be added and removed at runtime, and the value of any property can be of any arbitrary type.

A ValueTree can also contain a list of child ValueTree objects (which is obviously required to implement the ‘tree’ part of the name).

ValueTrees may be round-tripped through XML without loss; it might be useful to think of them as a live, runtime XML document. There are some obvious differences between them (no namespacing, no schema support, and ValueTrees cannot contain text elements), but it can be a useful comparison to keep in mind.

Application code can listen for changes to a ValueTree, including events like

  • changes to property values
  • addition of child trees
  • removal of child trees
  • re-ordering of child trees

ValueTrees can also manage undo/redo operations, simplifying a frequently error-prone part of application development.

Internally, a ValueTree object points at a shared object that actually contains the storage for the tree. When you first create a ValueTree valueTree1, that shared object is automatically created. Creating a second ValueTree valueTree2 that’s a copy of valueTree1 increases the reference count on the shared object and points valueTree2 to the shared object as well. Any changes made to valueTree1 are visible to code that has access to valueTree2 and vice-versa.

This code:

juce::ValueTree valueTree1 { "typeName" };
juce::ValueTree valueTree2 { valueTree1 };

Creates a graph of objects that looks like:

graph LR
 A[valueTree1] --> B(Shared Object)
 C[valueTree2] --> B

Thanks to reference counting, the shared object containing the data of interest will stay alive until the last ValueTree that points at it is destroyed.

Because copying a ValueTree is implemented as a simple pointer assignment and increment of a reference count, ValueTrees are very lightweight and can be passed around by value.

Share via BlueskyLinkedInMastodonEmailRedditX


Published

Cello

Category

Cello

Tags

Find Me: