Reference to current routine?

I’m trying to do something that essentially boils down to this:

{
	20.do {
		var synth = {SinOsc.ar(1.0.rand.linexp(0, 1, 440, 880), mul:0.1)}.play;
		synth.onFree(thisRoutine.stop);
		1.0.yield;
	}
}.fork

I’m starting a process that starts a bunch of synths, but I’d like it if, whenever any one of those synths is freed, it stops the process of creating more of them.

So it feels like I need a reference to the current routine. But thisRoutine doesn’t exist. Is there some way of getting the current routine?

thisThread does.

Note that for Tasks, you may need thisThread.threadPlayer (but your given example doesn’t use Tasks).

hjh

There’s a separate problem here, which is: your Routine is paused when the onFree function is triggered. That doesn’t get called in the context of your Routine, so even if there WAS a thisRoutine, you wouldn’t be inside of it.

@jamshark is right to mention Tasks - if you ran the above as a Task, then you just need to keep track of the task and stop it in the onFree action.

You can also do this by just checking all your synths to see if they’ve stopped running - the onFree action is handled implicitly when you run synth.register.

(
{
    var synths;
    
    while { synths.select({ |n| n.isPlaying.not }).size == 0 } {
        var synth = { SinOsc.ar(1.0.rand.linexp(0, 1, 440, 880), mul:0.1)}.play;
        synth.register(assumePlaying:true); // ensures isPlaying is set to false when the synth ends
        synths = synths.add(synth); // add it to the list
        1.wait;
    }
}.fork
)

This keeps track of the synths you run, calls register to ensure that their isPlaying property is updated (e.g. if they are stopped it becomes false), and then just loops until the list of not-playing synths is not zero (this is ofc not efficient if you have thousands of synths, but the code is simple and reliable, and works entirely within your Routine).

IF you are dealing with synths that have a very random or hard-to-calculate duration, e.g. envelope times are random, DetectSilence, or they only stop when triggered in some way, then you probably have to do the onFree / isPlaying detection here. But, if you basically know how long your synths will last (e.g. you are passing them a duration parameters), then its easier and probably more reliable to just do this from the language side.

For example, another version:

(
    fork {
        var endTime = inf;
        while { thisThread.clock.seconds > endTime } {
            var duration = rrand(5, 10);
            var synth = { SinOsc.ar(100) * Env.sine(duration).kr(doneAction:2) };
            endTime = min(endTime, thisThread.clock.seconds + duration); // you want to end when the FIRST synth ends, e.g. the soonest possible end time of a synth
        }
    }
)

If you end up having to add more logic to your routine (e.g. all Synths should actually have a long reverb tail, but we should stop the pattern when only the sustain part of the synth ends), this will be much easier.

thisThread.threadPlayer is not specifically about Task, though!

thisThread returns the current Routine. thisThread.threadPlayer returns the Object (Routine, Task, etc.) that is currently played on a clock. These are two are not necessarily the same.

You can totally use thisThread.threadPlayer to stop a currently playing Routine from the outside.

Similarly, CondVar internally uses thisThread.threadPlayer to store the currently playing thread in a list before yielding, so it can be resumed later.

Here’s an example:

(
fork {
	var thread = thisThread.threadPlayer;
	
	OSCFunc({
		"stop thread".postln;
		thread.stop;
	}, '/stop').oneShot;
	
	1000.do { |i|
		i.postln;
		1.wait;
	}
}
)

// stop the playing Routine above
NetAddr.localAddr.sendMsg('/stop');

(
// 'threadPlayer' ensures that this also works with nested or wrapped Routines!
fork {
	var inner = Routine {
		var thread = thisThread.threadPlayer;
		
		OSCFunc({
			"stop thread".postln;
			thread.stop;
		}, '/stop').oneShot;
		
		1000.do { |i|
			i.postln;
			1.wait;
		}
	};
	
	loop {
		var result = inner.next;
		result .wait;
	}
}
)

// stop the playing Routine above
NetAddr.localAddr.sendMsg('/stop');

If you look at the implementation of Routine#-threadPlayer, you will see that it recursively walks up the parent chain until either

  1. the Routine has an associated thread player (e.g. a Task)
  2. the Routine has no parent or the parent is equal to thisProcess.mainThread

Task is a wrapper around Routine and – unlike Routine – always assumes to be played on a clock, that’s why it overrides the threadPlayer method to return this. At the same time, it sets the threadPlayer member of the wrapped Routine to this so that thisThread.threadPlayer (in that or any inner Routine) will return the playing Task.

To summarize then –

Scztt was correct to point out that thisThread won’t work in your case, because the onFree function runs outside of the routine. (I missed that.) Also the recommendations about while loops are on point, for a task that should run until xxx condition fails.

