Next: , Up: Time   [Contents][Index]


4.3.1 Signals

(use-modules (sly signal))

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.

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

This signal describes a relationship between the arrow keys on the keyboard and the position of the player. signal-sample is used to trigger a signal update upon every game tick that provides the current state of the arrow keys. 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 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.

Scheme Procedure: signal? obj

Return #t if obj is a signal.

Scheme Procedure: make-signal value

Wrap value in a signal.

Scheme Syntax: define-signal name value

Create a top-level signal variable called 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 make-signal.

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

Scheme Procedure: signal-ref signal

Return the value stored within signal.

Scheme Procedure: signal-ref-maybe object

Return the value stored within object if object is a signal. Otherwise, return object.

Scheme Syntax: signal-let ((var signal) …) body

Evaluate body in the context of the local bindings defined by the two-element lists ((var signal) …). signal-let works like regular let, except that it derefences signal before binding to var.

Scheme Syntax: signal-let* ((var signal) …) body

Similar to 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.

Scheme Procedure: signal-set! signal-box value

Change the contents of signal to value. This procedure should almost never be used, except to bootstrap a root node of a signal graph.

Scheme Procedure: hook->signal hook init proc

Create a new signal whose initial value is init and whose future values are calculated by applying proc to the arguments passed when hook is run.

Scheme Procedure: signal-merge signal1 signal2 . rest

Create a new signal whose value is the that of the most recently updated signal in signal1, signal2, etc. The initial value is that of signal1.

Scheme Procedure: signal-zip . signals

Create a new signal whose value is a list of the values stored in signals.

Scheme Procedure: signal-map proc signal . rest

Create a new signal that applies proc to the values of SIGNAL. More than one input signal may be specified, in which case proc must accept as many arguments as there are input signals.

Scheme Procedure: signal-sample-on value-signal sample-signal

Create a new signal that takes on the value of value-signal whenever sample-signal receives a new value.

Scheme Procedure: signal-negate signal

Create a new signal whose value is the negation of signal by applying not to each value received.

Scheme Procedure: signal-fold proc init signal . rest

Create a new signal that applies proc with the value received from signal and the past value of itself, starting with init. Like signal-map, more than one input signal may be given.

Scheme Procedure: signal-filter predicate default signal

Create a new signal that takes on the value received from signal when it satisfies the procedure predicate. The value of the signal is default in the case that the predicate is never satisfied.

Scheme Procedure: signal-drop predicate default signal

Create a new signal that takes on the value received from signal when it does not satisfy the procedure predicate. The value of the signal is default in the case that the predicate is never satisfied.

Scheme Procedure: signal-drop-repeats signal [equal?]

Create a new signal that drops the value received from signal when it is equivalent to the current value. By default, equal? is used for testing equivalence.

Scheme Procedure: signal-switch predicate on off

Create a new signal whose value is that of the signal on when the signal predicate is true, or the value of the signal off otherwise.

Scheme Procedure: signal-constant constant signal

Create a new signal whose value is always constant no matter the value received from signal.

Scheme Procedure: signal-count signal [start] [step]

Create a new signal that increments a counter by step when a value from signal is received, starting from start. By default, start is 0 and step is 1.

Scheme Procedure: signal-tap proc signal

Create a new signal that applies proc for side-effects when a value from signal is received. The value of the new signal will always be the value of 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.

Scheme Procedure: signal-timestamp signal

Create a new signal whose value is a pair, the car of which is the time that the value of signal was received and the cdr of which is the received value.

Scheme Procedure: signal-time signal

Create a new signal whose value is the time that the value of signal was received.

Scheme Procedure: signal-sample step signal

Create a new signal that takes on the value of signal every step ticks.

Scheme Procedure: signal-every step

Create a new signal that emits step every step ticks.

Scheme Procedure: signal-since step signal

Create a new signal that emits the time since signal was updated ever step ticks.

Scheme Procedure: signal-delay delay signal

Create a new signal that delays propagation of signal by delay ticks..

Scheme Procedure: signal-throttle delay signal

Create a new signal that propagates signal at most once every delay ticks.

Scheme Syntax: signal-generator body

Create a new signal whose value is the most recently yielded value of the coroutine defined by body. A special yield syntax is available within body to specify which values are passed to the signal.


Next: , Up: Time   [Contents][Index]