@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