209 raylib examples written in
jank — native Clojure compiled to a real binary
through C++/LLVM, no JVM. They reach raylib as ordinary C++ through
(:include "raylib.h") and the cpp/ prefix:
no FFI layer, no binding DSL, no generated shim.
core 46 · shapes 41 · text 16 · textures 31 · models 29 · shaders 35 · audio 11 — every one of them, with a preview.
Jolt and JVM Clojure cross the boundary to C at the call: a
call marshals values across, but a raylib struct can otherwise sit in
an ordinary variable. jank draws the boundary around the
value. A Color here is a real C++ value the
compiler tracks, not a handle — and that one difference is what shapes
every example in this repo.
A jank function cannot return a Color, and one
cannot ride along in loop/recur state.
Bound to a let-local or built inline as a call argument,
it is fine. So examples thread a plain jank id — an int, a keyword —
and construct the native value at the point of use. It is also why
the lighting helper is a C header rather than a jank namespace:
jank_rlights.h keeps the light array on the C side and
exposes index-based scalar wrappers, because a jank function could
neither take nor return a Light.
if branch is type-checked
cond and case expand to an if
whose implicit trailing branch is nil, and nil
is not a Color. Picking a native value by index gives
Mismatched 'if' branch types 'Color' and 'nil'. The fix
is hand-nested if forms that all end in a concrete
value. Returning ordinary jank data from a cond is
unaffected — the clash only exists at the native boundary.
mod and quot return reals, and a raylib
int parameter rejects one outright. cpp/float
wants the opposite. And an arithmetic chain built purely from
literals and native field reads stays an unboxed f64
that fails codegen, so it has to be re-boxed through any collection
operation first. Three rules, and between them they explain most
first-compile failures.
flowchart LR
subgraph ex["209 example namespaces"]
e["starfield · raymarching · basic-pbr
penrose-tile · vr-simulator · …"]
end
subgraph sys["jank-raylib-sys — the -sys wrapper"]
bb["jank-build.bb builds raylib
with CMake, emits include-dir,
link-dir and link-library"]
hh["jank_rlights.h
C-side helpers for what
jank values cannot cross"]
end
rl["raylib, built from the
vendored submodule"]
e -->|"(:include raylib.h) + cpp/ calls"| rl
bb --> rl
e --> hh
hh --> rl
There is no binding layer to maintain, because there is nothing to
bind: each example includes the C header and calls raylib directly.
Adding an example touches exactly four places — the source namespace, a
project.clj profile, a bb.edn task, and a row
in the bb/helpers.clj registry.
No FFI DSL, no codegen, no C shim. (cpp/DrawCircle x y r cpp/RAYWHITE) is a C++ call the jank compiler emits directly.
jank compiles through C++/LLVM to a real binary. There is no Java interop to fall back on either — no Math/sin, no format. C's math.h and str take their place.
raylib is a vendored submodule, built from source by the -sys wrapper. No system raylib, and no third-party Maven or Clojars artifact is fetched.
jank_rlights.h adapts raylib's rlights.h into index-based scalar wrappers, so lighting examples need no per-file shim block.
A windowed example proves itself unattended with a timed run whose SIGALRM exit is the success signal, plus a grep for the jank/raylib failure vocabulary.
All 209 carry an animated preview in the catalog, recorded from the real running example rather than mocked up.
git clone --recurse-submodules git@github.com:burinc/b12n-raylib-jnk.git
bb info # grouped cheat-sheet of everything
bb install # build raylib + install jank-raylib-sys into ~/.m2
bb starfield # run one (opens a window)
bb run-all 10 # demo reel: every example, 10s each
Needs the jank
compiler with lein-jank 2026.06-1 or newer, a C++ compiler and CMake.
babashka
gives every example a friendly task; without it each one is a
lein with-profile +<name> run away.
Released under the zlib license — the same one raylib itself uses.