Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/presentation2/src/libraries/core/Game.cc @ 6166

Last change on this file since 6166 was 6161, checked in by rgrieder, 15 years ago

Fixed uninitialised value problem with the statistics (at least for the first few calls to getAvgFPS() before Game::run()).

  • Property svn:eol-style set to native
File size: 23.4 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29/**
30@file
31@brief
32    Implementation of the Game class.
33*/
34
35#include "Game.h"
36
37#include <exception>
38#include <boost/weak_ptr.hpp>
39
40#include "util/Clock.h"
41#include "util/Debug.h"
42#include "util/Exception.h"
43#include "util/ScopeGuard.h"
44#include "util/Sleep.h"
45#include "util/SubString.h"
46#include "CommandLineParser.h"
47#include "ConsoleCommand.h"
48#include "Core.h"
49#include "CoreIncludes.h"
50#include "ConfigValueIncludes.h"
51#include "GameMode.h"
52#include "GameState.h"
53#include "GUIManager.h"
54
55namespace orxonox
56{
57    static void stop_game()
58        { Game::getInstance().stop(); }
59    SetConsoleCommandShortcutExternAlias(stop_game, "exit");
60    static void key_esc()
61        { Game::getInstance().keyESC(); }
62    SetConsoleCommandShortcutExternAlias(key_esc, "keyESC");
63    static void printFPS()
64        { COUT(0) << Game::getInstance().getAvgFPS() << std::endl; }
65    SetConsoleCommandShortcutExternAlias(printFPS, "printFPS");
66    static void printTickTime()
67        { COUT(0) << Game::getInstance().getAvgTickTime() << std::endl; }
68    SetConsoleCommandShortcutExternAlias(printTickTime, "printTickTime");
69
70    std::map<std::string, GameStateInfo> Game::gameStateDeclarations_s;
71    Game* Game::singletonPtr_s = 0;
72
73    //! Represents one node of the game state tree.
74    struct GameStateTreeNode
75    {
76        std::string name_;
77        weak_ptr<GameStateTreeNode> parent_;
78        std::vector<shared_ptr<GameStateTreeNode> > children_;
79    };
80
81    Game::Game(const std::string& cmdLine)
82        // Destroy factories before the Core!
83        : gsFactoryDestroyer_(Game::GameStateFactory::getFactories(), &std::map<std::string, shared_ptr<GameStateFactory> >::clear)
84    {
85        this->bAbort_ = false;
86        bChangingState_ = false;
87
88#ifdef ORXONOX_PLATFORM_WINDOWS
89        minimumSleepTime_ = 1000/*us*/;
90#else
91        minimumSleepTime_ = 0/*us*/;
92#endif
93
94        // reset statistics
95        this->statisticsStartTime_ = 0;
96        this->statisticsTickTimes_.clear();
97        this->periodTickTime_ = 0;
98        this->periodTime_ = 0;
99        this->avgFPS_ = 0.0f;
100        this->avgTickTime_ = 0.0f;
101        this->excessSleepTime_ = 0;
102
103        // Create an empty root state
104        this->declareGameState<GameState>("GameState", "emptyRootGameState", true, false);
105
106        // Set up a basic clock to keep time
107        this->gameClock_.reset(new Clock());
108
109        // Create the Core
110        this->core_.reset(new Core(cmdLine));
111
112        // Do this after the Core creation!
113        ClassIdentifier<Game>::getIdentifier("Game")->initialiseObject(this, "Game", true);
114        // Remove us from the object lists again to avoid problems when destroying the Game
115        this->unregisterObject();
116        this->setConfigValues();
117
118        // After the core has been created, we can safely instantiate the GameStates that don't require graphics
119        for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
120            it != gameStateDeclarations_s.end(); ++it)
121        {
122            if (!it->second.bGraphicsMode)
123                constructedStates_[it->second.stateName] = GameStateFactory::fabricate(it->second);
124        }
125
126        // The empty root state is ALWAYS loaded!
127        this->rootStateNode_ = shared_ptr<GameStateTreeNode>(new GameStateTreeNode());
128        this->rootStateNode_->name_ = "emptyRootGameState";
129        this->loadedTopStateNode_ = this->rootStateNode_;
130        this->loadedStates_.push_back(this->getState(rootStateNode_->name_));
131    }
132
133    /**
134    @brief
135        All destruction code is handled by scoped_ptrs and SimpleScopeGuards.
136    */
137    Game::~Game()
138    {
139    }
140
141    void Game::setConfigValues()
142    {
143        SetConfigValue(statisticsRefreshCycle_, 250000)
144            .description("Sets the time in microseconds interval at which average fps, etc. get updated.");
145        SetConfigValue(statisticsAvgLength_, 1000000)
146            .description("Sets the time in microseconds interval at which average fps, etc. gets calculated.");
147        SetConfigValue(fpsLimit_, 50)
148            .description("Sets the desired framerate (0 for no limit).");
149    }
150
151    /**
152    @brief
153        Main loop of the orxonox game.
154    @note
155        We use the Ogre::Timer to measure time since it uses the most precise
156        method an any platform (however the windows timer lacks time when under
157        heavy kernel load!).
158    */
159    void Game::run()
160    {
161        if (this->requestedStateNodes_.empty())
162            COUT(0) << "Warning: Starting game without requesting GameState. This automatically terminates the program." << std::endl;
163
164        // START GAME
165        // first delta time should be about 0 seconds
166        this->gameClock_->capture();
167        // A first item is required for the fps limiter
168        StatisticsTickInfo tickInfo = {0, 0};
169        statisticsTickTimes_.push_back(tickInfo);
170        while (!this->bAbort_ && (!this->loadedStates_.empty() || this->requestedStateNodes_.size() > 0))
171        {
172            // Generate the dt
173            this->gameClock_->capture();
174
175            // Statistics init
176            StatisticsTickInfo tickInfo = {gameClock_->getMicroseconds(), 0};
177            statisticsTickTimes_.push_back(tickInfo);
178            this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();
179
180            // Update the GameState stack if required
181            this->updateGameStateStack();
182
183            // Core preUpdate (doesn't throw)
184            try
185                { this->core_->preUpdate(*this->gameClock_); }
186            catch (...)
187            {
188                COUT(0) << "An exception occurred in the Core preUpdate: " << Exception::handleMessage() << std::endl;
189                COUT(0) << "This should really never happen! Closing the program." << std::endl;
190                this->stop();
191                break;
192            }
193
194            // Update the GameStates bottom up in the stack
195            this->updateGameStates();
196
197            // Core postUpdate (doesn't throw)
198            try
199                { this->core_->postUpdate(*this->gameClock_); }
200            catch (...)
201            {
202                COUT(0) << "An exception occurred in the Core postUpdate: " << Exception::handleMessage() << std::endl;
203                COUT(0) << "This should really never happen! Closing the program." << std::endl;
204                this->stop();
205                break;
206            }
207
208            // Evaluate statistics
209            this->updateStatistics();
210
211            // Limit framerate
212            this->updateFPSLimiter();
213        }
214
215        // UNLOAD all remaining states
216        while (this->loadedStates_.size() > 1)
217            this->unloadState(this->loadedStates_.back()->getName());
218        this->loadedTopStateNode_ = this->rootStateNode_;
219        this->requestedStateNodes_.clear();
220    }
221
222    void Game::updateGameStateStack()
223    {
224        while (this->requestedStateNodes_.size() > 0)
225        {
226            shared_ptr<GameStateTreeNode> requestedStateNode = this->requestedStateNodes_.front();
227            assert(this->loadedTopStateNode_);
228            if (!this->loadedTopStateNode_->parent_.expired() && requestedStateNode == this->loadedTopStateNode_->parent_.lock())
229                this->unloadState(loadedTopStateNode_->name_);
230            else // has to be child
231            {
232                try
233                {
234                    this->loadState(requestedStateNode->name_);
235                }
236                catch (...)
237                {
238                    COUT(1) << "Error: Loading GameState '" << requestedStateNode->name_ << "' failed: " << Exception::handleMessage() << std::endl;
239                    // All scheduled operations have now been rendered inert --> flush them and issue a warning
240                    if (this->requestedStateNodes_.size() > 1)
241                        COUT(4) << "All " << this->requestedStateNodes_.size() - 1 << " scheduled transitions have been ignored." << std::endl;
242                    this->requestedStateNodes_.clear();
243                    break;
244                }
245            }
246            this->loadedTopStateNode_ = requestedStateNode;
247            this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());
248        }
249    }
250
251    void Game::updateGameStates()
252    {
253        // Note: The first element is the empty root state, which doesn't need ticking
254        for (GameStateVector::const_iterator it = this->loadedStates_.begin() + 1;
255            it != this->loadedStates_.end(); ++it)
256        {
257            try
258            {
259                // Add tick time for most of the states
260                uint64_t timeBeforeTick = 0;
261                if ((*it)->getInfo().bIgnoreTickTime)
262                    timeBeforeTick = this->gameClock_->getRealMicroseconds();
263                (*it)->update(*this->gameClock_);
264                if ((*it)->getInfo().bIgnoreTickTime)
265                    this->subtractTickTime(static_cast<int32_t>(this->gameClock_->getRealMicroseconds() - timeBeforeTick));
266            }
267            catch (...)
268            {
269                COUT(1) << "An exception occurred while updating '" << (*it)->getName() << "': " << Exception::handleMessage() << std::endl;
270                COUT(1) << "This should really never happen!" << std::endl;
271                COUT(1) << "Unloading all GameStates depending on the one that crashed." << std::endl;
272                shared_ptr<GameStateTreeNode> current = this->loadedTopStateNode_;
273                while (current->name_ != (*it)->getName() && current)
274                    current = current->parent_.lock();
275                if (current && current->parent_.lock())
276                    this->requestState(current->parent_.lock()->name_);
277                else
278                    this->stop();
279                break;
280            }
281        }
282    }
283
284    void Game::updateStatistics()
285    {
286        // Add the tick time of this frame (rendering time has already been subtracted)
287        uint64_t currentTime = gameClock_->getMicroseconds();
288        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
289        this->statisticsTickTimes_.back().tickLength += currentRealTime - currentTime;
290        this->periodTickTime_ += currentRealTime - currentTime;
291        if (this->periodTime_ > this->statisticsRefreshCycle_)
292        {
293            std::list<StatisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();
294            assert(it != this->statisticsTickTimes_.end());
295            int64_t lastTime = currentTime - this->statisticsAvgLength_;
296            if (static_cast<int64_t>(it->tickTime) < lastTime)
297            {
298                do
299                {
300                    assert(this->periodTickTime_ >= it->tickLength);
301                    this->periodTickTime_ -= it->tickLength;
302                    ++it;
303                    assert(it != this->statisticsTickTimes_.end());
304                } while (static_cast<int64_t>(it->tickTime) < lastTime);
305                this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);
306            }
307
308            uint32_t framesPerPeriod = this->statisticsTickTimes_.size();
309            this->avgFPS_ = static_cast<float>(framesPerPeriod) / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0f;
310            this->avgTickTime_ = static_cast<float>(this->periodTickTime_) / framesPerPeriod / 1000.0f;
311
312            this->periodTime_ -= this->statisticsRefreshCycle_;
313        }
314    }
315
316    void Game::updateFPSLimiter()
317    {
318        // Why fpsLimit_ - 1? No idea, but otherwise the fps rate is always (from 10 to 200!) one frame too high
319        uint32_t nextTime = gameClock_->getMicroseconds() - excessSleepTime_ + static_cast<uint32_t>(1000000.0f / (fpsLimit_ - 1));
320        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
321        while (currentRealTime < nextTime - minimumSleepTime_)
322        {
323            usleep(nextTime - currentRealTime);
324            currentRealTime = gameClock_->getRealMicroseconds();
325        }
326        // Integrate excess to avoid steady state error
327        excessSleepTime_ = currentRealTime - nextTime;
328        // Anti windup
329        if (excessSleepTime_ > 50000) // 20ms is about the maximum time Windows would sleep for too long
330            excessSleepTime_ = 50000;
331    }
332
333    void Game::keyESC()
334    {
335        if( this->getState("mainMenu") && this->getState("mainMenu")->getActivity().active==true )
336            this->stop();
337        else
338            GUIManager::getInstance().keyESC();
339    }
340
341    void Game::stop()
342    {
343        this->bAbort_ = true;
344    }
345
346    void Game::subtractTickTime(int32_t length)
347    {
348        assert(!this->statisticsTickTimes_.empty());
349        this->statisticsTickTimes_.back().tickLength -= length;
350        this->periodTickTime_ -= length;
351    }
352
353
354    /***** GameState related *****/
355
356    void Game::requestState(const std::string& name)
357    {
358        if (!this->checkState(name))
359        {
360            COUT(2) << "Warning: GameState named '" << name << "' doesn't exist!" << std::endl;
361            return;
362        }
363
364        if (this->bChangingState_)
365        {
366            COUT(2) << "Warning: Requesting GameStates while loading/unloading a GameState is illegal! Ignoring." << std::endl;
367            return;
368        }
369
370        shared_ptr<GameStateTreeNode> lastRequestedNode;
371        if (this->requestedStateNodes_.empty())
372            lastRequestedNode = this->loadedTopStateNode_;
373        else
374            lastRequestedNode = this->requestedStateNodes_.back();
375        if (name == lastRequestedNode->name_)
376        {
377            COUT(2) << "Warning: Requesting the currently active state! Ignoring." << std::endl;
378            return;
379        }
380
381        // Check children first
382        std::vector<shared_ptr<GameStateTreeNode> > requestedNodes;
383        for (unsigned int i = 0; i < lastRequestedNode->children_.size(); ++i)
384        {
385            if (lastRequestedNode->children_[i]->name_ == name)
386            {
387                requestedNodes.push_back(lastRequestedNode->children_[i]);
388                break;
389            }
390        }
391
392        if (requestedNodes.empty())
393        {
394            // Check parent and all its grand parents
395            shared_ptr<GameStateTreeNode> currentNode = lastRequestedNode;
396            while (currentNode != NULL)
397            {
398                if (currentNode->name_ == name)
399                    break;
400                currentNode = currentNode->parent_.lock();
401                requestedNodes.push_back(currentNode);
402            }
403            if (currentNode == NULL)
404                requestedNodes.clear();
405        }
406
407        if (requestedNodes.empty())
408            COUT(1) << "Error: Requested GameState transition is not allowed. Ignoring." << std::endl;
409        else
410            this->requestedStateNodes_.insert(requestedStateNodes_.end(), requestedNodes.begin(), requestedNodes.end());
411    }
412
413    void Game::requestStates(const std::string& names)
414    {
415        SubString tokens(names, ",;", " ");
416        for (unsigned int i = 0; i < tokens.size(); ++i)
417            this->requestState(tokens[i]);
418    }
419
420    void Game::popState()
421    {
422        shared_ptr<GameStateTreeNode> lastRequestedNode;
423        if (this->requestedStateNodes_.empty())
424            lastRequestedNode = this->loadedTopStateNode_;
425        else
426            lastRequestedNode = this->requestedStateNodes_.back();
427        if (lastRequestedNode != this->rootStateNode_)
428            this->requestState(lastRequestedNode->parent_.lock()->name_);
429        else
430            COUT(2) << "Warning: Can't pop the internal dummy root GameState" << std::endl;
431    }
432
433    shared_ptr<GameState> Game::getState(const std::string& name)
434    {
435        GameStateMap::const_iterator it = constructedStates_.find(name);
436        if (it != constructedStates_.end())
437            return it->second;
438        else
439        {
440            std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
441            if (it != gameStateDeclarations_s.end())
442                COUT(1) << "Error: GameState '" << name << "' has not yet been loaded." << std::endl;
443            else
444                COUT(1) << "Error: Could not find GameState '" << name << "'." << std::endl;
445            return shared_ptr<GameState>();
446        }
447    }
448
449    void Game::setStateHierarchy(const std::string& str)
450    {
451        // Split string into pieces of the form whitespacesText
452        std::vector<std::pair<std::string, int> > stateStrings;
453        size_t pos = 0;
454        size_t startPos = 0;
455        while (pos < str.size())
456        {
457            int indentation = 0;
458            while(pos < str.size() && str[pos] == ' ')
459                ++indentation, ++pos;
460            startPos = pos;
461            while(pos < str.size() && str[pos] != ' ')
462                ++pos;
463            stateStrings.push_back(std::make_pair(str.substr(startPos, pos - startPos), indentation));
464        }
465        if (stateStrings.empty())
466            ThrowException(GameState, "Emtpy GameState hierarchy provided, terminating.");
467        // Add element with large identation to detect the last with just an iterator
468        stateStrings.push_back(std::make_pair("", -1));
469
470        // Parse elements recursively
471        std::vector<std::pair<std::string, int> >::const_iterator begin = stateStrings.begin();
472        parseStates(begin, this->rootStateNode_);
473    }
474
475    /*** Internal ***/
476
477    void Game::parseStates(std::vector<std::pair<std::string, int> >::const_iterator& it, shared_ptr<GameStateTreeNode> currentNode)
478    {
479        SubString tokens(it->first, ",");
480        std::vector<std::pair<std::string, int> >::const_iterator startIt = it;
481
482        for (unsigned int i = 0; i < tokens.size(); ++i)
483        {
484            it = startIt; // Reset iterator to the beginning of the sub tree
485            if (!this->checkState(tokens[i]))
486                ThrowException(GameState, "GameState with name '" << tokens[i] << "' not found!");
487            if (tokens[i] == this->rootStateNode_->name_)
488                ThrowException(GameState, "You shouldn't use 'emptyRootGameState' in the hierarchy...");
489            shared_ptr<GameStateTreeNode> node(new GameStateTreeNode());
490            node->name_ = tokens[i];
491            node->parent_ = currentNode;
492            currentNode->children_.push_back(node);
493
494            int currentLevel = it->second;
495            ++it;
496            while (it->second != -1)
497            {
498                if (it->second <= currentLevel)
499                    break;
500                else if (it->second == currentLevel + 1)
501                    parseStates(it, node);
502                else
503                    ThrowException(GameState, "Indentation error while parsing the hierarchy.");
504            }
505        }
506    }
507
508    void Game::loadGraphics()
509    {
510        if (!GameMode::showsGraphics())
511        {
512            core_->loadGraphics();
513            Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);
514
515            // Construct all the GameStates that require graphics
516            for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
517                it != gameStateDeclarations_s.end(); ++it)
518            {
519                if (it->second.bGraphicsMode)
520                {
521                    // Game state loading failure is serious --> don't catch
522                    shared_ptr<GameState> gameState = GameStateFactory::fabricate(it->second);
523                    if (!constructedStates_.insert(std::make_pair(
524                        it->second.stateName, gameState)).second)
525                        assert(false); // GameState was already created!
526                }
527            }
528            graphicsUnloader.Dismiss();
529        }
530    }
531
532    void Game::unloadGraphics()
533    {
534        if (GameMode::showsGraphics())
535        {
536            // Destroy all the GameStates that require graphics
537            for (GameStateMap::iterator it = constructedStates_.begin(); it != constructedStates_.end();)
538            {
539                if (it->second->getInfo().bGraphicsMode)
540                    constructedStates_.erase(it++);
541                else
542                    ++it;
543            }
544
545            core_->unloadGraphics();
546        }
547    }
548
549    bool Game::checkState(const std::string& name) const
550    {
551        std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
552        if (it == gameStateDeclarations_s.end())
553            return false;
554        else
555            return true;
556    }
557
558    void Game::loadState(const std::string& name)
559    {
560        this->bChangingState_ = true;
561        LOKI_ON_BLOCK_EXIT_OBJ(*this, &Game::resetChangingState);
562
563        // If state requires graphics, load it
564        Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);
565        if (gameStateDeclarations_s[name].bGraphicsMode && !GameMode::showsGraphics())
566            this->loadGraphics();
567        else
568            graphicsUnloader.Dismiss();
569
570        shared_ptr<GameState> state = this->getState(name);
571        state->activateInternal();
572        if (!this->loadedStates_.empty())
573            this->loadedStates_.back()->activity_.topState = false;
574        this->loadedStates_.push_back(state);
575        state->activity_.topState = true;
576
577        graphicsUnloader.Dismiss();
578    }
579
580    void Game::unloadState(const std::string& name)
581    {
582        this->bChangingState_ = true;
583        try
584        {
585            shared_ptr<GameState> state = this->getState(name);
586            state->activity_.topState = false;
587            this->loadedStates_.pop_back();
588            if (!this->loadedStates_.empty())
589                this->loadedStates_.back()->activity_.topState = true;
590            state->deactivateInternal();
591        }
592        catch (...)
593        {
594            COUT(2) << "Warning: Unloading GameState '" << name << "' threw an exception: " << Exception::handleMessage() << std::endl;
595            COUT(2) << "         There might be potential resource leaks involved! To avoid this, improve exception-safety." << std::endl;
596        }
597        // Check if graphics is still required
598        if (!bAbort_)
599        {
600            bool graphicsRequired = false;
601            for (unsigned i = 0; i < loadedStates_.size(); ++i)
602                graphicsRequired |= loadedStates_[i]->getInfo().bGraphicsMode;
603            if (!graphicsRequired)
604                this->unloadGraphics();
605        }
606        this->bChangingState_ = false;
607    }
608
609    /*static*/ std::map<std::string, shared_ptr<Game::GameStateFactory> >& Game::GameStateFactory::getFactories()
610    {
611        static std::map<std::string, shared_ptr<GameStateFactory> > factories;
612        return factories;
613    }
614
615    /*static*/ shared_ptr<GameState> Game::GameStateFactory::fabricate(const GameStateInfo& info)
616    {
617        std::map<std::string, shared_ptr<Game::GameStateFactory> >::const_iterator it = getFactories().find(info.className);
618        assert(it != getFactories().end());
619        return it->second->fabricateInternal(info);
620    }
621}
Note: See TracBrowser for help on using the repository browser.