Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 32 additions & 42 deletions xsofy/render.lg
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@
(swap! sgr-pen assoc :bg c))))

;; Synchronized output (DEC 2026) lives in xsofy.screenfx now (shared with the
;; title/descent screens), used here as sfx/sync-begin! and sfx/flush-frame!.
;; title/descent screens). Full-screen frames wrap their emit body in
;; (sfx/with-frame ...), which opens the sync and always closes it (end-sync +
;; flush) via finally — so a frame can't leave the sync mode dangling.

;; --- Appearance buffer (render-perf: skip frame-to-frame unchanged cells) ---
;; The real per-move waste isn't redundant same-color runs (SGR elision above
Expand Down Expand Up @@ -825,21 +827,20 @@
(let [cur-dirty (vfx-dirty-cells vfx)
;; restore cells from previous frame that aren't in current frame
to-restore (core/into prev-dirty cur-dirty)]
;; restore all previously dirty cells first
(sfx/sync-begin!)
(when (seq prev-dirty)
(restore-dirty-cells! world r prev-dirty))
;; render current vfx overlay
(render-vfx-frame (assoc world :vfx vfx) r)
(sfx/flush-frame!)
;; restore all previously dirty cells first, then paint this frame's
;; overlay — all inside one synced frame
(sfx/with-frame
(when (seq prev-dirty)
(restore-dirty-cells! world r prev-dirty))
;; render current vfx overlay
(render-vfx-frame (assoc world :vfx vfx) r))
(sfx/pause! (vfx-frame-delay vfx))
(let [next-vfx (tick-vfx vfx)]
(if (seq next-vfx)
(recur next-vfx cur-dirty)
;; final frame — restore all dirty cells in one more synced pass
(do (sfx/sync-begin!)
(restore-dirty-cells! world r cur-dirty)
(sfx/flush-frame!)))))))))
(sfx/with-frame
(restore-dirty-cells! world r cur-dirty)))))))))

;; --- Item Display Helpers ---

Expand Down Expand Up @@ -924,7 +925,7 @@
inv)))))

(defn render-quick-menu [world]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
action (or (:menu-action world) :equip)
items (menu-filter world action)
Expand Down Expand Up @@ -956,13 +957,12 @@
fg bg))))
(ui/write-line inner (dec (:h inner))
" [a-z] select [enter] confirm [esc] cancel"
[60 60 60] [20 18 16])
(sfx/flush-frame!)))
[60 60 60] [20 18 16]))))

;; --- Message Log Screen (modal) ---

(defn render-messages-screen [world]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
r (ui/rect 1 1 tw th)
inner (ui/box r "Message Log" [180 170 140] ui/bg)
Expand All @@ -988,8 +988,7 @@
(str (inc (:start window)) "-" (:end window) "/" (:count window))
"0/0")
" [k/up older j/down newer PgUp/PgDn g/G Esc]")
[80 80 80] ui/bg)
(sfx/flush-frame!)))
[80 80 80] ui/bg))))

;; --- Help Screen (modal) ---

Expand Down Expand Up @@ -1018,7 +1017,7 @@
(swap! row inc)))))

(defn render-help-screen [world]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
r (ui/rect 1 1 tw th)
inner (ui/box r "Keybindings" [180 170 140] ui/bg)
Expand All @@ -1028,8 +1027,7 @@
(render-help-column right (subvec help-categories 2))
(ui/write-line inner (dec (:h inner))
" [press any key to close]"
[80 80 80] ui/bg)
(sfx/flush-frame!)))
[80 80 80] ui/bg))))

;; --- Inventory Screen (modal) ---

Expand Down Expand Up @@ -1091,7 +1089,7 @@
(ui/write-line r (inc @rune-row) cmds [120 120 100] ui/bg)))))))

(defn render-inventory-screen [world]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
r (ui/rect 1 1 tw th)
inner (ui/box r "Inventory" [180 170 140] ui/bg)
Expand Down Expand Up @@ -1148,8 +1146,7 @@
;; controls at bottom
(ui/write-line inner (- (:h inner) 1)
" [a-z/jk] select [esc/i] close"
[60 60 60] ui/bg)
(sfx/flush-frame!)))
[60 60 60] ui/bg))))

