summaryrefslogtreecommitdiff
path: root/doc/api/time.texi
blob: cc637f27dff48f4d52cee41c90fdd71cd44c4ff1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
@node Time
@section Time

In the context of Sly, time is not the seconds that are passing in the
real world, but the virtual clock that is ticking within the game
loop.  This manual refers to a ``tick'' as the smallest unit of time.
There are a user-defined amount of ticks in a real world second, but
by default it is 60.

Sly includes several useful modules for working with time.  Coroutines
are procedures that can be paused and resumed, agendas are procedure
schedulers, and signals built atop both agendas and coroutines to
provide a high-level functional reactive programming API.

For most use-cases, the signal module should be used exclusively, but
the agenda and coroutine modules are useful for building new
high-level abstractions.

@menu
* Signals::                     Functional reactive programming.
* Coroutines::                  Cooperative multi-tasking.
* Agendas::                     Deferred procedure scheduling.
@end menu

@node Signals
@subsection Signals

@example
(use-modules (sly signal))
@end example

Game state is a function of time.  The player's score, the current
stage, an enemy's hit points, etc. all change in response to events
that happen at discrete points in time.  Typically, this means that a
number of callback procedures are registered to respond to events
which mutate the relevant data structures.  However, this approach,
while simple and effective, comes at the price of readability,
reproducibility, and expression.  Instead of explicitly mutating data
and entering ``callback hell'', Sly abstracts and formalizes the
process using a functional reactive programming style.

In Sly, time-varying values are called ``signals'', and they are
defined in a declarative and functional manner.  Rather than
describing the process of mutation procedurally, one describes the
relationships between signals instead.  The result is a ``signal
graph'', a directed acyclic graph of event responses.

@example
(define-signal position
  (signal-fold v+ (vector2 320 240)
               (signal-map (lambda (v) (v* v 4))
                           (signal-sample 1 key-arrows))))
@end example

This signal describes a relationship between the arrow keys on the
keyboard and the position of the player.  @code{signal-sample} is used
to trigger a signal update upon every game tick that provides the
current state of the arrow keys.  @code{key-arrows} is a 2D vector
that maps to the current state of the arrow keys, allowing for 8
directional movement.  This vector is then scaled 4x to make the
player move faster.  Finally, the scaled vector is added to the
previous player position via @code{signal-fold}.  The player's
position is at (320, 240) initially.  As you can see, there are no
callbacks and explicit mutation needed, and the position seems to
magically change with the passage of time.

