Aller au contenu

Démarrage rapide

Cette page présente le modèle SIECS. Elle suppose que siecs.h et siecs.c sont déjà intégrés dans votre application ; consultez Compiler et intégrer pour les deux parcours supportés.

SIECS utilise C17 pour le runtime et C++20 pour le wrapper typé. Les deux API pilotent le même monde actif.

Le monde possède toutes les entités, composants, requêtes, systèmes, ressources et observateurs. Initialisez-le avant toute opération typée et détruisez-le lorsque l’application s’arrête.

ecs_init();
/* Créer des entités, enregistrer les systèmes, exécuter des frames. */
ecs_fini();

Les handles appartiennent à ce monde. Ne conservez ni entité ni pointeur de composant après ecs_fini().

Une entité est une identité. Les composants sont les données attachées à cette identité. Déclarez les données une fois, enregistrez-les en C, puis ajoutez-les aux entités.

ECS_COMPONENT(Position, { float x; float y; });
ECS_COMPONENT(Velocity, { float x; float y; });
ECS_TAG(Enemy);
ECS_COMPONENT_REGISTER(Position);
ECS_COMPONENT_REGISTER(Velocity);
ECS_COMPONENT_REGISTER(Enemy);
ecs_entity_t enemy = ecs_new();
ecs_set(enemy, Position, { 10.0f, 20.0f });
ecs_set(enemy, Velocity, { 1.0f, 0.0f });
ecs_add(enemy, Enemy);

set ajoute le composant s’il est absent. get suppose sa présence ; utilisez try_get lorsqu’une absence est valide.

Un système est un callback de requête nommé. SIECS l’invoque pour chaque lot non vide d’entités correspondantes lorsque la phase sélectionnée est exécutée.

static void Move(ecs_iter_t *it) {
Position *position = ecs_field(it, 0);
const Velocity *velocity = ecs_field(it, 1);
for (uint32_t i = 0; i < it->count; i++) {
position[i].x += velocity[i].x;
position[i].y += velocity[i].y;
}
}
ecs_system({
.name = "Move",
.phase = EcsOnUpdate,
.query.components = { ecs_inout(Position), ecs_in(Velocity) },
.callback = Move,
});

Exécutez une frame avec ecs_progress() ou ecs::progress(). La requête d’un système est persistante : c’est le choix normal pour le travail répété.

Utilisez une requête lorsque vous devez parcourir les données hors d’un système. Les termes expriment des composants obligatoires, optionnels, filtrés, exclus, hérités ou relationnels.

ecs_query_id_t moving = ecs_query({
.components = { ecs_inout(Position), ecs_in_optional(Velocity) },
});
ecs_iter_t it = ecs_query_iter(moving);
while (ecs_iter_next(&it)) {
Position *position = ecs_field(&it, 0);
const Velocity *velocity = ecs_field(&it, 1);
for (uint32_t i = 0; i < it.count; i++) {
if (velocity) position[i].x += velocity[i].x;
}
}
ecs_query_fini(moving);

Un champ optionnel est présent ou absent pour tout le lot. Ne gardez pas les pointeurs de champs à travers un changement structurel comme add ou remove.

Une ressource est une valeur typée unique pour tout le monde. Utilisez-la pour le temps, la configuration, l’entrée, le rendu ou tout état partagé.

ECS_RESOURCE(Time, { float dt; });
ECS_RESOURCE_REGISTER(Time);
ecs_set_resource(Time, { .dt = 1.0f / 60.0f });
const Time *time = ecs_get_resource_read(Time);

Les ressources ne sont pas des composants d’entité et ne deviennent pas des champs de requête. Un système C++ peut demander ecs::res<const Time>.

Les relations connectent les entités. ChildOf, intégré, modélise une hiérarchie ; les relations personnalisées modélisent des liens comme GroupOf.

ecs_entity_t parent = ecs_new();
ecs_entity_t child = ecs_new();
ecs_relate(child, ChildOf, parent);
ecs_entity_t current_parent = ecs_target(child, ChildOf);

L’héritage construit des bases abstraites réutilisables avec IsA. Les requêtes en lecture voient une valeur héritée ; les requêtes en écriture exigent un remplacement local.

Les observateurs réagissent à un événement précis au lieu de s’exécuter à chaque frame. Les événements intégrés couvrent l’ajout, le retrait et l’écriture de composants, ainsi que les transitions de relations.

static void OnPositionSet(ecs_observer_event_t *event) {
const Position *value = event->trigger_data;
printf("x = %f\n", value->x);
}
ecs_observer({
.on = EcsOnSet,
.query.components = { ecs_in(Position) },
.callback = OnPositionSet,
});

Les payloads d’observateurs sont empruntés pendant le callback. Utilisez-les immédiatement, sans conserver de pointeur vers les données d’événement.