Skip to main content
gamedev.net
🔒 Locked

States

Started by deks 5 replies 1.3k views
Original Post
deks
deks
Hi all! I just started doing some tests implementing states in cpp. I wrote this piece of code in the last hour, and it works pretty well. I'm using function pointers to member functions, which calls a virtual function, so derived classes can easily override a particular state function. The only drawback I see right now is that having arbitrary parameters to state functions would add a lot of complexity to the generated code. I'm looking for feedback, ideas, comments from the community! Thanks! EDIT: Had to remove trailling slashes in macros so source code shows correcly.

#define DECLARE_STATE_FUNCCALL(Class, FuncName)
    void (Class::*FuncName##FnPtr)();
    void FuncName(void)
    {
        (this->*##FuncName##FnPtr)();
    }

#define BEGIN_STATE_DEF
    virtual void GotoState(int iStateID)
    {
        switch(iStateID)
        {

#define BEGIN_STATE_DEF_INHERIT(ParentClassName)
    virtual void GotoState(int iStateID)
    {
        ParentClassName::GotoState(iStateID);
        switch(iStateID)
        {

#define END_STATE_DEF
        }
    }

#define STATE_DEF_START(StateID)
    case(StateID):

#define STATE_DEF_END
    break;

#define IMPLEMENT_STATE_FUNC(StateName, FuncName)
    FuncName##FnPtr = StateName##FuncName##Call;

#define DEFINE_STATE_FUNC_CALL(StateName, FuncName)
    void StateName##FuncName##Call()
    {
        StateName##FuncName();
    }

class kPlayer
{
protected:
    virtual void NormalWalk()
    {
        printf("%s.NormalWalk()\n", m_pPlayerName);
    }
    virtual void NormalShoot()
    {
        printf("%s.NormalShoot()\n", m_pPlayerName);
    }
    virtual void DeadWalk()
    {
        printf("%s.DeadWalk()\n", m_pPlayerName);
    }
    virtual void DeadShoot()
    {
        printf("%s.DeadShoot()\n", m_pPlayerName);
    }

public:
    kPlayer(char* pPlayerName) : m_pPlayerName(pPlayerName) {}
    char* m_pPlayerName;

    // States
    DECLARE_STATE_FUNCCALL(kPlayer, Walk)
    DECLARE_STATE_FUNCCALL(kPlayer, Shoot)

    DEFINE_STATE_FUNC_CALL(Normal, Walk)
    DEFINE_STATE_FUNC_CALL(Normal, Shoot)
    DEFINE_STATE_FUNC_CALL(Dead, Walk)
    DEFINE_STATE_FUNC_CALL(Dead, Shoot)

    BEGIN_STATE_DEF
        STATE_DEF_START(0)
            IMPLEMENT_STATE_FUNC(Normal, Walk)
            IMPLEMENT_STATE_FUNC(Normal, Shoot)
        STATE_DEF_END
        STATE_DEF_START(1)
            IMPLEMENT_STATE_FUNC(Dead, Walk)
            IMPLEMENT_STATE_FUNC(Dead, Shoot)
        STATE_DEF_END
    END_STATE_DEF
};

class kInvinciblePlayer : public kPlayer
{
protected:
    void DeadWalk()
    {
        printf("%s.NotDeadWalk()\n", m_pPlayerName);
    }
    void DeadShoot()
    {
        printf("%s.NotDeadShoot()\n", m_pPlayerName);
    }
    void NormalCastSpell()
    {
        printf("%s.NormalCastSpell()\n", m_pPlayerName);
    }
    void DeadCastSpell()
    {
        printf("%s.NotDeadCastSpell()\n", m_pPlayerName);
    }

public:
    kInvinciblePlayer(char* pPlayerName) : kPlayer(pPlayerName) {}

    // States
    DECLARE_STATE_FUNCCALL(kInvinciblePlayer, CastSpell)

    DEFINE_STATE_FUNC_CALL(Normal, CastSpell)
    DEFINE_STATE_FUNC_CALL(Dead, CastSpell)

    BEGIN_STATE_DEF_INHERIT(kPlayer)
        STATE_DEF_START(0)
            IMPLEMENT_STATE_FUNC(Normal, CastSpell)
        STATE_DEF_END
        STATE_DEF_START(1)
            IMPLEMENT_STATE_FUNC(Dead, CastSpell)
        STATE_DEF_END
    END_STATE_DEF
};

int _tmain(int argc, _TCHAR* argv[])
{
    kPlayer Player("bob");
    Player.GotoState(0);
    Player.Walk();
    Player.Shoot();
    Player.GotoState(1);
    Player.Walk();
    Player.Shoot();

    kInvinciblePlayer InvinciblePlayer("god");
    InvinciblePlayer.GotoState(0);
    InvinciblePlayer.Walk();
    InvinciblePlayer.Shoot();
    InvinciblePlayer.CastSpell();
    InvinciblePlayer.GotoState(1);
    InvinciblePlayer.Walk();
    InvinciblePlayer.Shoot();
    InvinciblePlayer.CastSpell();

    return 0;
}

=[dEkS]=
dimebolt
dimebolt
Wow.



I must be stupid, because I'm not capable of coprehending that piece of code.

Quote:

I'm using function pointers to member functions, which calls a virtual function, so derived classes can easily override a particular state function.

I'm curious, could you explain why the normal inheritance functionality povided by C++ is not sufficient for your needs?

Tom
Dave Hunt
Dave Hunt
Quote:
Original post by dimebolt
Quote:

I'm using function pointers to member functions, which calls a virtual function, so derived classes can easily override a particular state function.

I'm curious, could you explain why the normal inheritance functionality povided by C++ is not sufficient for your needs?

Tom


That was my response as well. I don't see the point to all of this macro obfuscation. All that's been accomplished is to add another level of indirection to get at the functions. Normal inheritance takes care of calling the correct instance's virtual methods without that extra layer.
deks
deks
Sorry if I wasn't clear enough. I want that my objects behave different depending on their current state. That piece of code:

Player->Shoot();

Might actually shoot if the player is in the 'normal' state, but would do nothing at all if the player is in the 'dead' state. The caller don't have to know the actual player state.

In the code I provided, calling GotoState() will update the function pointers of all the state functions. Those functions simply calls normal virtual functions, that you can overload in derived classes. The output of the sample code I provided would be:

bob.NormalWalk()
bob.NormalShoot()
bob.DeadWalk()
bob.DeadShoot()
god.NormalWalk()
god.NormalShoot()
god.NormalCastSpell()
god.NotDeadWalk()
god.NotDeadShoot()
god.NotDeadCastSpell()

And notice that I always call Walk(), Shoot() and CastSpell().
=[dEkS]=
Horatius83
Horatius83
I realize that V-Table look-ups are evil and all, but wouldn't it be easier just to use standard polymorphism? Is the whole point of this an attempt to use state-based design methodologies in C++? It's interesting what you're doing with this code as far as state-based programming is concerned, but I think you could make it easier to read if you had, say a pointer to an interface (a pure virtual base-class) and then change the pointer to various player classes depending on what kind of state you want to use.

Edit: Should have read the title of the discussion, d'oh!

class player
(...)
IEnt* m_pEnt;
inline void SetState(IEnt* pEnt)
{
if(m_pEnt)
delete m_pEnt;
m_pEnt = 0;
m_pEnt = pEnt;
};
(...)

class invinciblePlayer : public IEnt
(...)

class deadPlayer : public IEnt
(...)

oPlayer.SetState(new invincibleplayer);
(...)
oPlayer.SetState(new deadPlayer);
(...)
"Think you Disco Duck, think!" Professor Farnsworth
Dave Hunt
Dave Hunt
Ahhh. That would have been much more obvious without all of the macros hiding what you were doing. Personally, I would lose the macros but keep the idea. That solves the problem of state function parameters and makes the code much easier to read and understand.
deks
deks
Quote:
Original post by Horatius83
I realize that V-Table look-ups are evil and all, but wouldn't it be easier just to use standard polymorphism? Is the whole point of this an attempt to use state-based design methodologies in C++? It's interesting what you're doing with this code as far as state-based programming is concerned, but I think you could make it easier to read if you had, say a pointer to an interface (a pure virtual base-class) and then change the pointer to various player classes depending on what kind of state you want to use.


Yes, that was my first approach. It works, but its more complex than my actual implementation. Also, you have to pass 'this' when calling the interface, which is anoying (also have to make them friend classes, etc). Also, it would be hard IMO to have a single instance of all interfaces (in your example, each stated object have its own set of state interfaces, which is really not what I want!

I also thought of manually changing the vtable. It would work, but I doubt it may be portable.

To answer about the 'macro obfuscation', this is because all the state code declaration is automatically generated by my state compiler, not by hand!

=[dEkS]=

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.