/******************************************************************************* Copyright (c) 2009, Charles McGarvey All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include #include // exit, srand #include // time #include #include #include #include #include "fastevents.h" #include "Core.hh" #include "Event.hh" #include "Log.hh" #include "Math.hh" #include "ModalDialog.hh" #include "Settings.hh" #include "Timer.hh" #include "Video.hh" namespace Mf { class Core::Impl { public: Impl() : mError(Error::NONE), mTimestep(0.01), mFramerate(0.02), mShowFps(false) {} void init() { unsigned randomSeed; if (settings.get("rngseed", randomSeed)) srand(randomSeed); else srand(time(0)); Scalar timestep = 80.0; settings.get("timestep", timestep); mTimestep = 1.0 / timestep; Scalar framerate = 40.0; settings.get("framerate", framerate); mFramerate = 1.0 / framerate; mShowFps = false; settings.get("showfps", mShowFps); } /** * The main loop. This just calls dispatchEvents(), update(), and draw() * over and over again. The timing of the update and draw are decoupled. * The actual frame rate is also calculated here. This function will return * the exit code used to stop the loop. */ void run() { init(); Scalar totalTime = 0.0; Scalar ticks = Timer::getTicks(); Scalar nextUpdate = ticks; Scalar nextDraw = ticks; Scalar nextSecond = ticks + SCALAR(1.0); mFps = 0; int frames = 0; const int MAX_FRAMESKIP = 15; const Scalar inverseTimestep = SCALAR(1.0) / mTimestep; ASSERT(video && "cannot run core without a current video context"); do { Timer::fireIfExpired(); dispatchEvents(); int i = 0; while (nextUpdate < Timer::getTicks() && i < MAX_FRAMESKIP) { totalTime += mTimestep; update(totalTime, mTimestep); nextUpdate += mTimestep; ++i; } if (nextDraw < (ticks = Timer::getTicks())) { ++frames; draw((ticks + mTimestep - nextUpdate) * inverseTimestep); video->swap(); nextDraw += mFramerate; if (mShowFps && nextSecond < ticks) { mFps = frames; frames = 0; logInfo << mFps << " fps" << std::endl; nextSecond += SCALAR(1.0); } } // be a good citizen and give back what you don't need Timer::sleep(0.0); } while (!mStack.empty()); mDispatch.dispatch("engine.stopping"); } void dispatchEvents() { SDL_Event event; while (FE_PollEvent(&event) == 1) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState() & KMOD_CTRL) ) { // emergency escape logWarning("escape forced"); exit(1); } break; case SDL_VIDEORESIZE: video->resize(event.resize.w, event.resize.h); break; } handleEvent(event); } } void update(Scalar t, Scalar dt) { for (mStackIt = mStack.begin(); mStackIt != mStack.end(); ++mStackIt) { (*mStackIt)->update(t, dt); } } void draw(Scalar alpha) { // FIXME - this will crash if the layer being drawn pops itself std::list::reverse_iterator it; for (it = mStack.rbegin(); it != mStack.rend(); ++it) { (*it)->draw(alpha); } } void handleEvent(const Event& event) { for (mStackIt = mStack.begin(); mStackIt != mStack.end(); ++mStackIt) { if ((*mStackIt)->handleEvent(event)) break; } } void push(LayerP layer) { ASSERT(layer && "cannot push null layer"); mStack.push_front(layer); logInfo << "stack: " << mStack.size() << " [pushed " << layer.get() << "]" << std::endl; layer->addedToCore(); } LayerP pop() { bool fixIt = false; if (mStack.begin() == mStackIt) fixIt = true; LayerP layer = mStack.front(); mStack.pop_front(); logInfo << "stack: " << mStack.size() << " [popped " << layer.get() << "]" << std::endl; layer->removedFromCore(); if (fixIt) mStackIt = --mStack.begin(); return layer; } LayerP pop(Layer* layer) { bool fixIt = false; std::list layers; std::list::iterator it; for (it = mStack.begin(); it != mStack.end(); ++it) { layers.push_back(*it); if (it == mStackIt) fixIt = true; if ((*it).get() == layer) { ++it; mStack.erase(mStack.begin(), it); for (it = layers.begin(); it != layers.end(); ++it) { (*it)->removedFromCore(); logInfo << "stack: " << mStack.size() << " [popped " << (*it).get() << "]" << std::endl; } if (fixIt) mStackIt = --mStack.begin(); return layers.back(); } } return LayerP(); } void clear() { mStack.clear(); mStackIt = mStack.begin(); logInfo("stack: 0 [cleared]"); } Error mError; Dispatch mDispatch; std::list mStack; std::list::iterator mStackIt; Scalar mTimestep; Scalar mFramerate; int mFps; bool mShowFps; }; Core::Core() : // pass through mImpl(new Core::Impl) {} void Core::init() { // pass through mImpl->init(); } int Core::getFps() const { return mImpl->mFps; } void Core::push(LayerP layer) { // pass through mImpl->push(layer); } LayerP Core::pop() { // pass through return mImpl->pop(); } LayerP Core::pop(Layer* layer) { // pass through return mImpl->pop(layer); } void Core::clear() { // pass through mImpl->clear(); } int Core::getSize() const { return mImpl->mStack.size(); } void Core::run() { // pass through return mImpl->run(); } Dispatch::Handler Core::addHandler(const std::string& event, const Dispatch::Function& callback) { return mImpl->mDispatch.addHandler(event, callback); } Dispatch::Handler Core::addHandler(const std::string& event, const Dispatch::Function& callback, Dispatch::Handler handler) { return mImpl->mDispatch.addHandler(event, callback, handler); } void Core::dispatch(const std::string& event, const Dispatch::Message* message) { mImpl->mDispatch.dispatch(event, message); } Core core; class Backend_; typedef boost::shared_ptr BackendP; class Backend_ { public: void init() { #if defined(_WIN32) || defined(__WIN32__) if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) #else if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTTHREAD) != 0) #endif { const char* error = SDL_GetError(); gError.init(Error::SDL_INIT, error); return; // fatal } else { char name[128]; SDL_VideoDriverName(name, sizeof(name)); logInfo << "initialized SDL; using video driver `" << name << "'" << std::endl; } if (FE_Init() != 0) { const char* error = FE_GetError(); gError.init(Error::FASTEVENTS_INIT, error); return; // fatal } mAlDevice = alcOpenDevice(0); mAlContext = alcCreateContext(mAlDevice, 0); if (!mAlDevice || !mAlContext) { const char* error = alcGetString(mAlDevice,alcGetError(mAlDevice)); gError.init(Error::OPENAL_INIT, error); return; } else { alcMakeContextCurrent(mAlContext); logInfo << "opened sound device `" << alcGetString(mAlDevice, ALC_DEFAULT_DEVICE_SPECIFIER) << "'" << std::endl; } gError.init(Error::NONE); } ~Backend_() { alcMakeContextCurrent(0); alcDestroyContext(mAlContext); alcCloseDevice(mAlDevice); FE_Quit(); SDL_Quit(); } static void retain() { if (gRetainCount++ == 0) { gInstance = BackendP(new Backend_); gInstance->init(); } } static void release() { if (--gRetainCount == 0) { gInstance.reset(); gError.reset(); } } static bool check(Error& error) { error = gError; return error.code() == Error::NONE; } private: ALCdevice* mAlDevice; ALCcontext* mAlContext; static Error gError; static int gRetainCount; static BackendP gInstance; }; Error Backend_::gError(Error::UNINITIALIZED); int Backend_::gRetainCount = 0; BackendP Backend_::gInstance; Backend::Backend() { Backend_::retain(); } Backend::~Backend() { Backend_::release(); } bool Backend::check(Error& error) { return Backend_::check(error); } } // namespace Mf /** vim: set ts=4 sw=4 tw=80: *************************************************/