2 /*******************************************************************************
4 Copyright (c) 2009, Charles McGarvey
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
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.
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.
27 *******************************************************************************/
30 #include <cstdlib> // exit, srand
31 #include <ctime> // time
37 #include "fastevents.h"
42 #include "Exception.hh"
45 #include "Settings.hh"
56 Impl(Engine
& engine
) :
61 // first, initialize the libraries
63 #if defined(_WIN32) || defined(__WIN32__)
64 if (SDL_Init(SDL_INIT_VIDEO
| SDL_INIT_TIMER
) != 0)
66 if (SDL_Init(SDL_INIT_VIDEO
| SDL_INIT_EVENTTHREAD
) != 0)
69 const char* error
= SDL_GetError();
70 throw Exception(ErrorCode::SDL_INIT
, error
);
75 SDL_VideoDriverName(vdName
, sizeof(vdName
));
76 logDebug("initialized SDL; using video driver `%s'", vdName
);
81 const char* error
= FE_GetError();
82 throw Exception(ErrorCode::FASTEVENTS_INIT
, error
);
85 mAlDevice
= alcOpenDevice(0);
86 mAlContext
= alcCreateContext(mAlDevice
, 0);
87 if (!mAlDevice
|| !mAlContext
)
89 const char* error
= alcGetString(mAlDevice
,alcGetError(mAlDevice
));
90 logError("error while creating audio context: %s", error
);
94 alcMakeContextCurrent(mAlContext
);
95 logDebug("opened sound device `%s'",
96 alcGetString(mAlDevice
, ALC_DEFAULT_DEVICE_SPECIFIER
));
99 // now load the settings the engine needs
101 Settings
& settings
= Settings::getInstance();
104 if (settings
.get("rngseed", randomSeed
)) srand(randomSeed
);
107 Scalar timestep
= 80.0;
108 settings
.get("timestep", timestep
);
109 mTimestep
= 1.0 / timestep
;
111 Scalar maxFps
= 40.0;
112 settings
.get("maxfps", maxFps
);
113 mMaxFps
= 1.0 / maxFps
;
116 settings
.get("printfps", mPrintFps
);
121 // the video object must be destroyed before we can shutdown SDL
124 alcMakeContextCurrent(0);
125 alcDestroyContext(mAlContext
);
126 alcCloseDevice(mAlDevice
);
134 * The main loop. This just calls dispatchEvents(), update(), and draw()
135 * over and over again. The timing of the update and draw are decoupled.
136 * The actual frame rate is also calculated here. This function will return
137 * the exit code used to stop the loop.
142 Scalar ticksNow
= Timer::getTicks();
144 Scalar nextStep
= ticksNow
;
145 Scalar nextDraw
= ticksNow
;
146 Scalar nextFpsUpdate
= ticksNow
+ 1.0;
148 Scalar totalTime
= 0.0;
149 Scalar deltaTime
= 0.0;
150 Scalar accumulator
= mTimestep
;
157 Scalar newTicks
= Timer::getTicks();
158 deltaTime
= newTicks
- ticksNow
;
161 // don't slow the animation until 4Hz, which is unplayable anyway
162 if (deltaTime
>= 0.25) deltaTime
= 0.25;
163 accumulator
+= deltaTime
;
165 Timer::fireIfExpired(ticksNow
);
168 while (accumulator
>= mTimestep
)
170 update(totalTime
, mTimestep
);
172 totalTime
+= mTimestep
;
173 accumulator
-= mTimestep
;
175 nextStep
+= mTimestep
;
177 if (ticksNow
>= nextStep
)
179 nextStep
= ticksNow
+ mTimestep
;
182 if (ticksNow
>= nextDraw
)
186 if (ticksNow
>= nextFpsUpdate
) // determine the actual fps
191 nextFpsUpdate
+= 1.0;
192 if (ticksNow
>= nextFpsUpdate
)
194 nextFpsUpdate
= ticksNow
+ 1.0;
199 logInfo("%d fps", mFps
);
203 draw(accumulator
/ mTimestep
);
207 if (ticksNow
>= nextDraw
)
209 // we missed some scheduled draws, so reset the schedule
210 nextDraw
= ticksNow
+ mMaxFps
;
214 // be a good citizen and give back what you don't need
215 Timer::sleep(std::min(std::min(nextStep
, nextDraw
),
216 Timer::getNextFire()), Timer::ACTUAL
);
218 while (!mStack
.empty());
221 void dispatchEvents()
225 while (FE_PollEvent(&event
) == 1)
230 if (event
.key
.keysym
.sym
== SDLK_ESCAPE
&&
231 (SDL_GetModState() & KMOD_CTRL
) )
234 logWarning("escape forced");
239 case SDL_VIDEORESIZE
:
240 mVideo
->resize(event
.resize
.w
, event
.resize
.h
);
249 void update(Scalar t
, Scalar dt
)
251 for (mStackIt
= mStack
.begin(); mStackIt
!= mStack
.end(); ++mStackIt
)
253 (*mStackIt
)->update(mInterface
, t
, dt
);
257 void draw(Scalar alpha
)
259 // FIXME - this will crash if the layer being drawn pops itself
260 std::list
<LayerP
>::reverse_iterator it
;
261 for (it
= mStack
.rbegin(); it
!= mStack
.rend(); ++it
)
263 (*it
)->draw(mInterface
, alpha
);
267 void handleEvent(const Event
& event
)
269 for (mStackIt
= mStack
.begin(); mStackIt
!= mStack
.end(); ++mStackIt
)
271 if ((*mStackIt
)->handleEvent(mInterface
, event
)) break;
276 void push(LayerP layer
)
278 ASSERT(layer
&& "cannot push null layer");
279 mStack
.push_front(layer
);
280 logDebug("stack: %d [pushed %X]", mStack
.size(), layer
.get());
281 layer
->pushed(mInterface
);
287 if (mStack
.begin() == mStackIt
) fixIt
= true;
289 LayerP layer
= mStack
.front();
291 logDebug("stack: %d [popped %X]", mStack
.size(), layer
.get());
292 layer
->popped(mInterface
);
294 if (fixIt
) mStackIt
= --mStack
.begin();
299 LayerP
pop(Layer
* layer
)
303 std::list
<LayerP
> layers
;
305 std::list
<LayerP
>::iterator it
;
306 for (it
= mStack
.begin(); it
!= mStack
.end(); ++it
)
308 layers
.push_back(*it
);
310 if (it
== mStackIt
) fixIt
= true;
312 if ((*it
).get() == layer
)
315 mStack
.erase(mStack
.begin(), it
);
317 for (it
= layers
.begin(); it
!= layers
.end(); ++it
)
319 (*it
)->popped(mInterface
);
320 logDebug("stack: %d [popped %X]", mStack
.size(), (*it
).get());
323 if (fixIt
) mStackIt
= --mStack
.begin();
325 return layers
.back();
335 mStackIt
= mStack
.begin();
336 logDebug("stack: 0 [cleared]");
342 if (mMaxFps
< mTimestep
)
344 logWarning("capping maximum fps to timestep (%f)", mTimestep
);
354 ALCdevice
* mAlDevice
;
355 ALCcontext
* mAlContext
;
357 std::list
<LayerP
> mStack
;
358 std::list
<LayerP
>::iterator mStackIt
;
370 mImpl(new Engine::Impl(*this)) {}
372 Engine
& Engine::getInstance()
374 static Engine engine
;
379 void Engine::setVideo(VideoP video
)
382 mImpl
->mVideo
= video
;
385 VideoP
Engine::getVideo() const
387 return mImpl
->mVideo
;
391 void Engine::setTimestep(int ts
)
393 mImpl
->mTimestep
= 1.0 / Scalar(ts
);
397 int Engine::getTimestep() const
399 return int(1.0 / mImpl
->mTimestep
);
403 void Engine::setMaxFps(int maxFps
)
405 mImpl
->mMaxFps
= 1.0 / Scalar(maxFps
);
409 int Engine::getMaxFps() const
411 return int(1.0 / mImpl
->mMaxFps
);
415 int Engine::getFps() const
421 void Engine::push(LayerP layer
)
433 LayerP
Engine::pop(Layer
* layer
)
436 return mImpl
->pop(layer
);
445 int Engine::getSize() const
447 return mImpl
->mStack
.size();
458 Dispatch::Handler
Engine::addHandler(const std::string
& event
,
459 const Dispatch::Function
& callback
)
461 return mImpl
->mDispatch
.addHandler(event
, callback
);
464 Dispatch::Handler
Engine::addHandler(const std::string
& event
,
465 const Dispatch::Function
& callback
, Dispatch::Handler handler
)
467 return mImpl
->mDispatch
.addHandler(event
, callback
, handler
);
470 void Engine::dispatch(const std::string
& event
,
471 const Dispatch::Message
* message
)
473 mImpl
->mDispatch
.dispatch(event
, message
);
479 /** vim: set ts=4 sw=4 tw=80: *************************************************/