[28.03.2017]
Watch the new gameplay features here:
What follows is purely a technical talk, so if you are not a programmer,
you can safely skip the rest of this post in its entirety.
So, I fancied introspection support for my C++ types!
No easy feat, if you ask me, considering that it requires a whole lot of template metaprogramming magic.
I've chosen the approach where it is enough to leave short comments in the structures that I need to introspect,
and then an external utility should find these comments and automatically generate introspector functions from the structure code.
So, given this:
I should have this:
Where "FIELD" is a macro that resolves to a name-value pair:
Needless to say, I had to implement such a utility and here it is:
[https://github.com/TeamHypersomnia/Introspector-generator]
It's under MIT license, and therefore free to use for everyone.
Now let us consider a simple usecase for that, apart from the obvious one which is text-based serialization.
I need to clone an entity.
It is only natural to, apart from the source entity itself, clone all children entities that the source entity has,
so that the cloned instance has its own identical children.
But what if the ids for these children entities are scattered across components?
For example, it makes sense to put child_entity_id engine_sound inside components::car,
and child_entity_id muzzle_smoke inside components::gun.
Do I manually access those fields and update the cloning logic whenever I decide to add a new child_entity_id to a component?
Not at all!
Thanks to the introspection magic, I can replace code like this:
With a generalized code like this:
augs::introspect takes a generic lambda as an argument, and later a variadic number of instances upon which the introspection is to be performed.
augs::introspect_if_not_leaf does the same, but only if the introspected type is not an enum or an arithmetic type.
The generic lambda must accept the name (std::string) of the field in its first argument (here, it is simply discarded).
Since there's no way to otherwise call a generic lambda inside itself, we need a trick like augs::recursive:
Recursion inside the introspection callback is necessary because a component may have such a structure:
Here it becomes obvious that to reach all child_entity_ids that components::car may possibly possess,
it is necessary to introspect deeper into the members left_engine and right_engine,
and not just omit these members because their types are not equal to child_entity_id.
So now if I want to add some more child_entity_ids to whichever components,
I just make sure that I give them child_entity_id type instead of the plain entity_id (which are otherwise identical in function),
launch the Introspector-generator, and the requisite code is generated for me, in an exact and failproof manner.
Relevant source files:
[https://github.com/TeamHypersomnia/Hypersomnia/blob/master/src/augs/templates/introspect.h]
[https://github.com/TeamHypersomnia/Hypersomnia/blob/master/src/augs/templates/introspection_traits.h]
What follows is purely a technical talk, so if you are not a programmer,
you can safely skip the rest of this post in its entirety.
So, I fancied introspection support for my C++ types!
No easy feat, if you ask me, considering that it requires a whole lot of template metaprogramming magic.
I've chosen the approach where it is enough to leave short comments in the structures that I need to introspect,
and then an external utility should find these comments and automatically generate introspector functions from the structure code.
So, given this:
I should have this:
Where "FIELD" is a macro that resolves to a name-value pair:
Needless to say, I had to implement such a utility and here it is:
[https://github.com/TeamHypersomnia/Introspector-generator]
It's under MIT license, and therefore free to use for everyone.
Now let us consider a simple usecase for that, apart from the obvious one which is text-based serialization.
I need to clone an entity.
It is only natural to, apart from the source entity itself, clone all children entities that the source entity has,
so that the cloned instance has its own identical children.
But what if the ids for these children entities are scattered across components?
For example, it makes sense to put child_entity_id engine_sound inside components::car,
and child_entity_id muzzle_smoke inside components::gun.
Do I manually access those fields and update the cloning logic whenever I decide to add a new child_entity_id to a component?
Not at all!
Thanks to the introspection magic, I can replace code like this:
With a generalized code like this:
augs::introspect takes a generic lambda as an argument, and later a variadic number of instances upon which the introspection is to be performed.
augs::introspect_if_not_leaf does the same, but only if the introspected type is not an enum or an arithmetic type.
The generic lambda must accept the name (std::string) of the field in its first argument (here, it is simply discarded).
Since there's no way to otherwise call a generic lambda inside itself, we need a trick like augs::recursive:
Recursion inside the introspection callback is necessary because a component may have such a structure:
Here it becomes obvious that to reach all child_entity_ids that components::car may possibly possess,
it is necessary to introspect deeper into the members left_engine and right_engine,
and not just omit these members because their types are not equal to child_entity_id.
So now if I want to add some more child_entity_ids to whichever components,
I just make sure that I give them child_entity_id type instead of the plain entity_id (which are otherwise identical in function),
launch the Introspector-generator, and the requisite code is generated for me, in an exact and failproof manner.
Relevant source files:
[https://github.com/TeamHypersomnia/Hypersomnia/blob/master/src/augs/templates/introspect.h]
[https://github.com/TeamHypersomnia/Hypersomnia/blob/master/src/augs/templates/introspection_traits.h]