This weekend, I wanted to have dynamic access to SDL for some interactive graphing, and again ran into the problem from More Fun and Swearing with Scheme. With more experienced eyes, I read the Chez Scheme User’s Guide and discovered everything I’d done wrong a year+ ago.
First, install Thunderchez somewhere, I just put it in
In particular, read ffi-utils.sls which provides somewhat easier wrapping of binary flags; thunderchez is a very low-level port.
Second, install SDL2, I used
port install libsdl2 and that puts it in
/opt/local/lib, but in production I’ll use a local dir. Read the SDL2 API for reference.
Took me a few hours to translate the basic functions I needed, and now I have:
#!/usr/bin/env scheme-script ;; sdltest.ss ;; Copyright © 2020 by Mark Damon Hughes. All Rights Reserved. (import (chezscheme) (sdl2) ;; thunderchez ) ;; Set this to your location. I used `port install libsdl2`, YMMV. ;; In production, I'll use a local dir with libs. (load-shared-object "/opt/local/lib/libSDL2.dylib") (define kWindowWidth 320) (define kWindowHeight 240) (define gWindow #f) (define gRender #f) (define gState 'quit) (define (gr-init title x y w h) (sdl-set-main-ready) (sdl-init sdl-initialization-everything) (set! gWindow (sdl-create-window title x y w h (sdl-window-flags 'shown 'allow-highdpi)) ) (set! gRender (sdl-create-renderer gWindow -1 (sdl-renderer-flags 'accelerated)) ) (sdl-show-window gWindow) (sdl-raise-window gWindow) ) (define (gr-event) (make-ftype-pointer sdl-event-t (foreign-alloc (ftype-sizeof sdl-event-t))) ) (define (gr-rect x y w h) (let [ (rect (make-ftype-pointer sdl-rect-t (foreign-alloc (ftype-sizeof sdl-rect-t)))) ] (ftype-set! sdl-rect-t (x) rect x) (ftype-set! sdl-rect-t (y) rect y) (ftype-set! sdl-rect-t (w) rect w) (ftype-set! sdl-rect-t (h) rect h) rect )) (define (gr-mainloop) (let [ (event (gr-event)) ] (let mainloop  ;; event (sdl-poll-event event) (let [ (evt-type (ftype-ref sdl-event-t (type) event)) ] (cond [(eqv? evt-type (sdl-event-type 'quit)) (set! gState 'quit)] [(eq? gState 'main) (main-event evt-type event) ] )) ;; TODO: loop polling until no event left ;; update (case gState [(main) (main-update) ] ) ;; render (sdl-render-clear gRender) (case gState [(main) (main-render) ] ) (sdl-render-present gRender) ;; loop ;; TODO: wait timer, currently burns CPU (unless (eq? gState 'quit) (mainloop)) )) (gr-shutdown) ) (define (gr-shutdown) (sdl-quit) ) (define (main-event evt-type event) #f ;; no event handling yet ) (define (main-update) #f ;; no updates yet ) (define (main-render) (sdl-set-render-draw-color gRender 0 255 0 255) ;; green (sdl-render-fill-rect gRender (gr-rect 0 0 kWindowWidth kWindowHeight)) ) (define (main argv) (gr-init "Hello, Thunderchez!" 100 100 kWindowWidth kWindowHeight) (set! gState 'main) (gr-mainloop) ) (main (command-line-arguments))
To start it I just need:
% CHEZSCHEMELIBDIRS=thunderchez-trunk: ./sdltest.ss
Green window means it’s working! And it can be closed with the window close button or Cmd-Q, but menu or other events don’t work yet.
So… to port my graphics library from Chicken to Chez is probably a week’s work, maybe less—I only fill rects, blit images, play sounds, and read a few events. My sound support on Chicken is minimal because the maintainer didn’t bother to port the audio API, I had to do that myself.
Rehosting my game on Chez is an unknowably difficult debugging problem, but I code in a way that’s usually portable. Large chunks of my large
marklib library exist only to work around the non-portable parts and missing features of R5RS.
But the advantages are:
- Chez’s R6RS is a better language than Chicken’s R5RS++/R7RS-not-really.
- Chez is much faster, sometimes just 10-25%, sometimes hundreds of times, even compiled.
- Chez’s JIT compiler is fast and invisible. Working on Chez is instant gratification. Chicken has a 30-second compile before I see anything, which has been impossibly frustrating.
At the very least, if I get my graphics library ported I can do the graphing I wanted last weekend.