;;;; The begin/end pairs, as macros that cannot come apart. ;;;; ;;;; raylib has five pairs this package binds — drawing, 2D camera, 3D camera, ;;;; render texture, scissor — and every one of them is the same hazard: two ;;;; calls that have to bracket a body, written by hand, with nothing checking ;;;; that the second one is there or that it is the matching one. An ;;;; EndDrawing that is missing hangs the frame; an EndMode2D that should have ;;;; been EndMode3D corrupts the matrix stack; a pair that drifted apart during ;;;; an edit does either. NEXT.md put `unwind-protect` for this and rejected ;;;; it, with the note that these are "three-line macros once the expander ;;;; lands". They are, and this is them. ;;;; ;;;; (rl/with-drawing ;;;; (rl/clear-background rl/raywhite) ;;;; (rl/draw-fps 10 10)) ;;;; ;;;; expands to exactly what the caller used to type, in the order they used ;;;; to type it: ;;;; ;;;; (do (rl/begin-drawing) ;;;; (rl/clear-background rl/raywhite) ;;;; (rl/draw-fps 10 10) ;;;; (rl/end-drawing)) ;;;; ;;;; The argument-taking ones put the argument where it always went, evaluated ;;;; once, in place. There is no `let` and no `gensym` anywhere below: nothing ;;;; here binds a name, so there is no name for a caller's to collide with, ;;;; and introducing one in a deliberately non-hygienic macro that does not ;;;; need it would be a step backwards. ;;;; ;;;; ── What this does not fix, and why it cannot ─────────────────────── ;;;; ;;;; The bug class removed is "the End* is missing, wrong, or no longer ;;;; beside its Begin*". The bug class NOT removed is a body that leaves ;;;; through the unwind path instead of the bottom: a `return`, or an ;;;; `invoke-restart` reaching an enclosing `restart-case`. Both of those skip ;;;; the rest of the `do` and the `End*` with it, exactly as they would skip a ;;;; hand-written one. ;;;; ;;;; The obvious fix is `defer`, and it is not available here. `defer` is a ;;;; compile-time construct — the forms are copied into the function's exit ;;;; paths — so docs/BUILT.md refuses it inside a loop or a branch, and a ;;;; begin/end pair lives inside the game loop essentially always. Were it ;;;; permitted there it would be wrong in the worse direction: one EndDrawing ;;;; at function exit for N BeginDrawings. A macro expanding to a defer would ;;;; therefore compile at a function body's top level and be refused in the ;;;; one place anybody writes it, which is a worse thing to hand somebody than ;;;; a macro that is honest about its extent. ;;;; ;;;; So the discipline sand.flan already writes down stays the discipline: ;;;; keep the restart boundary OUTSIDE the pair, so choosing `continue` for a ;;;; frame abandons the update and still reaches the drawing. spec-conditions ;;;; §5 is the rule behind that — a transfer runs the intervening frames' ;;;; defers and moves control, and there are no defers here to run. ;;;; ;;;; ── Why this is a file of its own ─────────────────────────────────── ;;;; ;;;; vector.flan's reasoning, unchanged: the split is on `declare-c`. ;;;; raylib.flan is the package's statement about C, it is the file the header ;;;; check reads hand-written signatures out of, and a wrong line in it stops ;;;; the build. There is not one `declare-c` below — every macro here expands ;;;; into names raylib.flan already declares — so there is nothing for the ;;;; header check to read and nothing raylib can make wrong. A package is a ;;;; directory, so this is another .flan beside the others and is qualified ;;;; `rl/` like the rest of it. ;;;; ;;;; The names come out qualified: the importer writes `(rl/with-drawing …)` ;;;; and a bare `(with-drawing …)` is an unknown name there, the rule every ;;;; declaration in this package follows. The expansions name this package's ;;;; own functions unqualified and the expander qualifies them on the way out ;;;; — see test/programs/pkgs/mac/mac.flan, which is that rule's worked ;;;; example. ;;;; ;;;; Each macro answers the value of its `End*` call, which is (). A pair was ;;;; never an expression worth reading anyway. ;; The frame. Everything drawn lands on the back buffer; end-drawing swaps it ;; and waits out the frame time set by set-target-fps. (defmacro with-drawing [args] (if (< (len args) 1) `(with-drawing-takes-a-body) `(do (begin-drawing) ~@args (end-drawing)))) ;; The 2D camera. The argument is a Camera2D value, evaluated once where it ;; always was. Remember that a fresh (Camera2D {}) has zoom 0.0 and is not ;; usable as an identity — raylib.flan says so beside the struct. (defmacro with-mode-2d [args] (if (< (len args) 2) `(with-mode-2d-takes-a-camera-and-a-body) `(do (begin-mode-2d ~(at args 0)) ~@(form-rest args 1) (end-mode-2d)))) ;; The 3D camera. Same shape, same argument-once rule, and the pair matters ;; more here than anywhere: ending a 3D mode with end-mode-2d type-checks ;; fine and leaves the projection matrix wrong for everything after it. (defmacro with-mode-3d [args] (if (< (len args) 2) `(with-mode-3d-takes-a-camera-and-a-body) `(do (begin-mode-3d ~(at args 0)) ~@(form-rest args 1) (end-mode-3d)))) ;; Render to a texture instead of the screen. The target's texture comes out ;; of the GPU upside down, so drawing it back wants a negative source height — ;; that correction is the caller's and is deliberately not hidden here, since ;; it belongs with the draw and not with the mode. (defmacro with-texture-mode [args] (if (< (len args) 2) `(with-texture-mode-takes-a-target-and-a-body) `(do (begin-texture-mode ~(at args 0)) ~@(form-rest args 1) (end-texture-mode)))) ;; Clip to a rectangle, in screen pixels with y down from the top. Four ;; scalars rather than a Rectangle, because that is what BeginScissorMode ;; takes and this file is not the place to invent a second spelling. (defmacro with-scissor-mode [args] (if (< (len args) 5) `(with-scissor-mode-takes-x-y-width-height-and-a-body) `(do (begin-scissor-mode ~(at args 0) ~(at args 1) ~(at args 2) ~(at args 3)) ~@(form-rest args 4) (end-scissor-mode))))