blob: d6a03459a27c9467d38e9c57be7bbebaa93ad48e (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
;;; guile-2d
;;; Copyright (C) 2013, 2014 David Thompson <dthompson2@worcester.edu>
;;;
;;; This program is free software: you can redistribute it and/or
;;; modify it under the terms of the GNU General Public License as
;;; published by the Free Software Foundation, either version 3 of the
;;; License, or (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program. If not, see
;;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; The game loop.
;;
;;; Code:
(define-module (2d game)
#:use-module (srfi srfi-9)
#:use-module ((sdl sdl) #:prefix SDL:)
#:use-module (gl)
#:use-module (2d agenda)
#:use-module (2d event)
#:use-module (2d math)
#:use-module (2d signal)
#:use-module (2d window)
#:export (tick-interval
max-ticks-per-frame
draw-hook
start-game-loop
stop-game-loop))
;;;
;;; Game Loop
;;;
;; Update 60 times per second by default.
(define tick-interval (floor (/ 1000 60)))
;; The maximum number of times the game loop will update game state in
;; a single frame. When this upper bound is reached due to poor
;; performance, the game will start to slow down instead of becoming
;; completely unresponsive and possibly crashing.
(define max-ticks-per-frame 4)
(define draw-hook (make-hook 2))
(define (draw dt alpha)
"Render a frame."
(let ((width (signal-ref window-width))
(height (signal-ref window-height)))
(gl-viewport 0 0 width height))
(gl-clear (clear-buffer-mask color-buffer depth-buffer))
(run-hook draw-hook dt alpha)
(SDL:gl-swap-buffers))
(define (update lag)
"Call the update callback. The update callback will be called as
many times as tick-interval can divide LAG. The return value
is the unused accumulator time."
(define (iter lag ticks)
(cond ((>= ticks max-ticks-per-frame)
lag)
((>= lag tick-interval)
(tick-agenda!)
(iter (- lag tick-interval) (1+ ticks)))
(else
lag)))
(iter lag 0))
(define (alpha lag)
"Calculate interpolation factor in the range [0, 1] for the
leftover frame time LAG."
(clamp 0 1 (/ lag tick-interval)))
(define (frame-sleep time)
"Sleep for the remainder of the frame that started at TIME."
(let ((t (- (+ time tick-interval)
(SDL:get-ticks))))
(usleep (max 0 (* t 1000)))))
(define (game-loop previous-time lag)
"Update game state, and render. PREVIOUS-TIME is the time in
milliseconds of the last iteration of the game loop."
(let* ((current-time (SDL:get-ticks))
(dt (- current-time previous-time)))
(process-events)
(let ((lag (update (+ lag dt))))
(draw dt (alpha lag))
(frame-sleep current-time)
(game-loop current-time lag))))
(define (start-game-loop)
"Start the game loop."
(call-with-prompt
'game-loop-prompt
(lambda ()
(game-loop (SDL:get-ticks) 0))
(lambda (cont callback)
(when (procedure? callback)
(callback cont)))))
(define (stop-game-loop)
"Abort the game loop."
(abort-to-prompt 'game-loop-prompt #f))
|