If you’d like to use a thread reference, it’s likely to be more reliable to save your own reference than to rely on magic variables. (The magic variable thisThread is necessary in the class library, where some methods need to access properties of the currently running thread.) There is a part of your code that knows which Routine to stop – the part that’s calling fork:

(
var thread;

thread = {
	20.do {
		var synth = {SinOsc.ar(1.0.rand.linexp(0, 1, 440, 880), mul:0.1)}.play;
		synth.onFree { thread.stop };
		1.0.yield;
	}
}.fork;
)

… for a least-intrusive edit.

Task is the reason why threadPlayer was introduced, though – “CondVar internally uses thisThread.threadPlayer to store the currently playing thread in a list” precisely because if it didn’t, there would be no way to signal a Task and thus no way to control it through the Task interface after it was paused and signaled. (The online documentation, from 3.12.2, still says Task doesn’t work with Condition [and by extension, CondVar]. threadPlayer is the fix for that.)

For the “parent routine” case, when possible I’d prefer to keep the reference myself, because I know which Routine I’d like to stop, and there’s a clear part of the code that has access to it. Better to store the reference explicitly so that readers can see the intention in the code.

E.g. here:

As noted, you’re not necessarily sure how far up the chain the threadPlayer will be, so, in the specific case, I think it may be clearer to grab it yourself:

(
var threadToBeStoppedLater = fork {
	var inner = Routine {

Or even:

(
fork {
	var threadToBeStoppedLater = thisThread;
	var inner = Routine {

(FWIW in that example, I’d also avoid mixing the Routine-as-scheduled-action idiom with the Routine-as-data-stream idiom – personal opinion, no problem if others disagree, but IMO the inner routine is yielding data, not waiting, and there’s not a strong reason to put the OSCFunc inside it. Clarifying the code also removes the need for threadPlayer then :wink: )

hjh

Scztt was correct to point out that thisThread won’t work in your case, because the onFree function runs outside of the routine. (I missed that.)

You just need to capture thisThread.threadPlayer in the enclosing function, just like I do in my example.

synth.onFree(thread.stop);

Shouldn’t this be synth.onFree { thread.stop }?

That’s interesting to know. I just wanted to point that it’s not specific to Task because it also applies to nested Routines.

As noted, you’re not necessarily sure how far up the chain the threadPlayer will be, so, in the specific case, I think it may be clearer to grab it yourself:

I’m not sure I understand that part. threadPlayer should always return the currently playing Routine/Task. There can be multiple Routines in the chain, but there is only ever a single thread player.

(FWIW in that example, I’d also avoid mixing the Routine-as-scheduled-action idiom with the Routine-as-data-stream idiom – personal opinion, no problem if others disagree, but IMO the inner routine is yielding data, not waiting, and there’s not a strong reason to put the OSCFunc inside it.

Yeah, that was just a minimal example :slight_smile: You can also use nested Routines for control flow, though, so it’s not only about scheduling VS data streams.

If you want to just stop the currently playing thread, then thisThread.threadPlayer seems perfectly fine to me.

That being said, I would agree that it’s probably better to be explicit. It might be worth pointing out that thisThread.threadPlayer doesn’t help you if you also want to stop the Routine that started the currently playing Routine. I’ve seen this come up in the forum. There is currently no facility in the Class Library to automatically stop a whole “tree” of Routines, neither from the top nor from the bottom.

Yes – bad copy/paste. I’ll edit above for clarity.

Maybe I’m too conservative about it, but I tend to think it would be difficult for the class library to anticipate a wide variety of user designs for thread management – this may be one of those jobs where, if you want it done right, do it yourself :wink:

hjh

I agree! Still I wish there was a convenient way to operate on a group of Tasks. Maybe some kind of TaskGroup class. Two common operations that come to my mind:

  1. stop all Tasks
  2. wait for all Tasks to finish

(1. and 2. may also be combined: stop all tasks and wait for them to finish)

Task actually notifies its dependants when it stops playing and this may be used to automatically remove it from the task list. Shouldn’t be too hard to build.

Off-topic side note

I have written my own task scheduler in Lua for my game engine. One typical use case I found is that I want all currently scheduled tasks to automatically stop when the initiating game object dies. Since I’m in full control of the system, I just decided that scheduled tasks can have an associated game object and the scheduler checks whether the object is still alive before it executes such task. This way I get automatic task cancellation without any explicit user code. That’s the beauty of hand-crafted systems :slight_smile:

The way I did it once upon a time was via the Command design pattern. Instead of starting a Task or Pattern or process object directly, a sequencer object would initiate a Command that’s responsible for tracking its target’s state. Stopping the top level sequencer would cascade through any active Commands (including nested commands) and stop them. It worked pretty well but I never documented it (oops).

hjh

1 Like

I forgot to reply to any of these, but just wanted to let you all know that not only did you provide exactly the information I was looking for, he also provided a bunch of extra useful information too. So thanks!