Optimize(Draw): Skip unchanged elements in draw callback
Previously every frame: cleared+redrawed 868 cells for coins, cleared+ redrawed all UI text, emptied+recreated pause layer, and swapped key sprites repeatedly. Now uses dirty flags and cached values: - Coins: only redrawn when coins-dirty? is set (on coin eat/key pickup) - Maze: only redrawn when a door is removed - UI: only redrawn when score or time string actually changes - Key: sprite swap happens exactly once, not every frame - Pause: layer only modified when paused? state transitions Exposes mark-coins-dirty! and mark-maze-dirty! messages so the game ADT can signal changes from the level. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,10 @@
|
|||||||
;; All graphics logic is isolated in this ADT. Game logic knows nothing about
|
;; All graphics logic is isolated in this ADT. Game logic knows nothing about
|
||||||
;; pixels, windows, or sprites. Grid-to-pixel conversion happens exclusively
|
;; pixels, windows, or sprites. Grid-to-pixel conversion happens exclusively
|
||||||
;; here.
|
;; here.
|
||||||
|
;;
|
||||||
|
;; Performance: coins, UI, maze, key, and pause are only redrawn when their
|
||||||
|
;; underlying state changes. The draw callback tracks previous values and
|
||||||
|
;; skips unchanged elements.
|
||||||
|
|
||||||
(define-library (pacman-project adt draw)
|
(define-library (pacman-project adt draw)
|
||||||
(import (scheme base)
|
(import (scheme base)
|
||||||
@@ -85,6 +89,17 @@
|
|||||||
(define ui-tile (make-tile width height))
|
(define ui-tile (make-tile width height))
|
||||||
((ui-layer 'add-drawable!) ui-tile)
|
((ui-layer 'add-drawable!) ui-tile)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Change tracking — skip redraws when state hasn't changed
|
||||||
|
;;
|
||||||
|
|
||||||
|
(define cached-score -1)
|
||||||
|
(define cached-time "")
|
||||||
|
(define cached-key-taken? #f)
|
||||||
|
(define key-sprite-swapped? #f)
|
||||||
|
(define cached-paused? #f)
|
||||||
|
(define coins-dirty? #t)
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; Coordinate conversion
|
;; Coordinate conversion
|
||||||
;;
|
;;
|
||||||
@@ -102,8 +117,10 @@
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
;; draw-maze! :: maze -> /
|
;; draw-maze! :: maze -> /
|
||||||
;; Draws all walls and doors.
|
;; Draws all walls and doors. Called once at startup and after door
|
||||||
|
;; removal — never per-frame.
|
||||||
(define (draw-maze! maze)
|
(define (draw-maze! maze)
|
||||||
|
((maze-tile 'clear!))
|
||||||
((maze 'for-each-cell)
|
((maze 'for-each-cell)
|
||||||
(lambda (row col cell-type)
|
(lambda (row col cell-type)
|
||||||
(cond
|
(cond
|
||||||
@@ -123,7 +140,7 @@
|
|||||||
"pink"))))))
|
"pink"))))))
|
||||||
|
|
||||||
;; draw-coins! :: maze -> /
|
;; draw-coins! :: maze -> /
|
||||||
;; Draws all coins in the maze.
|
;; Redraws all coins. Only called when coins-dirty? is true.
|
||||||
(define (draw-coins! maze)
|
(define (draw-coins! maze)
|
||||||
((coins-tile 'clear!))
|
((coins-tile 'clear!))
|
||||||
((maze 'for-each-cell)
|
((maze 'for-each-cell)
|
||||||
@@ -137,15 +154,12 @@
|
|||||||
"yellow")))))
|
"yellow")))))
|
||||||
|
|
||||||
;; draw-key! :: key -> /
|
;; draw-key! :: key -> /
|
||||||
;; Draws the key at its position, or shows it in UI if taken.
|
;; Swaps the key sprite once when taken. No-op on subsequent frames.
|
||||||
(define (draw-key! key-obj)
|
(define (draw-key! key-obj)
|
||||||
(if (key-obj 'taken?)
|
(when (and (key-obj 'taken?) (not key-sprite-swapped?))
|
||||||
(begin
|
((key-layer 'remove-drawable!) key-sprite)
|
||||||
((key-layer 'remove-drawable!) key-sprite)
|
((key-layer 'add-drawable!) key-ui-sprite)
|
||||||
((key-layer 'add-drawable!) key-ui-sprite))
|
(set! key-sprite-swapped? #t)))
|
||||||
(let ((pos (key-obj 'position)))
|
|
||||||
((key-sprite 'set-x!) (grid->pixel-x (pos 'col)))
|
|
||||||
((key-sprite 'set-y!) (grid->pixel-y (pos 'row))))))
|
|
||||||
|
|
||||||
;; animate-pacman! :: number -> /
|
;; animate-pacman! :: number -> /
|
||||||
;; Advances the Pac-Man sprite animation based on elapsed time.
|
;; Advances the Pac-Man sprite animation based on elapsed time.
|
||||||
@@ -156,58 +170,83 @@
|
|||||||
(set! time-since-last-animation 0)))
|
(set! time-since-last-animation 0)))
|
||||||
|
|
||||||
;; draw-pacman! :: pacman -> /
|
;; draw-pacman! :: pacman -> /
|
||||||
;; Draws Pac-Man at its current position with correct rotation.
|
;; Updates Pac-Man position and rotation. Lightweight — just sets
|
||||||
|
;; properties on an existing sprite.
|
||||||
(define (draw-pacman! pacman)
|
(define (draw-pacman! pacman)
|
||||||
(let* ((pos (pacman 'position))
|
(let* ((pos (pacman 'position))
|
||||||
(direction (pacman 'direction)))
|
(direction (pacman 'direction)))
|
||||||
;; Set position
|
|
||||||
((pacman-sprite 'set-x!) (grid->pixel-x (pos 'col)))
|
((pacman-sprite 'set-x!) (grid->pixel-x (pos 'col)))
|
||||||
((pacman-sprite 'set-y!) (grid->pixel-y (pos 'row)))
|
((pacman-sprite 'set-y!) (grid->pixel-y (pos 'row)))
|
||||||
;; Set rotation based on direction
|
|
||||||
(cond ((eq? direction 'right) ((pacman-sprite 'rotate!) rotation-right))
|
(cond ((eq? direction 'right) ((pacman-sprite 'rotate!) rotation-right))
|
||||||
((eq? direction 'left) ((pacman-sprite 'rotate!) rotation-left))
|
((eq? direction 'left) ((pacman-sprite 'rotate!) rotation-left))
|
||||||
((eq? direction 'up) ((pacman-sprite 'rotate!) rotation-up))
|
((eq? direction 'up) ((pacman-sprite 'rotate!) rotation-up))
|
||||||
((eq? direction 'down) ((pacman-sprite 'rotate!) rotation-down)))))
|
((eq? direction 'down) ((pacman-sprite 'rotate!) rotation-down)))))
|
||||||
|
|
||||||
;; draw-ui! :: score, timer -> /
|
;; draw-ui! :: score, timer -> /
|
||||||
;; Draws the score and time limit on screen.
|
;; Redraws score and time only when their values have changed.
|
||||||
(define (draw-ui! score timer)
|
(define (draw-ui! score timer)
|
||||||
((ui-tile 'clear!))
|
(let ((current-score (score 'points))
|
||||||
;; Score
|
(current-time ((timer 'format-time))))
|
||||||
((ui-tile 'draw-text!)
|
(when (or (not (= current-score cached-score))
|
||||||
(number->string (score 'points))
|
(not (string=? current-time cached-time)))
|
||||||
score-text-size score-text-x score-text-y "white")
|
((ui-tile 'clear!))
|
||||||
;; Separator line
|
;; Score
|
||||||
((ui-tile 'draw-rectangle!)
|
((ui-tile 'draw-text!)
|
||||||
separator-x 0 separator-width height "white")
|
(number->string current-score)
|
||||||
;; Time limit
|
score-text-size score-text-x score-text-y "white")
|
||||||
((ui-tile 'draw-text!)
|
;; Separator line
|
||||||
"Time remaining:" time-text-size time-label-x time-label-y "white")
|
((ui-tile 'draw-rectangle!)
|
||||||
((ui-tile 'draw-text!)
|
separator-x 0 separator-width height "white")
|
||||||
((timer 'format-time))
|
;; Time limit
|
||||||
score-text-size time-value-x time-value-y "white"))
|
((ui-tile 'draw-text!)
|
||||||
|
"Time remaining:" time-text-size time-label-x time-label-y "white")
|
||||||
|
((ui-tile 'draw-text!)
|
||||||
|
current-time
|
||||||
|
score-text-size time-value-x time-value-y "white")
|
||||||
|
;; Update cache
|
||||||
|
(set! cached-score current-score)
|
||||||
|
(set! cached-time current-time))))
|
||||||
|
|
||||||
;; draw-pause! :: boolean -> /
|
;; draw-pause! :: boolean -> /
|
||||||
;; Shows or hides the pause screen.
|
;; Only redraws when pause state actually changes.
|
||||||
(define (draw-pause! paused?)
|
(define (draw-pause! paused?)
|
||||||
((pause-layer 'empty!))
|
(when (not (eq? paused? cached-paused?))
|
||||||
(when paused?
|
((pause-layer 'empty!))
|
||||||
(let ((pause-tile (make-tile width height)))
|
(when paused?
|
||||||
((pause-layer 'add-drawable!) pause-tile)
|
(let ((pause-tile (make-tile width height)))
|
||||||
((pause-tile 'draw-rectangle!) 0 90 670 height "black")
|
((pause-layer 'add-drawable!) pause-tile)
|
||||||
((pause-tile 'draw-text!) "Game Paused" 40 200 400 "red"))))
|
((pause-tile 'draw-rectangle!) 0 90 670 height "black")
|
||||||
|
((pause-tile 'draw-text!) "Game Paused" 40 200 400 "red")))
|
||||||
|
(set! cached-paused? paused?)))
|
||||||
|
|
||||||
|
;; mark-coins-dirty! :: -> /
|
||||||
|
;; Called by the game when a coin is eaten, so coins are redrawn
|
||||||
|
;; next frame.
|
||||||
|
(define (mark-coins-dirty!)
|
||||||
|
(set! coins-dirty? #t))
|
||||||
|
|
||||||
|
;; mark-maze-dirty! :: -> /
|
||||||
|
;; Called by the game when a door is removed, so the maze is
|
||||||
|
;; redrawn next frame.
|
||||||
|
(define (mark-maze-dirty!)
|
||||||
|
(set! coins-dirty? #t))
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; Main draw function
|
;; Main draw function
|
||||||
;;
|
;;
|
||||||
|
|
||||||
;; draw-game! :: game -> /
|
;; draw-game! :: game -> /
|
||||||
;; Draws the full game (registered as draw callback).
|
;; Draws the full game. Only redraws changed elements.
|
||||||
(define (draw-game! game)
|
(define (draw-game! game)
|
||||||
(let ((level (game 'level)))
|
(let ((level (game 'level)))
|
||||||
|
;; Always update (lightweight sprite property sets)
|
||||||
(draw-pacman! (level 'pacman))
|
(draw-pacman! (level 'pacman))
|
||||||
|
;; Only redraw when dirty / changed
|
||||||
(draw-key! (level 'key))
|
(draw-key! (level 'key))
|
||||||
(draw-coins! (level 'maze))
|
(when coins-dirty?
|
||||||
|
(draw-coins! (level 'maze))
|
||||||
|
(draw-maze! (level 'maze))
|
||||||
|
(set! coins-dirty? #f))
|
||||||
(draw-ui! (level 'score) (level 'timer))
|
(draw-ui! (level 'score) (level 'timer))
|
||||||
(draw-pause! (level 'paused?))))
|
(draw-pause! (level 'paused?))))
|
||||||
|
|
||||||
@@ -226,9 +265,10 @@
|
|||||||
;; start-drawing! :: game -> /
|
;; start-drawing! :: game -> /
|
||||||
;; Starts drawing by setting the draw callback.
|
;; Starts drawing by setting the draw callback.
|
||||||
(define (start-drawing! game)
|
(define (start-drawing! game)
|
||||||
;; Initial maze and coins draw (one-time)
|
;; Initial draw (one-time)
|
||||||
(draw-maze! ((game 'level) 'maze))
|
(draw-maze! ((game 'level) 'maze))
|
||||||
(draw-coins! ((game 'level) 'maze))
|
(draw-coins! ((game 'level) 'maze))
|
||||||
|
(set! coins-dirty? #f)
|
||||||
((window 'set-draw-callback!)
|
((window 'set-draw-callback!)
|
||||||
(lambda () (draw-game! game))))
|
(lambda () (draw-game! game))))
|
||||||
|
|
||||||
@@ -241,6 +281,8 @@
|
|||||||
((eq? msg 'set-key-callback!) set-key-callback!)
|
((eq? msg 'set-key-callback!) set-key-callback!)
|
||||||
((eq? msg 'start-drawing!) start-drawing!)
|
((eq? msg 'start-drawing!) start-drawing!)
|
||||||
((eq? msg 'animate-pacman!) animate-pacman!)
|
((eq? msg 'animate-pacman!) animate-pacman!)
|
||||||
|
((eq? msg 'mark-coins-dirty!) mark-coins-dirty!)
|
||||||
|
((eq? msg 'mark-maze-dirty!) mark-maze-dirty!)
|
||||||
(else (error "Draw ADT -- Unknown message:" msg))))
|
(else (error "Draw ADT -- Unknown message:" msg))))
|
||||||
|
|
||||||
dispatch-draw))))
|
dispatch-draw))))
|
||||||
|
|||||||
Reference in New Issue
Block a user