New Synchronisation Primitives - Replacement for CondVar and Condition

Following on from a previous thread…

CondVar and Condition are too complicated and error prone for general use. Lets make something(s) better and get it added into sclang, whilst changing the existing documentation to make it obvious people shouldn’t be using the old ones be default.

As a part of the design, making them really hard/impossible to use incorrectly should be a design goal. Meaning they might not cover every use case, but as many as possible so long as they are safe.

But what do people want in synchronisation? Personally, I think futures and promises might be too complicated here, but channels (as in Go channels) might be really useful?

There are a number of good Quarks that add synchronisation methods, @scztt’s Deferred is one already mentioned - although it might be too broad and easy to use incorrectly. Does anyone know of any others?

Looking for people to help, so please get involved!

Plan for action here might be:

  1. Discuss what types synchronisation methods people want
  2. Make each one as a Quark (easier to share), feeding back as we go.
  3. Change old docs
  4. Put together a pull request.
1 Like

I like the idea of Barrier, which will execute many process in parallel, then wait for them all to finish. It has two main methods, do and collect.

Do is useful for launching many voices in polyphony and waiting for them all to finish, whereas collect returns an array of value.

Quarks.install("https://github.com/JordanHendersonMusic/Barrier");

r = Routine.run {
	~b  = Barrier.do(
		{ var v = 5; v.wait; v.postln},
		{ var v = 1.0.rand; v.wait; v.postln},
		{ var v = 1.0.rand; v.wait; v.postln},
		{ var v = 1.0.rand; v.wait; v.postln}
	);
	\waiting.postln;
	~b.wait;
	\done.postln;
};
r = Routine.run {
	var b  = Barrier.collect(
		{ var v = 1.0.rand; v.wait; v},
		{ var v = 1.0.rand; v.wait; v},
		{ var v = 1.0.rand; v.wait; v},
		{ var v = 1.0.rand; v.wait; v}
	);
	\waiting.postln;
	b.value.postln;
	\done.postln;
};
r = Routine.run {
	var b  = Barrier.doNTimes(10,
		{ var v = 1.0.rand; v.wait; v.postln}
	);
	\waiting.postln;
	b.wait;
	\done.postln;
};
r = Routine.run {
	var b = Barrier.collectNTimes(10,
		{ var v = 1.0.rand; v.wait; v}
	);
	\waiting.postln;
	b.value.postln;
	\done.postln;
};
A musical example from the help doc
( // eval whole block - sound
s.waitForBoot {
	SynthDef(\bleep, {
		var env = Env.perc(\atk.kr(0.01), \rel.kr(1)).ar(doneAction: 2);
		var sig = SinOsc.ar(Vibrato.kr(\freq.kr(220), depth: 0.01, delay: \rel.kr / 3));
		var stereo = Pan2.ar(sig, Rand(-1.0, 1.0)) * env * \amp.kr(0.2);
		Out.ar(\out.kr(0), stereo);
	}).add;

	s.sync;

	Routine.run {
		var randMajorPitch = {|root|
			(Scale.major.semitones.choose + root).midicps
		};

		"staring main voice".warn;

		5.do{ |count|
			var delta = 3.0.rand + 0.1;

			"main voice".postln;
			Synth(\bleep, [\freq, randMajorPitch.(60), \rel, delta]);
			// wait less on last note
			if(count != 4, {delta.wait}, {(delta/4).wait});
		};

		"staring 10 other voices".warn;

		Barrier.doNTimes(10, { |n|
			5.do{ |count|
				var delta = 6.0.rand + 3;

				format("other voice %", n).postln;
				Synth(\bleep, [
					\freq, randMajorPitch.(55),
					\atk, delta/2,
					\rel, delta/2,
					\amp, 0.05
				]);
				// wait less on last note
				if(count != 4, {delta.wait}, {(delta/4).wait});
			}
		})
		.wait; // main voice (this thread) waits here.


		"staring main voice again".warn;


		5.do{ |count|
			var delta = 3.0.rand + 3;

			"main voice".postln;
			Synth(\bleep, [\freq, randMajorPitch.(65), \rel, delta * 3]);
			// wait less on last note
			if(count != 4, {delta.wait}, {(delta/4).wait});
		};



		"staring other voices again".warn;
		"but two other voices join, playing for as long as the barrier plays".warn;

		Barrier.doNTimes(20, { |n|
			25.do{ |count|
				var delta = 3.0.rand + 0.3;

				format("other voice: % count: %", n, count).postln;
				Synth(\bleep, [
					\freq, randMajorPitch.(63),
					\atk, delta/2,
					\rel, delta/2,
					\amp, 0.05
				]);

				delta.wait
			}
		})

		// runs for as long as the main barrier is processing, see help docs for note
		.loopWhileExecuting({
			var delta = 1.0.rand + 0.2;
			"high pitched voice".postln;
			Synth(\bleep, [
				\freq, randMajorPitch.(87),
				\atk, delta * 0.9,
				\rel, delta * 0.1,
				\amp, 0.2
			]);
			(delta * (1 + 2.0.rand)).wait;
		})

		.loopWhileExecuting({
			var delta = 3.0.rand + 1;
			"low thumps voice".postln;
			Synth(\bleep, [
				\freq, randMajorPitch.(40),
				\rel, delta / 10,
				\amp, 0.6
			]);
			delta.wait;
		})

		.wait;

		"done".warn;
	}
};
)
2 Likes

