Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/archive/waypoints/src/libraries/core/Game.cc @ 11183

Last change on this file since 11183 was 8861, checked in by landauf, 13 years ago

added some additional output for loading steps that might take some time on slow systems

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