Mantra

Un Entity-Component-System en C++

a marqué ce sujet comme résolu.

Bonjour.

Je suis Benoît, Praetonus sur les internets, et j'ai la joie de vous présenter ma réalisation, Mantra.

Fonctionnalités

Mantra est une bibliothèque implémentant la structure Entité-Composant-Système. Comme on peut s'y attendre de la part d'une telle bibliothèque, Mantra permet de définir des composants et des systèmes agissant sur ces composants.

Particularités

La principale distinction entre Mantra et les implémentations les plus courantes d'ECS est que Mantra est entièrement typée statiquement. Cela signifie

  • Aucun besoin de polymorphisme, d'allocation dynamique individuelle ou de RTTI pour les composants ;
  • Un stockage contigu et un recyclage des composants ;
  • Une implémentation plus efficace pour les opérations de recherche et de tri d'entités (benchmark à venir) ;
  • La possibilité de contraintes compile-time sur les composants. Ce point est détaillé ci-dessous.

Ces contraintes, exprimées sous la forme de composants primaires et secondaires, forment la seconde particularité. Pour résumer, pour un système donné

  • Un composant primaire est modifiable depuis ce système. Un système possède un composant primaire ou aucun (un système sans composant primaire ne peut modifier aucun composant) ;
  • Un composant secondaire est profondément immuable depuis ce système. Un système peut posséder un nombre arbitraire de composants secondaires ;
  • Un composant ni primaire ni secondaire n'est pas visible depuis ce système (à part pour vérifier sa présence ou son absence).

En l'état actuel, ce système n'a pas d'avantage particulier (à part éviter un minimum la mise en place d'une mauvaise conception), mais se révèlera crucial lors de l'implémentation de l'aspect parallèle de la bibliothèque pour empêcher les possibilités de data-races dès la compilation.

Utilisation

Mantra requiert un compilateur supportant C++14. La bibliothèque est header-only et n'a donc pas besoin d'être compilée à part en amont. Les bibliothèques Boost sont nécessaires au fonctionnement de la bibliothèque.

La documentation de l'interface et un tutoriel basique sont disponibles et peuvent être générés avec Doxygen.

À venir

La plus grosse feature à implémenter est un parallélisme sûr et scalable. Aucune date n'est prévue de ce côté.

Je compte également ajouter des fonctionnalités à l'interface présente, notamment pour la recherche et le tri d'entités.

Licence

Mantra est distribuée sous licence CeCILL-B.


Merci de m'avoir lu, n'hésitez pas à essayer la bibliothèque et à me donner votre avis sur l'interface et l'implémentation. Si vous rencontrez un problème, ce sujet et les issues sont disponibles.

+1 -0
  • using expand = std::initializer_list<int> permet de ne pas avoir d'élément dans la liste et de s'affranchir de mettre le 0.

  • Un moyen rapide et léger de vérifier l'unicité d'une liste de type est l'héritage.

  • Un moyen rapide de vérifié la présence d'un type dans une liste est un héritage et std::is_base_of.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
template<class>
struct identity
{};

template <typename... Ts>
struct TypeList : private identity<Ts>... //Supplied same type multiple times
{
        static_assert(sizeof...(Ts) > 0, "No types supplied");

        template <typename T>
        bool constexpr contains() const noexcept
        {
                return std::is_base_of<identity<T>, TypeList>::value;
        }
};

Le message d'erreur passe en commentaire, car afficher par le compilateur lorsqu'il y a une erreur.

Perso, je trouve l'utilisation de std::tuple extrêmement coûteux en temps et en mémoire (au niveau compilateur). Surtout que si je suis bien, toutes les tuples contiennent une liste de types unique et récupéré à travers std::get<T>. Si je puis me permettre, un héritage + static_cast est beaucoup moins lourd (une implémentation bateau ici).

TypeToIndex peut elle aussi être facilement implémenté à travers un héritage + pair type,index (l'héritage, c'est la base :o)):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
template<class Ints, class... Ts>
class IndexedTypeImpl;

template<std::size_t, class>
class IndexedTypeBase
{};

template<std::size_t... Ints, class... Ts>
struct IndexedTypeImpl<std::index_sequence<Ints...>, Ts...>
: IndexedTypeBase<Ints, Ts>...
{};

template<class... Ts>
using IndexedType = IndexedTypeImpl<std::make_index_sequence<sizeof...(Ts)>, Ts...>;

template<class T, std::size_t Id>
constexpr std::size_t index_of(IndexedTypeBase<Id, T>) {
  return Id;
}

//index_of<float>(IndexedType<int, float>{}) == 1

Ça me fait penser que c'est une fonction intéressante à mettre dans mon implémentation de tuple.

