Is there a way to make a Routine not "ignore" an environment `use`?

Contrast

~foo = 12
f = {~foo + 2}
(foo: 44).use { f.value } // -> 46

with

r = r { loop { yield (~foo + 2) } }
(foo: 44).use { r.value } // -> 14

Is there an external way (i.e. not changing the routine code) to make the routine r do the lookup like the function f does, i.e. not “ignore” the use?

The Routine has an Environment assigned:

{ loop { yield (~foo + 2) } }.r.dump

A common way to differentiate behaviour would be something like:

~foo = 12
f = { loop { yield (~foo + 2) } }
g = f.inEnvir

(foo: 44).use { r(f).value }
(foo: 44).use { r(g).value }

I see. So basically, there’s no way to change a Routine’s (Thread’s actually) environment reference “from the outside”, after it has been created? So the only solution is to “unwrap” the routine’s function and only create the Routine proper from the right/desired environment?

If that’s the case then there’s no solution for the equivalence I was seeking short of using “naked routine bodies” (i.e. functions that yield, everywhere)… I’m guessing that’s why embedInStream is done like this (and it’s also prone to infinite loops in Pn etc., if the inner pattern doesn’t yield.)

(Asde: I find it rather unpleasant that neither the help page of Routine, nor that of Thread mention environments at all, and so not a peep in there about this startup environment capture…)

You could try overwriting environment before calling next – it may be risky but you seem not to be afraid of trying things that devs hadn’t thought of before, so I don’t see why you need to stop at this boundary.

“But there’s no setter”… so, add one, as a private extension. To test things, you can do whatever you want. That doesn’t guarantee success or promise later integration, but as a proof of concept, nobody is stopping you.

It’s your choice whether you wish to focus your attention on what is wrong with SuperCollider, or on what you can do with it despite it being flawed.

hjh

Well, so I’ve done obvious hack and it worked 100% basically

+ Routine {
	envir {	^environment }
	envir_ { arg env; environment = env; ^this }
}

~foo = 3
r = r {  } 
r.envir // -> Environment[ (foo -> 3) ]
r = r { loop { ~foo.yield; } } 
r.next // -> 3
r.envir[\foo] = 4
r.next // -> 4
r.envir = (foo: 5)
r.next // -> 5

So it looks quite straightforward to implement use equivalent for a Routine too.

I kinda thought so.

For safety, protect / unwind:

r = Routine { loop { ~xyz.yield } };
~xyz = 2;
e = Environment[\xyz -> 3];

(
// slotAt and slotPut are hacks, of course!
~routineNextInEnvir = { |routine, envir, inval|
	var saveEnvir = routine.slotAt(\environment);
	var result;
	protect {
		if(envir.notNil) {
			routine.slotPut(\environment, envir);
		};
		result = routine.next(inval);
	} {
		routine.slotPut(\environment, saveEnvir);
	};
	result
};
)

~routineNextInEnvir.(r);  // 2
~routineNextInEnvir.(r, e);  // 3

hjh

1 Like

Sure, you can do so, as James also stated. But be aware that Routine is one of the classes where you might encounter unexpected behaviour with such. IMO there’s good reason why Thread.sc contains a warning inlcuding Routine:

// you must not make any change at all to the order or number of
// instance variables in these classes!
// You should also not muck with the contents of the instance
// variables unless you are sure you know what you are doing.
// You may add methods.

I once wrote a subclass of Routine (which is not explicitely covered by the warning) and ran into quirks on linux. It turned out that already a trivial subclass of Routine immediately caused a crash, nobody knew to say anything about it. I don’t find the example in the archives right now, but Routine’s sensitivity is a fact.

The environment is probably less sensitive than the state code or stack related variables, and using protect to restore the original one makes it, I’d guess, almost certainly safe. It would need testing I guess…?

If a routine yields and you change the numArgsPushed, yeah, expect trouble.

hjh

1 Like

@dkmayer: Yes, I’m aware of the potential trouble. That’s one reason I’ve implemented the “hasYielded” prototype with a side lookup. (Which gets us to the issue that in the absence of “weak reference” support in SC, garbage collecting those side-lookups is troublesome.)

For the environment business, I had no choice but change an actual member variable. I plan to look at the C++ source to see exactly what happens with environments in threads/rountimes (the documentation, like I said, is silent on the topic), but it’s not a priority for me at the moment as I’m not currently planning to propose this as an official patch.