SimpleController for Environments/Events

hey there,

i would like to track three different actions for an evironment of a Tdef. i would like a function to be evaluated every time:

  • a key/value pair has been added
  • a key/value pair has been removed
  • a value has changed

is this possible with SimpleController?

all best,
moritz

Not to sure about SimpleController, haven’t found anyone who uses the MVC stuff.
However, since you are already in an Event, why not just extend it.
Below is a pretty basic implementation that gives you a onKeyChanged, onAnyChanged,
onKeyRemoved, and onKeyChangedDefault. You do, however, have to call ~set.(\someKey, "SomeValue") when you want to change something.

~onAnyChangedStore /* : FunctionList(Environment, ChangedKeySymbol, Value) */ = FunctionList();
~onKeyChangedStore /* : Event[KeySymbol -> FunctionList(Value)] */ = ();
~onKeyRemovedStore /* : Event[KeySymbol -> FunctionList()] */ = ();

~onAnyChanged = {|func|	currentEnvironment[\onAnyChangedStore].addFunc(func) };
~onKeyChanged = {|key, func|
	var f_list_safe = currentEnvironment[\onKeyChangedStore][key.asSymbol] ?? {
		currentEnvironment[\onKeyChangedStore][key.asSymbol] = FunctionList();
		currentEnvironment[\onKeyChangedStore][key.asSymbol]
	};
	f_list_safe.addFunc(func);
};
~onKeyChangedDefault = {|key, default, func|
	var f_list = currentEnvironment[\onKeyChanged].(key, func);
	f_list.array.last.(default);
};
~onKeyRemoved = { |key, func|
	var f_list_safe = currentEnvironment[\onKeyRemovedStore][key.asSymbol] ?? {
		currentEnvironment[\onKeyRemovedStore][key.asSymbol] = FunctionList();
		currentEnvironment[\onKeyRemovedStore][key.asSymbol]
	};
	f_list_safe.addFunc(func);
};

~set_ = {
	|k, v|
	if(currentEnvironment[k.asSymbol] != v, {
		currentEnvironment[k.asSymbol] = v;
		currentEnvironment[\onAnyChangedStore].(currentEnvironment, k.asSymbol, currentEnvironment[k.asSymbol]);
		currentEnvironment[\onKeyChangedStore][k.asSymbol].(currentEnvironment[k.asSymbol]);
	});
	currentEnvironment[k.asSymbol]
};
~remove_ = {
	|k|
	currentEnvironment[k.asSymbol] = nil;
	currentEnvironment[\onAnyChangedStore].(currentEnvironment, k.asSymbol, currentEnvironment[k.asSymbol]);
	currentEnvironment[\onKeyChangedStore][k.asSymbol].(currentEnvironment[k.asSymbol]);
	currentEnvironment[\onKeyRemovedStore][k.asSymbol].();
};

~onAnyChanged.({|envir, key, value|
	postf("key: % changed to: %\n", key, value)
});
~onKeyChanged.(\a, { |value|
	postf("\a has changed to: %\n", value)
});
~onKeyChanged.(\b, { |value|
	postf("\b has changed to: %\n", value)
});

~onKeyChanged.(\c, { |value|
	postf("\c has changed to: %\n", value)
});

~onKeyRemoved.(\rm, {
	"rm was removed".postln;
});


~set_.(\a, 2)
~set_.(\b, 4)
~set_.(\c, 1)

~rm = "value" // do not do this.
~set_.(\rm, 2) // instead do this.

~onKeyChanged.(\rm, {|v| "rm changed".postln })
~set_.(\rm, 2) // instead do this.


~onKeyRemoved.(\rm, { "rm removed".postln })

~remove_.(\rm)
~rm

// is evaled when first defined with the default value.
~onKeyChangedDefault.(\c, "defaultValue", { |value|
	postf("\c has changed to: %\n", value)
});

However, to create this by default and make things easier, you should be able to do this…


