/*] Copyright (c) 2009-2010, Charles McGarvey [************************** **] All rights reserved. * * vi:ts=4 sw=4 tw=75 * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * **************************************************************************/ #include #include // exit, srand #include // time #include #include #include "fastevents.h" #include "Error.hh" #include "Event.hh" #include "Log.hh" #include "Math.hh" #include "ModalDialog.hh" #include "Settings.hh" #include "Timer.hh" #include "Video.hh" #include "View.hh" namespace Mf { class RootView : public View { void update(Scalar t, Scalar dt) { if (children().size() == 0) stop(); } }; static RootView gRootView; class View::Impl { public: Impl(View* view, Settings& settings, Video& video) : mView(*view), mSettings(&settings), mVideo(&video), mParent(&gRootView) { 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); } Impl(View* view) : mView(*view), mSettings(0), mVideo(0), mParent(&gRootView) { init(); } void init() { mTimestep = SCALAR(0.01); mFramerate = SCALAR(0.02); mShowFps = false; } /** * 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() { ASSERT(mVideo && "running without video set"); Scalar totalTime = 0.0; Scalar ticks = Timer::getTicks(); Scalar nextUpdate = ticks; Scalar nextDraw = ticks; Scalar nextSecond = ticks + SCALAR(1.0); mFps = 0; int frameCount = 0; const Scalar timestep = mTimestep; const Scalar framerate = mFramerate; const int MAX_FRAMESKIP = 15; const Scalar inverseTimestep = SCALAR(1.0) / timestep; mIsRunning = true; for (;;) { Timer::fireIfExpired(); // 1. fire timers dispatchEvents(); // 2. dispatch events if (!mIsRunning) break; int i = 0; while (nextUpdate < Timer::getTicks() && i < MAX_FRAMESKIP) { totalTime += timestep; // 3. update state mView.update(totalTime, timestep); nextUpdate += timestep; ++i; if (!mIsRunning) break; } if (nextDraw < (ticks = Timer::getTicks())) { mView.draw( (ticks + timestep - nextUpdate) * inverseTimestep); mVideo->swap(); // 4. draw state nextDraw += framerate; ++frameCount; if (nextSecond < Timer::getTicks()) { mFps = frameCount; frameCount = 0; if (mShowFps) logInfo << mFps << " fps" << std::endl; nextSecond += SCALAR(1.0); } } if (!mIsRunning) break; ticks = Timer::getTicks(); // 5. yield timeslice if (ticks < nextUpdate && ticks < nextDraw) Timer::sleep(0.0); } } void stop() { mIsRunning = false; } void dispatchEvents() { 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: mVideo->resize(event.resize.w, event.resize.h); break; } mView.handleEvent(event); } } bool handleEvent(const Event& event) { std::list::iterator it; for (it = mChildren.begin(); it != mChildren.end(); ++it) { if ((*it)->handleEvent(event)) return true; } return false; } void update(Scalar t, Scalar dt) { std::list::iterator it; for (it = mChildren.begin(); it != mChildren.end(); ++it) { (*it)->update(t, dt); } } void draw(Scalar alpha) { std::list::iterator it; for (it = mChildren.begin(); it != mChildren.end(); ++it) { (*it)->draw(alpha); } } void addChild(ViewP child) { ASSERT(child && "adding null view"); ASSERT(child.get() != &mView && "adding view to itself"); child->mImpl->mParent->removeChild(child); mChildren.push_back(child); child->mImpl->mParent = &mView; child->mImpl->percolateObjects(); child->didAddToView(); } void percolateObjects() { bool recurseAgain = false; if (mParent->mImpl->mVideo && mParent->mImpl->mVideo != mVideo) { mVideo = mParent->mImpl->mVideo; recurseAgain = true; } if (mParent->mImpl->mSettings && mParent->mImpl->mSettings != mSettings) { mSettings = mParent->mImpl->mSettings; recurseAgain = true; } if (recurseAgain) { std::list::iterator it; for (it = mChildren.begin(); it != mChildren.end(); ++it) { (*it)->mImpl->percolateObjects(); } } } ViewP removeChild(View* child) { ASSERT(child && "cannot remove null child"); std::list::iterator it; for (it = mChildren.begin(); it != mChildren.end(); ++it) { if ((*it).get() == child) { ViewP found = *it; found->willRemoveFromView(); mChildren.erase(it); found->mImpl->mParent = &gRootView; return found; } } return ViewP(); } void clear() { mChildren.clear(); } bool mIsRunning; View& mView; Settings* mSettings; Video* mVideo; View* mParent; std::list mChildren; Scalar mTimestep; Scalar mFramerate; int mFps; bool mShowFps; }; View::View(Settings& settings, Video& video) : // pass through mImpl(new View::Impl(this, settings, video)) {} View::View() : mImpl(new View::Impl(this)) {} void View::update(Scalar t, Scalar dt) { // pass through mImpl->update(t, dt); } void View::draw(Scalar alpha) const { // pass through mImpl->draw(alpha); } bool View::handleEvent(const Event& event) { // pass through return mImpl->handleEvent(event); } void View::addChild(ViewP view) { // pass through mImpl->addChild(view); } ViewP View::removeChild(View* view) { // pass through return mImpl->removeChild(view); } ViewP View::removeChild(ViewP view) { // pass through return mImpl->removeChild(view.get()); } void View::clear() { // pass through mImpl->clear(); } View& View::parent() const { return *(mImpl->mParent); } const std::list& View::children() const { return mImpl->mChildren; } Settings& View::settings() const { ASSERT(mImpl->mSettings && "accessing null reference"); // pass through return *(mImpl->mSettings); } Video& View::video() const { ASSERT(mImpl->mVideo && "accessing null reference"); // pass through return *(mImpl->mVideo); } void View::run() { // pass through return mImpl->run(); } void View::stop() { // pass through return mImpl->stop(); } bool View::isRunning() const { // pass through return mImpl->mIsRunning; } } // namespace Mf