Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/Game.cc @ 8215

Last change on this file since 8215 was 8215, checked in by dafrick, 13 years ago

Bugfix, so that orxonox will start in modes that don't show any graphics, e.g. dedicated or masterserver mode.

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