You may have a point there, but there’s no clock involved in your example. Merely picking the output of a stream (or not) by calling it (or not) is what PauseStream used to do before 2012. The complicated issue is if your Routines also try to hang on a Condition or use a Semaphore, which doesn’t happen in your example. threadPlayer
was added for the latter.
(
var fva = FlowVar.new ! 5;
var ra = fva.collect { |r, i| Routine {
var v;
"Routine % starts".format(i).postln;
v = fva[i].value;
"Routine % got its value % to work with".format(i, v).postln;
v.yield;
}};
~ra = ra;
~selectStream = Pswitch1(ra, Pwhite(0, 4, inf)).asStream;
~fva = fva;
)
~selectStream.next
// Routine 1 starts (or some other of them)
// -> hang
So now Pswitch1 needs to understand \hangs
, i.e. know something about how synchronization primitive in SC cross-talk with routine value-return method (yield).
~fva.do { |fv, i| ~fva[i].value = i }
// this does work past that, once the real values are returned
~selectStream.nextN(5)
// prints something like
// Routine 0 starts
// Routine 2 starts
// Routine 2 got its value 2 to work with
// -> [ hang, hang, 2, nil, nil ]
Using synchronization “primitives” inside Routines that go into streams is not at all transparent at the moment, in terms of values returned, i.e. they are a super-leaky abstraction as currently implemented.
Let’s try to put Pswitch1 on a clock. Will it do the right thing, meaning eventually wake up when all the flovars are filled? Probably not.
~ra do: _.reset
AppClock.play(~selectStream)
// Routine 4 starts (e.g.)
~fva.do { |fv, i| ~fva[i].value = i }
// Nothing gets resumed
I see that Pswitch1 turns its output into nils after it sees any any nil on its inputs. But
even if change the routines to never return any nil, it still doesn’t work as one might hope
(
~fva = FlowVar.new ! 5;
~ra = ~fva.collect { |r, i| Routine {
var v;
"Routine % starts".format(i).postln;
v = ~fva[i].value + 0.1; // avoid zeroes "just in case".
"Routine % got its value % to work with".format(i, v).postln;
v.alwaysYield; // changed so no nils returned, "just in case..."
}};
~selectStream = Pswitch1(~ra, Pwhite(0, 4, inf)).asStream;
)
AppClock.play(~selectStream)
// Routine 0 starts
~fva.do { |fv, i| ~fva[i].value = i }
// Nothing gets resumed; Pswitch1 is emitting 4 more hangs to the clock.
4 do: { AppClock.play(~selectStream) }
// Routine 4 starts
// Routine 2 starts
// Routine 1 starts
// Routine 0 got its value 0.1 to work with
// Routine 3 starts
And that happens despite the fact that the FlowVars are all filled and so enabled all the routines to execute. Pswitch1 is basically “looking into the past” when the Routines were waiting on Conditions (embedded in the FlowVars) even after the routines are not doing that anymore, because Pswitch1 doesn’t understand anything about synchronization “primitives” (or rather protocols) in SC.
And it makes zero difference if you do
~selectStream.play
instead, because that’s also from Stream, not the smarty version that something like PauseStream overrides, and which a true synchronization-aware StreamSwitcher would also have to do.