I’ve also made a Channel, it looks like this…

~channel = Channel();

~inserter = Routine.run{
	loop  {
		var r = 5.0.rand;
		r.wait;
		\inserting.postln;
		~channel.insert( r );
	}
};

~extractor = Routine.run {
	loop {
		\waiting.postln;
		2.wait;
		~channel.extract.postln;
	}
};
1 Like

Excited to test and consider… I have used and abused Condition CondVar and Deferred - while each of these worked better for me than the ones before still…

Let me know how you get on, I’ve updated it a bunch since this morning so it would probably need updating. The docs should be pretty thorough now too.

Responding to this from another thread, as that one was a question, which has been answered.

TLDR: Don’t use Barrier for this.

I have never found an instance where synchronising with routines and the server is more performant than just writing it synchronously and calling s.sync.
I am not too sure on how this is implemented — I’m have trouble locating the part of the library that handles this —, but as I understand, s.sync will be added to the server’s synchronous to do list, once all previous commands have been executed, the message is sent back to the language to continue. This means other sclang threads might have to wait for other thread’s buffer allocations to finish before it can. Therefore, it is pointless to call them asynchronously. I spent a lot of time with FluCoMa trying to eek out more performance this way… huge waste of time (the optimisation, not the library). Now perhaps something could be done by using completion messages (not too sure on that), but that isn’t something Barrier (or perhaps even any class/function) can address. One option (which I quite like) might be making a Buffer.readBlocking method that uses the action callback to unblock, wrapping all the details of that inside so for the user (and in the context of Barrier) the code appears synchronous. Better yet would be if Buffer.read just returned a Future that automatically decayed to a buffer if present, waited if in a routine, and throws an error if not.

Or, I think this could be a good use for a Channel, or even just using the action directly…

~bufChan = Channel();

Buffer.read(s, "..", action: ~bufChan.insert(_))

fork {
	var b = ~bufChan.extract();
    ... blah blah blah
}

If you have any counter examples, please let me know!

… or even better for this specific issue, a multiple buffer loader class, since this issue pops up so many times on this forum.

The aim here is to make synchronisation methods that, if they don’t throw on error (hopefully an early error), they work. That isn’t always possible, but requiring the user to remember to do something seem contrary to this. Actually, what you are suggesting seems just like CondVar and the solution from the other thread, but with a slightly nicer syntax.

Sure, buffer reading isn’t a convincing specific example.

Let me try to summarize, see if I understand:

  • Barrier is for timed tasks only, and has no mechanism to release a thread explicitly.

  • Channel handles explicit messaging.

