Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/core/Game.cc @ 3307

Last change on this file since 3307 was 3307, checked in by landauf, 16 years ago

Completely rewrote TclThreadManager. This fixes several bugs from the initial implementation. The main features work fine now, but some tasks are still open (for example destroying a thread or implementing a queue size limit).

I hope this doesn't cause any troubles because I used the TclThreadManager-version from netp6 as a draft for the reimplementation. I've also applied the c-style-cast-fixes from core.

I tested this with boost 1.37, I hope it also works with 1.35 (I haven't seen any remarkable changes in the log). However it won't work with boost 1.34.1 and lower, but this change already happened back in netp6, so please update your dependencies if you're still using an old version (the current release is 1.39 btw).

  • Property svn:eol-style set to native
File size: 19.7 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 "GameState.h"
51
52namespace orxonox
53{
54    using boost::shared_ptr;
55    using boost::weak_ptr;
56
57    static void stop_game()
58        { Game::getInstance().stop(); }
59    SetConsoleCommandShortcutExternAlias(stop_game, "exit");
60
61    std::map<std::string, Game::GameStateInfo> Game::gameStateDeclarations_s;
62    Game* Game::singletonRef_s = 0;
63
64
65    /**
66    @brief
67        Represents one node of the game state tree.
68    */
69    struct GameStateTreeNode
70    {
71        GameState* state_;
72        weak_ptr<GameStateTreeNode> parent_;
73        std::vector<shared_ptr<GameStateTreeNode> > children_;
74    };
75
76
77    /**
78    @brief
79        Another helper class for the Game singleton: we cannot derive
80        Game from OrxonoxClass because we need to handle the Identifier
81        destruction in the Core destructor.
82    */
83    class GameConfiguration : public OrxonoxClass
84    {
85    public:
86        GameConfiguration()
87        {
88            RegisterRootObject(GameConfiguration);
89            this->setConfigValues();
90        }
91
92        void setConfigValues()
93        {
94            SetConfigValue(statisticsRefreshCycle_, 250000)
95                .description("Sets the time in microseconds interval at which average fps, etc. get updated.");
96            SetConfigValue(statisticsAvgLength_, 1000000)
97                .description("Sets the time in microseconds interval at which average fps, etc. gets calculated.");
98            SetConfigValue(fpsLimit_, 50)
99                .description("Sets the desired framerate (0 for no limit).");
100        }
101
102        unsigned int statisticsRefreshCycle_;
103        unsigned int statisticsAvgLength_;
104        unsigned int fpsLimit_;
105    };
106
107
108    /**
109    @brief
110        Non-initialising constructor.
111    */
112    Game::Game(int argc, char** argv)
113    {
114        if (singletonRef_s != 0)
115        {
116            COUT(0) << "Error: The Game singleton cannot be recreated! Shutting down." << std::endl;
117            abort();
118        }
119        singletonRef_s = this;
120
121        this->bAbort_ = false;
122        bChangingState_ = false;
123
124        // Create an empty root state
125        declareGameState<GameState>("GameState", "emptyRootGameState", true, false);
126
127        // reset statistics
128        this->statisticsStartTime_ = 0;
129        this->statisticsTickTimes_.clear();
130        this->periodTickTime_ = 0;
131        this->periodTime_ = 0;
132        this->avgFPS_ = 0.0f;
133        this->avgTickTime_ = 0.0f;
134
135        // Set up a basic clock to keep time
136        this->gameClock_ = new Clock();
137
138        // Create the Core
139        this->core_ = new Core(argc, argv);
140
141        // After the core has been created, we can safely instantiate the GameStates
142        for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
143            it != gameStateDeclarations_s.end(); ++it)
144        {
145            // Only create the states appropriate for the game mode
146            //if (GameMode::showsGraphics || !it->second.bGraphicsMode)
147            GameStateConstrParams params = { it->second.stateName, it->second.bIgnoreTickTime };
148            gameStates_[getLowercase(it->second.stateName)] = GameStateFactory::fabricate(it->second.className, params);
149        }
150
151        // The empty root state is ALWAYS loaded!
152        this->rootStateNode_ = shared_ptr<GameStateTreeNode>(new GameStateTreeNode());
153        this->rootStateNode_->state_ = getState("emptyRootGameState");
154        this->activeStateNode_ = this->rootStateNode_;
155        this->activeStates_.push_back(this->rootStateNode_->state_);
156
157        // Do this after the Core creation!
158        this->configuration_ = new GameConfiguration();
159    }
160
161    /**
162    @brief
163    */
164    Game::~Game()
165    {
166        // Destroy the configuration helper class instance
167        delete this->configuration_;
168
169        // Destroy the GameStates (note that the nodes still point to them, but doesn't matter)
170        for (std::map<std::string, GameState*>::const_iterator it = gameStates_.begin();
171            it != gameStates_.end(); ++it)
172            delete it->second;
173
174        // Destroy the Core and with it almost everything
175        delete this->core_;
176        delete this->gameClock_;
177
178        // Take care of the GameStateFactories
179        GameStateFactory::destroyFactories();
180
181        // Don't assign singletonRef_s with NULL! Recreation is not supported
182    }
183
184    /**
185    @brief
186        Main loop of the orxonox game.
187    @note
188        We use the Ogre::Timer to measure time since it uses the most precise
189        method an any platform (however the windows timer lacks time when under
190        heavy kernel load!).
191    */
192    void Game::run()
193    {
194        if (this->requestedStateNodes_.empty())
195            COUT(0) << "Warning: Starting game without requesting GameState. This automatically terminates the program." << std::endl;
196
197        // START GAME
198        // first delta time should be about 0 seconds
199        this->gameClock_->capture();
200        // A first item is required for the fps limiter
201        StatisticsTickInfo tickInfo = {0, 0};
202        statisticsTickTimes_.push_back(tickInfo);
203        while (!this->bAbort_ && (!this->activeStates_.empty() || this->requestedStateNodes_.size() > 0))
204        {
205            uint64_t currentTime = this->gameClock_->getRealMicroseconds();
206
207            uint64_t nextTickTime = statisticsTickTimes_.back().tickTime + static_cast<uint64_t>(1000000.0f / configuration_->fpsLimit_);
208            if (currentTime < nextTickTime)
209            {
210                usleep(nextTickTime - currentTime);
211                continue;
212            }
213            this->gameClock_->capture();
214
215            // STATISTICS
216            StatisticsTickInfo tickInfo = {currentTime, 0};
217            statisticsTickTimes_.push_back(tickInfo);
218            this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();
219
220            // UPDATE STATE STACK
221            while (this->requestedStateNodes_.size() > 0)
222            {
223                shared_ptr<GameStateTreeNode> requestedStateNode = this->requestedStateNodes_.front();
224                assert(this->activeStateNode_);
225                if (!this->activeStateNode_->parent_.expired() && requestedStateNode == this->activeStateNode_->parent_.lock())
226                    this->unloadState(this->activeStateNode_->state_);
227                else // has to be child
228                {
229                    try
230                    {
231                        this->loadState(requestedStateNode->state_);
232                    }
233                    catch (const std::exception& ex)
234                    {
235                        COUT(1) << "Error: Loading GameState '" << requestedStateNode->state_->getName() << "' failed: " << ex.what() << 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(1) << "All " << this->requestedStateNodes_.size() - 1 << " scheduled transitions have been ignored." << std::endl;
239                        this->requestedStateNodes_.clear();
240                        break;
241                    }
242                }
243                this->activeStateNode_ = requestedStateNode;
244                this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());
245            }
246
247            // UPDATE, Core first
248            bool threwException = false;
249            try
250            {
251                this->core_->update(*this->gameClock_);
252            }
253            catch (const std::exception& ex)
254            {
255                threwException = true;
256                COUT(0) << "Exception while ticking the Core: " << ex.what() << std::endl;
257            }
258            catch (...)
259            {
260                threwException = true;
261            }
262            if (threwException)
263            {
264                COUT(0) << "An exception occured while ticking the Core. This should really never happen!" << std::endl;
265                COUT(0) << "Closing the program." << std::endl;
266                this->stop();
267                break;
268            }
269
270            // UPDATE, GameStates bottom to top in the stack
271            // Note: The first element is the empty root state, which doesn't need ticking
272            for (std::vector<GameState*>::const_iterator it = this->activeStates_.begin() + 1;
273                it != this->activeStates_.end(); ++it)
274            {
275                bool threwException = false;
276                try
277                {
278                    // Add tick time for most of the states
279                    uint64_t timeBeforeTick;
280                    if (!(*it)->ignoreTickTime())
281                        timeBeforeTick = this->gameClock_->getRealMicroseconds();
282                    (*it)->update(*this->gameClock_);
283                    if (!(*it)->ignoreTickTime())
284                        this->addTickTime(static_cast<uint32_t>(this->gameClock_->getRealMicroseconds() - timeBeforeTick));
285                }
286                catch (const std::exception& ex)
287                {
288                    threwException = true;
289                    COUT(0) << "Exception while ticking: " << ex.what() << std::endl;
290                }
291                catch (...)
292                {
293                    threwException = true;
294                }
295                if (threwException)
296                {
297                    COUT(1) << "An exception occured while ticking GameState '" << (*it)->getName() << "'. This should really never happen!" << std::endl;
298                    COUT(1) << "Unloading all GameStates depending on the one that crashed." << std::endl;
299                    if ((*it)->getParent() != NULL)
300                        this->requestState((*it)->getParent()->getName());
301                    else
302                        this->stop();
303                    break;
304                }
305
306            }
307
308            // STATISTICS
309            if (this->periodTime_ > this->configuration_->statisticsRefreshCycle_)
310            {
311                std::list<StatisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();
312                assert(it != this->statisticsTickTimes_.end());
313                int64_t lastTime = currentTime - this->configuration_->statisticsAvgLength_;
314                if (static_cast<int64_t>(it->tickTime) < lastTime)
315                {
316                    do
317                    {
318                        assert(this->periodTickTime_ >= it->tickLength);
319                        this->periodTickTime_ -= it->tickLength;
320                        ++it;
321                        assert(it != this->statisticsTickTimes_.end());
322                    } while (static_cast<int64_t>(it->tickTime) < lastTime);
323                    this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);
324                }
325
326                uint32_t framesPerPeriod = this->statisticsTickTimes_.size();
327                this->avgFPS_ = static_cast<float>(framesPerPeriod) / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0f;
328                this->avgTickTime_ = static_cast<float>(this->periodTickTime_) / framesPerPeriod / 1000.0f;
329
330                this->periodTime_ -= this->configuration_->statisticsRefreshCycle_;
331            }
332        }
333
334        // UNLOAD all remaining states
335        while (this->activeStates_.size() > 1)
336            this->unloadState(this->activeStates_.back());
337        this->activeStateNode_ = this->rootStateNode_;
338        this->requestedStateNodes_.clear();
339    }
340
341    void Game::stop()
342    {
343        this->bAbort_ = true;
344    }
345
346    void Game::addTickTime(uint32_t length)
347    {
348        assert(!this->statisticsTickTimes_.empty());
349        this->statisticsTickTimes_.back().tickLength += length;
350        this->periodTickTime_+=length;
351    }
352
353
354    /***** GameState related *****/
355
356    void Game::requestState(const std::string& name)
357    {
358        GameState* state = this->getState(name);
359        if (state == NULL)
360            return;
361
362        //if (this->bChangingState_)
363        //{
364        //    COUT(2) << "Warning: Requesting GameStates while loading/unloading a GameState is illegal! Ignoring." << std::endl;
365        //    return;
366        //}
367
368        shared_ptr<GameStateTreeNode> lastRequestedNode;
369        if (this->requestedStateNodes_.empty())
370            lastRequestedNode = this->activeStateNode_;
371        else
372            lastRequestedNode = this->requestedStateNodes_.back();
373        if (state == lastRequestedNode->state_)
374        {
375            COUT(2) << "Warning: Requesting the currently active state! Ignoring." << std::endl;
376            return;
377        }
378
379        // Check children first
380        std::vector<shared_ptr<GameStateTreeNode> > requestedNodes;
381        for (unsigned int i = 0; i < lastRequestedNode->children_.size(); ++i)
382        {
383            if (lastRequestedNode->children_[i]->state_ == state)
384            {
385                requestedNodes.push_back(lastRequestedNode->children_[i]);
386                break;
387            }
388        }
389
390        if (requestedNodes.empty())
391        {
392            // Check parent and all its grand parents
393            shared_ptr<GameStateTreeNode> currentNode = lastRequestedNode;
394            while (currentNode != NULL)
395            {
396                if (currentNode->state_ == state)
397                    break;
398                currentNode = currentNode->parent_.lock();
399                requestedNodes.push_back(currentNode);
400            }
401        }
402
403        if (requestedNodes.empty())
404            COUT(1) << "Error: Requested GameState transition is not allowed. Ignoring." << std::endl;
405        else
406            this->requestedStateNodes_.insert(requestedStateNodes_.end(), requestedNodes.begin(), requestedNodes.end());
407    }
408
409    void Game::requestStates(const std::string& names)
410    {
411        SubString tokens(names, ",;", " ");
412        for (unsigned int i = 0; i < tokens.size(); ++i)
413            this->requestState(tokens[i]);
414    }
415
416    void Game::popState()
417    {
418        shared_ptr<GameStateTreeNode> lastRequestedNode;
419        if (this->requestedStateNodes_.empty())
420            lastRequestedNode = this->activeStateNode_;
421        else
422            lastRequestedNode = this->requestedStateNodes_.back();
423        if (lastRequestedNode != this->rootStateNode_)
424            this->requestState(lastRequestedNode->parent_.lock()->state_->getName());
425        else
426            COUT(2) << "Warning: Can't pop the internal dummy root GameState" << std::endl;
427    }
428
429    GameState* Game::getState(const std::string& name)
430    {
431        std::map<std::string, GameState*>::const_iterator it = gameStates_.find(getLowercase(name));
432        if (it != gameStates_.end())
433            return it->second;
434        else
435        {
436            COUT(1) << "Error: Could not find GameState '" << name << "'. Ignoring." << std::endl;
437            return 0;
438        }
439    }
440
441    void Game::setStateHierarchy(const std::string& str)
442    {
443        // Split string into pieces of the form whitespacesText
444        std::vector<std::pair<std::string, unsigned> > stateStrings;
445        size_t pos = 0;
446        size_t startPos = 0;
447        while (pos < str.size())
448        {
449            unsigned indentation = 0;
450            while(pos < str.size() && str[pos] == ' ')
451                ++indentation, ++pos;
452            startPos = pos;
453            while(pos < str.size() && str[pos] != ' ')
454                ++pos;
455            stateStrings.push_back(std::make_pair(str.substr(startPos, pos - startPos), indentation));
456        }
457        unsigned int currentLevel = 0;
458        shared_ptr<GameStateTreeNode> currentNode = this->rootStateNode_;
459        for (std::vector<std::pair<std::string, unsigned> >::const_iterator it = stateStrings.begin(); it != stateStrings.end(); ++it)
460        {
461            std::string newStateName = it->first;
462            unsigned newLevel = it->second + 1; // empty root is 0
463            GameState* newState = this->getState(newStateName);
464            if (!newState)
465                ThrowException(GameState, "GameState with name '" << newStateName << "' not found!");
466            if (newState == this->rootStateNode_->state_)
467                ThrowException(GameState, "You shouldn't use 'emptyRootGameState' in the hierarchy...");
468            shared_ptr<GameStateTreeNode> newNode(new GameStateTreeNode);
469            newNode->state_ = newState;
470
471            if (newLevel <= currentLevel)
472            {
473                do
474                    currentNode = currentNode->parent_.lock();
475                while (newLevel <= --currentLevel);
476            }
477            if (newLevel == currentLevel + 1)
478            {
479                // Add the child
480                newNode->parent_ = currentNode;
481                currentNode->children_.push_back(newNode);
482                currentNode->state_->addChild(newNode->state_);
483            }
484            else
485                ThrowException(GameState, "Indentation error while parsing the hierarchy.");
486            currentNode = newNode;
487            currentLevel = newLevel;
488        }
489    }
490
491    /*** Internal ***/
492
493    void Game::loadState(GameState* state)
494    {
495        this->bChangingState_ = true;
496        state->activate();
497        if (!this->activeStates_.empty())
498            this->activeStates_.back()->activity_.topState = false;
499        this->activeStates_.push_back(state);
500        state->activity_.topState = true;
501        this->bChangingState_ = false;
502    }
503
504    void Game::unloadState(orxonox::GameState* state)
505    {
506        this->bChangingState_ = true;
507        state->activity_.topState = false;
508        this->activeStates_.pop_back();
509        if (!this->activeStates_.empty())
510            this->activeStates_.back()->activity_.topState = true;
511        try
512        {
513            state->deactivate();
514        }
515        catch (const std::exception& ex)
516        {
517            COUT(2) << "Warning: Unloading GameState '" << state->getName() << "' threw an exception: " << ex.what() << std::endl;
518            COUT(2) << "         There might be potential resource leaks involved! To avoid this, improve exception-safety." << std::endl;
519        }
520        this->bChangingState_ = false;
521    }
522
523    std::map<std::string, Game::GameStateFactory*> Game::GameStateFactory::factories_s;
524
525    /*static*/ GameState* Game::GameStateFactory::fabricate(const std::string& className, const GameStateConstrParams& params)
526    {
527        std::map<std::string, GameStateFactory*>::const_iterator it = factories_s.find(className);
528        assert(it != factories_s.end());
529        return it->second->fabricate(params);
530    }
531
532    /*static*/ void Game::GameStateFactory::destroyFactories()
533    {
534        for (std::map<std::string, GameStateFactory*>::const_iterator it = factories_s.begin(); it != factories_s.end(); ++it)
535            delete it->second;
536        factories_s.clear();
537    }
538}
Note: See TracBrowser for help on using the repository browser.