Feature request: ValueTree::setPropertyExcludingAllListeners()

ValueTree has a very useful method setPropertyExcludingListener() which enables a specific listener to be excluded from update notifications.

However, sometimes we want to set a piece of state and have none of the listeners notified. It would therefore be useful to either be able to exclude all listeners, or pass in a list of listeners to be excluded.

Yes, I would argue that ValueTree::setPropertyExcludingAllListeners() would be good for modularity/encapsulation, as one object doesn’t necessarily know all the other Listeners that have been added to a ValueTree.

1 Like

And, sorry to piggyback on this FR, but while changes were being made, would it be possible to have these as well?

ValueTree::addChildExcludingAllListeners()
ValueTree::removeChildExcludingAllListeners()

3 Likes

yeah, excluding a listener or a list of listeners, or even all listeners with addchild and set property would make my days SOOOO much better.

1 Like

Can someone explain a good use case for this?

Personally, I don’t think it makes sense to provide a listener mechanism where one object can prevent other listeners from receiving the callbacks they’re expecting, especially when those objects don’t necessarily know about each other.

How would that look as an API? “Hey you can register as a listener to this object and sometimes be notified about changes but also sometimes not, kinda depends whether these other guys want you to be notified. Oh no sorry, I can’t tell you who stopped you getting these callbacks, that’s a secret.”

The intent of the existing excludingListener method is to allow an object that listens to a tree to set a property on the tree without being notified of the change itself - otherwise you can get stuck in infinite loops. The suggestion to add a method to allow for multiple listeners to be excluded is not so bad because the caller will at least know what objects it’s blocking, however a method to exclude all listeners completely defeats the purpose of the listener/observer pattern.

2 Likes

Seems that really what you’d want to do is take a deep copy of the tree being modified, set the new property on that, then you can copy it back to the original tree when you’re ready.

You could wrap that up in a free function, then your listener mechanism still works as intended and everybody’s happy.

1 Like

I’m not sure, but I believe you can set a property without notifying listeners by accessing the tree property [“”] tree[identifier] = newvalue. And there is a ton of use case where you don’t want to notify at all. As example, imagine you have an object that is costly to update, and you know that this object always requires many properties to update, instead of updating at each value tree change received, you can make it update when you want. I don’t think that getting that little bit more of control is a bad thing.

That doesn’t sound like good architecture to me - sounds like those objects are too tightly coupled.

The costly object should either decide for itself when’s best to respond to the value changes, or you should use some sort of events system so the object writing the properties can send an alert when it’s finished. Or maybe you can split up the costly object into multiple units each handling a single property to avoid repeatedly doing one big, costly operation when any of the properties changes.

I’d say that this is the perfect use case for the AsyncUpdater.

Let your object that’s costly to update inherit from AsyncUpdater (as well as being a ValuetreeListener). Then when the valueTreePropertyChanged callback is triggered by changes in your objects parameters, you do a call to AsyncUpdater::triggerAsyncUpdate(). These triggerAsyncUpdate:s will be coalesced into one single call to AsyncUpdater::handleAsyncUpdate() in which you do your costly update.

Meaning, regardless if you update one or ten properties of the costly object (at the same time and place in the code, that is) it will only trigger one costly update. And the undomanagement will work too…

sounds about right

:raised_hands:

Maybe a small example to lighten things up…

#include "JuceHeader.h"

#define IDENTIFIER(a) const Identifier a(#a);

namespace ID
{
	IDENTIFIER(bw)
	IDENTIFIER(qFactor)
	IDENTIFIER(speed)
	IDENTIFIER(feedback)
	IDENTIFIER(gain)
	IDENTIFIER(freq)
	IDENTIFIER(complexObject)
}

UndoManager undomanager;

class ComplexObject : public AsyncUpdater, ValueTree::Listener
{
public:
	ComplexObject()
	: valueTree(ID::complexObject)
	, bandWidth(valueTree, ID::bw, &undomanager, 100.0f)
	, qFactor(valueTree, ID::qFactor, &undomanager, 0.5f)
	, gain(valueTree, ID::gain, &undomanager, 1.0f)
	, speed(valueTree, ID::speed, &undomanager, 10.0f)
	, freq(valueTree, ID::freq, &undomanager, 1000.0f)
	, feedback(valueTree, ID::feedback, &undomanager, 0.25f)
	// etc...
	{
		valueTree.addListener(this);
	}

	void handleAsyncUpdate() override
	{
		//do costly complex updates to the complex object 
	}

	void valueTreePropertyChanged(ValueTree& valueTree, const Identifier& property) override
	{
		if (valueTree.getType() == ID::complexObject)
			triggerAsyncUpdate();	//all these calls will coalesce into one call of handleAsyncUpdate
	}

	void doSomeFunnyUpdates()
	{
		/*do updates to one, a few or all parameters => will trigger only one call to 
		* handleAsyncUpdate() i.e rebuild of the complex object
		* */

		undomanager.beginNewTransaction("Changed the complex object");

		bandWidth = Random::getSystemRandom().nextFloat();
		qFactor = Random::getSystemRandom().nextFloat();
		speed = Random::getSystemRandom().nextFloat();
		//... optionally fill up w/ more
	}

private:

	ValueTree valueTree;

	CachedValue<float> bandWidth;
	CachedValue<float> qFactor;
	CachedValue<float> gain;
	CachedValue<float> speed;
	CachedValue<float> freq;
	CachedValue<float> feedback;
	//etc

};

#undef IDENTIFIER