Abuse of DynamicObject in ValueTrees


#1

I’d appreciate some feedback on how disgusting a misuse of the technology this following approach is; ValueTree is still pretty new business, so there’s little in the way of precedent/example to work from.

WHAT

I am using a ValueTree to hold all the data in my UserDocument. This is lovely.

I also make use of a ‘NameRegister’ class (used to handle/intercept name clashes when creating/editing nodes).

HOWEVER, whilst I can easily have a ‘NameRegister’ in my UserDocument class to handle one set of names, I also need each child of a particular node to have its own corresponding NameRegister [i.e., only name clashes within a given node branch are forbidden].

In EASYWORDS, certain ValueTree nodes must have an associated non-ValueTree instance.

The obvious idea for implementing this was to have a mapping in the UserDocument class. There would be a ‘getNameRegisterForNode(ValueTree)’ function, which would pick out the right one when needed. This approach is a bit messy though; changes to these nodes would also have to be paired with some kind of fixup operation on that separate map. I don’t want to have to deal with that!

So, what I’ve been experimenting with is wrapping such associated ‘real objects’ in a DynamicObject, and storing them within the ValueTree itself. This means I can access them directly from the node, and not have to worry about keeping a separate list in sync. In my current case, they’re run-time-only objects, so they don’t need to be serialised (so it’s handy that they just disappear when saving), and it’s easy for me to just stick’em’in when loading it back in.

Here is the code I’ve been using for the job…

template <class ObjectType>
class DynamicObjectWrapper	:	public DynamicObject
{
public:

	typedef ReferenceCountedObjectPtr<DynamicObjectWrapper<ObjectType>> Ptr;

	DynamicObjectWrapper(ObjectType* objectToWrap)
		:	wrappedObject (objectToWrap)
	{
	}

	virtual ~DynamicObjectWrapper()
	{
	}

	static Ptr getFromValueTree (const ValueTree& node, const Identifier& propName)
	{
		DynamicObject* baseObject = node[propName].getObject();
		if (baseObject)
		{
			DynamicObjectWrapper<ObjectType>* obj = dynamic_cast<DynamicObjectWrapper<ObjectType>*>(baseObject);
			return obj;
		}
		return 0;
	}

	static ObjectType* getWrappedObjectFromValueTree (const ValueTree& node, const Identifier& propName)
	{
		Ptr p = getFromValueTree (node, propName);
		if (p.getObject())
		{
			return p->getWrappedObject();
		}
		return 0;
	}

	ObjectType* getWrappedObject ()
	{
		return wrappedObject;
	}

private:

	ScopedPointer<ObjectType> wrappedObject;
};

… so, in my example case, I have a typedef DynamicObjectWrapper WrappedNameRegister, which I use when my NameRegister lives inside a node.

NOW THE QUESTION IS: Should I be ashamed? Is this ‘Bad’?


#2

Never tried it myself, but… no… it’s not really too bad, probably. DynamicObjects are ref-counted so it should be safe. As long as you don’t do crazy stuff like have DynamicObjects that contain ValueTrees, it should all just work… I guess!


#3

I presume by “DynamicObjects that contain ValueTrees” you mean making some kind of bastard branch of actual data intended to be part of the document; if so, then - yes, that’s not what I’d be using it for at all! :slight_smile: [If it’s ‘tree data’, then it should just live in the tree as normal!]. However, I would assume there’s nothing inherently wrong with such objects holding ValueTrees, so long as that just happens to be how they hold their own information.

This idea is for things that aren’t actually part of the data, but essential to its proper manipulation; when an ‘operational structure’ must be linked to (and retrievable based on) a specific node.

I’d be interested to hear of any other approaches people may have used for such things.


#4

Yeah, I was just thinking that you’d be able to create recursive loops of valuetree->dynamicObject->valuetree that would make a bit of a mess…


#5

Hehe, since you’ve made me think about it, I have been wondering what would happen if I dared made the UserDocument a DynamicObject - one which would insert itself into the root node of it’s own ValueTree. Whilst it would be terrifying, it would also mean that the document information would be automatically accessible to any part of the program interested in any other part of the structure - they would need only ever be given a ValueTree node. That’s quite a fun prospect, despite the utterly irresponsible insanity of the idea.

I wonder if… fZZZZZTTTt-***%###¥$€______________________________________infiniteUniverseCollapseE0042


#6

hoho, well. taking this insanity to its logical conclusion, I have come up with the following nightmare. Just for fun.

Three templates (making dirty use of CRTP).
The first simply turns a derived object into ‘node utility’ - a reference-counted class which can live in a ValueTree node property.
The second is an extension of this, providing a means of storing a WeakReference to some other object in the same way.

The third… well. The third is a proper mad scientist affair. It contains a root ValueTree node, and has an ‘entangle’ function, which inserts a weak reference to itself within the root node. From anywhere else in the code, you can call MyRootClass::findEntangledRootObject(node), from any child node, and it will return the object which contains the tree - if it still exists.

