Best way to handle input and state changes for multiple states?

I have an application that has multiple states, with each state reacting differently to input.

The original implementation was done with a large switch statement which I refactored using the state pattern (at least I think it is a state pattern. I'm kind of new to using design patterns so I tend to confuse them) -

class App {

  public:
    static App * getInstance();
    void addState(int state_id, AppState * state) { _states[state_id] = state; }
    void setCurrentState(int state_id) { _current_state = _states[state_id]; }

  private:
    App()
    ~App();
    std::map<int, AppState *> _states;
    AppState * _current_state;
    static App * _instance;
}

class AppState {

  public:
    virtual void handleInput() = 0;
    virtual ~AppState();

  protected:
    AppState();

}

      

Currently, each state polls the OS for input and acts accordingly. This means that every particular state has a huge switch statement with an argument for every valid keystroke. Some cases call functions and other cases cause state changes using App :: setCurrentState (newstate). The trick is that a key that does something in one state may do nothing (or, in rare cases, it may do something different) in another state.

Okay, I think the background is appropriate. Here's the actual question -

First, what's the best way to eliminate huge switch statements in specific states? This question suggests a command pattern, but I don't understand how I would use it here. Can anyone help explain this or suggest another solution?

As a side note, I considered (and did not mind) the idea of ​​letting the App class poll the OS and then pass the inputs to _current_state-> handleInput. In fact, something tells me that I want to do this as part of the refactoring. I haven't done it yet.

Second, state changes are made by calling App :: setCurrentState (newstate). I realize this is akin to using globals, but I'm not sure about the best way to do it. My main goal is to add states without changing the App class. Suggestions are also welcome here.

+1


source to share


5 answers


Given your refactoring, it looks like the question now is how to reduce the amount of parsing code that would be duplicated in different concrete AppState implementations. As you say, this results in several switch statements that choose which code to call to handle the keypress input.



Depending on how critical the code is, you can split this code decoding logic into a processInput (int keycode) method in your application (or as a specific method in AppState) and create a set of * Pressed () descriptor functions in your AppState classes. Depending on how many types of keystrokes you are looking at, this might make sense, or it might result in too many methods to implement.

+1


source


I redid things a bit -

I've eliminated direct calls to App :: setCurrentState by requiring a pointer to the state machine (application) to be passed to the AppState constructor. Thus, all the necessary calls can be made using this pointer.

I added a handleInput parameter and made it so that the application will make the OS input request and pass any input to the current state.



The new code looks like this:

class App {

  public:
    static App * getInstance();
    void addState(int state_id, AppState * state) { _states[state_id] = state; }
    void setCurrentState(int state_id) { _current_state = _states[state_id]; }

  private:
    App()
    ~App();
    std::map<int, AppState *> _states;
    AppState * _current_state;
    static App * _instance;
}

class AppState {

  public:
    virtual void handleInput(int keycode) = 0;
    virtual ~AppState();

  protected:
    AppState(App * app);
    AppState * _app;

}

      

So this still leaves a large statement statement in each state that translates keystrokes into state-dependent actions. I suppose I can replace the switch to a keymap to actions, but I'm still wondering if there is a better way.

+1


source


I wrote a library where I reorganized a lot of staff stuff. It's clean and pretty OO, and doesn't really add a lot of overhead, but it's a very different way of coding a state machine.

It includes a couple of tests if nothing else they can give you some ideas.

You can have a look at it if you like: http://code.google.com/p/state-machine/

0


source


If you can capture all OS input in a class, then you can have a single object listening for input and use the chain of responsibility pattern to notify OS specific login actions.

0


source


You look at things like:

I have a static container for states (your application) and many states (you AppState) that can contain data and only one handler.

Instead, look at it like:

I have one StateMachine class. (I may or may not have many examples of this.) This contains the data needed to interact with the outside world. Also contains a static set of eventHandlers, one for each state. These eventHandler classes contain no data.

class StateMachine {
public:
    void handleInput() { //there is now only one dispatcher
        if( world.doingInput1() )
            _current_state->handleInput1( *this );

        else if( world.doingInput2() )
            _current_state->handleInput2( *this, world.get_Input2Argument() );

        //...
    }

    //the states, just a set of event handlers
    static const State& state1;
    static const State& state2;
    //...

    StateMachine( OutsideWorld& world )
       :world( world )  
    {
        setCurrentState( StateMachine::state1 );
    }

    void setCurrentState( const State& state ) { _current_state = &state; }

    OutsidWorld& world;
private:
    State* _current_state;
};

class State {
public:
    //virtual ~State(); //no resources so no cleanup
    virtual void handleInput1( StateMachine& sm ) const {};
    virtual void handleInput2( StateMachine& sm, int myParam ) const {};
    //...
};

class State1 {
public:
    //define the ones that actually do stuff
    virtual void handleInput1( StateMachine& sm ) const { 
       sm.world.DoSomething();
       sm.setCurrentState( StateMachine::state27 );
    }
    virtual void handleInput27( StateMachine& sm, int myParam ) const { 
       sm.world.DoSomethingElse( myParam );
    };
};
const State& StateMachine::state1 = *new State1();

//... more states

      

0


source







All Articles