Hi, I’ve reached the stage in a large project where I can’t do much without supporting saving the current state of a plugin. The state goes beyond simple parameters, I have GUI objects that need to know where to be based on state of the processor.
I’ve done this before, I used the serializing way, keeping an xml entry per variable, saving states to xml and reading them back. I don’t want to do that this time, it would take a great deal of time.
I need to implement undo, so each time a user can accomplish something, I need to be able to store that and be able to reference it, maybe as a type of string, so things like DoAction(“Some Action”), UndoAction(“Some Action”).
My question is, does undo pretty much remove any need to support direct xml serialization of variables? It seems like it does.
I think you want to look at ValueTree (and if you are new to that to the talks from Dave at ADC on YouTube). They bring the undo functionality and serialisation. They can be used to implement an MVC pattern in a great way and loading/saving undo/redo will all work out of the box when used correctly.
Interesting, it seems the value tree can be used in a similar way to serializing variables, as in accounting for their current state by use of setting a property’s value with unique identifier, but in a way which supports undo by default. I had thought it was the other way around. I will investigate these valuetrees, thanks.
In that case, here is the talk telling you what you need to know up front: https://www.youtube.com/watch?v=3IaMjH5lBEY
In my software design, I’m not doing it exactly has Dave is suggesting it, but all principles still stand.
The biggest issue I had with Dave’s approach (if I remember it correctly), is that it didn’t have enough type safety for my taste.
Thanks, one concern is I have a lot of listeners in my code, when something gets added or removed, other parts of code need to know what was added or removed, if I rely simply on a state and not an action I may have lost the ability to detect if something has been added or removed. I’ll need to adjust code elsewhere probably.
The ValueTree is basically an XML tree. It does offer nested trees and reordering etc. all with undo and observing capabilities.
I mean the differentiation between whether it was an add or a remove, I may have to add code that doesn’t so much listen out for a specific action but a general carte blanche ‘change’, then compare it to what it thinks should be there.
Oh I see, that’s not too bad. An object that has a value tree can listen to it’s own tree for when the undo/redo kicks in. It can then be the one to make a state check, delete/add it’s objects, then can continue with it’s own listener broadcast system.
I like it, this is good.
I have another question about ValueTrees, I thought it would be best to add it here.
I can serialize and deserialize nicely now. I can read in a block of memory into a value tree, and it will have children. I need to initialize objects with the children, so I use GetChildWithName(“NameOfChild”). I can then initialize the child object absolutely fine.
there is one problem, GetChildWithName returns by value, not reference, it’s not the ‘actual’ object. The end result being I can hold onto this copy in the child object, edit it, and the parent won’t know about it via the listening system.
I can think of various ways around it, I’m leaning toward not caring and simply using ParentTree.copyPropertiesAndChildren("NameOfChild).(ChildObject.MyFunctionGetTreeCopy()) when it comes time to serialize again.
I was just wondering if anyone else experiences this and find it’s best to always be able to listen out for changes to children in the tree when it happens?
That is not true, ValueTree::getChildWithName returns a reference to your value tree structure the same way all other functions do. It might return an invalid ValueTree if no child with that name is found. Use ValueTree:: GetChildWithName if that is a problem.
What you might be experiencing, is the following:
juce::ValueTree parent("parent");
juce::ValueTree child("child");
parent.appendChild(child, nullptr);
parent.copyPropertiesAndChildrenFrom(/* memory block stuff etc. */, nullptr);
Now, child won’t actually be part of parent anymore. parent.copyPropertiesAndChildrenFrom removes all current children (calling the appropriate value tree listeners) and add completely new value trees as children. The original children are still valid value trees, but are probably not connected to your application backend anymore.
This behaviour is pretty annoying, if your root ValueTree consists of a constant number of lists you want to keep “alive” during the entire runtime of your application.
This is way I deserialise with this utility function
void copyPropertiesAndChildsKeepingDirectChildren(ValueTree& target, const ValueTree& source, UndoManager* undo)
{
jassert(target.getType() == source.getType());
jassert(target.getNumChildren() == source.getNumChildren());
for (auto i = 0; i < target.getNumChildren(); ++i)
{
jassert(target.getChild(i).getType() == source.getChild(i).getType());
target.getChild(i).copyPropertiesAndChildrenFrom(source.getChild(i), undo);
}
target.copyPropertiesFrom(source, undo);
}
This is probably necessary if you have a structure like:
<rendering>
<tracks>
<track/>
<track/>
...
</tracks>
</rendering>
and you are observing the value tree tracks for creation and deletion of child objects in all of your various UI elements, but you don’t actually monitor rendering for the complete deletion of the tracks object, as that wouldn’t make much sense from a sementical standpoint anyway.
thank you for your post. My issue happens before I get to use copyPropertiesAndChildrenFrom(), you raise a good point there, and it’s what I’m trying to avoid by keeping my ever-live tree where I can edit something in a child and be present in parent. It’s something that I don’t have right now, this code should demonstrate it better.
valueTreePropertyChanged will be called when m_testChild.setProperty runs but not when treeCopy.setProperty runs. isReallyChildOf will be true, so treeCopy knows who it’s parent is, but m_valueTree has lost track of it, I had assumed because it’s been retrieved by value.
I’ve yet to fully debug it, I assume although it’s returned by value it’s simply a wrapper for dynamic memory that has gone through an appropriate copy. It just doesn’t seem to do what I thought it would.
m_testChild = { "TestChild", {{"blah", 0.5f}, {"Width", getWidth()}, {"Height", getHeight()}} };
m_valueTree.appendChild(m_testChild, nullptr);
ValueTree treeCopy = m_valueTree.getChildWithName("TestChild");
m_valueTree.addListener(this);
m_testChild.setProperty("blah", 0.6f, nullptr);//runs valueTreePropertyChanged
treeCopy.setProperty("blah", 0.6f, nullptr); //fails to run valueTreePropertyChanged
bool isReallyChildOf = treeCopy.isAChildOf(m_valueTree);
static int i = 0;
++i;
}
void CrocTabAssemblyComponent::valueTreePropertyChanged(ValueTree& treeWhosePropertyHasChanged, const Identifier& property)
{
Oh I see, it was only after I pasted the code that I see the problem, they’re both setting to the same value. juce library code is clever enough to know that no property setting really took place.
Crisis averted.