;; --- Death Screen ---

Expand Down Expand Up @@ -1180,7 +1177,7 @@
(sfx/scatter-runes tw th 30 [200 50 30])) ;; crimson runes

(defn render-death-screen [world runes scroll-offset frame]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
cx (quot tw 2)
cy (quot th 2)
Expand Down Expand Up @@ -1323,13 +1320,12 @@
(term/move-cursor (max 1 (- cx (quot (count prompt) 2))) (dec th))
(term/set-fg 80 70 60)
(term/set-bg 10 10 10)
(term/write prompt))
(sfx/flush-frame!)))
(term/write prompt)))))

;; --- Rune Codex Screen ---

(defn render-rune-codex [world]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
r (ui/rect 1 1 tw th)
inner (ui/box r "Rune Codex" [170 140 220] ui/bg)
Expand All @@ -1356,13 +1352,12 @@
[100 80 140] ui/bg)))))
(ui/write-line inner (dec (:h inner))
" [press any key to close]"
[80 80 80] ui/bg)
(sfx/flush-frame!)))
[80 80 80] ui/bg))))

;; --- Inscription Screen ---

(defn render-inscribe-screen [world]
(sfx/sync-begin!)
(sfx/with-frame
(let [[tw th] (term-dims)
r (ui/rect 1 1 tw th)
inner (ui/box r "Inscribe Runes" [200 160 240] ui/bg)
Expand Down Expand Up @@ -1416,8 +1411,7 @@
;; controls
(ui/write-line inner (- (:h inner) 1)
" [jk] select [space] add [x] remove last [enter] confirm [esc] cancel"
[80 80 80] ui/bg)
(sfx/flush-frame!)))
[80 80 80] ui/bg))))

;; --- Public API ---

Expand All @@ -1438,7 +1432,7 @@
(let [[tw th] (term-dims)
panels (ui/layout tw th)
mr (with-camera world (:map panels))]
(sfx/sync-begin!)
(sfx/with-frame
;; blank entire terminal with bg color
(ui/apply-bg ui/bg)
(ui/apply-fg ui/bg)
Expand All @@ -1451,8 +1445,7 @@
(render-topbar world (:topbar panels))
(render-sidebar world (:sidebar panels))
(render-log world (:log panels))
(render-status world (:status panels))
(sfx/flush-frame!)
(render-status world (:status panels)))
(commit-camera! world mr)))

(defn render-dirty [world prev-world]
Expand All @@ -1470,7 +1463,7 @@
old-fov (:fov prev-world)
new-fov (:fov world)
prev-cam (or @last-camera mr)]
(sfx/sync-begin!)
(sfx/with-frame
(if (or (not= (:cx prev-cam) (:cx mr))
(not= (:cy prev-cam) (:cy mr)))
;; Camera shifted — viewport contents moved; redraw map fully.
Expand All @@ -1479,9 +1472,7 @@
(render-topbar world (:topbar panels))
(render-sidebar world (:sidebar panels))
(render-log world (:log panels))
(render-status world (:status panels))
(sfx/flush-frame!)
(commit-camera! world mr))
(render-status world (:status panels)))
;; Camera held — FOV-delta path. map-cell handles visibility check;
;; no outer bounds guard needed because x,y are world coords.
(let [lights (or (:lights world) [])
Expand Down Expand Up @@ -1526,6 +1517,5 @@
(render-topbar world (:topbar panels))
(render-sidebar world (:sidebar panels))
(render-log world (:log panels))
(render-status world (:status panels))
(sfx/flush-frame!)
(commit-camera! world mr)))))
(render-status world (:status panels)))))
(commit-camera! world mr)))
32 changes: 25 additions & 7 deletions xsofy/screenfx.lg
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,31 @@
(when sync-output? (term/write end-sync))
(term/flush))

