78 raylib examples on the JVM, calling the C library
directly through coffi over JDK 22+'s Foreign
Function & Memory API. No JNI, no wrapper library, no codegen —
defcfn binds a C function and you call it.
core 23 · models 21 · shapes 15 · games 9 · audio 4 · text 3 · textures 2 · shaders 1 — every one of them, with a preview.
Calling C from the JVM used to mean JNI: a C shim, a build step, and a native library you had to ship yourself. Project Panama removed the shim. coffi puts a Clojure face on it, and what's left is small enough to read in one sitting — which is what makes this a decent place to learn the FFI rather than just a pile of games.
defcfn takes the C name, a vector of parameter types
and a return type, and interns a var. That is the whole binding
layer — src/raylib/ is roughly 220 of these, split to
mirror raylib's own module layout, and adding one is a five-line
diff rather than a build-system change.
A Vector2 crossing the boundary is
{:x 100.0 :y 200.0} on the Clojure side; coffi
serializes it from the defalias layout. Nothing is
wrapped in a handle object, so ordinary assoc and
destructuring work on values that are about to become C structs.
raylib mutates several structs through a pointer. Those calls
allocate in a confined-arena, serialize in, call, and
deserialize back out — the one place the abstraction deliberately
stops hiding the machine, because the arena's lifetime is the thing
you actually have to reason about.
Every example starts an embedded nREPL on port 7888. Connect an editor to a running window and redefine a draw function mid-frame — the loop picks up the new var on its next pass, without a restart.
flowchart LR
subgraph ex["78 example namespaces"]
e["asteroids · tetris · lorenz-attractor
basic-lighting · solar-system · …"]
end
subgraph bind["src/raylib — the binding layer"]
d["defcfn per C function,
split by raylib module"]
s["structs.clj — defalias
layouts for Color, Vector2,
Rectangle, Camera3D, …"]
end
co["coffi"]
pa["JDK 22+ Panama
Foreign Function & Memory API"]
rl["libraylib 5.5.0
bundled under libs/"]
e --> d
d --> s
d --> co
co --> pa
pa -->|"C ABI, no JNI shim"| rl
Adding an example touches exactly four places — the source namespace, a
deps.edn alias, a bb.edn task, and a row in
the bb/helpers.bb registry. Miss the registry row and the
example still runs, but nothing lists it.
Panama links raylib's C ABI directly. There is no C file in this repo and nothing to compile before you start.
raylib 5.5.0 ships prebuilt for macOS, Linux and Windows under libs/, picked by OS and architecture at load time.
An embedded nREPL on port 7888 per example, so a draw function can be redefined without closing the game.
A clj-kondo hook teaches it to see through defcfn, so a binding called with the wrong arity is caught statically instead of as a native crash.
bb check requires all 104 namespaces without opening a window — the check that catches a broken example the one you ran wouldn't.
All 78 carry an animated preview in the catalog, captured from the real running example rather than mocked up.
git clone git@github.com:burinc/b12n-raylib-clj.git
cd b12n-raylib-clj
bb info # grouped cheat-sheet of every task
bb asteroids # run one (opens a window)
bb examples # all 78, grouped by category
bb check # headless compile of every namespace
Needs JDK 22 or newer — that is where the Foreign
Function & Memory API arrives, and there is no fallback on an older
JVM.
babashka
gives every example a friendly task and is the only path that works on
both macOS and Linux; without it, macOS can use
clojure -M:<alias> directly.
Released under the zlib license — the same one raylib itself uses.