Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
*.fas
*.so
lib/
raylib.h
shim.h
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ lib/lisp-raylib.dll:
lib/lisp-raylib-shim.dll: c/shim.c c/raylib.h
cd c && x86_64-w64-mingw32-gcc -L"../lib" -llisp-raylib -O3 -fPIC -shared -o lisp-raylib-shim.dll shim.c
mv c/lisp-raylib-shim.dll lib/

# --- MacOS --- #

macos: lib/ lib/liblisp-raylib.dylib lib/liblisp-raylib-shim.dylib raylib.h shim.h

lib/liblisp-raylib.dylib:
cd vendored/raylib-c/src/ && $(MAKE) PLATFORM=$(PLATFORM)
cp vendored/raylib-c/src/liblisp-raylib.5.5.0.dylib lib/liblisp-raylib.dylib

lib/liblisp-raylib-shim.dylib: c/shim.c lib/liblisp-raylib.dylib
$(CC) -O3 -fPIC -dynamiclib -o lib/liblisp-raylib-shim.dylib c/shim.c -Ivendored/raylib-c/src/ -Llib/ -llisp-raylib -Wl,-rpath,@loader_path
113 changes: 81 additions & 32 deletions lisp/ecl.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
;; For access to `free'.
(ffi:clines "#include <stdlib.h>")

;; On macOS, window operations must run on the main thread.
(defmacro with-main-thread (&body body)
"Execute BODY on the main thread on macOS, or directly on other platforms."
#+darwin
`(tmt:call-in-main-thread (lambda () ,@body))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this cause a lambda allocation upon every invocation?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some testing and you're right it would. Both SBCL and ECL.

;; On macOS, window operations must run on the main thread.
(defmacro with-main-thread (&body body)
  "Execute BODY on the main thread on macOS, or directly on other platforms."
  #+darwin
  (let ((thunk (gensym "THUNK")))
    `(flet ((,thunk () ,@body))
       (declare (dynamic-extent #',thunk))
       (tmt:call-in-main-thread #',thunk)))
  #-darwin
  `(progn ,@body))

Using flet and dynamic-extend makes SBCL optimise out the allocation but it doesn't work in ECL, which stays the same either way. I don't know of a way around it for ECL personally.

@fosskers fosskers Jan 13, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try some some additional optimize flags, like:

(declare (optimize (speed 3)))

within the quoted part of the macro.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey man sorry for the late response, I already tried that when I tested before and unfortunately it didn't change anything for ecl.

My thoughts are if the extra allocations aren't acceptable (which I totally get) you could close this merge and just include the changes to the makefile + the dylib loading? then just tell people that on mac window related code needs to be executed on the main thread and suggest trivial-main-thread. Then the user can just wrap the whole entry point in a call-in-main-thread call rather than each function being individually wrapped. I only did it this way as to try and hide it. That would also solve the dependency issue lol.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yes I'd like to avoid those allocations.

and just include the changes to the makefile + the dylib loading?

I'm happy to follow this as well.

Then the user can just wrap the whole entry point in a call-in-main-thread

Since I don't have a mac, could you test this out for me and confirm that it works?

#-darwin
`(progn ,@body))

;; NOTE: 2025-01-03 This is highly bespoke and comes directly from the maintainer of ECL.
(defun free! (ptr)
"A custom call to C's `free' that ensures everything is properly reset."
Expand Down Expand Up @@ -178,9 +186,10 @@
:returning :void)

(defun draw-texture (texture pos-x pos-y tint)
(draw-texture-raw (texture-pointer texture)
pos-x pos-y
(color-pointer tint)))
(with-main-thread
(draw-texture-raw (texture-pointer texture)
pos-x pos-y
(color-pointer tint))))

(ffi:def-function ("_DrawTextureV" draw-texture-v-raw)
((texture (* texture-raw))
Expand All @@ -189,9 +198,10 @@
:returning :void)

(defun draw-texture-v (texture position tint)
(draw-texture-v-raw (texture-pointer texture)
(vector2-pointer position)
(color-pointer tint)))
(with-main-thread
(draw-texture-v-raw (texture-pointer texture)
(vector2-pointer position)
(color-pointer tint))))

