Atualmente, estou redesenhando meu Sistema de Entidades , para C ++, e tenho muitos Gerentes. No meu design, eu tenho essas classes, para amarrar minha biblioteca. Ouvi muitas coisas ruins quando se trata de aulas de "gerente", talvez eu não esteja nomeando minhas aulas adequadamente. No entanto, não tenho mais idéia de como nomeá-los.
A maioria dos gerentes, na minha biblioteca, é composta dessas classes (embora varie um pouco):
- Contêiner - um contêiner para objetos no gerenciador
- Atributos - atributos para os objetos no gerenciador
No meu novo design para minha biblioteca, tenho essas classes específicas, a fim de vincular minha biblioteca.
ComponentManager - gerencia componentes no Sistema de entidades
- ComponentContainer
- ComponentAttributes
- Cena * - uma referência a uma Cena (veja abaixo)
SystemManager - gerencia sistemas no Sistema de entidades
- SystemContainer
- Cena * - uma referência a uma Cena (veja abaixo)
EntityManager - gerencia entidades no sistema de entidades
- EntityPool - um conjunto de entidades
- EntityAttributes - atributos de uma entidade (isso só estará acessível às classes ComponentContainer e System)
- Cena * - uma referência a uma Cena (veja abaixo)
Cena - une todos os gerentes
- ComponentManager
- SystemManager
- EntityManager
Eu estava pensando em colocar todos os contêineres / piscinas na própria classe Scene.
ie
Em vez disso:
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();
Seria o seguinte:
Scene scene; // create a Scene
Entity entity = scene.getEntityPool().create();
Mas não tenho certeza. Se eu fizesse o último, isso significaria que eu teria muitos objetos e métodos declarados dentro da minha classe Scene.
NOTAS:
- Um sistema de entidades é simplesmente um design usado para jogos. É composto por três partes principais: componentes, entidades e sistemas. Os componentes são simplesmente dados, que podem ser "adicionados" às entidades, para que as entidades sejam distintas. Uma entidade é representada por um número inteiro. Os sistemas contêm a lógica de uma entidade, com componentes específicos.
- A razão pela qual estou mudando meu design para minha biblioteca é porque acho que pode ser alterado bastante, não gosto da sensação / fluxo dela no momento.
fonte
Respostas:
DeadMG é direto sobre as especificidades do seu código, mas sinto que falta um esclarecimento. Também não concordo com algumas de suas recomendações que não se aplicam a contextos específicos, como o desenvolvimento de videogames de alto desempenho, por exemplo. Mas ele está globalmente certo que a maior parte do seu código não é útil no momento.
Como Dunk diz, as classes de gerente são chamadas assim porque "gerenciam" as coisas. "gerenciar" é como "dados" ou "do", é uma palavra abstrata que pode conter quase qualquer coisa.
Eu estava no mesmo lugar que você há cerca de 7 anos e comecei a pensar que havia algo errado na minha maneira de pensar, porque havia muito esforço para codificar para não fazer nada ainda.
O que eu mudei para corrigir isso é mudar o vocabulário que eu uso no código. Eu evito totalmente palavras genéricas (a menos que seja código genérico, mas é raro quando você não está criando bibliotecas genéricas). I evitar a nomear qualquer tipo "Manager" ou "objeto".
O impacto é direto no código: força você a encontrar a palavra certa correspondente à responsabilidade real do seu tipo. Se você acha que o tipo faz várias coisas (mantenha um índice de Livros, mantenha-os vivos, crie / destrua livros), precisará de tipos diferentes, cada um com uma responsabilidade, e combiná-los no código que os utiliza. Às vezes eu preciso de uma fábrica, às vezes não. Às vezes, preciso de um registro, por isso configurei um (usando contêineres padrão e indicadores inteligentes). Às vezes, preciso de um sistema composto por vários subsistemas diferentes, para separar tudo o que puder para que cada parte faça algo útil.
Nunca nomeie um tipo de "gerente" e verifique se todo o seu tipo tem uma única função única. É minha recomendação. Pode ser difícil encontrar nomes em algum momento, mas é uma das coisas mais difíceis de fazer na programação em geral
fonte
dynamic_cast
estão matando seu desempenho, use o sistema de tipo estático - é para isso que serve. É realmente um contexto especializado, não geral, que precisa rolar seu próprio RTTI, como o LLVM. Mesmo assim, eles não fazem isso.template <typename T> class Class { ... };
é, na verdade, para atribuir IDs para diferentes classes (componentes e sistemas personalizados). A atribuição de ID é usada para armazenar componentes / sistemas em um vetor, pois um mapa pode não trazer o desempenho necessário para um jogo.Bem, eu li alguns dos códigos aos quais você vinculou e sua postagem, e meu resumo honesto é que a maioria deles é basicamente completamente inútil. Desculpe. Quero dizer, você tem todo esse código, mas não conseguiu nada. Em absoluto. Vou ter que me aprofundar um pouco aqui, então tenha paciência comigo.
Vamos começar com o ObjectFactory . Em primeiro lugar, ponteiros de função. Elas são apátridas, a única maneira útil e apátrida de criar um objeto é
new
e você não precisa de uma fábrica para isso. Criar um objeto com estado é o que é útil e ponteiros de função não são úteis para esta tarefa. E em segundo lugar, cada objeto precisa saber como destruir em si , não ter que encontrar essa instância exata criando para destruí-la. Além disso, você deve retornar um ponteiro inteligente, não um ponteiro bruto, para a exceção e a segurança dos recursos do usuário. Toda a sua classe ClassRegistryData é justastd::function<std::unique_ptr<Base, std::function<void(Base*)>>()>
, mas com os buracos mencionados acima. Não precisa existir.Então temos o AllocatorFunctor - já existem interfaces de alocador padrão fornecidas - Sem mencionar que elas são apenas wrappers
delete obj;
ereturn new T;
- que novamente já são fornecidas e não interagem com nenhum alocador de memória com estado ou personalizado que exista.E ClassRegistry é simples,
std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>
mas você escreveu uma classe para ela em vez de usar a funcionalidade existente. É o mesmo, mas totalmente mutável estado global desnecessário, com várias macros ruins.O que estou dizendo aqui é que você criou algumas classes nas quais poderia substituir a coisa toda pela funcionalidade criada a partir das classes Standard e as piorou ainda mais, tornando-as em um estado mutável global e envolvendo um monte de macros, que é o pior coisa que você poderia fazer.
Então nós temos identificável . É muito parecido com a história aqui -
std::type_info
já realiza o trabalho de identificar cada tipo como um valor em tempo de execução e não requer clichê excessivo por parte do usuário.Problema resolvido, mas sem nenhuma das duzentas linhas anteriores de macros e outros enfeites. Isso também marca o seu IdentifiableArray como nada, mas
std::vector
você precisa usar a contagem de referências, mesmo que a propriedade exclusiva ou a não propriedade sejam melhores.Ah, e eu mencionei que Types contém um monte de typedefs absolutamente inúteis ? Você literalmente não obtém nenhuma vantagem sobre o uso direto dos tipos brutos e perde uma boa parte da legibilidade.
O próximo passo é ComponentContainer . Você não pode escolher a propriedade, não pode ter contêineres separados para classes derivadas de vários componentes, não pode usar sua própria semântica vitalícia. Excluir sem remorso.
Agora, o ComponentFilter pode realmente valer um pouco, se você o refatorar bastante. Algo como
Isso não lida com classes derivadas daquelas com as quais você está tentando lidar, mas nem a sua.
O resto disso é praticamente a mesma história. Não há nada aqui que não poderia ser feito muito melhor sem o uso da sua biblioteca.
Como você evitaria os gerentes nesse código? Exclua basicamente tudo isso.
fonte
typeid
oudynamic_cast
. Obrigado pelo feedback, porém, eu aprecio isso.O problema com as classes do gerente é que um gerente pode fazer qualquer coisa. Você não pode ler o nome da turma e saber o que ela faz. EntityManager .... Eu sei que ele faz algo com Entidades, mas quem sabe o quê. SystemManager ... sim, ele gerencia o sistema. Isso é muito útil.
Meu palpite é que suas classes de gerente provavelmente fazem muitas coisas. Aposto que, se você segmentar essas coisas em partes funcionais intimamente relacionadas, provavelmente poderá criar nomes de classe relacionados às partes funcionais que qualquer pessoa pode dizer o que faz simplesmente olhando para o nome da classe. Isso tornará muito mais fácil manter e entender o design.
fonte
Na verdade, eu não acho
Manager
tão ruim nesse contexto, encolher os ombros. Se há um lugar que eu acho queManager
é perdoável, seria para um sistema de componente de entidade, pois ele realmente está " gerenciando " a vida útil de componentes, entidades e sistemas. No mínimo, esse é um nome mais significativo aqui do que, digamos,Factory
que não faz tanto sentido nesse contexto, uma vez que um ECS realmente faz um pouco mais do que criar coisas.Suponho que você poderia usar
Database
, comoComponentDatabase
. Ou você pode apenas usarComponents
,Systems
eEntities
(é isso que eu pessoalmente uso e principalmente porque gosto de identificadores concisos, embora ele admita que transmita ainda menos informações, talvez, que "Gerente").A parte que acho um pouco desajeitada é a seguinte:
Isso é um monte de coisas
get
antes que você possa criar uma entidade e parece que o design está vazando um pouco os detalhes da implementação se você precisar conhecer os pools de entidades e os "gerentes" para criá-los. Eu acho que coisas como "pools" e "gerentes" (se você se ater a esse nome) devem ser detalhes de implementação do ECS, não algo exposto ao cliente.Mas sei lá, eu vi alguns usos muito sem imaginação e genéricas de
Manager
,Handler
,Adapter
e nomes desse tipo, mas acho que este é um caso de uso razoavelmente perdoável quando a coisa chamada "Manager" é realmente responsável por "gestão" ( "controlar? "," manipulação? ") a vida útil dos objetos, sendo responsável por criar e destruir e fornecer acesso a eles individualmente. Esse é o uso mais direto e intuitivo do "Manager" para mim, pelo menos.Dito isto, uma estratégia geral para evitar "Gerente" em seu nome é apenas retirar a parte "Gerente" e adicionar uma
-s
no final. :-D Por exemplo, digamos que você tenha umThreadManager
e é responsável por criar threads e fornecer informações sobre eles e acessá-los e assim por diante, mas ele não agrupa threads e não podemos chamá-loThreadPool
. Bem, nesse caso, você apenas chamaThreads
. É o que faço de qualquer maneira quando não tenho imaginação.Eu sou meio lembrado quando o equivalente analógico do "Windows Explorer" era chamado "Gerenciador de Arquivos", assim:
E, na verdade, acho que "Gerenciador de Arquivos", neste caso, é mais descritivo que "Windows Explorer", mesmo que haja algum nome melhor por aí que não envolva "Gerenciador". Portanto, pelo menos, não exagere nos nomes e comece a nomear coisas como "ComponentExplorer". O "ComponentManager" pelo menos me dá uma idéia razoável do que essa coisa faz quando alguém costumava trabalhar com mecanismos ECS.
fonte
Manager
é apropriado. A classe pode ter problemas de design , mas o nome é spot-on.