summaryrefslogtreecommitdiff
path: root/doc/api/time.texi
diff options
context:
space:
mode:
Diffstat (limited to 'doc/api/time.texi')
-rw-r--r--doc/api/time.texi362
1 files changed, 362 insertions, 0 deletions
diff --git a/doc/api/time.texi b/doc/api/time.texi
new file mode 100644
index 0000000..cc637f2
--- /dev/null
+++ b/doc/api/time.texi
@@ -0,0 +1,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