I’m playing around for the first time with ValueTrees for an application and some things seem odd…obviously it could just be my lack of understanding.
For my top level ValueTree, I want to have a member variable, but…:
class MyMain
{
// member variables
ValueTree topTree; // this is invalid
ValueTree topTree(Identifier("TREE")); // can't do this
static Identifier treeID("TREE"); // can't do this
ValueTree topTree(treeID);
// so it seems I have to do this:
MyMain()
{
ValueTree fake(Identifier("TREE")); // this just seems odd
topTree = fake;
}
}
I also don’t see a clean way to create static Identifiers. I saw @dave96’s talk in which he does this:
Maybe it’s just me, but that seems like a workaround for something that’s not fully worked out. I mean, I’ve been trying to get away from using #defines and the like…but it does work.
So now I’m doing this:
class MyMain
{
MyMain()
{
ValueTree dummy(IDs::TREE);
tree = dummy;
}
ValueTree tree;
}
Sorry for the whining, but as I said, this all seems odd. Maybe it will sink it later as I use ValueTrees more, but any thoughts would help. Thanks.
Totally! lol… I should put together a macro, since my scoping is slightly different than his. Mostly it hasn’t been an issue, since each new wrapper starts as a copy/paste.
@cpr that’s where I started as well. I still use it from time to time, when I allow the tree to be accessed from other classes and let them know the Identifiers…
Now I have the namespace IDs in the cpp, so it is nicely hidden from all other classes.
And ideally have accessor methods instead of allowing anyone to go directly into the ValueTree, that’s much better encapsulation.
@daniel I could probably could hide the Id’s as well, since, as you describe, the Wrappers actual hide all of the ‘ValueTreen-ess’ from clients using them. I have setters, getters, and std::function callbacks that the clients use.
So there’s really no advantage to creating the same ID in multiple scopes except maybe it’s an indication of what properties you should be setting on the tree;
I do get that if you expose your raw ValueTrees you are leaving them open to any form of mutation. (In Tracktion Engine this is actually desired as apps can store custom data in the tree which is ignored by the Engine).
This is a bit of a philosophical debate on how much protection you should give to your state and what the contract of your class is.
If I was doing some major refactoring I think I’d expose everything via type-safe, validated CachedValues (we have a ConstrainedCachedValue<T> in the Engine we’re starting to adopt) and avoid having to go via the ValueTree directly at all.
I’ll defer to your ValueTree experience, as I have only been working them for a year or so now. I’m not wrapping them to prevent mutation, but instead to simplify the usage of the data model, ie. not requiring my client code to do ValueTree things. CachedValue is half the battle, but since I want to respond to changes, I also wanted a simple callback mechanism like Button::onClick.
CachedValue<T>::onPropertyChange was my exact though when you mentioned CachedValue. As you said, I’m also ‘inching closer’ to something I am happy with. What I have now has made coding super quick to create new models and use them, but it needs more usage for the patterns to be seen more clearly. Regarding ValueTree mutation, I do allow access to the underlying VauleTree with getValueTree, and getValueTreeRef, accessors.
This is all starting to make me wonder about all the conversion efficiency when this scales up to a larger plugin or app. @dave96, I’m sure you know better, but it seems like there is potentially a lot of conversion from float to various intermediate types (var, Value, CachedValue, etc) when all I really need in my app is a simple float. And what happens when many variables are spread all over a large code base…how much time is spent in converting types? I haven’t looked at the underlying code but my inner optimization brain makes me think about it.