~e = Environment.make{
	~onAnyChangedStore /* : FunctionList(Environment, ChangedKeySymbol, Value) */ = FunctionList();
	~onKeyChangedStore /* : Event[KeySymbol -> FunctionList(Value)] */ = ();
	~onKeyRemovedStore /* : Event[KeySymbol -> FunctionList()] */ = ();
	
	~onAnyChanged = {|self, func|	
		self[\onAnyChangedStore].addFunc(func) 
	};
	~onKeyChanged = {|self, key, func|
		var f_list_safe = self[\onKeyChangedStore][key.asSymbol] ?? {
			self[\onKeyChangedStore][key.asSymbol] = FunctionList();
			self[\onKeyChangedStore][key.asSymbol]
		};
		f_list_safe.addFunc(func);
	};
	~onKeyChangedDefault = {|self, key, default, func|
		var f_list = self[\onKeyChanged].(key, func);
		f_list.array.last.(default);
	};
	~onKeyRemoved = { |self, key, func|
		var f_list_safe = self[\onKeyRemovedStore][key.asSymbol] ?? {
			self[\onKeyRemovedStore][key.asSymbol] = FunctionList();
			self[\onKeyRemovedStore][key.asSymbol]
		};
		f_list_safe.addFunc(func);
	};
	
	~set_ = {
		|self, k, v|
		if(self[k.asSymbol] != v, {
			self[k.asSymbol] = v;
			self[\onAnyChangedStore].(self, k.asSymbol, self[k.asSymbol]);
			self[\onKeyChangedStore][k.asSymbol].(self[k.asSymbol]);
		});
		currentEnvironment[k.asSymbol]
	};
	~remove_ = {
		|self, k|
		self[k.asSymbol] = nil;
		self[\onAnyChangedStore].(currentEnvironment, k.asSymbol, currentEnvironment[k.asSymbol]);
		self[\onKeyChangedStore][k.asSymbol].(currentEnvironment[k.asSymbol]);
		self[\onKeyRemovedStore][k.asSymbol].();
	};
};

~e.know = true;

Then you can do this…

~e.onAnyChanged({|envir, key, value|
	postf("key: % changed to: %\n", key, value)
});

~e.onKeyChanged(\b, { |value|
	postf("B has changed to: %\n", value)
});
~e.set_(\a, 1)
~e.set_(\b, 20)

Hopefully that’s helpful?

1 Like

thank you so much for the elaborate reply @jordan, this is great!!! <:
after having a look at the EnvirGui source i now found another way of going about using SkipJack which is a little easier for my use-case as I have to use the envir of a Tdef.

(
Tdef(\y).set(\a, 12);
Tdef(\y).set(\c, 15);
Tdef(\y).set(\b, 201);
)

Tdef(\y).envir


(
var lastEnv = ();
var env = Tdef(\y).envir;
k.stop;
k = SkipJack({
	if(env != lastEnv, {
		lastEnv.keysValuesDo({|k,v| if(env[k].isNil, {"key % removed".format(k).postln})});
		env.keysValuesDo({|k,v|
			if(lastEnv[k].isNil, {"key % added".format(k).postln}, {
				if(v != lastEnv[k], {"key % changed to %".format(k,v).postln})
			})
		});
		lastEnv = env.copy; 
	})
}, 0.1);
)


Tdef(\y).set(\b, 21)
Tdef(\y).set(\x, 30)
Tdef(\y).envir.removeAt(\x)

Tdef(\y).envir

edit: running the function in the background should be acceptable for my performance needs (:

I don’t have time to check whether this applies to removeAt, but EnvironmentRedirect calls a user function when anything is put into it. This covers “added” and “changed” for sure.

So you could set the function to do myEnvir.changed(\put, key, value) (up to you to check the function arguments) and then it’s available to SimpleController.

EDIT: Unfortunately there’s currently no dispatch for removeAt, so EnvironmentRedirect is at best inconsistent. I’ve filed an issue report.

hjh