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;
	// END GEN INTROSPECTOR

	basic_inventory_slot_id();
	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_
) {
	FIELD(type);
	FIELD(container_entity);
}

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:

[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:

// 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:

	augs::introspect(
		augs::recursive([&](auto&& self, auto, auto& into, const auto& from) {
			if constexpr(std::is_same_v<decltype(into), child_entity_id&>) {
				into = cosm.clone_entity(from);
			}
			else {
				augs::introspect_if_not_leaf(augs::recursive(self), into, from);
			}
		}),
		cloned_to_component,
		cloned_from_component
	);


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:
	template <class F>
	auto recursive(F&& callback) {
		return [&](auto&&... args) { 
			callback(
				std::forward<F>(callback), 
				std::forward<decltype(args)>(args)...
			);
		};
	}

Recursion inside the introspection callback 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;
	// END GEN INTROSPECTOR
};

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

		car_engine_entities left_engine;
		car_engine_entities right_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.

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]