I’m looking over Channel, and I don’t see immediately whether it will handle this case: “I have some number of threads that depend on a resource. That resource may or may not be available at the time(s) when the threads initiate (which may be at different times). If the resource is available, the new thread should not wait; otherwise, it should wait.” In the buffer loading example, you would insert once when the buffer is loaded, and one thread would extract the buffer, and any further threads trying to extract would get nothing (if I’m reading the code right), and hang indefinitely. That is, Channel seems to assume there’s only one consumer per item, but that wouldn’t be true of an object managing shared buffers.

hjh

What you have described is Deferred or a Future.

Barrier is where each thread is a race horse, the main thread stands on the finish line, fires the starter pistol (creates the Barrier) then waits at the finish line until all the horses have crossed. If the horses have to form a single file line (synchronous) then its the wrong choice. Its good for spawning multiple voices, and waiting for them all to finish.

Channel is just a consumer/producer pipeline, things go in, things go out. It does not hold the values post extraction. There is no way to know which consumer they will go to.

Deferred (or Future) is when a value will be created at some point. Supercollider is really silly here, in that all server resources (e.g., Buffers) are actually futures in disguise, its just requires the user to remember to call sync or suffer horrific error messages. Things like Buffer.read should probably return a FutureServerResource which decays to the intended type on .value() — Value is chosen because Buffer.value just returns itself. Alternatively, doeaBotUnderstand could be used, but the bug of not getting named arguments to pass should be fixed first.
If the value is known it returns, or tries to wait if not (throwing an error when not called in a routine informing the user can use a routine, or just hang on a tick).

If that where the case (and all classes that used said resource type called value on it before using it), what you have described would literally look no different from the naive implementation without synchronisation methods.

That is what I think this thread should try to work towards.

Okay, so I had a quick go at implementing this, I think it works really well! Its completely transparent to the user, but is INCREDIBLY naughty (there might be weird bugs with GC, that I haven’t found yet). If anyone knows how GC is done is sc (not the C++ side, but in Object) please do let me know! @jamshark70 I think this is how your example should be done in supercollider.

Here is what it looks like to use…

e.g. 1

s.waitForBoot {
	~b = Buffer.readAsResource(s, '...');
	
	~b.query; // This automatically waits
	~b.class; // yup returns Buffer (said this was naughty)
}

e.g. 2

(
~b = Buffer.readAsResource(s, '...');

~b.query; // This one throws as you cannot wait here...
// ERROR: Buffer's value has not completed,  either use it in a Routine/Thread, or, literally wait until the resource has loaded and try again

)

~b.query;  // ...but now it works since enough time has passed

I’ll put the implementation here… again, its naughty as it basically re implements Object because the class is so god dam big…

