The Ginkgo and the Unanswered Questions

In my longest post about our in-progress component-based 2D game engine I talked about our component system. There have been a number of unanswered questions after that article and I’d like to answer those before I proceed. Be sure to check out the first three parts of this series of blog posts.

Brooksoid asked:

I’d love more details on what’s going on in your macros, as information on Terrence’s COMPONENT macro is non-existent.

Actually the only macro that I mentioned in the article but did not properly explain is GIN_IMPLEMENT_COMPONENT. This macro expands to the following code:

#define GIN_IMPLEMENT_COMPONENT(className, stages) \
#define GIN_IMPLEMENT_DERIVED_COMPONENT(className, stages, baseName) \
  ComponentPool* className::_pool; \
  ComponentInfo className::_info = { ComponentManager::unregisteredType, #className, ComponentManager::unregisteredType, #baseName, SymbolArray(0) }; \
  void className::preMain() { \
  	_pool = new ComponentPool; \
  	ComponentManager::instance().registerType(&_info, 		\
  							static_cast(_pool), 	\
  							stages); 			\
  	staticInitialise(); \

As you can see, the macro mostly adds static functions to the component class. The preMain function is actually a misnomer. It is called before the engine enters the runloop, but in main(). Still, preMain allocates a static component pool for the class and registers the class type. The info structure used for registration is similarly static. At last, it calls the staticInitialise (which is obviously British) function that registers all properties (functors) of the class. Maybe I should write about our property system at some point. For now, I’ll just say: The property system registers component variables for XML import and export, the in-game editor and (soon) scripting.

GIN_IMPLEMENT_INITIALIZE hooks up the preMain function following Gregory’s Game Engine Architecture:

#define GIN_IMPLEMENT_INITIALIZE(classname)	\
  bool classname::preMainRegistered = false;	\
  bool classname::registerPreMain()			\
  { \
  	if ( !preMainRegistered )				\
  	{ \
  		Initializer::AddInitializer(classname::preMain); \
  		preMainRegistered = true; \
  	} \
  	return preMainRegistered; \
  } \
  static bool classname##RegisteredInitialize = classname::registerPreMain();

The class Initializer is a singleton that calls all registered initialize functions before our engine enters the runloop.

coolbeenz asked:

I would also be interested to hear if and how you support a GetComponent( ComponentType ) on an object instance.

We have several getComponent() functions in the Game Object. Bear in mind that the components are stored in contiguous arrays in the ComponentPools. The Game Object just stores a linked list of its components in what we call a component chain. This list can be queried for a specific component type. The Game Object also offers handy functions to access other object’s components, but those are rarely used. Each component can have a name that might also be used for retrieving it. The most commonly used method for getting a specific component is a template function that expands to getComponentThatImplements(T::type()), where type() is the static type stored in the _info structure and registered by the GIN_IMPLEMENT_COMPONENT macro above. getComponentThatImplements first tries to return a component of the correct class. If it fails, it returns a component of a derived class.

Brooksoid asked:

I figured (naively?!) that the vtable indirection would be absorbed by the d-cache once you’ve updated the first component of a type, and all the repeated calls (for the rest of the components in the pool) wouldn’t really cost…

This is the result of Entity System architecture proposed by Adam Martin. Peter wanted to stick to the OO features that C++ offers. In the end, we settled for a compromise that is neither optimal, nor satisfying for anyone. Maybe it’s time to refactor that bit. We’ll profile that part when we’re on the brink of moving into production with our current game.

My next post will be about component communication. We’ve implemented more than enough methods of cross-component interaction and I’d like to publicly discuss those. I’d appreciate feedback, so please keep it coming!