Actualmente estoy rediseñando mi Sistema de Entidades , para C ++, y tengo muchos Gerentes. En mi diseño, tengo estas clases, para unir mi biblioteca. He escuchado muchas cosas malas cuando se trata de clases de "gerente", quizás no estoy nombrando mis clases apropiadamente. Sin embargo, no tengo idea de qué más nombrarlos.
La mayoría de los gerentes, en mi biblioteca, se componen de estas clases (aunque varía un poco):
- Contenedor: un contenedor para objetos en el administrador
- Atributos: atributos para los objetos en el administrador
En mi nuevo diseño para mi biblioteca, tengo estas clases específicas, para unir mi biblioteca.
ComponentManager: administra componentes en el Sistema de entidades
- ComponentContainer
- Atributos de componente
- Escena *: una referencia a una escena (ver más abajo)
SystemManager: administra sistemas en el sistema de entidades
- Contenedor de sistema
- Escena *: una referencia a una escena (ver más abajo)
EntityManager: administra entidades en el sistema de entidades
- EntityPool - un grupo de entidades
- Atributos de entidad: atributos de una entidad (esto solo será accesible para las clases ComponentContainer y System)
- Escena *: una referencia a una escena (ver más abajo)
Escena: une a todos los gerentes
- ComponentManager
- Administrador de sistemas
- EntityManager
Estaba pensando en poner todos los contenedores / piscinas en la clase Scene.
es decir
En lugar de esto:
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();
Sería esto:
Scene scene; // create a Scene
Entity entity = scene.getEntityPool().create();
Pero no estoy seguro. Si tuviera que hacer esto último, eso significaría que tendría muchos objetos y métodos declarados dentro de mi clase de escena.
NOTAS
- Un sistema de entidad es simplemente un diseño que se utiliza para juegos. Se compone de 3 partes principales: componentes, entidades y sistemas. Los componentes son simplemente datos, que pueden "agregarse" a las entidades, para que las entidades sean distintivas. Una entidad está representada por un número entero. Los sistemas contienen la lógica de una entidad, con componentes específicos.
- La razón por la que estoy cambiando mi diseño para mi biblioteca es porque creo que se puede cambiar bastante, no me gusta la sensación / flujo en este momento.
fuente
Respuestas:
DeadMG es acertado en los detalles de su código, pero siento que falta una aclaración. Además, no estoy de acuerdo con algunas de sus recomendaciones que no se cumplen en algunos contextos específicos, como la mayoría de los desarrollos de videojuegos de alto rendimiento, por ejemplo. Pero tiene razón a nivel mundial de que la mayoría de su código no es útil en este momento.
Como dice Dunk, las clases de Manager se llaman así porque "gestionan" las cosas. "administrar" es como "datos" o "hacer", es una palabra abstracta que puede contener casi cualquier cosa.
Hace más de 7 años estuve en el mismo lugar que usted, y comencé a pensar que había algo mal en mi forma de pensar porque era mucho esfuerzo codificar para no hacer nada todavía.
Lo que cambié para arreglar esto es cambiar el vocabulario que uso en el código. Evito totalmente las palabras genéricas (a menos que sea un código genérico, pero es raro cuando no está creando bibliotecas genéricas). Yo evito nombrar cualquier tipo "Administrador" o "objeto".
El impacto es directo en el código: te obliga a encontrar la palabra correcta correspondiente a la responsabilidad real de tu tipo. Si siente que el tipo hace varias cosas (mantener un índice de libros, mantenerlos vivos, crear / destruir libros), entonces necesita diferentes tipos, cada uno tendrá una responsabilidad, luego los combina en el código que los usa. A veces necesito una fábrica, a veces no. En algún momento necesito un registro, así que configuro uno (usando contenedores estándar y punteros inteligentes). En algún momento necesito un sistema que se compone de varios subsistemas diferentes, así que separo todo lo que puedo para que cada parte haga algo útil.
Nunca nombre a un tipo "gerente" y asegúrese de que todo su tipo tenga un rol único único es mi recomendación. Puede ser difícil encontrar nombres alguna vez, pero es una de las cosas más difíciles de hacer en general.
fuente
dynamic_cast
está matando su rendimiento, entonces use el sistema de tipo estático, para eso está. Es realmente un contexto especializado, no general, tener que rodar su propio RTTI como lo hizo LLVM. Incluso entonces, no hacen esto.template <typename T> class Class { ... };
en realidad es para asignar ID para diferentes clases (es decir, componentes personalizados y sistemas personalizados). La asignación de ID se usa para almacenar componentes / sistemas en un vector, ya que un mapa puede no proporcionar el rendimiento que necesito para un juego.Bueno, leí algunos de los códigos a los que se vinculó, y su publicación, y mi resumen honesto es que la mayoría es básicamente inútil. Lo siento. Quiero decir, tienes todo este código, pero no has logrado nada. En absoluto. Voy a tener que profundizar un poco aquí, así que tengan paciencia conmigo.
Comencemos con ObjectFactory . En primer lugar, los punteros de función. Estos son sin estado, la única forma útil sin estado de crear un objeto es
new
y no necesita una fábrica para eso. Crear un objeto con estado es lo que es útil y los punteros de función no son útiles para esta tarea. Y en segundo lugar, cada objeto necesita saber cómo destruirse a sí mismo , sin tener que encontrar esa instancia de creación exacta para destruirlo. Además, debe devolver un puntero inteligente, no un puntero sin formato, para la excepción y la seguridad de los recursos de su usuario. Toda su clase ClassRegistryData es justastd::function<std::unique_ptr<Base, std::function<void(Base*)>>()>
, pero con los agujeros mencionados anteriormente. No necesita existir en absoluto.Luego tenemos AllocatorFunctor, ya hay interfaces de asignadores estándar proporcionadas, sin mencionar que son solo envolturas
delete obj;
yreturn new T;
, lo que de nuevo, ya está provisto, y no interactuarán con ningún asignador de memoria con estado o personalizado que exista.Y ClassRegistry es simplemente
std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>
pero escribiste una clase para él en lugar de usar la funcionalidad existente. Es el mismo, pero completamente mutable estado innecesariamente global con un montón de macros cutres.Lo que estoy diciendo aquí es que ha creado algunas clases en las que podría reemplazar todo con una funcionalidad construida a partir de clases estándar, y luego las hizo aún peor al convertirlas en un estado mutable global e involucrar un montón de macros, que es el Lo peor que podrías hacer.
Entonces tenemos identificable . Aquí es una gran parte de la misma historia:
std::type_info
ya realiza el trabajo de identificar cada tipo como un valor de tiempo de ejecución, y no requiere una placa repetitiva excesiva por parte del usuario.Problema resuelto, pero sin ninguna de las doscientas líneas de macros anteriores y demás. Esto también marca su IdentifiableArray como nada pero
std::vector
tiene que usar el recuento de referencias incluso si la propiedad única o la no propiedad serían mejores.Ah, y ¿mencioné que Tipos contiene un montón de tipos de letra absolutamente inútiles ? Literalmente, no obtiene ninguna ventaja sobre el uso directo de los tipos sin formato y pierde una buena parte de la legibilidad.
El siguiente es ComponentContainer . No puede elegir la propiedad, no puede tener contenedores separados para clases derivadas de varios componentes, no puede usar su propia semántica de por vida. Eliminar sin remordimiento.
Ahora, el ComponentFilter podría valer un poco, si lo refactorizaste mucho. Algo como
Esto no trata con las clases derivadas de las que estás tratando de tratar, pero tampoco las tuyas.
El resto de esto es más o menos la misma historia. No hay nada aquí que no se pueda hacer mucho mejor sin el uso de su biblioteca.
¿Cómo evitarías a los gerentes en este código? Eliminar básicamente todo.
fuente
typeid
odynamic_cast
. Sin embargo, gracias por los comentarios, lo aprecio.El problema con las clases Manager es que un gerente puede hacer cualquier cosa. No puede leer el nombre de la clase y saber qué hace la clase. EntityManager ... Sé que hace algo con las Entidades, pero quién sabe qué. SystemManager ... sí, administra el sistema. Eso es muy útil
Supongo que sus clases de gerentes probablemente hagan muchas cosas. Apuesto a que si segmentas esas cosas en piezas funcionales estrechamente relacionadas, entonces probablemente puedas encontrar nombres de clase relacionados con las piezas funcionales que cualquiera puede decir lo que hacen simplemente mirando el nombre de la clase. Esto hará que sea mucho más fácil mantener y comprender el diseño.
fuente
En realidad no creo que
Manager
sea tan malo en este contexto, encogerse de hombros. Si hay un lugar donde creo queManager
es perdonable, sería para un sistema de entidad-componente, ya que realmente está " gestionando " las vidas de componentes, entidades y sistemas. Por lo menos, ese es un nombre más significativo aquí que, por ejemplo,Factory
lo que no tiene mucho sentido en este contexto, ya que un ECS realmente hace un poco más que crear cosas.Supongo que podrías usar
Database
, comoComponentDatabase
. O simplemente podrías usarComponents
,Systems
yEntities
(esto es lo que yo uso personalmente y principalmente porque me gustan los identificadores concisos, aunque es cierto que transmite incluso menos información, tal vez, que "Gerente").La única parte que encuentro un poco torpe es esta:
Eso es un montón de cosas para
get
antes de que pueda crear una entidad y parece que el diseño está filtrando un poco los detalles de implementación si tiene que saber sobre los grupos de entidades y los "gerentes" para crearlos. Creo que cosas como "grupos" y "gerentes" (si se apega a este nombre) deberían ser detalles de implementación del ECS, no algo expuesto al cliente.Pero sé, he visto algunos usos muy poco imaginativas y genéricas de
Manager
,Handler
,Adapter
, y nombres de este tipo, pero creo que este es un caso de uso razonablemente perdonables cuando la cosa llamada "Administrador" es realmente responsable de la "gestión" ( "controlar? "," manejo? ") la vida útil de los objetos, siendo responsable de crear y destruir y proporcionar acceso a ellos individualmente. Ese es el uso más directo e intuitivo de "Manager" para mí al menos.Dicho esto, una estrategia general para evitar "Gerente" en su nombre es simplemente sacar la parte "Gerente" y agregar una
-s
al final. :-D Por ejemplo, supongamos que tiene unThreadManager
y es responsable de crear hilos y proporcionar información sobre ellos y acceder a ellos, etc., pero no agrupa los hilos, por lo que no podemos llamarloThreadPool
. Bueno, en ese caso, solo llámaloThreads
. Eso es lo que hago de todos modos cuando no tengo imaginación.Me recuerda un poco cuando el equivalente analógico de "Windows Explorer" se llamaba "Administrador de archivos", así:
Y en realidad creo que "Administrador de archivos" en este caso es más descriptivo que "Explorador de Windows", incluso si hay algún nombre mejor que no implique "Administrador". Así que, al menos, no seas tan elegante con tus nombres y comienza a nombrar cosas como "ComponentExplorer". "ComponentManager" al menos me da una idea razonable de lo que hace esa cosa cuando alguien solía trabajar con motores ECS.
fuente
Manager
es apropiado. La clase puede tener problemas de diseño , pero el nombre es perfecto