Bug: ValueTree and connected Value objects loose connection

Hi there!

Here’s a MWE of the problem, written as a small GoogleTest:

// create ValueTree and set a property
ValueTree treeA("myValueTree");
treeA.setProperty("myProperty", 1, nullptr);

// connect a juce::Value to this property
Value connectedValue;
connectedValue.referTo(treeA.getPropertyAsValue("myProperty", nullptr, true));
EXPECT_EQ(connectedValue.getValue().operator int(), 1);

// simulate loading the state of the tree from XML by replacing 
// the entire tree with a new one with different data in it
ValueTree treeB("myValueTree");
treeB.setProperty("myProperty", 5, nullptr);
treeA = treeB;

// This fails.
EXPECT_EQ(connectedValue.getValue().operator int(), 5);

The solution would be to overwrite void valueTreeRedirected(...) in ValueTreePropertyValueSource AND/OR to make ValueTree::operator=() send a ValueTree::Listener::valueTreePropertyChanged(...) callback to its listeners.

I know I can use ValueTree::copyPropertiesAndChildrenFrom() but it is not very intuitive why I must do this.

Interestingly, the AudioProcessorValueTreeState has the exact same problem: When loading the state of the tree from XML (which is the recommended way of doing it - see the tutorials), internally, operator=() is used - showing the same problems. See here: APVTS: Value returned by getParameterAsValue() breaks connection to the tree

(Just a side-node, apart from the original issue, comparing float-Values, specially if they are not integer numbers, even more worse when converted back and forth from decimal text-represantation (xml), is no accurate testing method.)

I know, but here we never go into any text representation. I edited the above example to use int, if that makes it better.

So this isn’t a bug, but intended behaviour. Both Value and ValueTree are just wrappers around shared data objects, and the assignment operator simply redirects the ValueTree to point at a new shared data object. So after you call :

treeA = treeB;

treeA is now pointing to the same object as treeB, but your connectedValue is still pointing to original Value object.

If you call copyPropertiesAndChildrenFrom() instead, then treeA and treeB will be pointing at different objects (that happen to be identical at this point) and connectedValue is still connected to object in treeA so it will get updated as you expect.

Its a little confusing at first, but once you get your head around this concept it should make much more sense!

It’s so confusing, even the AudioProcessorValueTree gets it wrong. :wink:
But I understand your point. I wonder if there was a way to make it easier to understand what the implications are…