ObjectPreCaller Class
// this is used to fix the decision to add everything into Object
// Some of these might break other stuff... only one way to find out.
ObjectPreCaller  {
	var <>pr_pr_pr_pr_underlying_object;
	var <>pr_pr_pre_func;

	// DO NOT RELY ON THESE METHOD AS THE INTERPRETER USES IT
	// OBJECT SHOULD REALLLLLLYYYY STOP YOU FROM IMPLEMENTING THESE, but lucky for us...
	asString { arg limit; ^pr_pr_pr_pr_underlying_object.asString( limit );  }
	class { ^pr_pr_pr_pr_underlying_object.class()}


	dump { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dump() }
	post { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.post()}
	postln { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.postln()}
	postc { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.postc()}
	postcln { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.postcln()}
	postcs { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.postcs()}
	// would these cause leaks?
	//totalFree { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.totalFree() }
	//largestFreeBlock { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.largestFreeBlock() }
	//gcDumpGrey { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.gcDumpGrey() }
	//gcDumpSet { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.gcDumpSet() }
	//gcInfo { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.gcInfo() }
	//gcSanity { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.gcSanity() }
	canCallOS { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.canCallOS() }
	size { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.size()}
	indexedSize { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.indexedSize()}
	flatSize { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.flatSize()}
	functionPerformList { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.functionPerformList() }
	copy { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.copy()}
	contentsCopy { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.contentsCopy()}
	shallowCopy { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.shallowCopy()}
	copyImmutable { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.copyImmutable() }
	deepCopy { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.deepCopy() }
	poll { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.poll()}
	value { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.value()}
	valueArray { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.valueArray()}
	valueEnvir { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.valueEnvir()}
	valueArrayEnvir { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.valueArrayEnvir()}
	basicHash { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.basicHash()}
	hash { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.hash()}
	identityHash { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.identityHash()}
	next { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.next()}
	reset { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.reset()}
	iter { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.iter()}
	stop { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.stop()}
	free { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.free()}
	clear { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.clear()}
	removedFromScheduler { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.removedFromScheduler()}
	isPlaying { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isPlaying()}
	embedInStream { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.embedInStream()}
	loop { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.loop()}
	asStream { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asStream()}
	eventAt { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.eventAt()}
	finishEvent { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.finishEvent()}
	atLimit { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.atLimit()}
	isRest { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isRest()}
	threadPlayer { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.threadPlayer()}
	threadPlayer_ { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.threadPlayer_()}
	isNil { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isNil()}
	notNil { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.notNil()}
	isNumber { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isNumber()}
	isInteger { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isInteger()}
	isFloat { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isFloat()}
	isSequenceableCollection { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isSequenceableCollection()}
	isCollection { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isCollection()}
	isArray { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isArray()}
	isString { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isString()}
	containsSeqColl { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.containsSeqColl()}
	isValidUGenInput { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isValidUGenInput()}
	isException { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isException()}
	isFunction { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isFunction()}
	trueAt { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.trueAt()}
	mutable { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.mutable()}
	frozen { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.frozen()}
	halt { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.halt() }
	prHalt { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.prHalt() }
	primitiveFailed { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.primitiveFailed() }
	reportError { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.reportError() }
	mustBeBoolean { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.mustBeBoolean()}
	notYetImplemented { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.notYetImplemented()}
	dumpBackTrace { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dumpBackTrace() }
	getBackTrace { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.getBackTrace() }
	throw { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.throw() }
	//species { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.species()}
	asCollection { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asCollection()}
	asSymbol { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asSymbol()}
	asCompileString { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asCompileString() }
	cs { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.cs()}
	storeArgs { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.storeArgs()}
	dereference { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dereference()}
	reference { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.reference()}
	asRef { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asRef()}
	dereferenceOperand { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dereferenceOperand()}
	asArray { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asArray()}
	asSequenceableCollection { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asSequenceableCollection()}
	rank { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.rank()}
	slice { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slice()}
	shape { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.shape()}
	unbubble { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.unbubble()}
	yield { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.yield() }
	alwaysYield { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.alwaysYield() }
	dependants { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dependants() }
	release { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.release() }
	releaseDependants { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.releaseDependants() }
	removeUniqueMethods { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.removeUniqueMethods() }
	inspect { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.inspect()}
	inspectorClass { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.inspectorClass()}
	inspector { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.inspector() }
	crash { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.crash() }
	stackDepth { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.stackDepth() }
	dumpStack { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dumpStack() }
	dumpDetailedBackTrace { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dumpDetailedBackTrace() }
	freeze { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.freeze() }
	beats_ { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.beats_()  }
	isUGen { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isUGen()}
	numChannels { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.numChannels()}
	clock_ { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.clock_()  }
	asTextArchive { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asTextArchive() }
	asBinaryArchive { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asBinaryArchive() }
	help { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.help()}
	asArchive { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asArchive() }
	initFromArchive { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.initFromArchive()}
	archiveAsCompileString { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.archiveAsCompileString()}
	archiveAsObject { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.archiveAsObject()}
	checkCanArchive { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.checkCanArchive()}
	isInputUGen { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isInputUGen()}
	isOutputUGen { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isOutputUGen()}
	isControlUGen { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isControlUGen()}
	source { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.source()}
	asUGenInput { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asUGenInput()}
	asControlInput { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asControlInput()}
	asAudioRateInput { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.asAudioRateInput()}
	slotSize { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotSize() }
	getSlots { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.getSlots() }
	instVarSize { pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.instVarSize()}

	do { arg function; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.do( function ); }
	generate { arg function, state; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.generate( function, state ); }
	isKindOf { arg aClass; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isKindOf( aClass );  }
	isMemberOf { arg aClass; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.isMemberOf( aClass ); }
	respondsTo { arg aSymbol; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.respondsTo( aSymbol ); }
	performMsg { arg msg; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performMsg( msg );  }
	perform { arg selector ... args; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.perform( selector, *args );  }
	performList { arg selector, arglist; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performList( selector, arglist );  }
	superPerform { arg selector ... args; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.superPerform( selector, *args );  }
	superPerformList { arg selector, arglist; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.superPerformList( selector, arglist );  }
	tryPerform { arg selector ... args; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.tryPerform( selector, *args );  }
	multiChannelPerform { arg selector ... args; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.multiChannelPerform( selector, *args );  }
	performWithEnvir { arg selector, envir; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performWithEnvir( selector, envir );  }
	performKeyValuePairs { arg selector, pairs; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performKeyValuePairs( selector, pairs );  }
	dup { arg n ; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.dup( n );  }
	! { arg n; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object !  n);  }
	== { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object ==  obj);  }
	!= { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object !=  obj);  }
	=== { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object ===  obj); }
	!== { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object !==  obj); }
	equals { arg that, properties; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.equals( that, properties );  }
	compareObject { arg that, instVarNames; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.compareObject( that, instVarNames );  }
	instVarHash { arg instVarNames; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.instVarHash( instVarNames );  }
	|==| { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object |==|  that);  }
	|!=| { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object |!=|  that);  }
	prReverseLazyEquals { arg that; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.prReverseLazyEquals( that );  }
	-> { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object ->  obj);  }
	first { arg inval; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.first( inval ); }
	cyc { arg n; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.cyc( n );  }
	fin { arg n; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.fin( n );  }
	repeat { arg repeats; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.repeat( repeats ); }
	nextN { arg n, inval; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.nextN( n, inval );  }
	streamArg { arg embed; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.streamArg( embed );  }
	composeEvents { arg event; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.composeEvents( event ); }
	? { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object ?  obj); }
	?? { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object ??  obj); }
	!? { arg obj; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object !?  obj); }
	matchItem { arg item; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.matchItem( item ); }
	falseAt { arg key; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.falseAt( key );  }
	pointsTo { arg obj; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.pointsTo( obj ); }
	subclassResponsibility { arg method; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.subclassResponsibility( method );  }
	doesNotUnderstand { arg selector ... args; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.doesNotUnderstand( selector, *args );  }
	shouldNotImplement { arg method; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.shouldNotImplement( method );  }
	outOfContextReturn { arg method, result; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.outOfContextReturn( method, result );  }
	immutableError { arg value; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.immutableError( value );  }
	deprecated { arg method, alternateMethod; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.deprecated( method, alternateMethod );  }
	printClassNameOn { arg stream; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.printClassNameOn( stream );  }
	printOn { arg stream; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.printOn( stream );  }
	storeOn { arg stream; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.storeOn( stream );  }
	storeParamsOn { arg stream; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.storeParamsOn( stream );  }
	simplifyStoreArgs { arg args; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.simplifyStoreArgs( args );  }
	storeModifiersOn { arg stream; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.storeModifiersOn( stream ); }
	as { arg aSimilarClass; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.as( aSimilarClass ); }
	deepCollect { arg depth, function, index, rank ; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.deepCollect( depth, function, index , rank ); }
	deepDo { arg depth, function, index , rank ; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.deepDo( depth, function, index , rank ); }
	bubble { arg depth, levels; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.bubble( depth, levels);  }
	obtain { arg index, default; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.obtain( index, default ); }
	instill { arg index, item, default; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.instill( index, item, default );  }
	addFunc { arg ... functions; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.addFunc(*functions );  }
	removeFunc { arg function; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.removeFunc( function );  }
	replaceFunc { arg find, replace; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.replaceFunc( find, replace );  }
	addFuncTo { arg variableName ... functions; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.addFuncTo( variableName, *functions );  }
	removeFuncFrom { arg variableName, function; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.removeFuncFrom( variableName, function );  }
	while { arg body; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.while( body );  }
	switch { arg ... cases; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.switch(*cases );  }
	yieldAndReset { arg reset ; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.yieldAndReset( reset );  }
	idle { arg val; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.idle( val );  }
	changed { arg what ... moreArgs; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.changed( what, *moreArgs );  }
	addDependant { arg dependant; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.addDependant( dependant );  }
	removeDependant { arg dependant; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.removeDependant( dependant );  }
	update { arg theChanged, theChanger; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.update( theChanged, theChanger ); }
	addUniqueMethod { arg selector, function; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.addUniqueMethod( selector, function );  }
	removeUniqueMethod { arg selector; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.removeUniqueMethod( selector );  }
	& { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object &  that); }
	| { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object |  that); }
	% { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object %  that); }
	** { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object **  that); }
	<< { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object <<  that); }
	>> { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object >>  that); }
	+>> { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object +>>  that); }
	<! { arg that; pr_pr_pre_func.(); ^(pr_pr_pr_pr_underlying_object <!  that); }
	blend { arg that, blendFrac; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.blend( that, blendFrac );  }
	blendAt { arg index, method; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.blendAt( index, method);  }
	blendPut { arg index, val, method; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.blendPut( index, val, method);  }
	fuzzyEqual { arg that, precision; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.fuzzyEqual( that, precision); }
	pair { arg that; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.pair( that ); }
	pairs { arg that; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.pairs( that );  }
	awake { arg beats, seconds, clock; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.awake( beats, seconds, clock );  }
	performBinaryOpOnSomething { arg aSelector, thing, adverb; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performBinaryOpOnSomething( aSelector, thing, adverb );  }
	performBinaryOpOnSimpleNumber { arg aSelector, thing, adverb; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performBinaryOpOnSimpleNumber( aSelector, thing, adverb );  }
	performBinaryOpOnSignal { arg aSelector, thing, adverb; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performBinaryOpOnSignal( aSelector, thing, adverb );  }
	performBinaryOpOnComplex { arg aSelector, thing, adverb; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performBinaryOpOnComplex( aSelector, thing, adverb );  }
	performBinaryOpOnSeqColl { arg aSelector, thing, adverb; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performBinaryOpOnSeqColl( aSelector, thing, adverb );  }
	performBinaryOpOnUGen { arg aSelector, thing, adverb; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.performBinaryOpOnUGen( aSelector, thing, adverb );  }
	writeDefFile { arg name, dir, overwrite; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.writeDefFile( name, dir, overwrite );  }
	slotAt { arg index; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotAt( index );  }
	slotPut { arg index, value; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotPut( index, value );  }
	slotKey { arg index; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotKey( index );  }
	slotIndex { arg key; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotIndex( key );  }
	slotsDo { arg function; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotsDo( function );  }
	slotValuesDo { arg function; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.slotValuesDo( function );  }
	setSlots { arg array; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.setSlots( array );  }
	instVarAt { arg index; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.instVarAt( index );  }
	instVarPut { arg index, item; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.instVarPut( index, item );  }
	writeArchive { arg pathname; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.writeArchive( pathname );  }
	writeTextArchive { arg pathname; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.writeTextArchive( pathname );  }
	getContainedObjects { arg objects; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.getContainedObjects( objects );  }
	writeBinaryArchive { arg pathname; pr_pr_pre_func.(); ^pr_pr_pr_pr_underlying_object.writeBinaryArchive( pathname );  }
}

