Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/core7/src/libraries/core/Game.cc @ 10513

Last change on this file since 10513 was 10479, checked in by landauf, 10 years ago

moved config values and all related functions from Game and Core to GameConfig and CoreConfig respectively. this ensures that no framework features are used by Game and Core before Core itself initialized the framework.

  • Property svn:eol-style set to native
File size: 24.3 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/Output.h"
43#include "util/Exception.h"
44#include "util/Sleep.h"
45#include "util/SubString.h"
46#include "Core.h"
47#include "commandline/CommandLineParser.h"
48#include "GameConfig.h"
49#include "GameMode.h"
50#include "GameState.h"
51#include "GraphicsManager.h"
52#include "GUIManager.h"
53#include "command/ConsoleCommandIncludes.h"
54
55namespace orxonox
56{
57    static void stop_game()
58        { Game::getInstance().stop(); }
59    SetConsoleCommand("exit", &stop_game);
60    static void printFPS()
61        { orxout(message) << Game::getInstance().getAvgFPS() << endl; }
62    SetConsoleCommand("Stats", "printFPS", &printFPS);
63    static void printTickTime()
64        { orxout(message) << Game::getInstance().getAvgTickTime() << endl; }
65    SetConsoleCommand("Stats", "printTickTime", &printTickTime);
66
67    std::map<std::string, GameStateInfo> Game::gameStateDeclarations_s;
68    Game* Game::singletonPtr_s = 0;
69
70    //! Represents one node of the game state tree.
71    struct GameStateTreeNode
72    {
73        std::string name_;
74        weak_ptr<GameStateTreeNode> parent_;
75        std::vector<shared_ptr<GameStateTreeNode> > children_;
76    };
77
78    Game::Game(const std::string& cmdLine)
79        : gameClock_(NULL)
80        , core_(NULL)
81        , bChangingState_(false)
82        , bAbort_(false)
83        , config_(NULL)
84        , destructionHelper_(this)
85    {
86        orxout(internal_status) << "initializing Game object..." << endl;
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_ = new Clock();
108
109        // Create the Core
110        orxout(internal_info) << "creating Core object:" << endl;
111        this->core_ = new Core(cmdLine);
112
113        // Do this after the Core creation!
114        this->config_ = new GameConfig();
115
116        // After the core has been created, we can safely instantiate the GameStates that don't require graphics
117        for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
118            it != gameStateDeclarations_s.end(); ++it)
119        {
120            if (!it->second.bGraphicsMode)
121                constructedStates_[it->second.stateName] = GameStateFactory::fabricate(it->second);
122        }
123
124        // The empty root state is ALWAYS loaded!
125        this->rootStateNode_ = shared_ptr<GameStateTreeNode>(new GameStateTreeNode());
126        this->rootStateNode_->name_ = "emptyRootGameState";
127        this->loadedTopStateNode_ = this->rootStateNode_;
128        this->loadedStates_.push_back(this->getState(rootStateNode_->name_));
129
130        orxout(internal_status) << "finished initializing Game object" << endl;
131    }
132
133    void Game::destroy()
134    {
135        orxout(internal_status) << "destroying Game object..." << endl;
136
137        assert(loadedStates_.size() <= 1); // Just empty root GameState
138        // Destroy all GameStates (shared_ptrs take care of actual destruction)
139        constructedStates_.clear();
140
141        GameStateFactory::getFactories().clear();
142        safeObjectDelete(&config_);
143        safeObjectDelete(&core_);
144        safeObjectDelete(&gameClock_);
145
146        orxout(internal_status) << "finished destroying Game object..." << endl;
147    }
148
149    /**
150    @brief
151        Main loop of the orxonox game.
152    @note
153        We use the Ogre::Timer to measure time since it uses the most precise
154        method an any platform (however the windows timer lacks time when under
155        heavy kernel load!).
156    */
157    void Game::run()
158    {
159        if (this->requestedStateNodes_.empty())
160            orxout(user_error) << "Starting game without requesting GameState. This automatically terminates the program." << endl;
161
162        // Update the GameState stack if required. We do this already here to have a properly initialized game before entering the main loop
163        this->updateGameStateStack();
164
165        orxout(user_status) << "Game loaded" << endl;
166        orxout(internal_status) << "-------------------- starting main loop --------------------" << endl;
167
168        // START GAME
169        // first delta time should be about 0 seconds
170        this->gameClock_->capture();
171        // A first item is required for the fps limiter
172        StatisticsTickInfo tickInfo = {0, 0};
173        statisticsTickTimes_.push_back(tickInfo);
174        while (!this->bAbort_ && (!this->loadedStates_.empty() || this->requestedStateNodes_.size() > 0))
175        {
176            // Generate the dt
177            this->gameClock_->capture();
178
179            // Statistics init
180            StatisticsTickInfo tickInfo = {gameClock_->getMicroseconds(), 0};
181            statisticsTickTimes_.push_back(tickInfo);
182            this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();
183
184            // Update the GameState stack if required
185            this->updateGameStateStack();
186
187            // Core preUpdate
188            try
189                { this->core_->preUpdate(*this->gameClock_); }
190            catch (...)
191            {
192                orxout(user_error) << "An exception occurred in the Core preUpdate: " << Exception::handleMessage() << endl;
193                orxout(user_error) << "This should really never happen! Closing the program." << endl;
194                this->stop();
195                break;
196            }
197
198            // Update the GameStates bottom up in the stack
199            this->updateGameStates();
200
201            // Core postUpdate
202            try
203                { this->core_->postUpdate(*this->gameClock_); }
204            catch (...)
205            {
206                orxout(user_error) << "An exception occurred in the Core postUpdate: " << Exception::handleMessage() << endl;
207                orxout(user_error) << "This should really never happen! Closing the program." << endl;
208                this->stop();
209                break;
210            }
211
212            // Evaluate statistics
213            this->updateStatistics();
214
215            // Limit frame rate
216            static bool hasVSync = GameMode::showsGraphics() && GraphicsManager::getInstance().hasVSyncEnabled(); // can be static since changes of VSync currently require a restart
217            if (this->config_->getFpsLimit() > 0 && !hasVSync)
218                this->updateFPSLimiter();
219        }
220
221        orxout(internal_status) << "-------------------- finished main loop --------------------" << endl;
222
223        // UNLOAD all remaining states
224        while (this->loadedStates_.size() > 1)
225            this->unloadState(this->loadedStates_.back()->getName());
226        this->loadedTopStateNode_ = this->rootStateNode_;
227        this->requestedStateNodes_.clear();
228    }
229
230    void Game::updateGameStateStack()
231    {
232        while (this->requestedStateNodes_.size() > 0)
233        {
234            shared_ptr<GameStateTreeNode> requestedStateNode = this->requestedStateNodes_.front();
235            assert(this->loadedTopStateNode_);
236            if (!this->loadedTopStateNode_->parent_.expired() && requestedStateNode == this->loadedTopStateNode_->parent_.lock())
237                this->unloadState(loadedTopStateNode_->name_);
238            else // has to be child
239            {
240                try
241                {
242                    this->loadState(requestedStateNode->name_);
243                }
244                catch (...)
245                {
246                    orxout(user_error) << "Loading GameState '" << requestedStateNode->name_ << "' failed: " << Exception::handleMessage() << endl;
247                    // All scheduled operations have now been rendered inert --> flush them and issue a warning
248                    if (this->requestedStateNodes_.size() > 1)
249                        orxout(internal_info) << "All " << this->requestedStateNodes_.size() - 1 << " scheduled transitions have been ignored." << endl;
250                    this->requestedStateNodes_.clear();
251                    break;
252                }
253            }
254            this->loadedTopStateNode_ = requestedStateNode;
255            this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());
256        }
257    }
258
259    void Game::updateGameStates()
260    {
261        // Note: The first element is the empty root state, which doesn't need ticking
262        for (GameStateVector::const_iterator it = this->loadedStates_.begin() + 1;
263            it != this->loadedStates_.end(); ++it)
264        {
265            try
266            {
267                // Add tick time for most of the states
268                uint64_t timeBeforeTick = 0;
269                if ((*it)->getInfo().bIgnoreTickTime)
270                    timeBeforeTick = this->gameClock_->getRealMicroseconds();
271                (*it)->update(*this->gameClock_);
272                if ((*it)->getInfo().bIgnoreTickTime)
273                    this->subtractTickTime(static_cast<int32_t>(this->gameClock_->getRealMicroseconds() - timeBeforeTick));
274            }
275            catch (...)
276            {
277                orxout(user_error) << "An exception occurred while updating '" << (*it)->getName() << "': " << Exception::handleMessage() << endl;
278                orxout(user_error) << "This should really never happen!" << endl;
279                orxout(user_error) << "Unloading all GameStates depending on the one that crashed." << endl;
280                shared_ptr<GameStateTreeNode> current = this->loadedTopStateNode_;
281                while (current->name_ != (*it)->getName() && current)
282                    current = current->parent_.lock();
283                if (current && current->parent_.lock())
284                    this->requestState(current->parent_.lock()->name_);
285                else
286                    this->stop();
287                break;
288            }
289        }
290    }
291
292    void Game::updateStatistics()
293    {
294        // Add the tick time of this frame (rendering time has already been subtracted)
295        uint64_t currentTime = gameClock_->getMicroseconds();
296        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
297        this->statisticsTickTimes_.back().tickLength += (uint32_t)(currentRealTime - currentTime);
298        this->periodTickTime_ += (uint32_t)(currentRealTime - currentTime);
299        if (this->periodTime_ > this->config_->getStatisticsRefreshCycle())
300        {
301            std::list<StatisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();
302            assert(it != this->statisticsTickTimes_.end());
303            int64_t lastTime = currentTime - this->config_->getStatisticsAvgLength();
304            if (static_cast<int64_t>(it->tickTime) < lastTime)
305            {
306                do
307                {
308                    assert(this->periodTickTime_ >= it->tickLength);
309                    this->periodTickTime_ -= it->tickLength;
310                    ++it;
311                    assert(it != this->statisticsTickTimes_.end());
312                } while (static_cast<int64_t>(it->tickTime) < lastTime);
313                this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);
314            }
315
316            uint32_t framesPerPeriod = this->statisticsTickTimes_.size();
317            // Why minus 1? No idea, but otherwise the fps rate is always (from 10 to 200!) one frame too low
318            this->avgFPS_ = -1 + static_cast<float>(framesPerPeriod) / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0f;
319            this->avgTickTime_ = static_cast<float>(this->periodTickTime_) / framesPerPeriod / 1000.0f;
320
321            this->periodTime_ -= this->config_->getStatisticsRefreshCycle();
322        }
323    }
324
325    void Game::updateFPSLimiter()
326    {
327        uint64_t nextTime = gameClock_->getMicroseconds() - excessSleepTime_ + static_cast<uint32_t>(1000000.0f / this->config_->getFpsLimit());
328        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
329        while (currentRealTime < nextTime - minimumSleepTime_)
330        {
331            usleep((unsigned long)(nextTime - currentRealTime));
332            currentRealTime = gameClock_->getRealMicroseconds();
333        }
334        // Integrate excess to avoid steady state error
335        excessSleepTime_ = (int)(currentRealTime - nextTime);
336        // Anti windup
337        if (excessSleepTime_ > 50000) // 20ms is about the maximum time Windows would sleep for too long
338            excessSleepTime_ = 50000;
339    }
340
341    void Game::stop()
342    {
343        orxout(user_status) << "Exit" << endl;
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            orxout(user_warning) << "GameState named '" << name << "' doesn't exist!" << endl;
362            return;
363        }
364
365        if (this->bChangingState_)
366        {
367            orxout(user_warning) << "Requesting GameStates while loading/unloading a GameState is illegal! Ignoring." << 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            orxout(user_warning) << "Requesting the currently active state! Ignoring." << 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            orxout(user_error) << "Requested GameState transition is not allowed. Ignoring." << 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            orxout(internal_warning) << "Can't pop the internal dummy root GameState" << 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                orxout(internal_error) << "GameState '" << name << "' has not yet been loaded." << endl;
444            else
445                orxout(internal_error) << "Could not find GameState '" << name << "'." << 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(std::string(), -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            orxout(user_status) << "Loading graphics" << endl;
514            orxout(internal_info) << "loading graphics in Game" << endl;
515
516            core_->loadGraphics();
517            Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);
518
519            // Construct all the GameStates that require graphics
520            for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
521                it != gameStateDeclarations_s.end(); ++it)
522            {
523                if (it->second.bGraphicsMode)
524                {
525                    // Game state loading failure is serious --> don't catch
526                    shared_ptr<GameState> gameState = GameStateFactory::fabricate(it->second);
527                    if (!constructedStates_.insert(std::make_pair(
528                        it->second.stateName, gameState)).second)
529                        assert(false); // GameState was already created!
530                }
531            }
532            graphicsUnloader.Dismiss();
533
534            orxout(internal_info) << "finished loading graphics in Game" << endl;
535        }
536    }
537
538    void Game::unloadGraphics()
539    {
540        if (GameMode::showsGraphics())
541        {
542            orxout(user_status) << "Unloading graphics" << endl;
543            orxout(internal_info) << "unloading graphics in Game" << endl;
544
545            // Destroy all the GameStates that require graphics
546            for (GameStateMap::iterator it = constructedStates_.begin(); it != constructedStates_.end();)
547            {
548                if (it->second->getInfo().bGraphicsMode)
549                    constructedStates_.erase(it++);
550                else
551                    ++it;
552            }
553
554            core_->unloadGraphics();
555        }
556    }
557
558    bool Game::checkState(const std::string& name) const
559    {
560        std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
561        if (it == gameStateDeclarations_s.end())
562            return false;
563        else
564            return true;
565    }
566
567    void Game::loadState(const std::string& name)
568    {
569        orxout(internal_status) << "loading state '" << name << "'" << endl;
570
571        this->bChangingState_ = true;
572        LOKI_ON_BLOCK_EXIT_OBJ(*this, &Game::resetChangingState); (void)LOKI_ANONYMOUS_VARIABLE(scopeGuard);
573
574        // If state requires graphics, load it
575        Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);
576        if (gameStateDeclarations_s[name].bGraphicsMode && !GameMode::showsGraphics())
577            this->loadGraphics();
578        else
579            graphicsUnloader.Dismiss();
580
581        shared_ptr<GameState> state = this->getState(name);
582        state->activateInternal();
583        if (!this->loadedStates_.empty())
584            this->loadedStates_.back()->activity_.topState = false;
585        this->loadedStates_.push_back(state);
586        state->activity_.topState = true;
587
588        graphicsUnloader.Dismiss();
589    }
590
591    void Game::unloadState(const std::string& name)
592    {
593        orxout(internal_status) << "unloading state '" << name << "'" << endl;
594
595        this->bChangingState_ = true;
596        try
597        {
598            shared_ptr<GameState> state = this->getState(name);
599            state->activity_.topState = false;
600            this->loadedStates_.pop_back();
601            if (!this->loadedStates_.empty())
602                this->loadedStates_.back()->activity_.topState = true;
603            state->deactivateInternal();
604        }
605        catch (...)
606        {
607            orxout(internal_warning) << "Unloading GameState '" << name << "' threw an exception: " << Exception::handleMessage() << endl;
608            orxout(internal_warning) << "There might be potential resource leaks involved! To avoid this, improve exception-safety." << endl;
609        }
610        // Check if graphics is still required
611        if (!bAbort_)
612        {
613            bool graphicsRequired = false;
614            for (unsigned i = 0; i < loadedStates_.size(); ++i)
615                graphicsRequired |= loadedStates_[i]->getInfo().bGraphicsMode;
616            if (!graphicsRequired)
617                this->unloadGraphics();
618        }
619        this->bChangingState_ = false;
620    }
621
622    /*static*/ std::map<std::string, shared_ptr<Game::GameStateFactory> >& Game::GameStateFactory::getFactories()
623    {
624        static std::map<std::string, shared_ptr<GameStateFactory> > factories;
625        return factories;
626    }
627
628    /*static*/ shared_ptr<GameState> Game::GameStateFactory::fabricate(const GameStateInfo& info)
629    {
630        std::map<std::string, shared_ptr<Game::GameStateFactory> >::const_iterator it = getFactories().find(info.className);
631        assert(it != getFactories().end());
632        return it->second->fabricateInternal(info);
633    }
634}
Note: See TracBrowser for help on using the repository browser.