Functors will eat your children

posted in kiwibonga's Blog
Published February 24, 2011
Advertisement
Oh yes they will. Here's what I've been doing instead of programming a game:

A typical game object class, before:


#define MSG_ONMOUSEOVER 1
#define MSG_ONMOUSEOUT 2
#define MSG_ONCLICK 3
#define MSG_ONUNCLICK 4

class ClickRect : public ScreenObj
{
public:

// ...

void processMessage(ActorMsg* msg);

void hoverCheck(int x, int y);
void clickCheck(int x, int y);
void unClickCheck(int x, int y);
};


A typical implementation of processMessage, before:


void ClickRect::processMessage(ActorMsg* msg)
{
switch(msg->type)
{
case MSG_TYPE_INPUT:
switch(msg->intData[0])
{
case MSG_MOUSE_POSITION:
hoverCheck(msg->intData[1],msg->intData[2]);
break;
case MSG_MOUSE_CLICK:
clickCheck(msg->intData[1],msg->intData[2]);
break;
case MSG_MOUSE_UNCLICK:
unClickCheck(msg->intData[1],msg->intData[2]);
break;
}
break;
}
}


Sending and receiving messages typically used to involve writing #defines for the different types of specific messages an object can send and receive (with no real distinction between the two), and a bunch of nested switches.

Well, it served its purpose quite well. The issue, of course, is that it simply was not elegant, especially when the only use for it was to call member functions of the target object.

So I needed a way to "store function calls" instead of messages. I couldn't just have this big message struct with all sorts of different containers that may or may not get used. I couldn't ask the programmer to come up with and remember all these macro names either, just so he would be able to delay calls to functions.

Enter member function pointers and templates.

A typical object class now:


class ClickRect : public ScreenObj
{
public:

// ...

MAKE_MESSAGEABLE2(hoverCheck,int,int);
void hoverCheck(int x, int y);

MAKE_MESSAGEABLE2(clickCheck,int,int);
void clickCheck(int x, int y);

MAKE_MESSAGEABLE2(unClickCheck,int,int);
void unClickCheck(int x, int y);
};


And code for sending a message to it:


ArgFunctorPair command(&ClickRect::hoverCheck, buildArgSet(50,-50));

command.executeOn(myClickRect); // calls myClickRect->ClickRect::hoverCheck(50,-50);


Isn't that much nicer? Just plug the MAKE_MESSAGEABLEn(function_name, arg_type1, arg_type2, ..., arg_typen) macro into your class's definition, and you now have a "messageable" member function.

MAKE_MESSAGEABLEn actually expands to an overloaded version of the function that takes a single argument; this ensures that all member function pointers have the same kind of signature, namely void(some_type::*)(ArgSet*), where ArgSet is the base class for a collection of templated "tuple" classes -- similar to std::pair.

The expanded function automatically casts the argument container to the proper type (i.e. the proper number of arguments).

So no more defines, no more switch statements, only arbitrary function calls.

One issue is that there's not really any runtime checks, so if you enter the wrong number of arguments, it crashes... Other than that, it's pretty much impossible to make a mistake; unlike in the previous implementation, you can't issue a command that doesn't exist, and you get all the benefits of autocomplete. It's also compatible with virtual functions.

(Official Template Wizard status acquired)

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement