Help understanding addDependant

I am trying to build a UI with proper decoupling of values and UI elements. I am running into some problems trying to understand the behaviour of addDependant and .change. Basically everything works fine if I update all UI elements every time one value has changed, but I cannot find a way to only update one UI element at a time. Here is first updating all UI elements every time, works as expected:

(
// event with 2 values, view with 2 values
~event = (val: 10, text: \sk);
v = View().layout_(
	HLayout(
		NumberBox(),
		StaticText().string_(\init)
	)
).front.alwaysOnTop_(true);
~updateEvent = {
	v.children[0].value = ~event[\val];
	v.children[1].string = ~event[\text]
};
~event.doKeys{|key|~event.key.release}; // remove old dependencies
~event.addDependant(~updateEvent);
)

~event.changed(\value) // set initial values

(
// update event - works as expected: the ui is updated
~event[\val] = 9;
~event[\text] = \sp;
~event.changed(\value)
)

Now trying to update one UI view at a time, this does work as expected for me:

(
~event = (val: 10, text: \sk);
v = View().layout_(
	HLayout(
		NumberBox(),
		StaticText().string_(\init)
	)
).front.alwaysOnTop_(true);
~updateEventVal = { v.children[0].value = ~event[\val] };
~updateEventText = { v.children[1].string = ~event[\text] };
~event[\val].release; // remove previous dependencies 
~event[\text].release; // remove previous dependencies
~event[\val].addDependant(~updateEventVal);
~event[\text].addDependant(~updateEventText);
)

(
// init - works as expected
~event[\val].changed(\value);
~event[\text].changed(\value);
~event[\val].dependants.debug(\valDependants);
~event[\text].dependants.debug(\textDependants)
)

(
// updating now fails
~event[\val] = 9;
~event[\text] = \sp;
~event[\val].changed(\value);
~event[\text].changed(\value);
)

~event[\val].dependants // now empty = no dependants
~event[\text].dependants // now empty = no dependants

What am I doing wrong?

Also, if a view consists of 40 elements and SC is asked to update all of them and only one of them has changed, is SC smart about not wasting resources setting the 39 that has not changed?

First, don’t add the dependent to the specific value. Your initial idea, to add the dependent to the event, was better.

Then, in the event:

~event = (
    val: 10,
    // basically the same as a setter in a class
    val_: { |self, val|
        self[\val] = val;  // note: do NOT write self.val = val here!!
        self.changed(\val, val);
    },
    ...
);

~watcher = SimpleController(~event)
.put(\val, { |obj, what, val|
    defer { ... gui update here ... }
})
... and put in more keys...
;

Now, when val updates, it will call only the val update function, which would update only the relevant widget(s) and not touch any others.

hjh

Thanks James, I still don’t understand how I call the SimpleController function by changing the event or should I use another method for changing the event?

(
~v = NumberBox().front.alwaysOnTop_(true);
~event = (
    val: 10,
    val_: { |self, val|
        self[\val] = val;  // note: do NOT write self.val = val here!!
        self.changed(\val, val);
    },
);

~watcher = SimpleController(~event)
.put(\val, { |obj, what, val|
	defer { ~v.value = val }
})
)

~event[\val] = 42 // no update to NumberBox

This bit creates a (pseudo-)setter method… so, use the setter method.

~event.val = 42;

// or
~event.val_(42);

hjh

1 Like

I am reviving this thread for a minute with some follow up questions.

I created the ~addSetter func and it works as intended, but only when assigning one value at a time. Is there a way to iterate over the event and report updates at the same time? Easier to demonstrate than to describe:

(
~addSetter = {|e, par = \par, val = 10|
	e.add(par -> val);
	
	e.add((par++\_).asSymbol -> { |self, val|
		self[par] = val;
		self.changed(par, val);
	});
};

~init = (val1: 1, val2: 5);
~e = ();
~init.pairsDo{|a, b| ~addSetter.(~e, a, b)};
SimpleController(~e).put(\val1, {
	\val1Update.postln
});
)

// works
~e.val1 = 5

// When iterating, no update is sent
~init.pairsDo{|a, b| ~e.a = 3}; // or
~e.keysDo{|key|~e.key = 2}; 
// really what I need is this:
~init.pairsDo{|a, b| ~e.a = b }

// ...but setter still works when asigning one value at a time

~e.val1 = 3

This is always setting a parameter named \a.

.methodName syntax always means to look up the method literally named methodName. It never means “look up the method name in a variable.”

Note that this isn’t only a limitation of events. If you have an object like:

TestObject {
	var <>a, <>b, <>c;
}

… you still can’t do:

o = TestObject.new;

[a: 1, b: 2, c: 3].pairsDo { |key, value|
	o.key = value
};

… because key is a literal method name.

perform allows the method name to be an expression:

~init.pairsDo { |a, b| ~e.perform(a.asSetter, b) }

hjh

2 Likes

Thanks, this was a nice method to learn! I am almost there, I managed to decouple all GUI elements. The last piece of the puzzle is to remove dependancies. So if you have something like:

e = (val1: 0, val2: 2, val3: 7)
e.keysDo{|n|SimpleController(e).put(n, {|obj, what, val| [obj, what, val].postln }) }
e.dependants // IdentitySet[ a SimpleController, a SimpleController, a SimpleController ]

How do you remove the dependants of e?

Ideally they should remove themselves: when the model gets freed, it broadcasts a message like \didFree and then the dependant responds to this by removing itself.

But in a pinch, you can also do object.releaseDependants – a nuclear option (blows away all dependants whether it’s “correct” to do so or not).

hjh

Thanks, another very useful method to know. What I am hoping to accomplish is

  • The event ‘e’ is part of the main code.
  • when the GUI is opened and connected, it both reflects values of individual keys of ‘e’ and is able to change the values of ‘e’ (this I already have).
  • when the GUI window is closed, the main code can still run headless, so all SimpleControllers should be removed (I am not there yet)
  • The GUI can be opened and closed any number of times without affecting the main code

I am struggling with the syntax of removing individual entries from SimpleController(e). I thought this would work, but it doesn’t

(
e = (val1: 9);
SimpleController(e).put(\val1, { |obj, what, val| val.debug(\val1) } );
e.val1_(3).changed(\val1, e.val1)
)

SimpleController(e).remove(\val1);
e.val1_(6).changed(\val1, e.val1); // still there
e.dependants

Ideally they should remove themselves: when the model gets freed, it broadcasts a message like \didFree and then the dependant responds to this by removing itself.

Is there a way to free the whole model, short of resetting ‘e’ , like e = ()…repopulate e? Or should I, once I get the syntax right, remove all SimpleControllers one by one by iterating over the relevant keys?