158 lines
7.6 KiB
Plaintext
158 lines
7.6 KiB
Plaintext
;;;; 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. TODO.org, "drop, and unwind-protect with it", 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.fln 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.fln's reasoning, unchanged: the split is on `declare-c`.
|
|
;;;; raylib.fln 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.fln 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 .fln 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.
|
|
|
|
;; Each guard below asks the same question twice over, and the second half is
|
|
;; the one worth explaining. A body that was not written can arrive two ways:
|
|
;; as no argument at all — (with-drawing) — and as a single bare () —
|
|
;; (with-drawing ()). The second used to slip past, because one argument is one
|
|
;; argument however empty it is: the () was spliced into the expansion
|
|
;; verbatim, and the report came out of the middle of the expanded (do) saying
|
|
;; that () is not an expression, several forms away from the line anyone wrote.
|
|
;; () has no value-position meaning in the language at all, so a lone one here
|
|
;; is never a body and can be answered with the same message the missing-body
|
|
;; case gets. (do) is what to write for a body that really is meant to be
|
|
;; empty, and it is an ordinary expression that needs none of this.
|
|
;;
|
|
;; Only a *lone* () is caught, and only where the body goes. () anywhere else —
|
|
;; as a camera, as a render target — is left to fail on its own, because
|
|
;; nothing here could say anything truer about it than the compiler already
|
|
;; does.
|
|
|
|
;; The frame. Everything drawn lands on the back buffer; end-drawing swaps it
|
|
;; and waits out the frame time set by set-target-fps.
|
|
macro with-drawing(& args)
|
|
if length(args) < 1 or (length(args) == 1 and is-form-empty-list(args[0]))
|
|
quote
|
|
with-drawing-takes-a-body()
|
|
else
|
|
quote
|
|
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.fln says so beside the struct.
|
|
macro with-mode-2d(& args)
|
|
if length(args) < 2 or (length(args) == 2 and is-form-empty-list(args[1]))
|
|
quote
|
|
with-mode-2d-takes-a-camera-and-a-body()
|
|
else
|
|
quote
|
|
begin-mode-2d(~(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.
|
|
macro with-mode-3d(& args)
|
|
if length(args) < 2 or (length(args) == 2 and is-form-empty-list(args[1]))
|
|
quote
|
|
with-mode-3d-takes-a-camera-and-a-body()
|
|
else
|
|
quote
|
|
begin-mode-3d(~(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.
|
|
macro with-texture-mode(& args)
|
|
if length(args) < 2 or (length(args) == 2 and is-form-empty-list(args[1]))
|
|
quote
|
|
with-texture-mode-takes-a-target-and-a-body()
|
|
else
|
|
quote
|
|
begin-texture-mode(~(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.
|
|
macro with-scissor-mode(& args)
|
|
if length(args) < 5 or (length(args) == 5 and is-form-empty-list(args[4]))
|
|
quote
|
|
with-scissor-mode-takes-x-y-width-height-and-a-body()
|
|
else
|
|
quote
|
|
begin-scissor-mode(~(args[0]), ~(args[1]), ~(args[2]), ~(args[3]))
|
|
~@(form-rest(args, 4))
|
|
end-scissor-mode()
|