(ffi:def-function ("_DrawTextureRec" draw-texture-rec-raw)
((texture (* texture-raw))
Expand All @@ -201,10 +211,11 @@
:returning :void)

(defun draw-texture-rec (texture source position tint)
(draw-texture-rec-raw (texture-pointer texture)
(rectangle-pointer source)
(vector2-pointer position)
(color-pointer tint)))
(with-main-thread
(draw-texture-rec-raw (texture-pointer texture)
(rectangle-pointer source)
(vector2-pointer position)
(color-pointer tint))))

;; --- Fonts --- ;;

Expand Down Expand Up @@ -461,54 +472,90 @@ every character you intend to print in your game."

;; --- Window --- ;;

(ffi:def-function ("InitWindow" init-window)
(ffi:def-function ("InitWindow" init-window-raw)
((width :int)
(height :int)
(title :cstring))
:returning :void)

(ffi:def-function ("CloseWindow" close-window) () :returning :void)
(defun init-window (width height title)
"Initialize window and OpenGL context."
(with-main-thread (init-window-raw width height title)))

(ffi:def-function ("CloseWindow" close-window-raw) () :returning :void)

(defun close-window ()
"Close window and unload OpenGL context."
(with-main-thread (close-window-raw)))

(ffi:def-function ("InitAudioDevice" init-audio-device-raw) () :returning :void)

(defun init-audio-device ()
"Initialize audio device and context."
(with-main-thread (init-audio-device-raw)))

(ffi:def-function ("InitAudioDevice" init-audio-device) () :returning :void)
(ffi:def-function ("CloseAudioDevice" close-audio-device-raw) () :returning :void)

(ffi:def-function ("CloseAudioDevice" close-audio-device) () :returning :void)
(defun close-audio-device ()
"Close the audio device and context."
(with-main-thread (close-audio-device-raw)))

(ffi:def-function ("SetTargetFPS" set-target-fps)
(ffi:def-function ("SetTargetFPS" set-target-fps-raw)
((fps :int))
:returning :void)

(defun set-target-fps (fps)
"Set target FPS (maximum)."
(with-main-thread (set-target-fps-raw fps)))

(ffi:def-function ("WindowShouldClose" window-should-close-raw) ()
;; HACK: 2025-01-03 Bool.
:returning :unsigned-byte)

(defun window-should-close ()
(= 1 (window-should-close-raw)))
(with-main-thread (= 1 (window-should-close-raw))))

(ffi:def-function ("BeginDrawing" begin-drawing-raw) () :returning :void)

(ffi:def-function ("BeginDrawing" begin-drawing) () :returning :void)
(defun begin-drawing ()
"Setup canvas (framebuffer) to start drawing."
(with-main-thread (begin-drawing-raw)))

(ffi:def-function ("EndDrawing" end-drawing) () :returning :void)
(ffi:def-function ("EndDrawing" end-drawing-raw) () :returning :void)

(defun end-drawing ()
"End canvas drawing and swap buffers (double buffering)."
(with-main-thread (end-drawing-raw)))

(ffi:def-function ("_BeginMode2D" begin-mode-2d-raw)
((camera (* camera-2d-raw)))
:returning :void)

(defun begin-mode-2d (camera)
(begin-mode-2d-raw (camera-2d-pointer camera)))
(with-main-thread (begin-mode-2d-raw (camera-2d-pointer camera))))

(ffi:def-function ("EndMode2D" end-mode-2d-raw) () :returning :void)

(ffi:def-function ("EndMode2D" end-mode-2d) () :returning :void)
(defun end-mode-2d ()
"Ends 2D mode with custom camera."
(with-main-thread (end-mode-2d-raw)))

(ffi:def-function ("_ClearBackground" clear-background-raw)
((color (* color-raw)))
:returning :void)

(defun clear-background (color)
(clear-background-raw (color-pointer color)))
(with-main-thread (clear-background-raw (color-pointer color))))

