Inconvenient OO design in state machine class?

Started by
11 comments, last by steven katic 16 years, 3 months ago
Since I started work, we've been using state machines everywhere to work with the different states of objects (as you would expect :D). Previously, I had been doing it the harder way, by creating an enum that lists the different states, having a member variable describing the current state and just having a large 'if else' in the object's update function. I decided to write one last night for my current demo, which is still in fairly early stages, so there was practically no changes necessary. each state is a single object with 3 functions: onEnter, onExit, onUpdate. Now, even the first thing I needed to do was to have a state for a particular game object and I had to write a base object for that state and then another object for each state.

class StateMachineState
{
public:

	virtual void onEnter() = 0;
	virtual void onUpdate() = 0;
	virtual void onExit() = 0;

};

class ObjectStateMachineState	:	public StateMachineState
{
private:
	Object* ptr;

public:

	ObjectStateMachineState(Object* p)
	:	ptr(p)
	{ }
};

And then I derive the needed classes, like 'move', etc, etc from 'ObjectStateMachineState'. The problem with this is that with simple states, like the one I implemented last night, which is nothing more than moving an object according to the current frame's timestep, still took 20 or so lines, apart from the one line creation and another to change the state. For a complicated class, this is no problem as complex classes are often longer and so 20 lines seems like very little, but with a class whose required functionality is two lines, having to write a 20 line class seems a bit much. Is it too much? Is it worth it leaving my states as individual objects?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Advertisement
I'd just use an enum with switch statements. Using inheritance for this seems unnecessary.

Can you give an example of a FSM that you are trying to implement?
-----------------------http://poita.org - C++ and D programming articles.
Quote:Original post by Poita
I'd just use an enum with switch statements. Using inheritance for this seems unnecessary.

Can you give an example of a FSM that you are trying to implement?


Yes, inheritance for this particular FSM is unnecessary, at least at the moment. I do intend to do some basic AI soon and I thought that a state machine would be useful.

In the state stuff I've done previously, I did use an enum with switch statement, but I found it ... well, iffy in terms of easily finding the enter and exit code, not to mention that the update function was usually a large, messy function.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
It seems to me that you're not usually going to write states like Move. Instead you'll probably use states like Chase, Evade, Patrol, etc, and those will most likely not take 2 (or 20) lines of code. With these you might see more benefit.
Do you think it would be easier to have an enum and an 'if else' in the object update function, for the simpler things like 'move', 'turn' and the simpler states, or should I just leave it as it is and use the state machine and state objects for everything to prevent confusion?

I would still use the state machine for the higher level AI states such as Chase, Evade, etc to make the descisions, regardless of whether I go the enum way for the simpler states.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
leave your state's as individual objects. You can always use some macros to reduce the code required for 2 line states. It's easy to implement, easy to read and understand, and easy to debug. In the long run, trust me, it's a very good idea.... (speaking from experiance)
rob can you give an example of the macros you might use?

can it be templated?
Geordi
George D. Filiotis
A really basic macro for declaring a base class state for an object

#define DECLARE_BASE_STATE(X)   class X##StateMachineState : public StateMachineState {   public:     X##StateMachineState(X* obj) : mObject(obj) {}   protected:     X* mObject;   };


then to replace the OP's original example, you'd just need to do :

DECLARE_BASE_STATE(Object)


Then extend with something like:

#define DECLARE_MOVE_FORWARD(X)   class X##MoveForwardState : public X##StateMachineState {   public:     X##MoveForwardState(X* obj) : X##StateMachineState(obj) {}     void onUpdate() {       mObject->Position += mObject->GetForwardDir()*GetTimeStep();     }   }; #define DECLARE_MOVE_BACKWARD(X)   class X##MoveBackwardState : public X##StateMachineState {   public:     X##MoveBackwardState(X* obj) : X##StateMachineState(obj) {}     void onUpdate() {       mObject->Position -= mObject->GetForwardDir()*GetTimeStep();     }   }; #define DECLARE_MOVE_FORWARD_BACKWARD(X)    DECLARE_MOVE_FORWARD(X)    DECLARE_MOVE_BACKWARD(X)


Yes, you could use a template such as:

  template<typename T>  class TStateMachineState     : public StateMachineState {   public:     TStateMachineState(T* obj)       : mObject(obj) {}   protected:     T* mObject;   };  template<typename T>  class TForwardStateMachineState     : public TStateMachineState<T> {   public:     void onUpdate() {      mObject->Position += mObject->GetForwardDir()*GetTimeStep();    }  };  template<typename T>  class TBackwardStateMachineState     : public TStateMachineState<T> {   public:     void onUpdate() {      mObject->Position -= mObject->GetForwardDir()*GetTimeStep();    }  };


personally speaking, I prefer the macro approach. - I'm now expecting someone to list the reasons as to why templates are better than macros. I'm not wanting to get into a religous war over it, so save yourself the effort ;)
sorry, the source tags remove the \'s from the macros.... tried duplicated without any formatting, but neither the source, code, pre tags, or untagged text allows you to use \'s.
If the little states follow a particular pattern then it might be sufficient to template them. If the little states are almost similar except for Update (for example) then it might be sufficient to make a common 'simple' state that takes a function object strategy for the very little difference.

But frankly, if you 'waste' 18 lines for a few little states but save a few dozen hours of developer time in modularity, readability and maintainability that's huge gain. Don't miss the forest for the trees.

This topic is closed to new replies.

Advertisement