Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Merging presentation branch back into trunk.
There are many new features and also a lot of other changes and bugfixes, if you want to know, digg through the svn log.
Not everything is yet working as it should, but it should be fairly stable. If you habe any bug reports, just send me an email.

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