Elysian Shadows

Dynamic Component Registration

[b]Solution Part 1[/b]

I tackled the problem in two parts. The first and most important matter was to allow for user-defined components to be added at compile-time without having to hard-code its allocation in the entity class. My solution was to give every component its own [b]static[/b] constructor that returns an instance of itself as a [b]component[/b] rather than specific component (RigidBody, Animation, Collidable, Sprite, etc). This gives each component:

class Sprite: public Component { 	static Component *Allocator() { ; 		return new Sprite(DEFAULT CONSTRUCTOR); 	} }; 

You will notice that the only difference between constructing a sprite with "Sprite *sprite = new Sprite()" and "Sprite *sprite = Sprite::Allocator()" is that the allocator is actually returning a [i]component[/i] rather than a sprite. 

Using this, we are now able to create a static "registry" within the Component class that contains each individual component's "Allocator()." The registry contains function pointers to the allocator (since they are all returning a Component, their signature is the same), and a corresponding name:

class Component { 	private: 		static std::map allocatorRegistry; }; 

We have now found a pretty decent solution to our problem. Provided that we register each component's allocator function, we can now implement the Entity's component construction like so:

int compAmount; file >> compAmount; for(unsigned int i = 0; i < compAmount; ++i) {     file >> name;     components.push_back(allocatorRegistry[name]()); } 

Now not only can we dynamically create new Components that can be allocated, but the existing, built-in components may all be allocated in a uniform way. 

[b]Solution Part 2[/b]

This is a very good solution, but it still leaves one minute issue to be discussed. You now are responsible for registering function pointers upon program initialization. Where are to do this? They MUST be initialized before any entities are loaded into the engine. Now we have to write some ugly global "RegisterComponentAllocators" function that essentially does this:

Component::AddRegistry("Sprite", Sprite::Allocator()); Component::AddRegistry("Collidable", Collidable::Allocator()); 

I don't know about you, but I think that is still kind of shitty.

C# offers a very trivial solution to the problem by having "static constructors"--constructors that are called when a class is loaded, not instantiated. So then each class could register itself... it would be a very beautiful solution without any external dependencies.

So I started my quest to figure out just how to emulate this behavior in C++--which has no support for static constructors. I wound up discovering something equally suitable, and slightly cheap-ass.

The only things in C++ that are guaranteed to be initialized at load (with most compilers) are static member variables. My solution was to give each component a "dummy" static member bool. These booleans are initialized with a call to the Component::RegisterAllocator() function, so that we have now discovered a cheap-ass way to ensure each class is able to register itself!

It's as easy as a static member initialization:

bool Sprite::dummyBool = Component::RegisterAllocator("Sprite", Sprite::Allocator());

So now we have a method by which each component is able to add itself to the component registry at compile-time, so that an entity is now given the ability to allocate any user-defined component.

The ONLY downside to this approach is that not every compiler has a defined point of static member initialization. Some compilers initialize when the variable is first [i]used[/i], which would screw everything up. The good thing is that Visual Studio (Windows), and GCC (PSP, DC, Mac, iPad, iPhone, everything else) both work fine, so we are totally in the clear!

Discussion Topic

Falco Girgis
Falco Girgis is the founder and lead software architect of the Elysian Shadows project. He was previously employed in the telecom industry before taking a chance on Kickstarter and quitting his job to live the dream. He is currently pursuing his masters in Computer Engineering with a focus on GPU architecture.