Sinon, c'est plutôt bien foutus. Manque la règle pour compiler l'exemple par contre.

using expand = std::initializer_list<int> permet de ne pas avoir d'élément dans la liste et de s'affranchir de mettre le 0.

Effectivement, mais le 0 est quand même nécessaire dans pas mal de cas à cause de l'expansion d'expressions renvoyant void.

  • Un moyen rapide et léger de vérifier l'unicité d'une liste de type est l'héritage.
  • Un moyen rapide de vérifié la présence d'un type dans une liste est un héritage et std::is_base_of.

Perso, je trouve l'utilisation de std::tuple extrêmement coûteux en temps et en mémoire (au niveau compilateur). Surtout que si je suis bien, toutes les tuples contiennent une liste de types unique et récupéré à travers std::get<T>. Si je puis me permettre, un héritage + static_cast est beaucoup moins lourd (une implémentation bateau ici).

TypeToIndex peut elle aussi être facilement implémenté à travers un héritage + pair type,index (l'héritage, c'est la base :o)):

J'aime beaucoup ces patterns à base d'héritage, je ne connaissais pas. C'est fait et effectivement les différences à la compilation se sentent. Un grand merci.

Manque la règle pour compiler l'exemple par contre.

En effet, c'est corrigé.

Merci à tous les deux pour vos réactions.

+0 -0

Bonjour,

J'ai déjà lu pas mal d'article à propos de l'ECS, et je trouve ça intéressant.

Mais pour faire des remarques constructives, il me faudrait savoir ton but : pourquoi avoir écrit cette bibliothèque ? De même, quel est le public cible ?

Pour la parallélisation, je crains que tu n'aies choisi la mauvaise approche, à savoir concevoir ton code, puis le paralléliser. Ça marche rarement correctement, et soit au prix d'une chute de la lisibilité/propreté du code, soit d'un mauvais passage à l'échelle. De plus, est-ce bien utile ? Un jeu a, en plus d'un moteur ECS, un moteur physique, une interface graphique, un rendu 2 ou 3D, un gestionnaire d'évènements… Bref, un ordinateur de particulier a de quoi utiliser tous ses processeurs de par la multiplicité des taches, et je ne pense pas que le moteur ECS soit la plus lourde (je penche plutôt pour la physique ou 3D, qui elles ont tout intérêt à être optimisée correctement).

Mais si tu codes pour l'apprentissage ou le jeu, la remarque ci-dessus ne tient plus, d'où ma première demande. ^^

Quoiqu'il en soit, j'ai le même sentiment que mehdidou sur l'ECS, c'est un concept qui a l'air intéressant.

+1 -0

Pourquoi avoir écrit cette bibliothèque ?

Principalement parce qu'aucune implémentation actuelle ne me convient. Je trouve que l'approche dynamique apporte plus d'inconvénients que d'avantages et est injustifiée.

De même, quel est le public cible ?

Je ne me suis pas réellement posé la question. N'importe qui pouvant en avoir l'utilité.

Pour la parallélisation, je crains que tu n'aies choisi la mauvaise approche, à savoir concevoir ton code, puis le paralléliser. Ça marche rarement correctement, et soit au prix d'une chute de la lisibilité/propreté du code, soit d'un mauvais passage à l'échelle.

En réalité, le modèle parallèle est l'un des premiers éléments de la bibliothèque que j'ai conçu, et je suis assez confiant. L'implémentation séquentielle actuelle n'est qu'un cas particulier du modèle (où aucun système n'est mis à jour simultanément), et les modifications à faire pour intégrer le tout sont minimes.

De plus, est-ce bien utile ? Un jeu a, en plus d'un moteur ECS, un moteur physique, une interface graphique, un rendu 2 ou 3D, un gestionnaire d'évènements… Bref, un ordinateur de particulier a de quoi utiliser tous ses processeurs de par la multiplicité des taches, et je ne pense pas que le moteur ECS soit la plus lourde (je penche plutôt pour la physique ou 3D, qui elles ont tout intérêt à être optimisée correctement).

Le principe fondamental de l'ECS est de gérer tous les traitements sur les éléments du même « monde » dans la même structure. Dans ton exemple, le moteur physique, le moteur de rendu, etc. seront des systèmes. Donc oui, optimiser la logique des systèmes eux-mêmes est important, mais je pense qu'un ECS avec un parallélisme au niveau des systèmes efficace, sûr et simple à utiliser est intéressant. Et à ma connaissance, il n'y en a pas actuellement.

+0 -0

Je ne me suis pas réellement posé la question. N'importe qui pouvant en avoir l'utilité.

Comme énormément de bibliothèque ou autre petit projet qu'on trouve sur le net, Mantra manque d'une bonne documentation pour être facilement utilisable. Beaucoup de développeurs négligent ce point, selon moi avec grand tord.