ServerResourceWrapper --- could be used for other things too later
// this needs tidying up 
ServerResourceWrapper : ObjectPreCaller {
	var <>pr_pr_cond_var;
	var <>pr_pr_has_finished_loading_on_server;

	*new{
		var self = super.new();

		self
		.pr_pr_has_finished_loading_on_server_(false)
		.pr_pr_cond_var_(CondVar());

		self.pr_pr_pre_func_({
			if(self.pr_pr_has_finished_loading_on_server.not, {
				try {
					self.pr_pr_cond_var.wait({ self.pr_pr_has_finished_loading_on_server })
				}
				{
					|er|
					if((er.class == PrimitiveFailedError) && (er.failedPrimitiveName == '_RoutineYield'),
						{  (self.class.asString ++ "'s value has not completed, "
							+ "either call use in a Routine/Thread, "
							+ "or, literally wait until the proccess has finished and try again").throw
						},
						{er.throw} // some other error
					);

				}
			})
		});
		^self
	}

	pr_pr_add_underlying_object {|obj|
		// only call this once
		this.pr_pr_pr_pr_underlying_object = obj
	}


	pr_pr_mark_has_finished_loading_on_server {
		pr_pr_has_finished_loading_on_server = true;
		pr_pr_cond_var.signalAll;
	}

	doesNotUnderstand { |selector ... args|
		this.pr_pr_pre_func.();
		^this.pr_pr_pr_pr_underlying_object.perform(selector.asSymbol, *args)
	}
}
Buffer.readAsResource
+ Buffer {
	*readAsResource { |server, path, startFrame=0, numFrames=(-1), action, bufnum|
		var r = ServerResourceWrapper();
		var buffer = Buffer.read(
			server,
			path,
			startFrame,
			numFrames,
			{|buf|
				r.pr_pr_mark_has_finished_loading_on_server();
				action !? {action.(buf)};
			},
			bufnum
		);
		r.pr_pr_add_underlying_object(buffer);
		^r
	}
}

