From cd56361421ff460768fbe4e9eaf1080a5507c5c3 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sat, 13 Dec 2014 13:31:57 -0500 Subject: docs: Add half-written manual. * doc/game.texi: Delete. * doc/api/init.texi: New file. * doc/api/input.texi: Likewise. * doc/api/math.texi: Likewise. * doc/api/rendering.texi: Likewise. * doc/api/time.texi: Likewise. * doc/api/utils.texi: Likewise. * doc/sly.texi: Add API reference. * doc/Makefile.am (sly_TEXINFOS): Add new files. --- doc/api/time.texi | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 doc/api/time.texi (limited to 'doc/api/time.texi') 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 -- cgit v1.2.3