Oh, I see you’re actually making the forked Routine do the postln
work of the REPL
Routine { // 'nowExecutingPath' will be in force within the Routine btw
res = func.value;
codeDump.value(code, res, func, this);
("-> " ++ res).postln;
}.play(AppClock);
thisProcess.nowExecutingPath = nil;
So no thread joining needed , but…
I don’t understand why thisProcess.nowExecutingPath = nil;
doesn’t actually exec before the Routine starts (so before the func
). I mean you somehow avoid the race, since I’ve actually ran your extension to check that, but I don’t understand why it works like that with respect to thisProcess.nowExecutingPath
.
I mean if I do this instead:
Routine { // 'nowExecutingPath' will be in force within the Routine btw
res = func.value;
codeDump.value(code, res, func, this);
("-> " ++ res).postln;
}.play(AppClock);
"Setting nowExecutingPath to nil".postln;
thisProcess.nowExecutingPath = nil;
And try it from a save file like
thisProcess.nowExecutingPath
it prints the messages in the order in which I think the execution happens, namely:
Setting nowExecutingPath to nil
-> M:/sc/interphack.scd
So I don’t understand why the routine didn’t see nowExecutingPath
as nil, even thought it clearly executed later.
Actually, I suspect I know what probably happens. Routine
probably copies the state of nowExecutingPath
as it was when it was constructed.
The Thread class has a field for that (in fact has two)
var <executingPath, <oldExecutingPath;
I’d have to look at _Thread_Init
to confirm the copying happens.
Yeah this is where the copy happens.
void initPyrThread( // ...
slotCopy(&thread->executingPath, &g->process->nowExecutingPath);
It also must be the case that switchToThread
does the opposite, i.e.
sets nowExecutingPath
from the thread’s own executingPath
. Yeah the copy in the opposite direction happens in prRoutineResume at the fabled line number 3333 .
There’s a wee bit of an issue if you do from REPL
\foo.yield
with this approach. There’s no error, but nothing is printed of course, since the routine just “hanged”, i.e. was de-scheduled and there’s no way to unhang it (i.e. reschedule it) since there’s no reference to it anywhere. It’s more of an issue if you type
s.sync
as that behaves the same way if there’s no server running. If there is one however, it will work and print
-> localhost
Something like
3.yield
also has a bit of a funny effect, that will execute something in the future, but that’s perhaps more of a feature… because something like
(
"hey".postln;
3.wait;
"later".postln;
)
actually works now as a newbie might expect. Also
v = FlowVar.new
v.value // hangs
v.value = 12 // resumes previously hanged and prints
works too with your patch. Albeit it’s slightly counterintuitive because it will print two things after the last line
-> a FlowVar
-> 12
But I think this is a decent price to pay for the simplicity of the solution.
Also something like
(
SynthDef(\zzxzxd, { Out.ar(0, 0.1 ! 2 * SinOsc.ar) }).add;
s.sync;
Synth(\zzxzxd)
)
works ok too if there is a server running because the whole parenthesized expression executes in the same routine, so there’s serial order ensured. But with no server, it will just hang and alas booting a server later doesn’t resuscitate it. So it’s not really a substitute for s.waitForBoot
, by itself. On the other hand,
(
SynthDef(\zzxzxd, { Out.ar(0, 0.1 ! 2 * SinOsc.ar) }).add;
s.bootSync;
Synth(\zzxzxd)
)
does work (with your patch applied) even when the server is not booted.
Given that it’s mostly an ok feature/approach, I guess it could be made optional whether the interpreter spawns a routine or not, i.e. have some classvar
boolean in Interpreter that picks the old or new behavior.