…we shall rid the world of s.sync one step at a time…

Sure, but can it do more than that? The examples so far have all depended on threads’ internal timing, but, say you need the threads to finish upon some user action. Actually I can already see that if you have a Barrier, and one Channel per sub-thread, each Channel would handle the user actions and hold its associated thread open until triggered. This would be a neat way to implement Max’s [buddy] (which doesn’t come up often in SC, but is an issue for e.g. 14-bit controller pairs in MIDI, if you’re not certain of the order in which messages will arrive).

Other question: If you have Barrier, Channel and Future, is that sufficient for just about every thread sync case you’d ever run into? Or another way to ask it is: How large is the thread sync vocabulary?

hjh

Here is what that [buddy] thing looks like…

~buddy = {|n, then|
	var args = Channel(_)!n;
	var bar = Barrier.collect(
		*args.collect({|chan| {chan.extract} })
	);
	fork { then.(bar.value) };
	args
}

~args = ~buddy.(2, _.postln);

~args[1].insert(\second);
~args[0].insert(\first);

Emphasis on the personal experience here…
Since, most of the sync stuff I need is with the server…
If the following post was adopted throughout the library, the issue with synchronising with the server almost disappears entirely. I would just be writing several synchronous functions and whacking them in Barrier, or in a loop.

any thought as to how you might handle SynthDef::add in this scheme? I suppose SynthDef would need a method with an OSCFunc to pick up the \done response from the server… should probably add the defname to the completion message as well - I’ll try this this afternoon time permitting

Just curious: why is the method called query? Why not something like “waitUntilReady”? As a user, I’d expect “query” to return information containing e.g. properties of a buffer (things like length and format maybe). (Query reminds me of a database, not immediately of something synchronization related).

Query is just an existing method of buffer… wait is called for all methods of buffer completely transparently for the user

Yeah, I don’t quite know how to deal with that one. Suppose you could keep a set of the added synthdefs, adding the new one when complete.

I see… now it makes much more sense to me :slight_smile: