rsc3/doc-schelp/HelpSource/Classes/Routine.scrbl

493 lines
11 KiB
Text
Raw Normal View History

2022-08-24 13:53:18 +00:00
#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;
)
::
]