diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | chickadee/scripting.scm | 10 | ||||
-rw-r--r-- | chickadee/scripting/channel.scm | 10 | ||||
-rw-r--r-- | chickadee/scripting/coroutine.scm | 90 | ||||
-rw-r--r-- | chickadee/scripting/script.scm | 90 | ||||
-rw-r--r-- | doc/api.texi | 96 |
6 files changed, 148 insertions, 150 deletions
diff --git a/Makefile.am b/Makefile.am index e80bb90..de01b08 100644 --- a/Makefile.am +++ b/Makefile.am @@ -71,7 +71,7 @@ SOURCES = \ chickadee/render.scm \ chickadee/window.scm \ chickadee/scripting/agenda.scm \ - chickadee/scripting/coroutine.scm \ + chickadee/scripting/script.scm \ chickadee/scripting/channel.scm \ chickadee/scripting.scm \ chickadee.scm \ diff --git a/chickadee/scripting.scm b/chickadee/scripting.scm index 0e29fa6..d88e422 100644 --- a/chickadee/scripting.scm +++ b/chickadee/scripting.scm @@ -20,7 +20,7 @@ #:use-module (chickadee math easings) #:use-module (chickadee scripting agenda) #:use-module (chickadee scripting channel) - #:use-module (chickadee scripting coroutine) + #:use-module (chickadee scripting script) #:export (forever sleep tween) @@ -30,7 +30,7 @@ (eval-when (eval load compile) (begin (define %public-modules - '(agenda channel coroutine)) + '(agenda channel script)) (for-each (let ((i (module-public-interface (current-module)))) (lambda (m) (module-use! i (resolve-interface @@ -42,9 +42,9 @@ (while #t body ...)) (define (sleep duration) - "Wait DURATION before resuming the current coroutine." - ;; Capture the current agenda before suspending the coroutine so - ;; that we schedule the continuation in the right place. + "Wait DURATION before resuming the current script." + ;; Capture the current agenda before suspending the script so that + ;; we schedule the continuation in the right place. (let ((agenda (current-agenda))) (yield (lambda (cont) diff --git a/chickadee/scripting/channel.scm b/chickadee/scripting/channel.scm index 0c78ffa..9402636 100644 --- a/chickadee/scripting/channel.scm +++ b/chickadee/scripting/channel.scm @@ -22,7 +22,7 @@ #:use-module (srfi srfi-9 gnu) #:use-module (srfi srfi-11) #:use-module (chickadee queue) - #:use-module (chickadee scripting coroutine) + #:use-module (chickadee scripting script) #:export (make-channel channel? channel-get @@ -58,16 +58,16 @@ (put-cont))))))) (define (channel-get channel) - "Retrieve a value from CHANNEL. The current coroutine suspends -until a value is available." + "Retrieve a value from CHANNEL. The current script suspends until a +value is available." (yield (lambda (cont) (enqueue! (channel-get-queue channel) cont) (maybe-deliver channel)))) (define (channel-put channel data) - "Send DATA to CHANNEL. The current coroutine suspends until another -coroutine is available to retrieve the value." + "Send DATA to CHANNEL. The current script suspends until another +script is available to retrieve the value." (yield (lambda (cont) (enqueue! (channel-put-queue channel) (cons data cont)) diff --git a/chickadee/scripting/coroutine.scm b/chickadee/scripting/coroutine.scm deleted file mode 100644 index 642ff83..0000000 --- a/chickadee/scripting/coroutine.scm +++ /dev/null @@ -1,90 +0,0 @@ -;;; Chickadee Game Toolkit -;;; Copyright © 2017 David Thompson <davet@gnu.org> -;;; -;;; Chickadee is free software: you can redistribute it and/or modify -;;; it under the terms of the GNU General Public License as published -;;; by the Free Software Foundation, either version 3 of the License, -;;; or (at your option) any later version. -;;; -;;; Chickadee is distributed in the hope that it will be useful, but -;;; WITHOUT ANY WARRANTY; without even the implied warranty of -;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -;;; General Public License for more details. -;;; -;;; You should have received a copy of the GNU General Public License -;;; along with this program. If not, see -;;; <http://www.gnu.org/licenses/>. - -(define-module (chickadee scripting coroutine) - #:use-module (ice-9 format) - #:use-module (srfi srfi-9) - #:use-module (srfi srfi-9 gnu) - #:export (coroutine? - coroutine-cancelled? - coroutine-running? - coroutine-complete? - spawn-coroutine - coroutine - cancel-coroutine - yield) - #:replace (yield)) - -(define-record-type <coroutine> - (make-coroutine status) - coroutine? - (status coroutine-status set-coroutine-status!)) - -(define (display-coroutine co port) - (format port "<coroutine status: ~a>" (coroutine-status co))) - -(set-record-type-printer! <coroutine> display-coroutine) - -(define (coroutine-cancelled? co) - "Return #t if CO has been cancelled." - (eq? 'cancelled (coroutine-status co))) - -(define (coroutine-running? co) - "Return #t if CO has not yet terminated or been cancelled." - (eq? 'cancelled (coroutine-status co))) - -(define (coroutine-complete? co) - "Return #t if CO has terminated." - (eq? 'cancelled (coroutine-status co))) - -(define (cancel-coroutine co) - "Prevent further execution of CO." - (set-coroutine-status! co 'cancelled) - *unspecified*) - -(define coroutine-prompt (make-prompt-tag 'coroutine)) - -(define (spawn-coroutine thunk) - "Apply THUNK as a coroutine." - (let ((co (make-coroutine 'running))) - (define (handler cont callback . args) - (define (resume . args) - ;; Call continuation that resumes the procedure, unless, of - ;; course, the coroutine has been cancelled in the meantime. - (unless (coroutine-cancelled? co) - (call-with-prompt coroutine-prompt - (lambda () (apply cont args)) - handler))) - (when (procedure? callback) - (apply callback resume args))) - (define task - (let ((dynamic-state (current-dynamic-state))) - (lambda () - (with-dynamic-state dynamic-state thunk) - (set-coroutine-status! co 'complete)))) - ;; Start the coroutine. - (call-with-prompt coroutine-prompt task handler) - co)) - -(define-syntax-rule (coroutine body ...) - "Evaluate BODY in a coroutine." - (spawn-coroutine (lambda () body ...))) - -(define (yield handler) - "Suspend the current coroutine and pass its continuation to the -procedure HANDLER." - (abort-to-prompt coroutine-prompt handler)) diff --git a/chickadee/scripting/script.scm b/chickadee/scripting/script.scm new file mode 100644 index 0000000..df2d6bb --- /dev/null +++ b/chickadee/scripting/script.scm @@ -0,0 +1,90 @@ +;;; Chickadee Game Toolkit +;;; Copyright © 2017 David Thompson <davet@gnu.org> +;;; +;;; Chickadee is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published +;;; by the Free Software Foundation, either version 3 of the License, +;;; or (at your option) any later version. +;;; +;;; Chickadee is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program. If not, see +;;; <http://www.gnu.org/licenses/>. + +(define-module (chickadee scripting script) + #:use-module (ice-9 format) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-9 gnu) + #:export (script? + script-cancelled? + script-running? + script-complete? + spawn-script + script + cancel-script + yield) + #:replace (yield)) + +(define-record-type <script> + (make-script status) + script? + (status script-status set-script-status!)) + +(define (display-script script port) + (format port "<script status: ~a>" (script-status script))) + +(set-record-type-printer! <script> display-script) + +(define (script-cancelled? script) + "Return #t if SCRIPT has been cancelled." + (eq? 'cancelled (script-status script))) + +(define (script-running? script) + "Return #t if SCRIPT has not yet terminated or been cancelled." + (eq? 'cancelled (script-status script))) + +(define (script-complete? script) + "Return #t if SCRIPT has terminated." + (eq? 'cancelled (script-status script))) + +(define (cancel-script script) + "Prevent further execution of SCRIPT." + (set-script-status! script 'cancelled) + *unspecified*) + +(define script-prompt (make-prompt-tag 'script)) + +(define (spawn-script thunk) + "Apply THUNK as a script." + (let ((script (make-script 'running))) + (define (handler cont callback . args) + (define (resume . args) + ;; Call continuation that resumes the procedure, unless, of + ;; course, the script has been cancelled in the meantime. + (unless (script-cancelled? script) + (call-with-prompt script-prompt + (lambda () (apply cont args)) + handler))) + (when (procedure? callback) + (apply callback resume args))) + (define task + (let ((dynamic-state (current-dynamic-state))) + (lambda () + (with-dynamic-state dynamic-state thunk) + (set-script-status! script 'complete)))) + ;; Start the script. + (call-with-prompt script-prompt task handler) + script)) + +(define-syntax-rule (script body ...) + "Evaluate BODY in a script." + (spawn-script (lambda () body ...))) + +(define (yield handler) + "Suspend the current script and pass its continuation to the +procedure HANDLER." + (abort-to-prompt script-prompt handler)) diff --git a/doc/api.texi b/doc/api.texi index 32a797b..1f69059 100644 --- a/doc/api.texi +++ b/doc/api.texi @@ -1134,16 +1134,16 @@ enemy follow and attack the player, or move an NPC back and forth in front of the item shop, or do both at the same time, a scripting system is a necessity. Chickadee comes with an asynchronous programming system in the @code{(chickadee scripting)} module. -Lightweight, cooperative threads known as ``coroutines'' allow the +Lightweight, cooperative threads known as ``scripts'' allow the programmer to write asynchronous code as if it were synchronous, and allow many such ``threads'' to run concurrently. -But before we dig deeper into coroutines, let's discuss the simple act +But before we dig deeper into scripts, let's discuss the simple act of scheduling tasks. @menu * Agendas:: Scheduling tasks. -* Coroutines:: Cooperative multitasking. +* Scripts:: Cooperative multitasking. * Tweening:: Animations. * Channels:: Publish data to listeners. @end menu @@ -1252,22 +1252,22 @@ Schedule @var{body} to be evaluated every @var{interval} amount of time. Repeat this @var{n} times, or indefinitely if not specified. @end deffn -@node Coroutines -@subsection Coroutines +@node Scripts +@subsection Scripts Now that we can schedule tasks, let's take things to the next level. It sure would be great if we could make procedures that described a series of actions that happened over time, especially if we could do so without contorting our code into a nest of callback procedures. -This is where coroutines come in. With coroutines we can write code -in a linear way, in a manner that appears to be synchronous, but with -the ability to suspend periodically in order to let other coroutines -have a turn and prevent blocking the game loop. Building on top of -the scheduling that agendas provide, here is a coroutine that models a -child trying to get their mother's attention: +This is where scripts come in. With scripts we can write code in a +linear way, in a manner that appears to be synchronous, but with the +ability to suspend periodically in order to let other scripts have a +turn and prevent blocking the game loop. Building on top of the +scheduling that agendas provide, here is a script that models a child +trying to get their mother's attention: @example -(coroutine +(script (while #t (display "mom!") (newline) @@ -1275,67 +1275,66 @@ child trying to get their mother's attention: @end example This code runs in an endless loop, but the @code{wait} procedure -suspends the coroutine and schedules it to be run later by the agenda. +suspends the script and schedules it to be run later by the agenda. So, after each iteration of the loop, control is returned back to the game loop and the program is not stuck spinning in a loop that will never exit. Pretty neat, eh? -Coroutines can suspend to any capable handler, not just the agenda. -The @code{yield} procedure will suspend the current coroutine and pass +Scripts can suspend to any capable handler, not just the agenda. +The @code{yield} procedure will suspend the current script and pass its ``continuation'' to a handler procedure. This handler procedure could do anything. Perhaps the handler stashes the continuation somewhere where it will be resumed when the user presses a specific key on the keyboard, or maybe it will be resumed when the player picks up an item off of the dungeon floor; the sky is the limit. -Sometimes it is necessary to abruptly terminate a coroutine after it -has been started. For example, when an enemy is defeated their AI -routine needs to be shut down. When a coroutine is spawned, a handle -to that coroutine is returned that can be used to cancel it when -desired. +Sometimes it is necessary to abruptly terminate a script after it has +been started. For example, when an enemy is defeated their AI routine +needs to be shut down. When a script is spawned, a handle to that +script is returned that can be used to cancel it when desired. @example -(define co (coroutine (while #t (display "hey\n") (wait 60)))) +(define script (script (while #t (display "hey\n") (wait 60)))) ;; sometime later -(cancel-coroutine co) +(cancel-script script) @end example -@deffn {Procedure} spawn-coroutine @var{thunk} -Apply @var{thunk} as a coroutine and return a handle to it. +@deffn {Procedure} spawn-script @var{thunk} +Apply @var{thunk} as a script and return a handle to it. @end deffn -@deffn {Syntax} coroutine @var{body} @dots{} -Evaluate @var{body} as a coroutine and return a handle to it. +@deffn {Syntax} script @var{body} @dots{} +Evaluate @var{body} as a script and return a handle to it. @end deffn -@deffn {Procedure} coroutine? @var{obj} -Return @code{#t} if @var{obj} is a coroutine handle. +@deffn {Procedure} script? @var{obj} +Return @code{#t} if @var{obj} is a script handle. @end deffn -@deffn {Procedure} coroutine-cancelled? @var{obj} +@deffn {Procedure} script-cancelled? @var{obj} Return @code{#t} if @var{obj} has been cancelled. @end deffn -@deffn {Procedure} coroutine-running? @var{obj} +@deffn {Procedure} script-running? @var{obj} Return @code{#t} if @var{obj} has not yet terminated or been cancelled. @end deffn -@deffn {Procedure} coroutine-complete? @var{obj} +@deffn {Procedure} script-complete? @var{obj} Return @code{#t} if @var{obj} has terminated. @end deffn -@deffn {Procedure} cancel-coroutine @var{co} -Prevent further execution of the coroutine @var{co}. +@deffn {Procedure} cancel-script @var{co} +Prevent further execution of the script @var{co}. @end deffn @deffn {Procedure} yield @var{handler} -Suspend the current coroutine and pass its continuation to the +Suspend the current script and pass its continuation to the procedure @var{handler}. @end deffn @deffn {Procedure} wait @var{duration} -Wait @var{duration} before resuming the current coroutine. +Wait @var{duration} before resuming the current script. @end deffn @deffn {Procedure} channel-get @var{channel} @@ -1352,11 +1351,11 @@ Evaluate @var{body} in an endless loop. Tweening is the process of transitioning something from an initial state to a final state over a pre-determined period of time. In other words, tweening is a way to create animation. The @code{tween} -procedure can be used within any coroutine like so: +procedure can be used within any script like so: @example (define x 0) -(coroutine +(script ;; 0 to 100 in 60 ticks of the agenda. (tween 60 0 100 (lambda (y) (set! x y)))) @end example @@ -1369,8 +1368,8 @@ amount of time between each update of the animation. To control how the animation goes from the initial to final state, an ``easing'' procedure may be specified. By default, the @code{smoothstep} easing is used, which is a more pleasing default -than a simplistic linear function. @xref{Easings} for a complete -list of available easing procedures. +than a simplistic linear function. @xref{Easings} for a complete list +of available easing procedures. The @var{interpolate} procedure computes the values in between @var{start} and @var{end}. By default, linear interpolation (``lerp'' @@ -1380,23 +1379,22 @@ for short) is used. @node Channels @subsection Channels -Channels are a tool for communicating amongst different coroutines. -One coroutine can write a value to the channel and another can read -from it. Reading or writing to a channel suspends that coroutine -until there is someone on the other end of the line to complete the -transaction. +Channels are a tool for communicating amongst different scripts. One +script can write a value to the channel and another can read from it. +Reading or writing to a channel suspends that script until there is +someone on the other end of the line to complete the transaction. Here's a simplistic example: @example (define c (make-channel)) -(coroutine +(script (forever (let ((item (channel-get c))) (pk 'got item)))) -(coroutine +(script (channel-put c 'sword) (channel-put c 'shield) (channel-put c 'potion)) @@ -1411,11 +1409,11 @@ Return @code{#t} if @var{obj} is a channel. @end deffn @deffn {Procedure} channel-get @var{channel} -Retrieve a value from @var{channel}. The current coroutine suspends +Retrieve a value from @var{channel}. The current script suspends until a value is available. @end deffn @deffn {Procedure} channel-put @var{channel} @var{data} -Send @var{data} to @var{channel}. The current coroutine suspends -until another coroutine is available to retrieve the value. +Send @var{data} to @var{channel}. The current script suspends until +another script is available to retrieve the value. @end deffn |