Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/resource/src/core/Game.cc @ 3358

Last change on this file since 3358 was 3358, checked in by rgrieder, 16 years ago

Moved startup argument parsing (console, dedicated, server, client and standalone) from GSRoot.cc to Main.cc.
This allows to finally prevent GameState request while loading/unloading them (changed the code in Game.cc to enforce this).

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