(ffi:def-function ("DrawFPS" draw-fps)
(ffi:def-function ("DrawFPS" draw-fps-raw)
((pos-x :int)
(pos-y :int))
:returning :void)

(defun draw-fps (pos-x pos-y)
"Draw current FPS."
(with-main-thread (draw-fps-raw pos-x pos-y)))

(ffi:def-function ("_DrawText" draw-text-raw)
((text (* :char))
(pos-x :int)
Expand All @@ -522,7 +569,8 @@ every character you intend to print in your game."
(let* ((octets (ext:string-to-octets text :null-terminate t))
(ctext (si:make-foreign-data-from-array octets)))
(declare (dynamic-extent octets))
(draw-text-raw ctext pos-x pos-y font-size (color-pointer color))
(with-main-thread
(draw-text-raw ctext pos-x pos-y font-size (color-pointer color)))
(ffi:free-foreign-object ctext)))

(ffi:def-function ("_DrawTextEx" draw-text-ex-raw)
Expand All @@ -540,12 +588,13 @@ every character you intend to print in your game."
(let* ((octets (ext:string-to-octets text :null-terminate t))
(ctext (si:make-foreign-data-from-array octets)))
(declare (dynamic-extent octets))
(draw-text-ex-raw (font-pointer font)
ctext
(vector2-pointer position)
(float font-size)
(float spacing)
(color-pointer tint))
(with-main-thread
(draw-text-ex-raw (font-pointer font)
ctext
(vector2-pointer position)
(float font-size)
(float spacing)
(color-pointer tint)))
(ffi:free-foreign-object ctext)))

#+nil
Expand All @@ -561,7 +610,7 @@ every character you intend to print in your game."

(declaim (ftype (function (fixnum fixnum fixnum fixnum color) null) draw-rectangle))
(defun draw-rectangle (pos-x pos-y width height color)
(draw-rectangle-raw pos-x pos-y width height (color-pointer color)))
(with-main-thread (draw-rectangle-raw pos-x pos-y width height (color-pointer color))))

(ffi:def-function ("_DrawLine" draw-line-raw)
((start-pos-x :int)
Expand All @@ -573,7 +622,7 @@ every character you intend to print in your game."

(declaim (ftype (function (fixnum fixnum fixnum fixnum color) null) draw-line))
(defun draw-line (start-pos-x start-pos-y end-pos-x end-pos-y color)
(draw-line-raw start-pos-x start-pos-x end-pos-x end-pos-y (color-pointer color)))
(with-main-thread (draw-line-raw start-pos-x start-pos-x end-pos-x end-pos-y (color-pointer color))))

(ffi:def-function ("_DrawPixel" draw-pixel-raw)
((pos-x :int)
Expand All @@ -582,7 +631,7 @@ every character you intend to print in your game."

(declaim (ftype (function (fixnum fixnum color) null) draw-pixel))
(defun draw-pixel (pos-x pos-y color)
(draw-pixel-raw pos-x pos-x (color-pointer color)))
(with-main-thread (draw-pixel-raw pos-x pos-x (color-pointer color))))

;; --- Collision --- ;;

Expand Down
13 changes: 12 additions & 1 deletion lisp/package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ restarted. Note the use of `:dont-save' below. This is to allow the package to
be compiled with `.so' files found in one location, but run with ones from another."
(let ((dir (case target
(:linux "/usr/lib/")
(:darwin #+arm64 "/opt/homebrew/lib/"
#-arm64 "/usr/local/lib/")
(t "lib/"))))
#+linux
(progn
Expand All @@ -57,7 +59,11 @@ be compiled with `.so' files found in one location, but run with ones from anoth
#+win32
(progn
(load-shared-object (merge-pathnames "lisp-raylib.dll" dir) :dont-save t)
(load-shared-object (merge-pathnames "lisp-raylib-shim.dll" dir) :dont-save t))))
(load-shared-object (merge-pathnames "lisp-raylib-shim.dll" dir) :dont-save t))
#+darwin
(progn
(load-shared-object (merge-pathnames "liblisp-raylib.dylib" dir) :dont-save t)
(load-shared-object (merge-pathnames "liblisp-raylib-shim.dylib" dir) :dont-save t))))

#+sbcl
(load-shared-objects)
Expand All @@ -69,6 +75,11 @@ be compiled with `.so' files found in one location, but run with ones from anoth
(ffi:load-foreign-library #p"lib/liblisp-raylib.so")
(ffi:load-foreign-library #p"lib/liblisp-raylib-shim.so"))

#+(and ecl darwin)
(progn
(ffi:load-foreign-library #p"lib/liblisp-raylib.dylib")
(ffi:load-foreign-library #p"lib/liblisp-raylib-shim.dylib"))

;; --- Keyboard and Gamepad --- ;;

;; HACK: 2024-12-29 Hacked manually as I couldn't figure out a first-class way
Expand Down
Loading