LoadTexture, UnloadTexture, the four DrawTexture variants and IsTextureValid. Nothing about them pushes against the aggregate rule: every raylib signature here takes its structs by value, and every one has an obvious pointer form the shim dereferences, so the declarations are scalars and pointers as before. The predicate is IsTextureValid and not IsTextureReady, which this version of raylib does not export at all — 5.5 renamed it, and calling the old name would be a link error rather than a silent miss. It is bound because the failure it reports is otherwise invisible: LoadTexture on a missing file returns a texture with an id of 0 and says so only on the trace log, and then every draw with it is a no-op that looks like a drawing bug. cstr's one caller used to be the window title, and its comment said so. A path is the second caller and wants far more than 256 bytes, so each caller now passes a buffer sized for what it holds. Truncating still beats reading past the end: a truncated path simply fails to open, and texture-valid? is how the program notices. None of this is in the acceptance table, and deliberately. Loading a texture needs a GL context, so anything headless would be asserting on the failure path while appearing to test the working one. It is exercised by sand.flan.
239 lines
10 KiB
Plaintext
239 lines
10 KiB
Plaintext
;;;; raylib, declared for Flan. The directory is the package (plan.org,
|
|
;;;; Modules), and (import rl "vendor:raylib") qualifies all of it as rl/…
|
|
;;;;
|
|
;;;; Nothing here names a raylib symbol. Every `declare` names a wrapper in
|
|
;;;; shim.c, and that is the whole design decision: raylib passes Color by
|
|
;;;; value and returns Vector2 by value, and how a small aggregate is passed
|
|
;;;; differs between x86-64, arm64 and wasm32. Written in C, clang classifies
|
|
;;;; each one correctly per target for free; written in emit.ml it would be
|
|
;;;; three calling conventions to reimplement and then maintain forever. This
|
|
;;;; is "one narrow host ABI, implemented twice" (plan.org, Targets), and it
|
|
;;;; is why the checker rejects an aggregate in a `declare` signature at all.
|
|
;;;;
|
|
;;;; So each struct that crosses does it through a pointer, and the `-raw`
|
|
;;;; declaration is wrapped by an ordinary Flan function just below it. The
|
|
;;;; `-raw` names are visible as rl/…-raw because there is no visibility rule
|
|
;;;; yet; they are not meant to be called.
|
|
|
|
;; Layouts are C's — no object headers anywhere — so these are exactly
|
|
;; raylib's structs and nothing marshals.
|
|
(defstruct Vector2 [x f32 y f32])
|
|
(defstruct Color [r u8 g u8 b u8 a u8])
|
|
|
|
;; Texture2D is five 4-byte fields in a row, which is the layout most likely
|
|
;; to be silently wrong: permute two of them and every field still reads as a
|
|
;; plausible number. Rectangle is four floats in x/y/width/height order.
|
|
(defstruct Texture2D [id u32 width i32 height i32 mipmaps i32 format i32])
|
|
(defstruct Rectangle [x f32 y f32 width f32 height f32])
|
|
|
|
;; KeyboardKey, the subset sand.flan uses. A keyword at a call site resolves
|
|
;; against these members at compile time and a typo is an error there.
|
|
(defenum Key
|
|
[space 32 apostrophe 39 comma 44 minus 45 period 46 slash 47
|
|
zero 48 one 49 two 50 three 51 four 52
|
|
five 53 six 54 seven 55 eight 56 nine 57
|
|
a 65 b 66 c 67 d 68 e 69 f 70 g 71 h 72 i 73
|
|
j 74 k 75 l 76 m 77 n 78 o 79 p 80 q 81 r 82
|
|
s 83 t 84 u 85 v 86 w 87 x 88 y 89 z 90
|
|
escape 256 enter 257 tab 258 backspace 259
|
|
right 262 left 263 down 264 up 265])
|
|
|
|
(defenum MouseButton
|
|
[left 0 right 1 middle 2 side 3 extra 4 forward 5 back 6])
|
|
|
|
(defenum TraceLogLevel
|
|
[all 0 trace 1 debug 2 info 3 warning 4 error 5 fatal 6 none 7])
|
|
|
|
;; ── Window ──────────────────────────────────────────────────────────
|
|
|
|
(declare init-window [width i32 height i32 title string] "flan_rl_init_window")
|
|
(declare close-window [] "flan_rl_close_window")
|
|
(declare window-should-close? [] bool "flan_rl_window_should_close")
|
|
(declare set-target-fps [fps i32] "flan_rl_set_target_fps")
|
|
(declare set-trace-log-level [level TraceLogLevel] "flan_rl_set_trace_log_level")
|
|
|
|
;; ── Input ───────────────────────────────────────────────────────────
|
|
|
|
(declare key-pressed? [key Key] bool "flan_rl_is_key_pressed")
|
|
(declare key-down? [key Key] bool "flan_rl_is_key_down")
|
|
(declare key-released? [key Key] bool "flan_rl_is_key_released")
|
|
|
|
(declare mouse-button-pressed? [button MouseButton] bool
|
|
"flan_rl_is_mouse_button_pressed")
|
|
(declare mouse-button-down? [button MouseButton] bool
|
|
"flan_rl_is_mouse_button_down")
|
|
(declare mouse-button-released? [button MouseButton] bool
|
|
"flan_rl_is_mouse_button_released")
|
|
|
|
(declare get-mouse-position-raw [out (Ptr Vector2)] "flan_rl_get_mouse_position")
|
|
|
|
(defn get-mouse-position [] Vector2
|
|
(let [v (Vector2 {})]
|
|
(get-mouse-position-raw (addr v))
|
|
v))
|
|
|
|
;; ── Colours ─────────────────────────────────────────────────────────
|
|
;;
|
|
;; A Color is four bytes in RGBA order, so it is *not* the little-endian
|
|
;; reading of the packed 0xRRGGBBAA integer — that is why get-color is a real
|
|
;; call and not a reinterpretation.
|
|
|
|
(declare get-color-raw [hex u32 out (Ptr Color)] "flan_rl_get_color")
|
|
|
|
(defn get-color [hex u32] Color
|
|
(let [c (Color {})]
|
|
(get-color-raw hex (addr c))
|
|
c))
|
|
|
|
(defconst black (Color {:r 0 :g 0 :b 0 :a 255}))
|
|
(defconst white (Color {:r 255 :g 255 :b 255 :a 255}))
|
|
|
|
;; ── Drawing ─────────────────────────────────────────────────────────
|
|
|
|
(declare begin-drawing [] "flan_rl_begin_drawing")
|
|
(declare end-drawing [] "flan_rl_end_drawing")
|
|
(declare draw-fps [x i32 y i32] "flan_rl_draw_fps")
|
|
|
|
(declare clear-background-raw [color (Ptr Color)] "flan_rl_clear_background")
|
|
|
|
(defn clear-background [color Color]
|
|
;; The copy is not ceremony: a parameter is not an assignable place
|
|
;; (spec-memory.md), so there is no address to take without one.
|
|
(let [c color]
|
|
(clear-background-raw (addr c))))
|
|
|
|
(declare draw-rectangle-raw
|
|
[x i32 y i32 width i32 height i32 color (Ptr Color)]
|
|
"flan_rl_draw_rectangle")
|
|
|
|
(defn draw-rectangle [x i32 y i32 width i32 height i32 color Color]
|
|
(let [c color]
|
|
(draw-rectangle-raw x y width height (addr c))))
|
|
|
|
;; ── Shapes texture ──────────────────────────────────────────────────
|
|
;;
|
|
;; raylib draws every shape from one atlas texture, and this pair sets and
|
|
;; reads it. It is bound here for a second reason: it is the only part of the
|
|
;; API that stores a Texture2D and a Rectangle and hands them back without
|
|
;; touching the GPU, so it is how the acceptance table checks both layouts
|
|
;; headlessly. Everything else that takes a texture needs a GL context.
|
|
;;
|
|
;; raylib substitutes a default ({1,1,1,1,7} / {0,0,1,1}) when the id or the
|
|
;; source's width or height is not positive, so a caller — and the test —
|
|
;; should keep clear of those values if it wants its own back.
|
|
|
|
(declare set-shapes-texture-raw
|
|
[texture (Ptr Texture2D) source (Ptr Rectangle)]
|
|
"flan_rl_set_shapes_texture")
|
|
|
|
(defn set-shapes-texture [texture Texture2D source Rectangle]
|
|
(let [t texture
|
|
r source]
|
|
(set-shapes-texture-raw (addr t) (addr r))))
|
|
|
|
(declare get-shapes-texture-raw [out (Ptr Texture2D)] "flan_rl_get_shapes_texture")
|
|
|
|
(defn get-shapes-texture [] Texture2D
|
|
(let [t (Texture2D {})]
|
|
(get-shapes-texture-raw (addr t))
|
|
t))
|
|
|
|
(declare get-shapes-texture-rectangle-raw
|
|
[out (Ptr Rectangle)] "flan_rl_get_shapes_texture_rectangle")
|
|
|
|
(defn get-shapes-texture-rectangle [] Rectangle
|
|
(let [r (Rectangle {})]
|
|
(get-shapes-texture-rectangle-raw (addr r))
|
|
r))
|
|
|
|
;; ── Shapes ──────────────────────────────────────────────────────────
|
|
;;
|
|
;; Rectangle intersection, which raylib computes from all four fields in
|
|
;; different ways. It is the one Rectangle call that needs no GPU, so it is
|
|
;; also how the acceptance table pins the layout: a store-and-return check is
|
|
;; symmetric and a permuted layout survives it untouched.
|
|
|
|
(declare get-collision-rec-raw
|
|
[a (Ptr Rectangle) b (Ptr Rectangle) out (Ptr Rectangle)]
|
|
"flan_rl_get_collision_rec")
|
|
|
|
(defn get-collision-rec [a Rectangle b Rectangle] Rectangle
|
|
(let [x a
|
|
y b
|
|
out (Rectangle {})]
|
|
(get-collision-rec-raw (addr x) (addr y) (addr out))
|
|
out))
|
|
|
|
;; ── Textures ────────────────────────────────────────────────────────
|
|
;;
|
|
;; Everything here needs a GL context, so a window has to be open first —
|
|
;; load-texture before init-window returns an id of 0 and raylib says so on
|
|
;; the log. texture-valid? is how that is noticed in the program rather than
|
|
;; only in the log; raylib 5.5 spells it IsTextureValid, and IsTextureReady,
|
|
;; which older code calls, does not exist in this version.
|
|
|
|
(declare load-texture-raw [path string out (Ptr Texture2D)] "flan_rl_load_texture")
|
|
|
|
(defn load-texture [path string] Texture2D
|
|
(let [t (Texture2D {})]
|
|
(load-texture-raw path (addr t))
|
|
t))
|
|
|
|
(declare texture-valid?-raw [texture (Ptr Texture2D)] bool "flan_rl_is_texture_valid")
|
|
|
|
(defn texture-valid? [texture Texture2D] bool
|
|
(let [t texture]
|
|
(texture-valid?-raw (addr t))))
|
|
|
|
(declare unload-texture-raw [texture (Ptr Texture2D)] "flan_rl_unload_texture")
|
|
|
|
(defn unload-texture [texture Texture2D]
|
|
(let [t texture]
|
|
(unload-texture-raw (addr t))))
|
|
|
|
(declare draw-texture-raw
|
|
[texture (Ptr Texture2D) x i32 y i32 tint (Ptr Color)]
|
|
"flan_rl_draw_texture")
|
|
|
|
(defn draw-texture [texture Texture2D x i32 y i32 tint Color]
|
|
(let [t texture
|
|
c tint]
|
|
(draw-texture-raw (addr t) x y (addr c))))
|
|
|
|
(declare draw-texture-v-raw
|
|
[texture (Ptr Texture2D) position (Ptr Vector2) tint (Ptr Color)]
|
|
"flan_rl_draw_texture_v")
|
|
|
|
(defn draw-texture-v [texture Texture2D position Vector2 tint Color]
|
|
(let [t texture
|
|
p position
|
|
c tint]
|
|
(draw-texture-v-raw (addr t) (addr p) (addr c))))
|
|
|
|
(declare draw-texture-ex-raw
|
|
[texture (Ptr Texture2D) position (Ptr Vector2) rotation f32 scale f32
|
|
tint (Ptr Color)]
|
|
"flan_rl_draw_texture_ex")
|
|
|
|
(defn draw-texture-ex [texture Texture2D position Vector2 rotation f32
|
|
scale f32 tint Color]
|
|
(let [t texture
|
|
p position
|
|
c tint]
|
|
(draw-texture-ex-raw (addr t) (addr p) rotation scale (addr c))))
|
|
|
|
;; A negative source width or height flips the sprite, which is how a sheet is
|
|
;; drawn facing the other way without a second image.
|
|
(declare draw-texture-rec-raw
|
|
[texture (Ptr Texture2D) source (Ptr Rectangle) position (Ptr Vector2)
|
|
tint (Ptr Color)]
|
|
"flan_rl_draw_texture_rec")
|
|
|
|
(defn draw-texture-rec [texture Texture2D source Rectangle position Vector2
|
|
tint Color]
|
|
(let [t texture
|
|
s source
|
|
p position
|
|
c tint]
|
|
(draw-texture-rec-raw (addr t) (addr s) (addr p) (addr c))))
|