#lang scribble/manual @(require (for-label racket)) @title{Routine} @section{categories} Core>Kernel Functions that can return in the middle and then resume where they left off@section{related} Classes/Stream @section{description} A Routine runs a link::Classes/Function:: and allows it to be suspended in the middle and be resumed again where it left off. This functionality is supported by the Routine's superclass link::Classes/Thread::. Effectively, Routines can be used to implement co-routines as found in Scheme and some other languages. A Routine is strong::started:: the first time link::#-next:: is called, which will run the Function from the beginning. It is strong::suspended:: when it "yields" (using link::Classes/Object#-yield:: within the Function), and then strong::resumed:: using link::#-next:: again. When the Function returns, the Routine is considered strong::stopped::, and calling link::#-next:: will have no effect - unless the Routine is strong::reset:: using link::#-reset::, which will rewind the Function to the beginning. You can stop a Routine before its Function returns using link::#-stop::. When a Routine is strong::scheduled:: on a link::Classes/Clock:: (e.g. using link::#-play::), it will be started or resumed at the scheduled time. The value yielded by the Routine will be used as the time difference for rescheduling the Routine. (See link::#-awake::). Since Routine inherits from link::Classes/Thread::, it has its own associated link::Classes/Thread#-beats#logical time::, etc. When a Routine is started or resumed, it becomes the link::Classes/Thread#.thisThread#current thread::. Routine also inherits from link::Classes/Stream::, and thus shares its ability to be combined using math operations and "filtered". @section{classMethods} @section{method} new Creates an instance of Routine, passing it the Function with code to run. @section{argument} func A Function with code for the Thread to run. @section{argument} stackSize Call stack size (an Integer). @section{discussion} @racketblock[ a = Routine.new({ 1.yield; 2.yield; }); a.next.postln; a.next.postln; a.next.postln; :: ] @section{instanceMethods} @section{method} next This method performs differently according to the Routine's state: @section{list} ## Starts the Routine, if it has not been started yet or it has been link::#-reset#reset::; i.e runs its Function from the beginning, passing on the @racketblock[inval:: argument. ## Resumes the Routine, if it has been suspended (it has yielded); i.e. resumes its Function from the point where link::Classes/Object#-yield#yield:: was called on an Object, passing the ] @racketblock[inval:: argument as the return value of ] @racketblock[yield::. ## Does nothing if the Routine has stopped (because its Function has returned, or link::#-stop:: has been called). :: Since Routine inherits from link::Classes/Thread::, it will become the emphasis::current thread:: when it is started or resumed; i.e. link::Classes/Thread#.thisThread#thisThread:: used in the Routine Function will return the Routine. It will inherit the parent thread's logical time and clock (see link::Classes/Thread#-parent::). Synonyms for ] @racketblock[next:: are link::#-value:: and link::#-resume::. ] @section{returns} @section{list} ## Either the value that the Routine yields (the Object on which link::Classes/Object#-yield#yield:: is called within the Routine Function), ## ...or @racketblock[nil::, if the Routine has stopped. :: ] @section{discussion} When a Routine is started by a call to this method (or one of its synonyms), the method's argument is passed on as the argument to the Routine Function: @racketblock[ Routine { arg inval; inval.postln; }.value("hello routine"); :: After the Routine has yielded (it has been suspended at the point in its Function where ] @racketblock[yield:: is called on an Object), a call to this method (or its synonyms) resumes executing the Function and the argument to this method becomes the return value of ] @racketblock[yield::. To access that value within the Function, you have to assign it to a variable - typically, the argument of the Function is reused: ] @racketblock[ ( r = Routine { arg inval; inval.postln; inval = 123.yield; inval.postln; } ) r.value("hello routine"); r.value("goodbye routine"); :: Typically, a Routine yields multiple times, and each time the result of the yield is reassigning to the argument of its Function. ] @racketblock[ ( r = Routine { arg inval; inval.postln; // Post the value passed in when started. 5.do { arg i; inval = (i + 10).yield; inval.postln; // Post the value passed in when resumed. } } ) ( 5.do { r.value("hello routine").postln; // Post the value that the Routine yields. } ) :: ] @section{method} value Equivalent to link::#-next::. @section{method} resume Equivalent to link::#-next::. @section{method} stop Equivalent to the Routine Function reaching its end or returning: after this, the Routine will never run again (the link::#-next:: method has no effect and returns @racketblock[nil::), unless link::#-reset:: is called. ] @section{method} reset Causes the Routine to start from the beginning next time link::#-next:: is called. @section{discussion} If a Routine is stopped (its Function has returned or link::#-stop:: has been called), it will never run again (the link::#-next:: method has no effect and returns @racketblock[nil::), unless this method is called. A Routine cannot reset itself, except by calling link::Classes/Object#-yieldAndReset::. See also: link::Classes/Object#-yield::, link::Classes/Object#-alwaysYield:: ] @section{method} play In the SuperCollider application, a Routine can be played using a link::Classes/Clock::, as can any link::Classes/Stream::. every time the Routine yields, it should do so with a float, the clock will interpret that, usually pausing for that many seconds, and then resume the routine, passing it the clock's current time. @section{argument} clock a Clock, TempoClock by default @section{argument} quant see the link::Classes/Quant:: helpfile @section{discussion} using link::Classes/Object#idle#Object:idle:: within a routine, return values until this time is over. Time is measured relative to the thread's clock. @racketblock[ // for 6 seconds, return 200, then continue ( r = Routine { 199.yield; 189.yield; 200.idle(6); 199.yield; 189.yield; }; fork { loop { r.value.postln; 1.wait; } } ); // the value can also be a stream or a function ( r = Routine { 199.yield; 189.yield; Routine { 100.do { |i| i.yield } }.idle(6); 199.yield; 189.yield; }; fork { loop { r.value.postln; 1.wait; } } ); :: ] @section{method} awake This method is called by a link::Classes/Clock:: on which the Routine was scheduled when its scheduling time is up. It calls link::#-next::, passing on the scheduling time in beats as an argument. The value returned by @racketblock[next:: (the value yielded by the Routine) will in turn be returned by this method, thus determining the time which the Routine will be rescheduled for. ] @section{argument} inBeats The scheduling time in beats. This is equal to the current logical time (link::Classes/Thread#-beats::). @section{argument} inSeconds The scheduling time in seconds. This is equal to the current logical time (link::Classes/Thread#-seconds::). @section{argument} inClock The clock which awoke the Routine. @section{subsection} Accessible instance variables Routine inherits from link::Classes/Thread::, which allows access to some of its state: @racketblock[ ( r = Routine { arg inval; loop { // thisThread refers to the routine. postf("beats: % seconds: % time: % \n", thisThread.beats, thisThread.seconds, Main.elapsedTime ); 1.0.yield; } }.play; ) r.stop; r.beats; r.seconds; r.clock; :: ] @section{method} beats @section{returns} The elapsed beats (logical time) of the routine. The beats do not proceed when the routine is not playing. @section{method} seconds @section{returns} The elapsed seconds (logical time) of the routine. The seconds do not proceed when the routine is not playing, it is the converted beat value. @section{method} clock @section{returns} The thread's clock. If it has not played, it is the SystemClock. @section{examples} @racketblock[ ( var r, outval; r = Routine.new({ arg inval; ("->inval was " ++ inval).postln; inval = 1.yield; ("->inval was " ++ inval).postln; inval = 2.yield; ("->inval was " ++ inval).postln; inval = 99.yield; }); outval = r.next('a'); ("<-outval was " ++ outval).postln; outval = r.next('b'); ("<-outval was " ++ outval).postln; r.reset; "reset".postln; outval = r.next('c'); ("<-outval was " ++ outval).postln; outval = r.next('d'); ("<-outval was " ++ outval).postln; outval = r.next('e'); ("<-outval was " ++ outval).postln; outval = r.next('f'); ("<-outval was " ++ outval).postln; ) :: ] @racketblock[ // wait ( var r; r = Routine { 10.do({ arg a; a.postln; // Often you might see Wait being used to pause a routine // This waits for one second between each number 1.wait; }); // Wait half second before saying we're done 0.5.wait; "done".postln; }.play; ) :: ] @racketblock[ // waitUntil ( var r; r = Routine { var times = { rrand(1.0, 10.0) }.dup(10) + thisThread.beats; times = times.sort; times.do({ arg a; waitUntil(a); a.postln; }); // Wait half second before saying we're done 0.5.wait; "done".postln; }.play; ) :: ] @racketblock[ // Using Routine to set button states on the fly. ( var update, w, b; w = SCWindow.new("State Window", Rect(150,SCWindow.screenBounds.height-140,380,60)); // a convenient way to set the button label update = { |but, string| but.states = [[string.asString, Color.black, Color.red]]; but.refresh; }; b = SCButton(w, Rect(10,10,360,40)); b.font_(Font("Impact", 24)); update.value(b, "there is only one state"); // if an action should do something different each time it is called, a routine is the // right thing to use. This is better than creating variables outside and setting them // from the action function to keep state from one action to the next b.action_(Routine { |butt| rrand(15, 45).do { |i| update.value(butt, "%. there is still only 1 state".format(i + 2)); 0.yield; // stop here }; w.close; }); w.front; ) :: ] @racketblock[ // drawing in a window dynamically with Pen ( var w, much = 0.02, string, synth; w = Window.new("swing", Rect(100, 100, 300, 500)).front; w.view.background_(Color.new255(153, 255, 102).vary); string = "swing ".dup(24).join; w.drawFunc = Routine { var i = 0; var size = 40; var func = { |i, j| sin(i * 0.07 + (j * 0.0023) + 1.5pi) * much + 1 }; var scale; Pen.font = Font("Helvetica-Bold", 40); loop { i = i + 1; string.do { |char, j| scale = func.value(i, j).dup(6); Pen.fillColor = Color.new255(0, 120, 120).vary; Pen.matrix = scale * #[1, 0, 0, 1, 1, 0]; Pen.stringAtPoint(char.asString, ((size * (j % 9)) - 10) @ (size * (j div: 9)) ); }; 0.yield // stop here, return something unimportant } }; fork { while { w.isClosed.not } { defer { w.refresh }; 0.04.wait; } }; w.front; ) :: ]