I should be shot for this, but… well, it actually works! And is kinda fun.


// Mixer template for enabling a utility class to be embedded in a ValueTree node property.

template <class SubclassType>
class NodeUtility	:	public DynamicObject
{
public:

	typedef ReferenceCountedObjectPtr<SubclassType> Ptr;

	void attachUtilityToNode (ValueTree node, const Identifier& propertyName)
	{
		node.setProperty (propertyName, this, 0);
	}

	static Ptr getUtilityFromNode (const ValueTree& node, const Identifier& propertyName)
	{
		Ptr utility = 0;

		DynamicObject* baseObject = node[propertyName].getObject();
		if (baseObject != 0)
		{
			utility = dynamic_cast<SubclassType*>(baseObject);
		}
		return utility;
	}

};


// Template for creating a WeakReferences as a NodeUtility

template <class ObjectType>
class WeakReferenceNodeUtility	:	public NodeUtility<WeakReferenceNodeUtility<ObjectType>>
{
public:

	WeakReferenceNodeUtility (ObjectType* objectToReference)
	:	weakRef (objectToReference)
	{
	}

	static ObjectType* getReferencedObjectFromNode (const ValueTree& node, const Identifier& propertyName)
	{
		WeakReferenceNodeUtility::Ptr utility = getUtilityFromNode (node, propertyName);
		if (utility.getObject ())
		{
			return utility->getReferencedObject();
		}
		return 0;
	}
	
	ObjectType* getReferencedObject () const
	{
		return weakRef;
	}

private:

	WeakReference<ObjectType>	weakRef;
};



// Ridiculous piece of insanity, allowing a ValueTree to provide access to its container.

template <class ObjectType>
class EntangledRootObject
{
public:

	typedef WeakReferenceNodeUtility<ObjectType> ReferenceUtility;
	typedef WeakReference<ObjectType> Reference;

	virtual ~EntangledRootObject<ObjectType> ()
	{
		masterReference.clear ();
	}

	void entangle (const Identifier& entanglementPropertyId)
	{
		ReferenceUtility::Ptr refUtil = new ReferenceUtility(static_cast<ObjectType*>(this));
		rootNode.setProperty (entanglementPropertyId, refUtil.getObject(), NULL);
	}

	void untangle (const Identifier& entanglementPropertyId)
	{
		rootNode.removeProperty (entanglementPropertyId,NULL);
	}

	static ObjectType* findEntangledRootObject (const ValueTree& node, const Identifier& entanglementPropertyId)
	{
		ValueTree root (node);
		while (root.getParent().isValid())
		{
			root = root.getParent();
		}

		return ReferenceUtility::getReferencedObjectFromNode (root, entanglementPropertyId);
	}

	ValueTree getRootNode () const
	{
		return rootNode;
	}

	typename const WeakReference<ObjectType>::SharedRef& getWeakReference()
	{
		return masterReference (static_cast<ObjectType*>(this));
	}

protected:

	ValueTree rootNode;

private:

	typename Reference::Master masterReference;
};

#7

Using quantum physics metaphors to name a complicated process - excellent stuff!


#8

Hehe, it becomes clear that I’ve been reading “The fabric of the cosmos” recently :slight_smile:

This setup is actually proving to be very useful indeed. I’ve realised that another benefit is that these utilities can also be inserted into NamedValueSets; this means I can hook useful things directly into any Component without requiring a specific subclass.

Also, as devious as it is, I’m finding the EntangledDocument setup remarkably handy. I’ve made a base EntangledDocumentEditorComponent, which holds a local WeakReference to a document; the constructor needs only a ValueTree (from ANY depth within the structure), and it is able to magically extract the correct document container, with all the information it could need (e.g. Undo managers etc…). It’s quite liberating not having to keep passing around all these additional management references so the various panels can do the more mundane things properly. I would even go so far as to recommend it, such is the extent to which it has simplified things. Perhaps for that I should indeed be shot!

Regardless, these tricks have made my life a whole lot easier! :slight_smile:


#9

It’s a neat trick. I’ve been using a slightly more conventional trick in the new jucer by having a copy-by-value class that contains a ValueTree and a reference to the document object that it belongs to, and then passing those around instead of valuetrees.


#10

I was using a similar approach for a while, with a bunch of wrapper classes which also provided more traditional functions for accessing the data. However, i ended up going in a funny direction with it and it all got a bit clumsy after a while when I had multiple different wrappers for different levels of the (very complex) document structure. :slight_smile: I wanted to try something a little different, and see if I could boil everything down to just ValueTrees so I didn’t end up painting myself into another tricky corner. [I’m sure the approach id used could have been repaired, but i was sick of looking at that code and wanted to forget about it completely!]