Sign in to follow this  
Endar

Inconvenient OO design in state machine class?

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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)

Share this post


Link to post
Share on other sites
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 ;)

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by Endar
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?


Forgive me for writing Python in C++ syntax, but I'd probably adopt something like this:

template<typename Object, typename Callable>
class StateMachineState
{
public:
StateMachineState(Object obj, Callable onEnter, Callable onUpdate, Callable onExit);
private:
Object object;
Callable onEnter;
Callable onUpdate;
Callable onExit;
public:
void enter() { onEnter(object); }
void update() { onUpdate(object); }
void exit() { onExit(object); }
};


No inheritance, just pass in function objects for the stuff you want called. Boost::bind or something similar will clean up member functions if you need them.

Share this post


Link to post
Share on other sites
I wrote a state machine system very similar to this. When I was implementing game logic in C++, it was a pain to write all the extra code, but when I moved the state code to Lua, it became obvious that this implementation was better than a big switch statement.

Share this post


Link to post
Share on other sites
Quote:

Is it too much? Is it worth it leaving my states as individual objects?


10 Reasons the Age of
Finite State Machines is Over


That would make for some interesting discussion(s)
(if you want to bleed at the edge ...so to speak)

I guess you have no choice at work ... but you do have a choice in your demo
to experiment/test out the merits of the claims of the article...and possibly earn some browny points.

We had to start somewhere with AI (and stuff), and FSM has long been
an important concept (comp. science 101 type stuff?). I've sort of viewed FSM concepts as already implied in
much programming anyhow (as has been the typical programmer's perogative to abuse the FSM concept?). But being so openly explicit about it with a StateMachineState class seems a bit too much: It's more to do with feeling (ewwwh... I mean holistic approach..) than with logical reasoning (I would have to concentrate too hard to give logical reasons; there it is again...my open ignorance!)

Anyhow... hope you find the link interesting .. I do..but it doesn't look like a particularly "better" alternative ( ...being up to the reader to decide).


[Edited by - steven katic on January 2, 2008 4:03:48 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this