copyPropertiesFrom() vs. copyPropertiesAndChildrenFrom()

The documentation is a bit unclear about these ValueTree methods:

ValueTree::copyPropertiesFrom() documentation says “Any properties that already exist will be updated.” —> This sounds great for my use, except it doesn’t seem to update child ValueTrees at all.

ValueTree::copyPropertiesAndChildrenFrom() documentation says “Replaces all children and properties of this object with copies of those from the source object.” —> This seems to handle child nodes also, but doesn’t seem to update them, but replaces them and severes any existing connections with CachedValues?

So the first one apparently ignores all the child ValueTrees, where as the second one does not.

But what about the properties contained in those ValueTrees? If I use juce::CachedValue which refers to a ValueTree property, and then I use copyPropertiesAndChildrenFrom() to update its content, will the CachedValue lose its reference and return garbage from that point on? Does the CachedValue need to be re-initialized with referTo() or will it retain it’s reference correctly to the new updated data in the same ValueTree variable?

What I’m trying to do here is ensure Undo/Redo works correctly with CachedValues.

CachedValue is based on a reference to ValueTree. All CachedValues pointing to the ValueTree you call ::copyPropertiesAndChildren on will still be valid. All CachedValue trees pointing to any child will be busted. children are first all removed and then readded with the new properties.

OK, so let’s say my situation is the following:

I have ValueTree called “Node” with X amount of properties and Y amount of child nodes.

I have CachedValues referring to Node’s properties all around the code base. None of them refer to it’s children.

class Z is listening to all changes happening to Node. When the listener callback happens, a lot of the data must be consistent with each other. So these callbacks must not happen before I have changed all sorts of things from it. I.e. listener callback must not happen every time I change a property value or child’s juce:vars. Only at the end is the callback allowed to happen. (Temp and Node are ValueTrees)

To make this happen, I intend to do the following:
Temp.copyPropertiesAndChildrenFrom(Node).

Now I can safely change all the required things in it, and then I do the following:
Node.copyPropertiesAndChildrenFrom(Temp, p_undo_manager).

Now hopefully class Z gets a single ValueTree listener update callback and all the CachedValues are still working. Also Undo/Redo hopefully works properly.

That would not be what happens. Class Z still gets one valueTreePropertyChanged per property that has been added/changed/removed. Going through this temp copy isn’t doing anything good for you.

The CachedValues attach their own valueTreePropertyChanged listener to the ValueTree. If you are using CachedValue::get inside valueTreePropertyChanged callbacks at any time, make sure to call CachedValue::forceUpdate first to avoid dealing with outdated data

EDIT: undo/redo would still work properly, if you call ::beginTransaction at the appropriate time, so this entrire copyProperities happens during one undo/redo call.

OK, so at least the Undo/Redo should work then, but the original issue still persists when I actually change the properties. Here’s my exact problem:

I have X amount of properties in the child ValueTrees. Each property is an array of juce::vars. Every single one of those arrays must be the same size when the ValueTree listener callback is called.

How do I change all their sizes so the ValueTree listener callback happens only when they’re all the same size? I.e. the callback shouldn’t happen every single time I call ValueTree::setProperty().

ValueTree::setPropertyExcludingListener() doesn’t work well with Redo. It doesn’t redo those property calls called with setPropertyExcludingListener(). So that’s not a solution either.

That is not possible with ValueTrees for a good reason.

FYI: you just did mention child trees being used. This might get busted very easily if you are not careful!

You need to design your application structure in the following way: Consider the following ValueTree structure:
RootNode( Child1( x: [1, 2, 3] y: [1, 2, 3] ), Child2( x: [4, 5] y: [4, 5] ) )
Now along comes the new data: RootNode( Child1( x: [6, 7] y: [6, 7] )).

What happens is:

  • calling child removed for Child1
  • calling child removed for Child2
  • calling child added for (new) Child1

As you can see, there are no property change messages generated. And this is what your (probably) UI should expect to happen. There is a list view subscribing to the RootNode. Every time a child is added, it adds an item to the list (maybe a custom component rendering that child) and every time a child is removed, the associated list item is removed. The custom component subscribes to the child node it is rendering and redraws itself, if the values of the array change.
Same would go for any arbitrary audio graph, if those children describe a processing step.

I can move all the properties (arrays of vars) from the children to the RootNode and not use the children at all, if necessary.

But this still begs for the question: how to make it so that the listener callback only gets called once all the properties (i.e. arrays) in RootNode have been set to be the same length?

Again, this is not possible. You would need to write your own Listener structure around the valuetree and trigger a custom callback once you know everything is updated.

The JUCE MPESampler examples does something very similar.

Hmm. I need to see if something like that is easy to do. I would only need a simple solution for this.

I have luckily separated the location where the properties are changed into only couple of places in my application. So it might enable me to even use ChangeListener or something, now that I think of it… I have to look into this.

EDIT:
Ah, but that would not solve the Undo/Redo then. Or would it… I don’t know. Seems a bit complicated at the moment.

I came up with a simple idea which might actually work:

I’ll add two new properties to RootNode: changeStart and changeEnd. Both of which are integers.

Now the listener listens only to those two properties.

Before I change any data in RootNode and its children, I increment changeStart.
Then I update whatever data in RootNode and it’s children.
Then at the end I increment changeEnd.

Now Undo/Redo and regular updates should work properly: the listener update might be called twice per update (at the beginning and end) but the data should be guaranteed to be correct and not in corrupted state.