]> Dogcows Code - chaz/yoink/blob - src/Moof/Engine.cc
stream-based logging classes
[chaz/yoink] / src / Moof / Engine.cc
1
2 /*******************************************************************************
3
4 Copyright (c) 2009, Charles McGarvey
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9
10 * Redistributions of source code must retain the above copyright notice,
11 this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27 *******************************************************************************/
28
29 #include <algorithm>
30 #include <cstdlib> // exit, srand
31 #include <ctime> // time
32 #include <list>
33 #include <string>
34
35 #include <AL/alc.h>
36 #include <SDL/SDL.h>
37 #include "fastevents.h"
38
39
40 #include "Engine.hh"
41 #include "Event.hh"
42 #include "Exception.hh"
43 #include "Log.hh"
44 #include "Math.hh"
45 #include "Settings.hh"
46 #include "Timer.hh"
47
48
49 namespace Mf {
50
51
52 class Engine::Impl
53 {
54 public:
55
56 Impl(Engine& engine) :
57 mInterface(engine),
58 mTimestep(0.01),
59 mPrintFps(false)
60 {
61 // first, initialize the libraries
62
63 #if defined(_WIN32) || defined(__WIN32__)
64 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
65 #else
66 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTTHREAD) != 0)
67 #endif
68 {
69 const char* error = SDL_GetError();
70 throw Exception(ErrorCode::SDL_INIT, error);
71 }
72 else
73 {
74 char vdName[128];
75 SDL_VideoDriverName(vdName, sizeof(vdName));
76 logDebug << "initialized SDL; using video driver `"
77 << vdName << "'" << std::endl;
78 }
79
80 if (FE_Init() != 0)
81 {
82 const char* error = FE_GetError();
83 throw Exception(ErrorCode::FASTEVENTS_INIT, error);
84 }
85
86 mAlDevice = alcOpenDevice(0);
87 mAlContext = alcCreateContext(mAlDevice, 0);
88 if (!mAlDevice || !mAlContext)
89 {
90 const char* error = alcGetString(mAlDevice,alcGetError(mAlDevice));
91 logError << "error while creating audio context: "
92 << error << std::endl;
93 }
94 else
95 {
96 alcMakeContextCurrent(mAlContext);
97 logDebug << "opened sound device `"
98 << alcGetString(mAlDevice, ALC_DEFAULT_DEVICE_SPECIFIER)
99 << "'" << std::endl;
100 }
101
102 // now load the settings the engine needs
103
104 Settings& settings = Settings::getInstance();
105
106 unsigned randomSeed;
107 if (settings.get("rngseed", randomSeed)) srand(randomSeed);
108 else srand(time(0));
109
110 Scalar timestep = 80.0;
111 settings.get("timestep", timestep);
112 mTimestep = 1.0 / timestep;
113
114 Scalar maxFps = 40.0;
115 settings.get("maxfps", maxFps);
116 mMaxFps = 1.0 / maxFps;
117 capFps();
118
119 settings.get("printfps", mPrintFps);
120 }
121
122 ~Impl()
123 {
124 // the video object must be destroyed before we can shutdown SDL
125 mVideo.reset();
126
127 alcMakeContextCurrent(0);
128 alcDestroyContext(mAlContext);
129 alcCloseDevice(mAlDevice);
130
131 FE_Quit();
132 SDL_Quit();
133 }
134
135
136 /**
137 * The main loop. This just calls dispatchEvents(), update(), and draw()
138 * over and over again. The timing of the update and draw are decoupled.
139 * The actual frame rate is also calculated here. This function will return
140 * the exit code used to stop the loop.
141 */
142
143 void run()
144 {
145 Scalar ticksNow = Timer::getTicks();
146
147 Scalar nextStep = ticksNow;
148 Scalar nextDraw = ticksNow;
149 Scalar nextFpsUpdate = ticksNow + 1.0;
150
151 Scalar totalTime = 0.0;
152 Scalar deltaTime = 0.0;
153 Scalar accumulator = mTimestep;
154
155 mFps = 0;
156 int frameAccum = 0;
157
158 do
159 {
160 Scalar newTicks = Timer::getTicks();
161 deltaTime = newTicks - ticksNow;
162 ticksNow = newTicks;
163
164 // don't slow the animation until 4Hz, which is unplayable anyway
165 if (deltaTime >= 0.25) deltaTime = 0.25;
166 accumulator += deltaTime;
167
168 Timer::fireIfExpired(ticksNow);
169 dispatchEvents();
170
171 while (accumulator >= mTimestep)
172 {
173 update(totalTime, mTimestep);
174
175 totalTime += mTimestep;
176 accumulator -= mTimestep;
177
178 nextStep += mTimestep;
179 }
180 if (ticksNow >= nextStep)
181 {
182 nextStep = ticksNow + mTimestep;
183 }
184
185 if (ticksNow >= nextDraw)
186 {
187 frameAccum++;
188
189 if (ticksNow >= nextFpsUpdate) // determine the actual fps
190 {
191 mFps = frameAccum;
192 frameAccum = 0;
193
194 nextFpsUpdate += 1.0;
195 if (ticksNow >= nextFpsUpdate)
196 {
197 nextFpsUpdate = ticksNow + 1.0;
198 }
199
200 if (mPrintFps)
201 {
202 logInfo << mFps << " fps" << std::endl;
203 }
204 }
205
206 draw(accumulator / mTimestep);
207 mVideo->swap();
208
209 nextDraw += mMaxFps;
210 if (ticksNow >= nextDraw)
211 {
212 // we missed some scheduled draws, so reset the schedule
213 nextDraw = ticksNow + mMaxFps;
214 }
215 }
216
217 // be a good citizen and give back what you don't need
218 Timer::sleep(std::min(std::min(nextStep, nextDraw),
219 Timer::getNextFire()), Timer::ACTUAL);
220 }
221 while (!mStack.empty());
222
223 mDispatch.dispatch("engine.stopping");
224 }
225
226 void dispatchEvents()
227 {
228 SDL_Event event;
229
230 while (FE_PollEvent(&event) == 1)
231 {
232 switch (event.type)
233 {
234 case SDL_KEYDOWN:
235 if (event.key.keysym.sym == SDLK_ESCAPE &&
236 (SDL_GetModState() & KMOD_CTRL) )
237 {
238 // emergency escape
239 logWarning("escape forced");
240 exit(1);
241 }
242 break;
243
244 case SDL_VIDEORESIZE:
245 mVideo->resize(event.resize.w, event.resize.h);
246 break;
247 }
248
249 handleEvent(event);
250 }
251 }
252
253
254 void update(Scalar t, Scalar dt)
255 {
256 for (mStackIt = mStack.begin(); mStackIt != mStack.end(); ++mStackIt)
257 {
258 (*mStackIt)->update(mInterface, t, dt);
259 }
260 }
261
262 void draw(Scalar alpha)
263 {
264 // FIXME - this will crash if the layer being drawn pops itself
265 std::list<LayerP>::reverse_iterator it;
266 for (it = mStack.rbegin(); it != mStack.rend(); ++it)
267 {
268 (*it)->draw(mInterface, alpha);
269 }
270 }
271
272 void handleEvent(const Event& event)
273 {
274 for (mStackIt = mStack.begin(); mStackIt != mStack.end(); ++mStackIt)
275 {
276 if ((*mStackIt)->handleEvent(mInterface, event)) break;
277 }
278 }
279
280
281 void push(LayerP layer)
282 {
283 ASSERT(layer && "cannot push null layer");
284 mStack.push_front(layer);
285 logDebug << "stack: " << mStack.size()
286 << " [pushed " << layer.get() << "]" << std::endl;
287 layer->pushed(mInterface);
288 }
289
290 LayerP pop()
291 {
292 bool fixIt = false;
293 if (mStack.begin() == mStackIt) fixIt = true;
294
295 LayerP layer = mStack.front();
296 mStack.pop_front();
297 logDebug << "stack: " << mStack.size()
298 << " [popped " << layer.get() << "]" << std::endl;
299 layer->popped(mInterface);
300
301 if (fixIt) mStackIt = --mStack.begin();
302
303 return layer;
304 }
305
306 LayerP pop(Layer* layer)
307 {
308 bool fixIt = false;
309
310 std::list<LayerP> layers;
311
312 std::list<LayerP>::iterator it;
313 for (it = mStack.begin(); it != mStack.end(); ++it)
314 {
315 layers.push_back(*it);
316
317 if (it == mStackIt) fixIt = true;
318
319 if ((*it).get() == layer)
320 {
321 ++it;
322 mStack.erase(mStack.begin(), it);
323
324 for (it = layers.begin(); it != layers.end(); ++it)
325 {
326 (*it)->popped(mInterface);
327 logDebug << "stack: " << mStack.size()
328 << " [popped " << (*it).get() << "]" << std::endl;
329 }
330
331 if (fixIt) mStackIt = --mStack.begin();
332
333 return layers.back();
334 }
335 }
336
337 return LayerP();
338 }
339
340 void clear()
341 {
342 mStack.clear();
343 mStackIt = mStack.begin();
344 logDebug("stack: 0 [cleared]");
345 }
346
347
348 void capFps()
349 {
350 if (mMaxFps < mTimestep)
351 {
352 logWarning << "capping maximum fps to timestep ("
353 << mTimestep << ")" << std::endl;
354 mMaxFps = mTimestep;
355 }
356 }
357
358
359 Engine& mInterface;
360 VideoP mVideo;
361 Dispatch mDispatch;
362
363 ALCdevice* mAlDevice;
364 ALCcontext* mAlContext;
365
366 std::list<LayerP> mStack;
367 std::list<LayerP>::iterator mStackIt;
368
369 Scalar mTimestep;
370 Scalar mMaxFps;
371
372 int mFps;
373 bool mPrintFps;
374 };
375
376
377 Engine::Engine() :
378 // pass through
379 mImpl(new Engine::Impl(*this)) {}
380
381 Engine& Engine::getInstance()
382 {
383 static Engine engine;
384 return engine;
385 }
386
387
388 void Engine::setVideo(VideoP video)
389 {
390 // pass through
391 mImpl->mVideo = video;
392 }
393
394 VideoP Engine::getVideo() const
395 {
396 return mImpl->mVideo;
397 }
398
399
400 void Engine::setTimestep(int ts)
401 {
402 mImpl->mTimestep = 1.0 / Scalar(ts);
403 mImpl->capFps();
404 }
405
406 int Engine::getTimestep() const
407 {
408 return int(1.0 / mImpl->mTimestep);
409 }
410
411
412 void Engine::setMaxFps(int maxFps)
413 {
414 mImpl->mMaxFps = 1.0 / Scalar(maxFps);
415 mImpl->capFps();
416 }
417
418 int Engine::getMaxFps() const
419 {
420 return int(1.0 / mImpl->mMaxFps);
421 }
422
423
424 int Engine::getFps() const
425 {
426 return mImpl->mFps;
427 }
428
429
430 void Engine::push(LayerP layer)
431 {
432 // pass through
433 mImpl->push(layer);
434 }
435
436 LayerP Engine::pop()
437 {
438 // pass through
439 return mImpl->pop();
440 }
441
442 LayerP Engine::pop(Layer* layer)
443 {
444 // pass through
445 return mImpl->pop(layer);
446 }
447
448 void Engine::clear()
449 {
450 // pass through
451 mImpl->clear();
452 }
453
454 int Engine::getSize() const
455 {
456 return mImpl->mStack.size();
457 }
458
459
460 void Engine::run()
461 {
462 // pass through
463 return mImpl->run();
464 }
465
466
467 Dispatch::Handler Engine::addHandler(const std::string& event,
468 const Dispatch::Function& callback)
469 {
470 return mImpl->mDispatch.addHandler(event, callback);
471 }
472
473 Dispatch::Handler Engine::addHandler(const std::string& event,
474 const Dispatch::Function& callback, Dispatch::Handler handler)
475 {
476 return mImpl->mDispatch.addHandler(event, callback, handler);
477 }
478
479 void Engine::dispatch(const std::string& event,
480 const Dispatch::Message* message)
481 {
482 mImpl->mDispatch.dispatch(event, message);
483 }
484
485
486 } // namespace Mf
487
488 /** vim: set ts=4 sw=4 tw=80: *************************************************/
489
This page took 0.050874 seconds and 4 git commands to generate.