Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 7266 was 7266, checked in by rgrieder, 14 years ago

Moved Loki library files to separate loki folder in externals.
Also added TypeManip.h (now used in Convert.h) and static_check.h.

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