@deffn {Scheme Procedure} signal? @var{obj}
Return @code{#t} if @var{obj} is a signal.
@end deffn

@deffn {Scheme Procedure} make-signal @var{value}
Wrap @var{value} in a signal.
@end deffn

@deffn {Scheme Syntax} define-signal @var{name} @var{value}
Create a top-level signal variable called @var{name}.  If the variable
already exists and refers to a signal then its outputs will be spliced
into the new signal.  If the given value is not a signal then it will
be put into one via @code{make-signal}.

@code{define-signal} is particularly useful when working at the REPL.
A top-level signal variable defined by @code{define-signal} can be
redefined at runtime, and the signals that depended on the old signal
will continue to work with the new signal.
@end deffn

@deffn {Scheme Procedure} signal-ref @var{signal}
Return the value stored within @var{signal}.
@end deffn

@deffn {Scheme Procedure} signal-ref-maybe object
Return the value stored within @var{object} if @var{object} is a
signal.  Otherwise, return @var{object}.
@end deffn

@deffn {Scheme Syntax} signal-let ((@var{var} @var{signal}) @dots{}) @var{body} @dots{}
Evaluate @var{body} in the context of the local bindings defined by
the two-element lists @code{((var signal) @dots{})}.
@code{signal-let} works like regular @code{let}, except that it
derefences @var{signal} before binding to @var{var}.
@end deffn

@deffn {Scheme Syntax} signal-let* ((@var{var} @var{signal}) @dots{}) @var{body} @dots{}
Similar to @code{signal-let}, but the variable bindings are performed
sequentially.  This means that all initialization expressions are
allowed to use the variables defined to the their left in the binding
list.
@end deffn

@deffn {Scheme Procedure} signal-set! signal-box value
Change the contents of @var{signal} to @var{value}.  This procedure
should almost never be used, except to bootstrap a root node of a
signal graph.
@end deffn

@deffn {Scheme Procedure} hook->signal @var{hook} @var{init} @var{proc}
Create a new signal whose initial value is @var{init} and whose future
values are calculated by applying @var{proc} to the arguments passed
when @var{hook} is run.
@end deffn

@deffn {Scheme Procedure} signal-merge @var{signal1} @var{signal2} . @var{rest}
Create a new signal whose value is the that of the most recently
updated signal in @var{signal1}, @var{signal2}, etc.  The initial
value is that of @var{signal1}.
@end deffn

@deffn {Scheme Procedure} signal-zip . @var{signals}
Create a new signal whose value is a list of the values stored in
@var{signals}.
@end deffn

@deffn {Scheme Procedure} signal-map @var{proc} @var{signal} . @var{rest}
Create a new signal that applies @var{proc} to the values of
@var{SIGNAL}.  More than one input signal may be specified, in which
case @var{proc} must accept as many arguments as there are input
signals.
@end deffn

@deffn {Scheme Procedure} signal-sample-on @var{value-signal} @var{sample-signal}
Create a new signal that takes on the value of @var{value-signal}
whenever @var{sample-signal} receives a new value.
@end deffn

@deffn {Scheme Procedure} signal-negate @var{signal}
Create a new signal whose value is the negation of @var{signal} by
applying @code{not} to each value received.
@end deffn

@deffn {Scheme Procedure} signal-fold @var{proc} @var{init} @var{signal} . @var{rest}
Create a new signal that applies @var{proc} with the value received
from @var{signal} and the past value of itself, starting with
@var{init}.  Like @code{signal-map}, more than one input signal may be
given.
@end deffn

@deffn {Scheme Procedure} signal-filter @var{predicate} @var{default} @var{signal}
Create a new signal that takes on the value received from @var{signal}
when it satisfies the procedure @var{predicate}.  The value of the
signal is @var{default} in the case that the predicate is never
satisfied.
@end deffn

@deffn {Scheme Procedure} signal-drop @var{predicate} @var{default} @var{signal}
Create a new signal that takes on the value received from @var{signal}
when it does @emph{not} satisfy the procedure @var{predicate}.  The
value of the signal is @var{default} in the case that the predicate is
never satisfied.
@end deffn

@deffn {Scheme Procedure} signal-drop-repeats @var{signal} [@var{equal?}]
Create a new signal that drops the value received from @var{signal}
when it is equivalent to the current value.  By default, @code{equal?}
is used for testing equivalence.
@end deffn

@deffn {Scheme Procedure} signal-switch @var{predicate} @var{on} @var{off}
Create a new signal whose value is that of the signal @var{on} when
the signal @var{predicate} is true, or the value of the signal
@var{off} otherwise.
@end deffn

@deffn {Scheme Procedure} signal-constant @var{constant} @var{signal}
Create a new signal whose value is always @var{constant} no matter the
value received from @var{signal}.
@end deffn

@deffn {Scheme Procedure} signal-count @var{signal} [@var{start}] [@var{step}]
Create a new signal that increments a counter by @var{step} when a
value from @var{signal} is received, starting from @var{start}.  By
default, @var{start} is 0 and @var{step} is 1.
@end deffn

@deffn {Scheme Procedure} signal-tap @var{proc} @var{signal}
Create a new signal that applies @var{proc} for side-effects when a
value from @var{signal} is received.  The value of the new signal will
always be the value of @var{signal}.  This signal is a convenient way
to sneak in a procedure that with a side-effect into a signal graph.
Such a signal might write text to a file, or play a sound.
@end deffn

@deffn {Scheme Procedure} signal-timestamp @var{signal}
Create a new signal whose value is a pair, the car of which is the
time that the value of @var{signal} was received and the cdr of which
is the received value.
@end deffn

@deffn {Scheme Procedure} signal-time @var{signal}
Create a new signal whose value is the time that the value of
@var{signal} was received.
@end deffn

@deffn {Scheme Procedure} signal-sample @var{step} @var{signal}
Create a new signal that takes on the value of @var{signal} every
@var{step} ticks.
@end deffn

@deffn {Scheme Procedure} signal-every @var{step}
Create a new signal that emits @var{step} every @var{step} ticks.
@end deffn

@deffn {Scheme Procedure} signal-since @var{step} @var{signal}
Create a new signal that emits the time since @var{signal} was updated
ever @var{step} ticks.
@end deffn

@deffn {Scheme Procedure} signal-delay @var{delay} @var{signal}
Create a new signal that delays propagation of @var{signal} by
@var{delay} ticks..
@end deffn

@deffn {Scheme Procedure} signal-throttle delay signal
Create a new signal that propagates @var{signal} at most once every
@var{delay} ticks.
@end deffn

@deffn {Scheme Syntax} signal-generator @var{body} @dots{}
Create a new signal whose value is the most recently yielded value of
the coroutine defined by @var{body}.  A special @code{yield} syntax is
available within @var{body} to specify which values are passed to the
signal.
@end deffn

@node Coroutines
@subsection Coroutines

@example
(use-modules (sly coroutine))
@end example

Coroutines are the building block for cooperative multitasking. When
used with agendas, they are a powerful mechanism for writing
algorithms that span multiple clock ticks in a straightforward, linear
fashion.  Sly's coroutines are built on top of Guile's delimited
continuations, called prompts.

To run a procedure as a coroutine, use the @code{call-with-coroutine}
procedure.  Once inside the coroutine prompt, the @code{yield}
procedure can be used to pause the procedure and pass its continuation
to a callback procedure.  The callback may call the continuation at
its convenience, resuming the original procedure.

Coroutines are particularly useful in conjunction with @ref{Agendas}.

@deffn {Scheme Procedure} call-with-coroutine @var{thunk}
Apply @var{thunk} within a coroutine prompt.
@end deffn

@deffn {Scheme Syntax} coroutine @var{body} @dots{}
Evaluate @var{body} within a coroutine prompt.
@end deffn

@deffn {Scheme Syntax} colambda @var{args} @var{body} @dots{}
Syntacic sugar for a @code{lambda} expression whose @var{body} is run
within a coroutine prompt.
@end deffn

@deffn {Scheme Syntax} codefine (@var{name} @var{formals} @dots{}) @var{body} @dots{}
Syntacic sugar for defining a procedure called @var{name} with formal
arguments @var{formals} whose @var{body} is run within a coroutine
prompt.
@end deffn

@deffn {Scheme Syntax} codefine* (@var{name} @var{formals} @dots{}) @var{body} @dots{}
Syntacic sugar for defining a procedure called @var{name} with
optional and keyword arguments @var{formals} whose @var{body} is run
within a coroutine prompt.
@end deffn

@deffn {Scheme Procedure} yield @var{callback}
Yield continuation to the procedure @var{callback}.
@end deffn

@node Agendas
@subsection Agendas

@example
(use-modules (sly agenda))
@end example

Agendas are used to schedule procedures to be called at distinct
points in time.  One agenda, stored in the @code{current-agenda}
parameter, is active at any given time.  A global agenda is initially
bound and is sufficient for most needs.  When a separate scheduler is
required (@pxref{REPL} for one such case), the parameter can be
rebound using @code{parameterize} or @code{with-agenda} form.

@deffn {Scheme Procedure} make-agenda
Create a new, empty agenda.
@end deffn

@deffn {Scheme Syntax} agenda? @var{obj}
Return @code{#t} if @var{obj} is an agenda.
@end deffn

@defvr {Scheme Variable} current-agenda
A parameter containing the current, dynamically scoped agenda object.
@end defvr

@deffn {Scheme Procedure} agenda-time
Return the time of the current agenda.
@end deffn

@deffn {Scheme Syntax} with-agenda @var{agenda} @var{body} @dots{}
Evaluate @var{body} with @code{current-agenda} bound to @var{agenda}.
@end deffn

@deffn {Scheme Procedure} agenda-tick!
Increment time by 1 for the current agenda and run scheduled
procedures.
@end deffn

@deffn {Scheme Procedure} agenda-clear!
Remove all scheduled procedures from the current agenda.
@end deffn

@deffn {Scheme Procedure} schedule @var{thunk} [@var{delay}]
Schedule @var{thunk} to be applied after @var{delay} ticks of the
current agenda.  The default @var{delay} is one tick.
@end deffn

@deffn {Scheme Procedure} schedule-interval @var{thunk} @var{interval}
Schedule @var{thunk} to be applied every @var{interval} ticks of the
current agenda, forever.
@end deffn

@deffn {Scheme Procedure} schedule-each @var{thunk}
Schedule @var{thunk} to be applied upon every tick of the current
agenda, forever.
@end deffn

@ref{Coroutines} become particularly useful for game programming when
combined with the agenda.  For example, a computer controller opponent
could periodically pause its AI algorithm to give the rest of the game
world a chance to do some processing.  By using the @code{wait}
procedure, algorithms that span multiple ticks of game time can be
written in a straightforward, natural way.

@deffn {Scheme Procedure} wait @var{delay}
Abort the current coroutine prompt and schedule the continuation to be
run after @var{delay} ticks of the current agenda.
@end deffn