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 <class id_type>
struct basic_inventory_slot_id {
	// GEN INTROSPECTOR struct basic_inventory_slot_id class id_type
	slot_function type;
	id_type container_entity;

	basic_inventory_slot_id(const slot_function, const id_type);

	void unset();

	bool operator==(const basic_inventory_slot_id b) const;
	bool operator!=(const basic_inventory_slot_id b) const;

I should have this:

template <class F, class id_type, class... Instances>
void introspect_body(
	const basic_inventory_slot_id<id_type>* const,
	F f,
	Instances&&... _t_
) {

Where "FIELD" is a macro that resolves to a name-value pair:

#define FIELD(x) f(#x, _t_.x...)

Needless to say, I had to implement such a utility and here it is:


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);

With a generalized code like this:

		bind_types_t<std::is_same, child_entity_id>,
		bind_types_t<std::is_same, const child_entity_id>
		bind_types_t<apply_negation_t<std::is_same>, shape_variant>,
		bind_types_t<apply_negation_t<std::is_same>, const shape_variant>
> (
	[&](auto, auto& cloned_into_id, const auto& cloned_from_id) {
		cloned_into_id = cosmos.clone_entity(cloned_from_id);

augs::introspect_recursive is a function that takes two template predicates as template arguments,
a generic lambda as an argument, and later a variadic number of instances upon which the introspection is to be performed.
The generic lambda must accept the name (std::string) of the field in its first argument (here, it is simply discarded),
and later references to the introspected members.

The first template predicate determines under what condition should the generic lambda be called.
Here, it is a conjunction of two conditions:

1. The first type must be equal to child_entity_id.
2. The second type must be equal to const child_entity_id (notice that the cloned_from_component is const, as modification is not needed).

apply_negation_t, bind_types_t and concat_unary_t are my magic spells that modify the behaviour of template predicates like std::is_same.
For example,
bind_types_t<std::is_same, const int>
yields a template that is a function of one argument, so this:
static_assert(bind_types<std::is_same, const int>::type<const int>::value, "Trait is wrong");

The second template predicate determines under what condition should the recursion proceed. Recursion of the introspection is necessary because a component may have such a structure:

struct car_engine_entities {
	// GEN INTROSPECTOR struct car_engine_entities
	child_entity_id physical;
	child_entity_id particles;

namespace components {
	struct car {
		// GEN INTROSPECTOR struct components::car
		// ...

		car_engine_entities left_engine;
		car_engine_entities right_engine;
		// ...

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.

In the presented case, the function recurses always with the exception of when it encounters shape_variant (which is a type-safe union),
which is not quite possible to introspect.

The function will also call an appropriate static_assert if the predicate determines that recursion is needed, but there is no introspector function available
(because I've forgot to put // GEN INTROSPECTOR and END GEN INTROSPECTOR comments, for example).

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: