A Pfset with closure

After struggling with the existing machinery, here’s my first slightly non-trivial improvement.

PfsetC is a Pfset with closure. Instead of passing in an additional function
for cleanup, the cleanup function is whatever the make/init function
returns. In contrast, this returned value is just ignored by plain Pfset. My
modification allows closure capture of “stuff” (resources) created by the
init/make function in init/make function-level variables, and so side steps
the mess of having to set up some other communication method between the
make/init and cleanup functions which is otherwise open to quite a few race
conditions if one is not very careful. For example, resetting the stream
before it finishes calls the make/init function twice in a row, without the
cleanup function being called in between (that’s the standard
EventStreamPlayer reset behavior). If one creates a closure for every
init/make invocation, which happens automatically with PfsetC then the “right”
cleanup is called for each init automatically, as long as you “save” (a
handle) to the resources created in a init/make function-level variable.
Prototypical usage idea

PfsetC({
	var saved = makemyresouce();
	~useresource = saved; // pass to event
	{ cleanunpmyresource(saved) } // cleanup function
}, pattern)

It’s essential the last statement (return value) of the init/make function be
itself a function, so its execution is actually deferred to cleanup
time!

Concrete example, clean up a group by first releasing “softly” it
and then freeing after a bit more of time.

p = Pbind(\dur, Pseq([1, 2, inf]));
(r = PfsetC({var grp = Group(); ~group = grp;
	{ grp.release.postln; fork { 3.wait; grp.free }}}, p).play);
r.reset; // /hit a few time before it reaches the infinite event
r.stop; // several groups freed.

By the way, this is somewhat similar to the Plazy { init; return-pattern } idiom, except in this (PfsetC) case, what is returned (from the init function) is the cleanup function rather than the pattern, which is passed “on the side” in PfsetC. With Plazy you can do almost the same thing by explicitly messing with the addToCleanup event message

p = Pbind(\dur, Pseq([1, 2, inf]));
(r = Plazy({ var grp = Group(); p <> (group: grp, addToCleanup:
	{  grp.release.postln; fork { 3.wait; grp.free }})}).trace.play)

The difference is that with PfsetC (as with Pfset), you only get a one-shot addToCleanup message; the Plazy “simulacre” will “spam” an addToCleanup on every event, which is usually not a problem because the cleanup is stored in an IdentitySet.

It’s actually possible to do a “proper” PfsetC equivalent by combining Plazy with the plain old Pfset, as follows: we’ll return from Plazy a Pfset that has a cleanup set via its last argument and an init that does event setting (so we can dispense with the Pchain from the previous example), but we must do the actual resource init “a layer above”, i.e in Plazy, in order for the proper closure to be formed:

p = Pbind(\dur, Pseq([1, 2, inf]));
(r = Plazy({ var grp = Group(); Pfset({~group = grp}, p,
	{grp.release.postln; fork { 3.wait; grp.free }})}).trace.play);

Since I think a non-trivial portion of SC users would have trouble discovering this combo by themselves, I think PfsetC is somewhat useful…

You can Download PfsetC from

1 Like

That’s a really good idea! Having init and cleanup in the same function scope avoids a lot of problems.

With some documentation, this would be a good addition to SC.

hjh

1 Like