Our conglomerate shall proudly argue that shutting down the cosmos necessarily conforms to a standard of right behaviour. ~ Resistance
They wished to descend into bodies of flesh, over and over again, (...) receiving mortality with incredible joy. ~ Metropolitan archives
hypersomnia - tendency of the Creator to immerse into inferior simulations, in spite of countless deaths experienced as a consequence. ~ Unknown Sophist of Atlantis
Grenades and my approach to C++ introspection [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:
template<classid_type>structbasic_inventory_slot_id{// GEN INTROSPECTOR struct basic_inventory_slot_id class id_type
slot_functiontype;id_typecontainer_entity;// END GEN INTROSPECTOR
basic_inventory_slot_id();basic_inventory_slot_id(constslot_function,constid_type);voidunset();booloperator==(constbasic_inventory_slot_idb)const;booloperator!=(constbasic_inventory_slot_idb)const;};
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:
// some kind of cosmos::clone_entity function
cloned_to_component.some_child_car_engine=cosmos.clone_entity(cloned_from_component.some_child_car_engine);cloned_to_component.some_child_muzzle_smoke=cosmos.clone_entity(cloned_from_component.some_child_muzzle_smoke);cloned_to_component.some_other_child=cosmos.clone_entity(cloned_from_component.some_other_child);
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:
structcar_engine_entities{// GEN INTROSPECTOR struct car_engine_entities
child_entity_idphysical;child_entity_idparticles;// END GEN INTROSPECTOR
};namespacecomponents{structcar{// GEN INTROSPECTOR struct components::car
// ...
car_engine_entitiesleft_engine;car_engine_entitiesright_engine;// ...
// END GEN INTROSPECTOR
}}
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.