summaryrefslogtreecommitdiff
path: root/chickadee
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee')
-rw-r--r--chickadee/game-loop.scm98
-rw-r--r--chickadee/sdl.scm207
2 files changed, 98 insertions, 207 deletions
diff --git a/chickadee/game-loop.scm b/chickadee/game-loop.scm
new file mode 100644
index 0000000..09f8a7b
--- /dev/null
+++ b/chickadee/game-loop.scm
@@ -0,0 +1,98 @@
+;;; Chickadee Game Toolkit
+;;; Copyright © 2016, 2018 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 game-loop)
+ #:export (run-game*
+ abort-game))
+
+
+;;;
+;;; Error handling
+;;;
+
+(define (call-with-error-handling handler thunk)
+ (if handler
+ (let ((stack #f))
+ (catch #t
+ (lambda ()
+ (thunk)
+ #f)
+ (lambda (key . args)
+ (handler stack key args)
+ #t)
+ (lambda (key . args)
+ (set! stack (make-stack #t 3)))))
+ (begin
+ (thunk)
+ #f)))
+
+(define-syntax-rule (with-error-handling handler body ...)
+ (call-with-error-handling handler (lambda () body ...)))
+
+(define (default-error-handler stack key args)
+ (apply throw key args))
+
+
+;;;
+;;; Game loop kernel
+;;;
+
+(define game-loop-prompt-tag (make-prompt-tag 'game-loop))
+
+(define (abort-game)
+ (abort-to-prompt game-loop-prompt-tag #f))
+
+(define* (run-game* #:key update render time error
+ (update-hz 60))
+ (let ((timestep (round (/ 1000 update-hz))))
+ (call-with-prompt game-loop-prompt-tag
+ (lambda ()
+ ;; Catch SIGINT and kill the loop.
+ (sigaction SIGINT
+ (lambda (signum)
+ (abort-game)))
+ ;; A simple analogy is that we are filling up a bucket
+ ;; with water. When the bucket fills up to a marked
+ ;; line, we dump it out. Our water is time, and each
+ ;; time we dump the bucket we update the game. Updating
+ ;; the game on a fixed timestep like this yields a
+ ;; stable simulation.
+ (let loop ((previous-time (time))
+ (buffer 0))
+ (let* ((current-time (time))
+ (delta (- current-time previous-time)))
+ (let update-loop ((buffer (+ buffer delta)))
+ (if (>= buffer timestep)
+ ;; Short-circuit the update loop if an error
+ ;; occurred, and reset the current time to now in
+ ;; order to discard the undefined amount of time
+ ;; that was spent handling the error.
+ (if (with-error-handling error (update timestep))
+ (loop (time) 0)
+ (begin
+ (usleep 1)
+ (update-loop (- buffer timestep))))
+ (begin
+ ;; We render upon every iteration of the loop, and
+ ;; thus rendering is decoupled from updating.
+ ;; It's possible to render multiple times before
+ ;; an update is performed.
+ (if (with-error-handling error (render (/ buffer timestep)))
+ (loop (time) 0)
+ (loop current-time buffer))))))))
+ (lambda (cont callback)
+ #f))))
diff --git a/chickadee/sdl.scm b/chickadee/sdl.scm
deleted file mode 100644
index 948b351..0000000
--- a/chickadee/sdl.scm
+++ /dev/null
@@ -1,207 +0,0 @@
-;;; Chickadee Game Toolkit
-;;; Copyright © 2018 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/>.
-
-;;; Commentary:
-;;
-;; Simple SDL + OpenGL game loop implementation.
-;;
-;;; Code:
-
-(define-module (chickadee sdl)
- #:use-module (sdl2)
- #:use-module (sdl2 events)
- #:use-module (sdl2 input game-controller)
- #:use-module (sdl2 input joystick)
- #:use-module (sdl2 input text)
- #:use-module (sdl2 mixer)
- #:use-module (sdl2 video)
- #:use-module (chickadee)
- #:use-module (chickadee math matrix)
- #:use-module (chickadee render)
- #:use-module (chickadee render color)
- #:use-module (chickadee render gl)
- #:use-module (chickadee render gpu)
- #:use-module (chickadee render viewport)
- #:export (current-window
- run-game/sdl))
-
-(define *controllers* (make-hash-table))
-
-(define (lookup-controller joystick-id)
- (hashv-ref *controllers* joystick-id))
-
-(define (add-controller joystick-index)
- (let ((controller (open-game-controller joystick-index)))
- (hashv-set! *controllers*
- (joystick-instance-id
- (game-controller-joystick controller))
- controller)
- controller))
-
-(define (remove-controller joystick-id)
- (hashv-remove! *controllers* joystick-id))
-
-(define (open-all-controllers)
- (let loop ((i 0))
- (when (< i (num-joysticks))
- (when (game-controller-index? i)
- (add-controller i))
- (loop (+ i 1)))))
-
-(define current-window (make-parameter #f))
-
-(define* (run-game/sdl #:key
- (window-title "Chickadee!")
- (window-width 640)
- (window-height 480)
- window-fullscreen?
- (update-hz 60)
- (load (const #t))
- (update (const #t))
- (draw (const #t))
- (quit abort-game)
- (key-press (const #t))
- (key-release (const #t))
- (text-input (const #t))
- (mouse-press (const #t))
- (mouse-release (const #t))
- (mouse-move (const #t))
- (controller-add (const #t))
- (controller-remove (const #t))
- (controller-press (const #t))
- (controller-release (const #t))
- (controller-move (const #t))
- error)
- (sdl-init)
- (false-if-exception (mixer-init))
- (open-audio)
- (start-text-input)
- (open-all-controllers)
- (let* ((window (make-window #:opengl? #t
- #:title window-title
- #:size (list window-width window-height)
- #:fullscreen? window-fullscreen?))
- (gl-context (make-gl-context window))
- (default-viewport (make-viewport 0 0 window-width window-height))
- (default-projection (orthographic-projection 0 window-width
- window-height 0
- 0 1)))
- (define (invert-y y)
- ;; SDL's origin is the top-left, but our origin is the bottom
- ;; left so we need to invert Y coordinates that SDL gives us.
- (- window-height y))
- (define (input-sdl)
- (define (process-event event)
- (cond
- ((quit-event? event)
- (quit))
- ((keyboard-down-event? event)
- (key-press (keyboard-event-key event)
- (keyboard-event-scancode event)
- (keyboard-event-modifiers event)
- (keyboard-event-repeat? event)))
- ((keyboard-up-event? event)
- (key-release (keyboard-event-key event)
- (keyboard-event-scancode event)
- (keyboard-event-modifiers event)))
- ((text-input-event? event)
- (text-input (text-input-event-text event)))
- ((mouse-button-down-event? event)
- (mouse-press (mouse-button-event-button event)
- (mouse-button-event-clicks event)
- (mouse-button-event-x event)
- (invert-y (mouse-button-event-y event))))
- ((mouse-button-up-event? event)
- (mouse-release (mouse-button-event-button event)
- (mouse-button-event-x event)
- (invert-y (mouse-button-event-y event))))
- ((mouse-motion-event? event)
- (mouse-move (mouse-motion-event-x event)
- (invert-y (mouse-motion-event-y event))
- (mouse-motion-event-x-rel event)
- (- (mouse-motion-event-y-rel event))
- (mouse-motion-event-buttons event)))
- ((and (controller-device-event? event)
- (eq? (controller-device-event-action event) 'added))
- (controller-add (add-controller
- (controller-device-event-which event))))
- ((and (controller-device-event? event)
- (eq? (controller-device-event-action event) 'removed))
- (let ((controller (lookup-controller
- (controller-device-event-which event))))
- (controller-remove controller)
- (remove-controller (controller-device-event-which event))
- (close-game-controller controller)))
- ((controller-button-down-event? event)
- (controller-press (lookup-controller
- (controller-button-event-which event))
- (controller-button-event-button event)))
- ((controller-button-up-event? event)
- (controller-release (lookup-controller
- (controller-button-event-which event))
- (controller-button-event-button event)))
- ((controller-axis-event? event)
- (controller-move (lookup-controller
- (controller-axis-event-which event))
- (controller-axis-event-axis event)
- (/ (controller-axis-event-value event) 32768.0)))))
- ;; Process all pending events.
- (let loop ((event (poll-event)))
- (when event
- (process-event event)
- (loop (poll-event)))))
- (define (update-sdl dt)
- (input-sdl)
- (update dt)
- ;; Free any GPU resources that have been GC'd.
- (gpu-reap!))
- (define (render-sdl-opengl alpha)
- ;; Switch to the null viewport to ensure that
- ;; the default viewport will be re-applied and
- ;; clear the screen.
- (gpu-state-set! *viewport-state* null-viewport)
- (with-viewport default-viewport
- (with-projection default-projection
- (draw alpha)))
- (swap-gl-window window))
- (dynamic-wind
- (const #t)
- (lambda ()
- (parameterize ((current-window window))
- ;; Attempt to activate vsync, if possible. Some systems do
- ;; not support setting the OpenGL swap interval.
- (catch #t
- (lambda ()
- (set-gl-swap-interval! 'vsync))
- (lambda args
- (display "warning: could not enable vsync\n"
- (current-error-port))))
- (load)
- ;; Notify about all controllers that were already connected
- ;; when the game was launched because SDL will not create
- ;; events for them.
- (hash-for-each (lambda (key controller)
- (controller-add controller))
- *controllers*)
- (run-game #:update update-sdl
- #:render render-sdl-opengl
- #:error error
- #:time sdl-ticks
- #:update-hz update-hz)))
- (lambda ()
- (delete-gl-context! gl-context)
- (close-window! window)))))