Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/input/InputManager.cc @ 6414

Last change on this file since 6414 was 6105, checked in by rgrieder, 15 years ago

Merged console branch back to trunk.

  • Property svn:eol-style set to native
File size: 25.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 InputManager and a static variable from the InputHandler.
33*/
34
35#include "InputManager.h"
36
37#include <cassert>
38#include <climits>
39#include <ois/OISException.h>
40#include <ois/OISInputManager.h>
41#include <boost/foreach.hpp>
42
43#include "util/Clock.h"
44#include "util/Convert.h"
45#include "util/Exception.h"
46#include "util/ScopeGuard.h"
47#include "core/CoreIncludes.h"
48#include "core/ConfigValueIncludes.h"
49#include "core/ConsoleCommand.h"
50#include "core/CommandLineParser.h"
51#include "core/Functor.h"
52#include "core/GraphicsManager.h"
53
54#include "InputBuffer.h"
55#include "JoyStick.h"
56#include "JoyStickQuantityListener.h"
57#include "Mouse.h"
58#include "Keyboard.h"
59
60namespace orxonox
61{
62    SetCommandLineSwitch(keyboard_no_grab).information("Whether not to exclusively grab the keyboard");
63
64    // Abuse of this source file for the InputHandler
65    InputHandler InputHandler::EMPTY;
66
67    InputManager* InputManager::singletonPtr_s = 0;
68
69    //! Defines the |= operator for easier use.
70    inline InputManager::State operator|=(InputManager::State& lval, InputManager::State rval)
71    {
72        return (lval = (InputManager::State)(lval | rval));
73    }
74
75    //! Defines the &= operator for easier use.
76    inline InputManager::State operator&=(InputManager::State& lval, int rval)
77    {
78        return (lval = (InputManager::State)(lval & rval));
79    }
80
81    // ############################################################
82    // #####                  Initialisation                  #####
83    // ##########                                        ##########
84    // ############################################################
85    InputManager::InputManager()
86        : internalState_(Bad)
87        , oisInputManager_(0)
88        , devices_(2)
89        , mouseMode_(MouseMode::Nonexclusive)
90        , emptyState_(0)
91        , calibratorCallbackHandler_(0)
92    {
93        RegisterRootObject(InputManager);
94
95        CCOUT(4) << "Constructing..." << std::endl;
96
97        this->setConfigValues();
98
99        if (GraphicsManager::getInstance().isFullScreen())
100            mouseMode_ = MouseMode::Exclusive;
101        this->loadDevices();
102
103        // Lowest priority empty InputState
104        emptyState_ = createInputState("empty", false, false, InputStatePriority::Empty);
105        emptyState_->setHandler(&InputHandler::EMPTY);
106        activeStates_[emptyState_->getPriority()] = emptyState_;
107
108        // Joy stick calibration helper callback
109        InputState* calibrator = createInputState("calibrator", false, false, InputStatePriority::Calibrator);
110        calibrator->setHandler(&InputHandler::EMPTY);
111        calibratorCallbackHandler_ = new InputBuffer();
112        calibratorCallbackHandler_->registerListener(this, &InputManager::stopCalibration, '\r', true);
113        calibrator->setKeyHandler(calibratorCallbackHandler_);
114
115        this->updateActiveStates();
116
117        // calibrate console command
118        this->getIdentifier()->addConsoleCommand(createConsoleCommand(createFunctor(&InputManager::calibrate, this), "calibrate"), true);
119        // reload console command
120        this->getIdentifier()->addConsoleCommand(createConsoleCommand(createFunctor(&InputManager::reload, this), "reload"), false);
121
122        CCOUT(4) << "Construction complete." << std::endl;
123        internalState_ = Nothing;
124    }
125
126    void InputManager::setConfigValues()
127    {
128    }
129
130    /**
131    @brief
132        Creates the OIS::InputMananger, the keyboard, the mouse and
133        the joys ticks. If either of the first two fail, this method throws an exception.
134    @param windowWidth
135        The width of the render window
136    @param windowHeight
137        The height of the render window
138    */
139    void InputManager::loadDevices()
140    {
141        CCOUT(4) << "Loading input devices..." << std::endl;
142
143        // When loading the devices they should not already be loaded
144        assert(internalState_ & Bad);
145        assert(devices_[InputDeviceEnumerator::Mouse] == 0);
146        assert(devices_[InputDeviceEnumerator::Keyboard] == 0);
147        assert(devices_.size() == InputDeviceEnumerator::FirstJoyStick);
148
149        // Fill parameter list
150        OIS::ParamList paramList;
151        size_t windowHnd = GraphicsManager::getInstance().getRenderWindowHandle();
152        paramList.insert(std::make_pair("WINDOW", multi_cast<std::string>(windowHnd)));
153#if defined(ORXONOX_PLATFORM_WINDOWS)
154        paramList.insert(std::make_pair("w32_keyboard", "DISCL_NONEXCLUSIVE"));
155        paramList.insert(std::make_pair("w32_keyboard", "DISCL_FOREGROUND"));
156        paramList.insert(std::make_pair("w32_mouse", "DISCL_FOREGROUND"));
157        if (mouseMode_ == MouseMode::Exclusive || GraphicsManager::getInstance().isFullScreen())
158        {
159            // Disable Windows key plus special keys (like play, stop, next, etc.)
160            paramList.insert(std::make_pair("w32_keyboard", "DISCL_NOWINKEY"));
161            paramList.insert(std::make_pair("w32_mouse", "DISCL_EXCLUSIVE"));
162        }
163        else
164            paramList.insert(std::make_pair("w32_mouse", "DISCL_NONEXCLUSIVE"));
165#elif defined(ORXONOX_PLATFORM_LINUX)
166        // Enabling this is probably a bad idea, but whenever orxonox crashes, the setting stays on
167        // Trouble might be that the Pressed event occurs a bit too often...
168        paramList.insert(std::make_pair("XAutoRepeatOn", "true"));
169
170        if (mouseMode_ == MouseMode::Exclusive || GraphicsManager::getInstance().isFullScreen())
171        {
172            if (CommandLineParser::getValue("keyboard_no_grab").getBool())
173                paramList.insert(std::make_pair("x11_keyboard_grab", "false"));
174            else
175                paramList.insert(std::make_pair("x11_keyboard_grab", "true"));
176            paramList.insert(std::make_pair("x11_mouse_grab",  "true"));
177            paramList.insert(std::make_pair("x11_mouse_hide", "true"));
178        }
179        else
180        {
181            paramList.insert(std::make_pair("x11_keyboard_grab", "false"));
182            paramList.insert(std::make_pair("x11_mouse_grab",  "false"));
183            paramList.insert(std::make_pair("x11_mouse_hide", "false"));
184        }
185#endif
186
187        try
188        {
189            oisInputManager_ = OIS::InputManager::createInputSystem(paramList);
190            // Exception-safety
191            Loki::ScopeGuard guard = Loki::MakeGuard(OIS::InputManager::destroyInputSystem, oisInputManager_);
192            CCOUT(4) << "Created OIS input manager." << std::endl;
193
194            if (oisInputManager_->getNumberOfDevices(OIS::OISKeyboard) > 0)
195                devices_[InputDeviceEnumerator::Keyboard] = new Keyboard(InputDeviceEnumerator::Keyboard, oisInputManager_);
196            else
197                ThrowException(InitialisationFailed, "InputManager: No keyboard found, cannot proceed!");
198
199            // Successful initialisation
200            guard.Dismiss();
201        }
202        catch (const std::exception& ex)
203        {
204            oisInputManager_ = NULL;
205            internalState_ |= Bad;
206            ThrowException(InitialisationFailed, "Could not initialise the input system: " << ex.what());
207        }
208
209        this->loadMouse();
210        this->loadJoySticks();
211
212        // Reorder states in case some joy sticks were added/removed
213        this->updateActiveStates();
214
215        CCOUT(4) << "Input devices loaded." << std::endl;
216    }
217
218    //! Creates a new orxonox::Mouse
219    void InputManager::loadMouse()
220    {
221        if (oisInputManager_->getNumberOfDevices(OIS::OISMouse) > 0)
222        {
223            try
224            {
225                devices_[InputDeviceEnumerator::Mouse] = new Mouse(InputDeviceEnumerator::Mouse, oisInputManager_);
226            }
227            catch (const std::exception& ex)
228            {
229                CCOUT(2) << "Warning: Failed to create Mouse:" << ex.what() << std::endl
230                         << "Proceeding without mouse support." << std::endl;
231            }
232        }
233        else
234            CCOUT(2) << "Warning: No mouse found! Proceeding without mouse support." << std::endl;
235    }
236
237    //! Creates as many joy sticks as are available.
238    void InputManager::loadJoySticks()
239    {
240        for (int i = 0; i < oisInputManager_->getNumberOfDevices(OIS::OISJoyStick); i++)
241        {
242            try
243            {
244                devices_.push_back(new JoyStick(InputDeviceEnumerator::FirstJoyStick + i, oisInputManager_));
245            }
246            catch (const std::exception& ex)
247            {
248                CCOUT(2) << "Warning: Failed to create joy stick: " << ex.what() << std::endl;
249            }
250        }
251
252        // inform all JoyStick Device Number Listeners
253        std::vector<JoyStick*> joyStickList;
254        for (unsigned int i = InputDeviceEnumerator::FirstJoyStick; i < devices_.size(); ++i)
255            joyStickList.push_back(static_cast<JoyStick*>(devices_[i]));
256        JoyStickQuantityListener::changeJoyStickQuantity(joyStickList);
257    }
258
259    // ############################################################
260    // #####                    Destruction                   #####
261    // ##########                                        ##########
262    // ############################################################
263
264    InputManager::~InputManager()
265    {
266        CCOUT(3) << "Destroying..." << std::endl;
267
268        // Destroy calibrator helper handler and state
269        this->destroyState("calibrator");
270        // Destroy KeyDetector and state
271        calibratorCallbackHandler_->destroy();
272        // destroy the empty InputState
273        this->destroyStateInternal(this->emptyState_);
274
275        // destroy all user InputStates
276        while (statesByName_.size() > 0)
277            this->destroyStateInternal((*statesByName_.rbegin()).second);
278
279        if (!(internalState_ & Bad))
280            this->destroyDevices();
281
282        CCOUT(3) << "Destruction complete." << std::endl;
283    }
284
285    /**
286    @brief
287        Destoys all input devices (joy sticks, mouse, keyboard and OIS::InputManager)
288    @throw
289        Method does not throw
290    */
291    void InputManager::destroyDevices()
292    {
293        CCOUT(4) << "Destroying devices..." << std::endl;
294
295        BOOST_FOREACH(InputDevice*& device, devices_)
296        {
297            if (device == NULL)
298                continue;
299            std::string className = device->getClassName();
300            try
301            {
302                delete device;
303                device = 0;
304                CCOUT(4) << className << " destroyed." << std::endl;
305            }
306            catch (...)
307            {
308                COUT(1) << className << " destruction failed: " << Exception::handleMessage() << std::endl
309                        << "    Potential resource leak!" << std::endl;
310            }
311        }
312        devices_.resize(InputDeviceEnumerator::FirstJoyStick);
313
314        assert(oisInputManager_ != NULL);
315        try
316        {
317            OIS::InputManager::destroyInputSystem(oisInputManager_);
318        }
319        catch (...)
320        {
321            COUT(1) << "OIS::InputManager destruction failed" << Exception::handleMessage() << std::endl
322                    << "    Potential resource leak!" << std::endl;
323        }
324        oisInputManager_ = NULL;
325
326        internalState_ |= Bad;
327        CCOUT(4) << "Destroyed devices." << std::endl;
328    }
329
330    // ############################################################
331    // #####                     Reloading                    #####
332    // ##########                                        ##########
333    // ############################################################
334
335    void InputManager::reload()
336    {
337        if (internalState_ & Ticking)
338        {
339            // We cannot destroy OIS right now, because reload was probably
340            // caused by a user clicking on a GUI item. The stack trace would then
341            // include an OIS method. So it would be a very bad thing to destroy it..
342            internalState_ |= ReloadRequest;
343        }
344        else if (internalState_ & Calibrating)
345            CCOUT(2) << "Warning: Cannot reload input system. Joy sticks are currently being calibrated." << std::endl;
346        else
347            reloadInternal();
348    }
349
350    //! Internal reload method. Destroys the OIS devices and loads them again.
351    void InputManager::reloadInternal()
352    {
353        CCOUT(3) << "Reloading ..." << std::endl;
354
355        this->destroyDevices();
356        this->loadDevices();
357
358        internalState_ &= ~Bad;
359        internalState_ &= ~ReloadRequest;
360        CCOUT(4) << "Reloading complete." << std::endl;
361    }
362
363    // ############################################################
364    // #####                  Runtime Methods                 #####
365    // ##########                                        ##########
366    // ############################################################
367
368    void InputManager::update(const Clock& time)
369    {
370        if (internalState_ & Bad)
371            ThrowException(General, "InputManager was not correctly reloaded.");
372
373        else if (internalState_ & ReloadRequest)
374            reloadInternal();
375
376        // check for states to leave
377        if (!stateLeaveRequests_.empty())
378        {
379            for (std::set<InputState*>::iterator it = stateLeaveRequests_.begin();
380                it != stateLeaveRequests_.end(); ++it)
381            {
382                (*it)->left();
383                // just to be sure that the state actually is registered
384                assert(statesByName_.find((*it)->getName()) != statesByName_.end());
385
386                activeStates_.erase((*it)->getPriority());
387                if ((*it)->getPriority() < InputStatePriority::HighPriority)
388                    (*it)->setPriority(0);
389                updateActiveStates();
390            }
391            stateLeaveRequests_.clear();
392        }
393
394        // check for states to enter
395        if (!stateEnterRequests_.empty())
396        {
397            for (std::set<InputState*>::const_iterator it = stateEnterRequests_.begin();
398                it != stateEnterRequests_.end(); ++it)
399            {
400                // just to be sure that the state actually is registered
401                assert(statesByName_.find((*it)->getName()) != statesByName_.end());
402
403                if ((*it)->getPriority() == 0)
404                {
405                    // Get smallest possible priority between 1 and maxStateStackSize_s
406                    for(std::map<int, InputState*>::reverse_iterator rit = activeStates_.rbegin();
407                        rit != activeStates_.rend(); ++rit)
408                    {
409                        if (rit->first < InputStatePriority::HighPriority)
410                        {
411                            (*it)->setPriority(rit->first + 1);
412                            break;
413                        }
414                    }
415                    // In case no normal handler was on the stack
416                    if ((*it)->getPriority() == 0)
417                        (*it)->setPriority(1);
418                }
419                activeStates_[(*it)->getPriority()] = (*it);
420                updateActiveStates();
421                (*it)->entered();
422            }
423            stateEnterRequests_.clear();
424        }
425
426        // check for states to destroy
427        if (!stateDestroyRequests_.empty())
428        {
429            for (std::set<InputState*>::iterator it = stateDestroyRequests_.begin();
430                it != stateDestroyRequests_.end(); ++it)
431            {
432                destroyStateInternal((*it));
433            }
434            stateDestroyRequests_.clear();
435        }
436
437        // check whether a state has changed its EMPTY situation
438        bool bUpdateRequired = false;
439        for (std::map<int, InputState*>::iterator it = activeStates_.begin(); it != activeStates_.end(); ++it)
440        {
441            if (it->second->hasExpired())
442            {
443                it->second->resetExpiration();
444                bUpdateRequired = true;
445            }
446        }
447        if (bUpdateRequired)
448            updateActiveStates();
449
450        // mark that we now start capturing and distributing input
451        internalState_ |= Ticking;
452
453        // Capture all the input and handle it
454        BOOST_FOREACH(InputDevice* device, devices_)
455            if (device != NULL)
456                device->update(time);
457
458        // Update the states
459        for (unsigned int i = 0; i < activeStatesTicked_.size(); ++i)
460            activeStatesTicked_[i]->update(time.getDeltaTime());
461
462        internalState_ &= ~Ticking;
463    }
464
465    /**
466    @brief
467        Updates the currently active states (according to activeStates_) for each device.
468        Also, a list of all active states (no duplicates!) is compiled for the general update().
469    */
470    void InputManager::updateActiveStates()
471    {
472        assert((internalState_ & InputManager::Ticking) == 0);
473        // temporary resize
474        for (unsigned int i = 0; i < devices_.size(); ++i)
475        {
476            if (devices_[i] == NULL)
477                continue;
478            std::vector<InputState*>& states = devices_[i]->getStateListRef();
479            bool occupied = false;
480            states.clear();
481            for (std::map<int, InputState*>::reverse_iterator rit = activeStates_.rbegin(); rit != activeStates_.rend(); ++rit)
482            {
483                if (rit->second->isInputDeviceEnabled(i) && (!occupied || rit->second->bAlwaysGetsInput_))
484                {
485                    states.push_back(rit->second);
486                    if (!rit->second->bTransparent_)
487                        occupied = true;
488                }
489            }
490        }
491
492        // update tickables (every state will only appear once)
493        // Using a std::set to avoid duplicates
494        std::set<InputState*> tempSet;
495        for (unsigned int i = 0; i < devices_.size(); ++i)
496            if (devices_[i] != NULL)
497                for (unsigned int iState = 0; iState < devices_[i]->getStateListRef().size(); ++iState)
498                    tempSet.insert(devices_[i]->getStateListRef()[iState]);
499
500        // copy the content of the std::set back to the actual vector
501        activeStatesTicked_.clear();
502        for (std::set<InputState*>::const_iterator it = tempSet.begin();it != tempSet.end(); ++it)
503            activeStatesTicked_.push_back(*it);
504
505        // Check whether we have to change the mouse mode
506        MouseMode::Value requestedMode = MouseMode::Dontcare;
507        std::vector<InputState*>& mouseStates = devices_[InputDeviceEnumerator::Mouse]->getStateListRef();
508        if (mouseStates.empty())
509            requestedMode = MouseMode::Nonexclusive;
510        else 
511            requestedMode = mouseStates.front()->getMouseMode();
512        if (requestedMode != MouseMode::Dontcare && mouseMode_ != requestedMode)
513        {
514            mouseMode_ = requestedMode;
515            if (!GraphicsManager::getInstance().isFullScreen())
516                this->reloadInternal();
517        }
518    }
519
520    void InputManager::clearBuffers()
521    {
522        BOOST_FOREACH(InputDevice* device, devices_)
523            if (device != NULL)
524                device->clearBuffers();
525    }
526
527    void InputManager::calibrate()
528    {
529        COUT(0) << "Move all joy stick axes fully in all directions." << std::endl
530                << "When done, put the axex in the middle position and press enter." << std::endl;
531
532        BOOST_FOREACH(InputDevice* device, devices_)
533            if (device != NULL)
534                device->startCalibration();
535
536        internalState_ |= Calibrating;
537        enterState("calibrator");
538    }
539
540    //! Tells all devices to stop the calibration and evaluate it. Buffers are being cleared as well!
541    void InputManager::stopCalibration()
542    {
543        BOOST_FOREACH(InputDevice* device, devices_)
544            if (device != NULL)
545                device->stopCalibration();
546
547        // restore old input state
548        leaveState("calibrator");
549        internalState_ &= ~Calibrating;
550        // Clear buffers to prevent button hold events
551        this->clearBuffers();
552
553        COUT(0) << "Calibration has been stored." << std::endl;
554    }
555
556    //! Gets called by WindowEventListener upon focus change --> clear buffers
557    void InputManager::windowFocusChanged()
558    {
559        this->clearBuffers();
560    }
561
562    std::pair<int, int> InputManager::getMousePosition() const
563    {
564        Mouse* mouse = static_cast<Mouse*>(devices_[InputDeviceEnumerator::Mouse]);
565        if (mouse != NULL)
566        {
567            const OIS::MouseState state = mouse->getOISDevice()->getMouseState();
568            return std::make_pair(state.X.abs, state.Y.abs);
569        }
570        else
571            return std::make_pair(0, 0);
572    }
573
574    // ############################################################
575    // #####                    Input States                  #####
576    // ##########                                        ##########
577    // ############################################################
578
579    InputState* InputManager::createInputState(const std::string& name, bool bAlwaysGetsInput, bool bTransparent, InputStatePriority priority)
580    {
581        if (name == "")
582            return 0;
583        if (statesByName_.find(name) == statesByName_.end())
584        {
585            if (priority >= InputStatePriority::HighPriority || priority == InputStatePriority::Empty)
586            {
587                // Make sure we don't add two high priority states with the same priority
588                for (std::map<std::string, InputState*>::const_iterator it = this->statesByName_.begin();
589                    it != this->statesByName_.end(); ++it)
590                {
591                    if (it->second->getPriority() == priority)
592                    {
593                        COUT(2) << "Warning: Could not add an InputState with the same priority '"
594                            << static_cast<int>(priority) << "' != 0." << std::endl;
595                        return 0;
596                    }
597                }
598            }
599            InputState* state = new InputState(name, bAlwaysGetsInput, bTransparent, priority);
600            statesByName_[name] = state;
601
602            return state;
603        }
604        else
605        {
606            COUT(2) << "Warning: Could not add an InputState with the same name '" << name << "'." << std::endl;
607            return 0;
608        }
609    }
610
611    InputState* InputManager::getState(const std::string& name)
612    {
613        std::map<std::string, InputState*>::iterator it = statesByName_.find(name);
614        if (it != statesByName_.end())
615            return it->second;
616        else
617            return 0;
618    }
619
620    bool InputManager::enterState(const std::string& name)
621    {
622        // get pointer from the map with all stored handlers
623        std::map<std::string, InputState*>::const_iterator it = statesByName_.find(name);
624        if (it != statesByName_.end())
625        {
626            // exists
627            if (activeStates_.find(it->second->getPriority()) == activeStates_.end())
628            {
629                // not active
630                if (stateDestroyRequests_.find(it->second) == stateDestroyRequests_.end())
631                {
632                    // not scheduled for destruction
633                    // prevents a state being added multiple times
634                    stateEnterRequests_.insert(it->second);
635                    return true;
636                }
637            }
638        }
639        return false;
640    }
641
642    bool InputManager::leaveState(const std::string& name)
643    {
644        if (name == "empty")
645        {
646            COUT(2) << "InputManager: Leaving the empty state is not allowed!" << std::endl;
647            return false;
648        }
649        // get pointer from the map with all stored handlers
650        std::map<std::string, InputState*>::const_iterator it = statesByName_.find(name);
651        if (it != statesByName_.end())
652        {
653            // exists
654            if (activeStates_.find(it->second->getPriority()) != activeStates_.end())
655            {
656                // active
657                stateLeaveRequests_.insert(it->second);
658                return true;
659            }
660        }
661        return false;
662    }
663
664    bool InputManager::destroyState(const std::string& name)
665    {
666        if (name == "empty")
667        {
668            COUT(2) << "InputManager: Removing the empty state is not allowed!" << std::endl;
669            return false;
670        }
671        std::map<std::string, InputState*>::iterator it = statesByName_.find(name);
672        if (it != statesByName_.end())
673        {
674            if (activeStates_.find(it->second->getPriority()) != activeStates_.end())
675            {
676                // The state is still active. We have to postpone
677                stateLeaveRequests_.insert(it->second);
678                stateDestroyRequests_.insert(it->second);
679            }
680            else if (this->internalState_ & Ticking)
681            {
682                // cannot remove state while ticking
683                stateDestroyRequests_.insert(it->second);
684            }
685            else
686                destroyStateInternal(it->second);
687
688            return true;
689        }
690        return false;
691    }
692
693    //! Destroys an InputState internally.
694    void InputManager::destroyStateInternal(InputState* state)
695    {
696        assert(state && !(this->internalState_ & Ticking));
697        std::map<int, InputState*>::iterator it = this->activeStates_.find(state->getPriority());
698        if (it != this->activeStates_.end())
699        {
700            this->activeStates_.erase(it);
701            updateActiveStates();
702        }
703        statesByName_.erase(state->getName());
704        state->destroy();
705    }
706}
Note: See TracBrowser for help on using the repository browser.