(defmacro with-frame
"Run body as one synchronized (DEC 2026) frame: open the sync on entry, then
always close it (end-sync + flush) on exit via finally — even if body throws
— so a frame can never leave the sync mode dangling on the terminal. Prefer
this over a hand-written (sync-begin!) … (flush-frame!) pair: the begin/end
can't drift apart or be skipped by an early return.

Not nesting-safe: mode 2026 is a boolean, so an inner with-frame's finally
would close the outer frame early. Wrap only top-level frames, one per paint.

Emitted symbols are fully namespace-qualified to xsofy.screenfx/* to work
around let-go's syntax-quote ns-qualification gap (see let-go#48)."
[& body]
`(do
(xsofy.screenfx/sync-begin!)
(try
~@body
(finally (xsofy.screenfx/flush-frame!)))))

(defn clear-screen
"Paint the whole tw×th screen the dark background colour, as one synced frame."
[tw th]
(sync-begin!)
(term/set-bg 10 10 10)
(term/set-fg 10 10 10)
(dotimes [y th]
(term/move-cursor 1 (inc y))
(term/write (apply str (repeat tw " "))))
(flush-frame!))
(with-frame
(term/set-bg 10 10 10)
(term/set-fg 10 10 10)
(dotimes [y th]
(term/move-cursor 1 (inc y))
(term/write (apply str (repeat tw " "))))))
14 changes: 14 additions & 0 deletions xsofy/test/render_test.lg
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@
(is (= screenfx/begin-sync (with-out-str (screenfx/sync-begin!))))
(is (= screenfx/end-sync (with-out-str (screenfx/flush-frame!))))))

(deftest with-frame-wraps-body-in-one-balanced-pair
(testing "with-frame opens the sync, runs the body, then closes with end-sync"
(is (= (str screenfx/begin-sync "frame" screenfx/end-sync)
(with-out-str (screenfx/with-frame (print "frame")))))))

(deftest with-frame-closes-sync-even-when-body-throws
;; The whole point of the finally: a frame that throws mid-emit must still
;; emit end-sync, so the terminal never stays stuck in synchronized mode.
(testing "end-sync is still emitted when the body throws"
(let [out (with-out-str
(try (screenfx/with-frame (print "partial") (throw "boom"))
(catch e nil)))]
(is (= (str screenfx/begin-sync "partial" screenfx/end-sync) out)))))

(deftest message-log-window-shows-newest-entries-by-default
(testing "newest entries are visible when scroll is zero"
(let [window (render/message-log-window ["a" "b" "c" "d"] 2 0)]
Expand Down
10 changes: 4 additions & 6 deletions xsofy/title.lg
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
;; screen.

(defn draw-frame [runes title subtitle tw th frame seed]
(sfx/sync-begin!)
(sfx/with-frame
(let [cx (quot tw 2)
cy (quot th 2)
tx (max 1 (- cx (quot (count title) 2)))
Expand Down Expand Up @@ -161,8 +161,7 @@
(term/move-cursor ssx (+ cy 4))
(term/set-fg 90 85 70)
(term/set-bg 10 10 10)
(term/write sline))
(sfx/flush-frame!)))
(term/write sline)))))

(defn show-title-screen [rng]
(let [seed (:seed rng) ;; capture before picks advance the rng — this is the run seed
Expand Down Expand Up @@ -230,7 +229,7 @@
(sfx/clear-screen tw th)
;; animate — runes drift upward, erase old position each frame
(dotimes [frame 15]
(sfx/sync-begin!)
(sfx/with-frame
(doseq [r runes]
(let [;; current y drifts upward (subtract), wrap around
cur-y (rem (+ th (:y r) (- (quot frame 2))) th)
Expand Down Expand Up @@ -264,6 +263,5 @@
(term/move-cursor mx (+ cy 1))
(term/set-fg (int (* 120 mfade)) (int (* 110 mfade)) (int (* 90 mfade)))
(term/set-bg 10 10 10)
(term/write msg))
(sfx/flush-frame!)
(term/write msg)))
(sfx/pause! 60)))))
Loading