Tuesday, December 16, 2008

12-16-08 - Libraries and Cinit



I need a kind of mini class-factory for Oodle. This is for when I load a paging bundle that's full of various resources,
I need to make each one. (BTW there will be a "low level" usage pattern for Oodle where you just load a paging bundle and
the game gets a handle to the bundle and does whatever it wants with it. This is for the "high level" layer that automates
a lot of things for you but is optional).



I guess what I'm going to have to do is require the game to give me a creator function pointer that knows how to make all
the objects. eg. it would give me something like



void * (*fp_factory) (int objectType);

and fp_factory would point to something like

void * GameObjectFactory(int type)
{
switch(type)
{
case OBJECT_PLAYER : return (void *) new Player;
case OBJECT_ENEMY: ...
}
}



Or something. As a game developer I hate that kind of global registry, because when you add a new object type you have
to remember to go update this global registry file, which becomes a frequent merge disaster for source control, etc. I really
like having everything you need to make a game object in a single CPP file. That means objects should be self-registering.



The way I usually do self-registering objects is with a little class that runs at cinit. Something like :



#define IMPLEMENT_FACTORY_PRODUCT(classname) namespace { ClassFactory::Registrar classname##registrar( classname::Factory , typeid(classname) ); }

then in Player.cpp you have :

IMPLEMENT_FACTORY_PRODUCT(Player);



That's all fine and dandy, but it's not so cool for me as a library maker.



For one thing, doing work during CINIT is kind of naughty as a library. I've been trying to make it a rule for myself that
I don't use object constructors to do cinit work the way I sometimes like to do. It's a perfectly normal thing to do in C++,
and if you are writing C++ code you should make all your systems instantiate-on-use like proper singletons so that CINIT
objects work - BUT as a library maker I can't require the clients to have done all that right. In particular if I do something
like allocations during CINIT, they might run before the client does its startup code to install custom allocators or whatever.



For another thing, there are problems with the linker and cinit in libraries. The linker can drop objects even though they are
doing cinit calls that register them in global factory databases. There are various tricks to prevent this, but they are
platform dependent and it is a little evil bit of spaghetti to get the client involved in.



I guess I probably also shouldn't rely on "typeid" or "dynamic_cast" or any other runtime type information existing either since
people like to turn that off in the compiler options for no good reason (it has basically zero cost if you don't use it). So without
that stuff I pretty much just have to rely on the game to give me type info manually anyway.



Bleh, I'm just rambling now...

No comments:

Post a Comment