En réalité, le modèle parallèle est l'un des premiers éléments de la bibliothèque que j'ai conçu

Dans ce cas, je n'ai rien dit. ^^

Le principe fondamental de l'ECS est de gérer tous les traitements sur les éléments du même « monde » dans la même structure. Dans ton exemple, le moteur physique, le moteur de rendu, etc. seront des systèmes.

J'avais lu que faire ça, c'était la merde (les commentaires sont plus intéressants que l'article lui-même).

+0 -0

Comme énormément de bibliothèque ou autre petit projet qu'on trouve sur le net, Mantra manque d'une bonne documentation pour être facilement utilisable. Beaucoup de développeurs négligent ce point, selon moi avec grand tord.

Je pensais avoir fait un travail correct de ce côté là, même si il y a des choses à ajouter et à améliorer. Quels sont les points que tu trouve insuffisants ?

Le principe fondamental de l'ECS est de gérer tous les traitements sur les éléments du même « monde » dans la même structure. Dans ton exemple, le moteur physique, le moteur de rendu, etc. seront des systèmes.

J'avais lu que faire ça, c'était la merde (les commentaires sont plus intéressants que l'article lui-même).

Je pense que les problèmes soulevés par l'article et certains commentaires viennent d'une mauvaise conception des composants/systèmes et pas d'un souci avec l'ECS lui même. Un ECS c'est simplement des données et des traitements sur ces données, et un moteur de rendu ou un moteur physique rentrent exactement dans ce cadre. Pour les évènements et compagnie, il y a surtout une incompréhension du principe, comme expliqué dans ce commentaire.

+0 -0

Je pensais avoir fait un travail correct de ce côté là, même si il y a des choses à ajouter et à améliorer. Quels sont les points que tu trouve insuffisants ?

J'ai compilé la doc depuis les sources (avec doxygen). Le fichier index est, j'imagine, le fichier principal. Outre le fait que la coloration syntaxique ne soit pas passé (j'ai des ``` qui trainent), je trouve que ce n'est pas hyper claire (mais je ne suis que moyennement à l'aise avec avec le modèle ECS, question de public, toujours !).

C'est en relisant le truc que j'ai réalisé que ce fichier était le commentaire de l'exemple minimal fourni. Je te conseilles de remonter la note à ce sujet en haut de page plutôt que planquée au fond, et mettre une note inverse dans le fichier basics qui dit que l'explication est faite dans le fichier index de la doc.

Et une fois là, si tu te dis que pour en savoir plus, tu va allez voir la doc, par exemple de mantra::System, tu apprendras que

Helper class for systems. You can inherit from this class to create systems.

Certes…

C'est une bonne doc développeur, tu sais que telle machine à un constructeur par copie qui est protected. Mais d'un point de vue utilisateur, tu ne sais pas par quel bout commencer.

C'est hyper courant comme problème : mésestimer la marche de départ, qui devra pourtant être franchie par une très grande part des utilisateurs. Après, je suis bien conscient que ce genre de chose est long, pas forcément gratifiant, ni la première chose à faire. Je signale car je trouve dommage qu'il existe autant de bibliothèque qui soient de qualité mais en pratique très dure à utiliser, ce qui encourage les gens à recoder leur solution maison, car au moins, ils connaissent le code.

+0 -0

Outre le fait que la coloration syntaxique ne soit pas passé (j'ai des ``` qui trainent)

C'est bizarre. Tu utilise quelle version de Doxygen ? Au passage, j'ai ajouté une cible pour la doc dans CMake. Ça me fait penser qu'il faut que je le précise dans le README.

C'est en relisant le truc que j'ai réalisé que ce fichier était le commentaire de l'exemple minimal fourni. Je te conseilles de remonter la note à ce sujet en haut de page plutôt que planquée au fond, et mettre une note inverse dans le fichier basics qui dit que l'explication est faite dans le fichier index de la doc.

Oui, ça sera plus clair comme ça.

Pour le reste, je vois ce que tu veux dire. Je vais voir ce que je peux faire de ce côté là.

+0 -0

C'est bizarre. Tu utilise quelle version de Doxygen ? Au passage, j'ai ajouté une cible pour la doc dans CMake. Ça me fait penser qu'il faut que je le précise dans le README.

Version 1.8.8.

+0 -0

Je viens de zieuter la doc de Doxygen, et il semble que ~~~~~~~~~~~~~~~{.c} soit la syntaxe pour les blocs de code. Un remplaçage de chaque ``` par ~~~~ dans le fichier doc/mainpage.md me donne le bon affichage.

Je n'en sais pas plus.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte