This repository has been archived by the owner on Feb 25, 2023. It is now read-only.
/
run.go
277 lines (228 loc) · 6.87 KB
/
run.go
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// Copyright (c) 2013-2018 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).
package cozely
import (
"github.com/cozely/cozely/internal"
"github.com/cozely/cozely/window"
)
////////////////////////////////////////////////////////////////////////////////
// GameLoop methods are called in a loop to React to player actions, Update the
// game state, and Render it.
type GameLoop interface {
// Enter is called once, after the framework initialization, but before the
// loop is started.
Enter()
// Leave is called when the loop is stopped.
Leave()
// React is called as often as possible, before Update and Render, to react to
// the player's actions. This is the only method that is guaranteed to run at
// least once per frame.
React()
// Update is called at fixed intervals, to update the game state (e.g. logic,
// physics, AI...).
Update()
// Render is called to display the game state to the player.
//
// Note that the framerate of Update and Render is independent, so the game
// state might need to be interpolated (see UpdateLag).
Render()
}
////////////////////////////////////////////////////////////////////////////////
var next GameLoop
////////////////////////////////////////////////////////////////////////////////
// Run initializes the framework, creates the ressources and loads all assets,
// and finally starts the game loop. The loop will run until Stop is called.
//
// Important: Run must be called from main.main, or at least from a function
// that is known to run on the main OS thread.
func Run(loop GameLoop) (err error) {
defer func() {
internal.Running = false
internal.QuitRequested = false
derr := internal.PolyCleanup()
if err == nil && derr != nil {
err = internal.Wrap("poly cleanup", derr)
return
}
derr = internal.PixelCleanup()
if err == nil && derr != nil {
err = internal.Wrap("pixel cleanup", derr)
return
}
derr = internal.InputCleanup()
if err == nil && derr != nil {
err = internal.Wrap("input cleanup", derr)
return
}
derr = internal.GLCleanup()
if err == nil && derr != nil {
err = internal.Wrap("gl cleanup", derr)
return
}
derr = internal.Cleanup()
if err == nil && derr != nil {
err = internal.Wrap("internal cleanup", derr)
return
}
}()
if internal.Running {
//TODO:
return nil
}
internal.Loop = loop
// Setup
err = internal.Setup()
if err != nil {
return internal.Wrap("internal setup", err)
}
err = internal.GLSetup()
if err != nil {
return internal.Wrap("gl setup", err)
}
err = internal.InputSetup()
if err != nil {
return internal.Wrap("input setup", err)
}
err = internal.PixelSetup()
if err != nil {
return internal.Wrap("pixel setup", err)
}
err = internal.PolySetup()
if err != nil {
return internal.Wrap("poly setup", err)
}
// First, send a fake resize window event
internal.PixelResize()
window.Events.Resize()
// Main Loop
internal.Running = true
internal.RenderDelta = 0.0
internal.UpdateLag = 0.0
then := internal.GetSeconds()
now := then
gametime := 0.0
internal.GameTime = gametime
internal.Loop.Enter()
for !internal.QuitRequested {
internal.RenderDelta = now - then
countFrames()
if internal.RenderDelta > 4*internal.UpdateStep {
// Prevent "spiral of death" when Render can't keep up with Update
internal.RenderDelta = 4 * internal.UpdateStep
}
// Update and Events
internal.UpdateLag += internal.RenderDelta
//TODO: ProcessEvents should always be called with GameTime = now!
if internal.UpdateLag < internal.UpdateStep {
// Process events even if there is no Update this frame
internal.GameTime = now //TODO: check if correct
internal.ProcessEvents(window.Events)
internal.InputNewFrame()
internal.Loop.React()
}
for internal.UpdateLag >= internal.UpdateStep {
// Do the Time Step
internal.UpdateLag -= internal.UpdateStep
gametime += internal.UpdateStep
internal.GameTime = gametime
// Events
internal.ProcessEvents(window.Events)
internal.InputNewFrame()
internal.Loop.React()
// Update
internal.Loop.Update()
}
// Render
//TODO: render before react and update?
err = internal.GLPrerender()
if err != nil {
return err
}
internal.GameTime = gametime + internal.UpdateLag //TODO: check if correct
internal.Loop.Render()
err = internal.PixelRender()
if err != nil {
return err
}
internal.SwapWindow()
if next != nil {
internal.Loop.Leave()
internal.Loop = next
next = nil
internal.Loop.Enter()
}
then = now
now = internal.GetSeconds()
}
internal.Loop.Leave()
return stopErr
}
////////////////////////////////////////////////////////////////////////////////
// Goto replaces the current running loop with l. The change take place at next
// frame.
func Goto(l GameLoop) {
next = l
}
////////////////////////////////////////////////////////////////////////////////
// GameTime returns the time elapsed in the game. It is updated before each call
// to Update and before each call to Render.
func GameTime() float64 {
return internal.GameTime
}
// UpdateDelta returns the time between previous update and current one. It is a
// fixed value, that only changes when configured with UpdateStep.
//
// See also UpdateLag.
func UpdateDelta() float64 {
return internal.UpdateStep
}
// RenderDelta returns the time elapsed between the previous frame and the one
// being rendered.
//
// See also UpdateDelta and UpdateLag.
func RenderDelta() float64 {
return internal.RenderDelta
}
// UpdateLag returns the time elapsed between the last Update and the frame
// being rendered. It should be used during Render to extrapolate (or
// interpolate) the game state.
//
// See also UpdateTime and RenderDelta.
func UpdateLag() float64 {
return internal.UpdateLag
}
////////////////////////////////////////////////////////////////////////////////
// RenderStats returns the average durations of frames; it is updated 4
// times per second. It also returns the number of overruns (i.e. frame time
// longer than the threshold) during the last measurment interval.
func RenderStats() (t float64, overruns int) {
return frAverage, xrunPrevious
}
func countFrames() {
frCount++
frSum += internal.RenderDelta
if internal.RenderDelta > xrunThreshold {
xrunCount++
}
if frSum >= frInterval {
frAverage = frSum / float64(frCount)
xrunPrevious = xrunCount
//TODO: microtext.PrintFrameTime(frAverage, xrunCount)
frSum = 0
frCount = 0
xrunCount = 0
}
}
const frInterval = 1.0 / 4.0
var frAverage float64
var frSum float64
var frCount int
const xrunThreshold float64 = 17 / 1000.0
var xrunCount, xrunPrevious int
////////////////////////////////////////////////////////////////////////////////
// Path returns the (slash-separated) path of the executable, with a trailing
// slash.
func Path() string {
return internal.Path
}
////////////////////////////////////////////////////////////////////////////////