Wednesday, July 9, 2008

C-style callbacks in C++ code

There have been a few questions about how to use BZFlag's callback pattern. We don't typically use either C++-style template callbacks or functors; we use C-style callbacks in C++ code. It's not an unusual pattern, so I thought I'd lay out how it works here so our SoC students and others can benefit.

Anatomy of a Callback
There are three major components on each side of a callback system:

On the callback ("user") side:
1. Registering function - this function (usually a constructor or initializer) is responsible for registering a callback. It calls the Registration Function, usually providing a pointer to a static member function of the same class with the callback prototype, and passing the 'this' pointer as the user data. There may be an accompanying deregistering function (for instance, an object's destructor) if the calling side has a deregistration function.

2. Primary callback function - this function is almost always a static member function. Its prototype matches the callback prototype provided by the calling side. If you passed 'this' as the user data when registering, you'll get the same pointer (i.e. an instance pointer to your class) in the void* user data when this callback is called.

3. Local callback function - since the primary callback function is static, there's frequently an instanced local callback function, invoked by the primary callback function - for instance, ((MyType*)data)->localCallbackHandler(...). This makes the code easier to follow and avoids access-specifier issues.

On the calling ("library") side:
1. Registration function - this function allows the user to register his desired callback. It stores the function pointer and user data in a table for lookup later. It is usually accompanied by a deregistration function to avoid crashing when the user data is dealloc'ed.

2. Callback prototype - this is a function prototype defined by a typedef that specifies how the callback function should look (parameters and return value). Minimally it includes the user data pointer (as a void*).

3. Calling function - this function iterates through the callback table and calls each one with its appropriate user data, plus any other data the calling side wishes to pass to the callback.

I've been told I need to use a callback - how do I?
First, familiarize yourself with the calling ("library") side. Find the callback prototype and find out what the calling function(s) are - this will tell you what data you have access to, and when the callback will be triggered. Also locate the registration and deregistration functions.

Second, add registration code to the object that you want to receive the callback. This will usually involve a call to the registration function in your constructor and the deregistration function in your destructor, though you may choose to put them elsewhere if your design calls for it.

Third, write your callback function. Follow the prototype you found earlier. Generally this will need to be a static member function. You may be able to do all your logic directly in the primary callback function, but usually you will need a local (instanced) callback also. In some cases you may want all your logic in the local callback, so all you'll need to do is cast the user data to your class type, and call its local callback member.

Example (the "user" side is at the end):

// this is the "additional information" we're going to
// pass back to our callback
typedef struct
{
// stuff
} callbackInfo;

// this is the callback prototype
typedef void (*cbfunc)(const callbackInfo*, void*);

// this is the "calling side" (library) class
class Calling
{
public:
// add a callback to the table
void registerCB(cbfunc function, void* data)
{
callbacks.push_back(
std::make_pair<cbfunc,void*>(function, data));
}

// remove a callback from the table
void deregisterCB(cbfunc function, void* data)
{
// left as an excercise for the reader
}

// this is the function that actually calls the callback
void trigger(void)
{
callbackInfo cbi; // fill this as appropriate
for (std::list<std::pair<cbfunc,void*> >::iterator
itr = callbacks.begin();
itr != callbacks.end(); ++itr)
{
(*itr).first(&cbi, (*itr).second);
}
}

private:
// callback table
std::list<std::pair<cbfunc,void*> > callbacks;
}

// we need an instance of the library class
Calling globalCaller;

// this is the "callback side" (user) class
class CallMe
{
public:
CallMe()
{
globalCaller.registerCB(&myCallback, this);
}
~CallMe()
{
globalCaller.deregisterCB(&myCallback, this);
}
static void myCallback(const callbackInfo* info, void* userData)
{
if (!userData) return;
((CallMe*)userData)->localCallback(info);
}
void localCallback(const callbackInfo* info)
{
// do stuff
}
}

Friday, May 30, 2008

Commits

I'm happy to see the first sets of commits coming in from our SoC students this year, and I just wanted to take a moment to remind everyone of two things:

1. Commit early, commit often. You should be committing once per (working) day, minimum; more often is better. Use the source control tools to your advantage. If it builds, check it in, with appropriate comments. It's just like making sure you hit the save button every once in a while, but better...you can always go back to older versions. Nobody is going to criticize you for committing a work-in-progress (and you might get some good ideas from people who review commits as they come in).

2. If it hasn't been committed, it doesn't exist. If you want to talk to someone about your code, it's best and easiest if it's in the repository. Likewise, if your mentor is evaluating your status, they're not going to go off of what may or may not be on your hard drive, they're going to go off of what they have access to.

Monday, April 21, 2008

The envelope please

Well, the deadline has come and gone and the students who will be participating in the summer of code for BZFlag have been chosen.

Congratulations to;
David Sanders (KingofCamelot)
Istvan Szakats (Wyk3d)
Joshua Bodine (Constitution)
Luke Rewega (Lukstr)
Kornel Kisielewicz (Epyon)
Harrison Reiser (bugQ)

We were very surprised to be given 6 slots for the project, as overall application counts were down. We would like to thank Google for this great opportunity and want to wish SoC students from all proejcts luck in the coming months.

We had a number of very good applications and are very sorry for those that did not make it this year. We do expect every single one of you to hang around and apply next year :).

Thursday, March 20, 2008

T-Shirt Progress

People have asked what we did with the money from the summer of code last year. Well... we are using it to print up some BZFlag T-shirts.





They are being printed now and will be sold on the BZFlag Online Store when they are ready.


I would like to thank saturos again for his most excellent design.

Monday, March 17, 2008

We are In!

BZFlag has is proud to have been officially accepted as a mentoring organization for the Google Summer of code program. We look forward to working with both the students and other mentoring organizations.

Students wishing to participate should look at our SoC2008 wiki page, specifically the Ideas Page. They should also be sure to read the Program FAQ that has been provided by Google.

Good luck to everyone.

Monday, March 10, 2008

Application Guidelines

I've prepared a list of submission guidelines for applicants that should hopefully improve the quality of applications we receive this year.  The guidelines are pretty applicable to all projects but hopefully give a better starting point than we gave last year since we (intentionally) don't use an application template.

The application guidelines are available here: 

Sunday, March 2, 2008

2008 Google Summer of Code promotional flyers


In order to get the word out again regarding our (anticipated) participation in the 2008 Google Summer of Code again like last year, there is a really awesome promotional flyer available thanks to our very own Saturos graphic artist extraordinaire:

http://my.bzflag.org/w/Google_Summer_of_Code/2008#Promotion_Flyers

More details to be posted later (presuming we are accepted), but feel free to post the flyer around local universities and colleges in the meantime.  There are translations presently available in German and Spanish thanks to whodaman and Manu respectively, and be sure to thank Saturos for the great design if you see him around!