Je suis en train de repenser mon système d'entité , pour C ++, et j'ai beaucoup de gestionnaires. Dans ma conception, j'ai ces classes, afin de lier ma bibliothèque. J'ai entendu beaucoup de mauvaises choses en ce qui concerne les classes "manager", peut-être que je ne nomme pas mes classes de manière appropriée. Cependant, je n'ai aucune idée quoi d'autre pour les nommer.
La plupart des gestionnaires, dans ma bibliothèque, sont composés de ces classes (bien que cela varie un peu):
- Conteneur - un conteneur pour les objets dans le gestionnaire
- Attributs - attributs pour les objets dans le gestionnaire
Dans mon nouveau design pour ma bibliothèque, j'ai ces classes spécifiques, afin de lier ma bibliothèque ensemble.
ComponentManager - gère les composants dans le système d'entité
- ComponentContainer
- ComponentAttributes
- Scène * - une référence à une scène (voir ci-dessous)
SystemManager - gère les systèmes dans le système d'entité
- SystemContainer
- Scène * - une référence à une scène (voir ci-dessous)
EntityManager - gère les entités dans le système d'entités
- EntityPool - un pool d'entités
- EntityAttributes - attributs d'une entité (cela ne sera accessible qu'aux classes ComponentContainer et System)
- Scène * - une référence à une scène (voir ci-dessous)
Scène - relie tous les managers
- ComponentManager
- SystemManager
- EntityManager
Je pensais simplement mettre tous les conteneurs / pools dans la classe Scene elle-même.
c'est à dire
Au lieu de cela:
Scene scene; // create a Scene
// NOTE:
// I technically could wrap this line in a createEntity() call in the Scene class
Entity entity = scene.getEntityManager().getPool().create();
Ce serait ceci:
Scene scene; // create a Scene
Entity entity = scene.getEntityPool().create();
Mais je ne suis pas sûr. Si je devais faire ce dernier, cela signifierait que beaucoup d'objets et de méthodes seraient déclarés dans ma classe Scene.
REMARQUES:
- Un système d'entités est simplement une conception utilisée pour les jeux. Il est composé de 3 parties principales: composants, entités et systèmes. Les composants sont simplement des données, qui peuvent être "ajoutées" aux entités, afin que les entités soient distinctives. Une entité est représentée par un entier. Les systèmes contiennent la logique d'une entité, avec des composants spécifiques.
- La raison pour laquelle je modifie la conception de ma bibliothèque, c'est parce que je pense qu'elle peut être beaucoup modifiée, je n'aime pas le toucher / le flux pour le moment.
la source
Réponses:
DeadMG est sur place sur les spécificités de votre code mais j'ai l'impression qu'il manque une clarification. De plus, je ne suis pas d'accord avec certaines de ses recommandations qui ne tiennent pas dans certains contextes spécifiques comme la plupart des développements de jeux vidéo haute performance par exemple. Mais il a globalement raison de dire que la plupart de votre code n'est pas utile pour le moment.
Comme le dit Dunk, les classes Manager sont appelées ainsi car elles "gèrent" les choses. "gérer" est comme "données" ou "faire", c'est un mot abstrait qui peut contenir presque n'importe quoi.
J'étais principalement au même endroit que vous il y a environ 7 ans, et j'ai commencé à penser qu'il y avait quelque chose qui n'allait pas dans ma façon de penser parce qu'il y avait tellement d'efforts à coder pour ne rien faire encore.
Ce que j'ai changé pour résoudre ce problème, c'est de changer le vocabulaire que j'utilise dans le code. J'évite totalement les mots génériques (sauf si c'est du code générique, mais c'est rare quand on ne fait pas de bibliothèques génériques). J'évite de nommer tout type « Manager » ou « objet ».
L'impact est direct dans le code: il vous oblige à trouver le bon mot correspondant à la vraie responsabilité de votre type. Si vous avez l'impression que le type fait plusieurs choses (garder un index des livres, les maintenir en vie, créer / détruire des livres), alors vous avez besoin de différents types qui auront chacun une responsabilité, puis vous les combinerez dans le code qui les utilise. Parfois j'ai besoin d'une usine, parfois non. Parfois, j'ai besoin d'un registre, j'en ai donc configuré un (en utilisant des conteneurs standard et des pointeurs intelligents). Parfois, j'ai besoin d'un système composé de plusieurs sous-systèmes différents, je sépare donc tout ce que je peux pour que chaque partie fasse quelque chose d'utile.
Ne nommez jamais un type «gestionnaire» et assurez-vous que tous vos types ont un seul rôle unique est ma recommandation. Il peut parfois être difficile de trouver des noms, mais c'est l'une des choses les plus difficiles à faire dans la programmation en général
la source
dynamic_cast
tue vos performances, utilisez le système de type statique, c'est à cela qu'il sert. C'est vraiment un contexte spécialisé, pas un contexte général, d'avoir à rouler votre propre RTTI comme LLVM l'a fait. Même alors, ils ne font pas ça.template <typename T> class Class { ... };
est en fait pour attribuer des ID pour différentes classes (à savoir les composants personnalisés et les systèmes personnalisés). L'attribution d'ID est utilisée pour stocker les composants / systèmes dans un vecteur, car une carte peut ne pas apporter les performances dont j'ai besoin pour un jeu.Eh bien, j'ai lu quelques-uns des codes auxquels vous avez lié, et votre message, et mon honnête résumé est que la majorité d'entre eux est pratiquement sans valeur. Désolé. Je veux dire, vous avez tout ce code, mais vous n'avez rien obtenu . Du tout. Je vais devoir approfondir ici, alors soyez indulgent avec moi.
Commençons par ObjectFactory . Premièrement, les pointeurs de fonction. Ce sont des apatrides, le seul moyen apatride utile pour créer un objet est
new
et vous n'avez pas besoin d'une usine pour cela. La création avec état d'un objet est ce qui est utile et les pointeurs de fonction ne sont pas utiles pour cette tâche. Et d' autre part, chaque objet a besoin de savoir comment détruire lui - même , ne pas avoir à constater que par exemple la création exacte de le détruire. De plus, vous devez renvoyer un pointeur intelligent, pas un pointeur brut, pour l'exception et la sécurité des ressources de votre utilisateur. Toute votre classe ClassRegistryData est justestd::function<std::unique_ptr<Base, std::function<void(Base*)>>()>
, mais avec les trous susmentionnés. Il n'a pas du tout besoin d'exister.Ensuite, nous avons AllocatorFunctor - il y a déjà des interfaces d'allocateur standard fournies - Sans parler du fait qu'ils ne sont que des wrappers
delete obj;
etreturn new T;
- ce qui est déjà fourni, et ils n'interagiront pas avec les allocateurs de mémoire avec état ou personnalisés qui existent.Et ClassRegistry est tout simplement,
std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>
mais vous avez écrit une classe pour elle au lieu d'utiliser la fonctionnalité existante. C'est le même état mutable global mais inutilement global avec un tas de macros merdiques.Ce que je dis ici, c'est que vous avez créé des classes où vous pouvez remplacer le tout par des fonctionnalités construites à partir de classes standard, puis les rendre encore pires en les rendant globalement mutables et en impliquant un tas de macros, ce qui est le pire chose que vous pourriez faire.
Ensuite, nous avons identifiable . C'est un peu la même histoire ici -
std::type_info
effectue déjà le travail d'identification de chaque type en tant que valeur d'exécution, et cela ne nécessite pas de passe-partout excessif de la part de l'utilisateur.Problème résolu, mais sans aucune des deux cents lignes de macros précédentes et ainsi de suite. Cela marque également votre tableau identifiable comme rien mais
std::vector
vous devez utiliser le comptage des références même si la propriété unique ou la non-propriété serait préférable.Oh, et ai-je mentionné que Types contient un tas de typedefs absolument inutiles ? Vous ne gagnez littéralement aucun avantage sur l'utilisation directe des types bruts et vous perdez une bonne partie de la lisibilité.
Ensuite, ComponentContainer . Vous ne pouvez pas choisir la propriété, vous ne pouvez pas avoir de conteneurs séparés pour les classes dérivées de divers composants, vous ne pouvez pas utiliser votre propre sémantique à vie. Supprimer sans remords.
Maintenant, le ComponentFilter pourrait en fait valoir un peu, si vous le refactorisiez beaucoup. Quelque chose comme
Cela ne concerne pas les classes dérivées de celles que vous essayez de gérer, mais la vôtre non plus.
Le reste est à peu près la même histoire. Il n'y a rien ici qui ne pourrait être fait beaucoup mieux sans l'utilisation de votre bibliothèque.
Comment éviteriez-vous les managers dans ce code? Supprimez pratiquement tout cela.
la source
typeid
oudynamic_cast
. Merci pour vos commentaires, je l'apprécie.Le problème avec les classes Manager est qu'un manager peut tout faire. Vous ne pouvez pas lire le nom de la classe et savoir ce que fait la classe. EntityManager .... Je sais que cela fait quelque chose avec Entities mais qui sait quoi. SystemManager ... oui, il gère le système. C'est très utile.
Je suppose que vos classes de manager font probablement beaucoup de choses. Je parie que si vous segmentiez ces choses en éléments fonctionnels étroitement liés, vous pourrez probablement trouver des noms de classe liés aux éléments fonctionnels que n'importe qui peut dire ce qu'ils font simplement en regardant le nom de la classe. Cela rendra la conception et la maintenance beaucoup plus faciles.
la source
En fait, je ne pense pas que ce
Manager
soit si mauvais dans ce contexte, haussement d'épaules. S'il y a un endroit où je pense qu'ilManager
est pardonnable, ce serait pour un système entité-composant, car il « gère » vraiment la durée de vie des composants, des entités et des systèmes. À tout le moins, c'est un nom plus significatif ici que, disons,Factory
qui n'a pas autant de sens dans ce contexte, car un ECS fait vraiment un peu plus que créer des choses.Je suppose que vous pourriez utiliser
Database
, commeComponentDatabase
. Ou vous pourriez utiliserComponents
,Systems
etEntities
(ce que j'utilise personnellement et surtout parce que je comme identificateurs de laconiques mais il est vrai convoie encore moins d' informations, peut - être que « gestionnaire »).La partie que je trouve un peu maladroite est la suivante:
C'est beaucoup de choses à
get
avant de pouvoir créer une entité et il semble que la conception fuit un peu les détails d'implémentation si vous devez connaître les pools d'entités et les "gestionnaires" pour les créer. Je pense que des choses comme "pools" et "managers" (si vous vous en tenez à ce nom) devraient être des détails d'implémentation de l'ECS, pas quelque chose exposé au client.Mais je sais pas, j'ai vu de très imaginatifs et les utilisations génériques de
Manager
,Handler
,Adapter
et les noms de ce genre, mais je pense que cela est un cas d'utilisation raisonnable pardonnable lorsque la chose appelée « gestionnaire » est en fait responsable de la « gestion » ( "contrôle? "," manipuler? ") la durée de vie des objets, étant responsable de la création et de la destruction et de l'accès à ceux-ci individuellement. C'est l'utilisation la plus simple et la plus intuitive de "Manager" pour moi au moins.Cela dit, une stratégie générale pour éviter «Manager» en votre nom consiste simplement à retirer la partie «Manager» et à en ajouter un
-s
à la fin. :-D Par exemple, disons que vous en avez unThreadManager
et qu'il est responsable de créer des threads et de fournir des informations à leur sujet et d'y accéder et ainsi de suite, mais il ne regroupe pas les threads, nous ne pouvons donc pas l'appelerThreadPool
. Eh bien, dans ce cas, vous l'appelezThreads
. C'est ce que je fais de toute façon quand je n'ai pas d'imagination.Je me rappelle un peu l'époque où l'équivalent analogique de "l'Explorateur Windows" s'appelait "Gestionnaire de fichiers", comme ceci:
Et je pense en fait que "Gestionnaire de fichiers" dans ce cas est plus descriptif que "Explorateur Windows" même s'il existe un meilleur nom qui n'implique pas "Gestionnaire". Donc, à tout le moins, ne soyez pas trop fantaisiste avec vos noms et commencez à nommer des choses comme "ComponentExplorer". "ComponentManager" me donne au moins une idée raisonnable de ce que cette chose fait en tant que personne habituée à travailler avec les moteurs ECS.
la source
Manager
est approprié. La classe peut avoir des problèmes de conception , mais le nom est sur place.