Estou trabalhando no meu próprio mecanismo de jogo e atualmente estou projetando meus gerentes. Eu li que para gerenciamento de memória, usar Init()
e CleanUp()
funções é melhor do que usar construtores e destruidores.
Eu tenho procurado exemplos de código C ++, para ver como essas funções funcionam e como eu posso implementá-las no meu mecanismo. Como funciona Init()
e CleanUp()
trabalho, e como posso implementá-las em meu motor?
Respostas:
É bem simples, na verdade:
Em vez de ter um construtor que faz sua configuração,
... peça ao seu construtor que faça pouco ou nada e escreva um método chamado
.init
or.initialize
, que faria o que seu construtor faria normalmente.Então agora, em vez de apenas ir como:
Você pode ir:
O benefício é que agora você pode usar injeção de dependência / inversão de controle com mais facilidade em seus sistemas.
Ao invés de dizer
Você pode construir o soldado, dar a ele um método de equipar, onde você entrega uma arma para ele, e ENTÃO chama todas as demais funções do construtor.
Então agora, em vez de subclassificar inimigos em que um soldado tem uma pistola e outro tem um rifle e outro tem uma espingarda, e essa é a única diferença, você pode apenas dizer:
O mesmo acordo com a destruição. Se você tiver necessidades especiais (remover ouvintes de eventos, remover instâncias de matrizes / quaisquer estruturas com as quais estiver trabalhando, etc.), você as chamaria manualmente, para saber exatamente quando e onde no programa que estava acontecendo.
EDITAR
Como Kryotan apontou, abaixo, isso responde às perguntas originais "Como" , mas na verdade não faz um bom trabalho com o "Por que".
Como você provavelmente pode ver na resposta acima, pode não haver muita diferença entre:
e escrever
enquanto apenas tendo uma função construtora maior.
Há um argumento a ser feito para objetos com 15 ou 20 pré-condições, o que tornaria um construtor muito, muito difícil de trabalhar, e facilitaria as coisas para ver e lembrar, puxando essas coisas para a interface , para que você possa ver como a instanciação funciona, um nível acima.
A configuração opcional de objetos é uma extensão natural disso; opcionalmente, definindo valores na interface, antes de executar o objeto.
O JS possui alguns ótimos atalhos para essa idéia, que parecem deslocados em linguagens do tipo c de tipo mais forte.
Dito isso, as chances são de que, se você estiver lidando com uma lista de argumentos tão longa em seu construtor, que seu objeto seja muito grande e faça muito, como é. Novamente, isso é uma coisa de preferência pessoal, e há exceções amplamente, mas se você estiver passando 20 coisas para um objeto, é provável que você encontre uma maneira de fazer com que esse objeto faça menos, criando objetos menores .
Um motivo mais pertinente e amplamente aplicável seria o fato de a inicialização de um objeto depender de dados assíncronos, que você não possui atualmente.
Você sabe que precisa do objeto e, portanto, irá criá-lo de qualquer maneira, mas para que ele funcione corretamente, ele precisa de dados do servidor ou de outro arquivo que agora precisa carregar.
Novamente, se você está passando os dados necessários para um init gigantesco ou construindo uma interface não é realmente importante para o conceito, é importante para a interface do seu objeto e o design do seu sistema ...
Mas em termos de construção do objeto, você pode fazer algo assim:
async_loader
pode receber um nome de arquivo ou um nome de recurso ou qualquer outra coisa, carregar esse recurso - talvez carregue arquivos de som ou dados de imagem ou carregue estatísticas de caracteres salvas ...... e então alimentaria esses dados novamente
obj_w_async_dependencies.init(result);
.Esse tipo de dinâmica é encontrado com frequência em aplicativos da web.
Não necessariamente na construção de um objeto, para aplicativos de nível superior: por exemplo, as galerias podem carregar e inicializar imediatamente e, em seguida, exibir fotos à medida que entram - isso não é realmente uma inicialização assíncrona, mas onde é visto com mais frequência seria nas bibliotecas JavaScript.
Um módulo pode depender de outro e, portanto, a inicialização desse módulo pode ser adiada até que o carregamento dos dependentes seja concluído.
Em termos de instâncias específicas do jogo, considere uma
Game
classe real .Por que não podemos chamar
.start
ou.run
no construtor?Os recursos precisam ser carregados - o resto de tudo já foi definido e está pronto, mas se tentarmos executar o jogo sem uma conexão com o banco de dados, ou sem texturas, modelos, sons ou níveis, não será possível. um jogo particularmente interessante ...
... então qual é a diferença entre o que vemos de um típico
Game
, exceto pelo fato de atribuirmos ao método "ir em frente" um nome mais interessante do que.init
(ou, inversamente, separar ainda mais a inicialização, para separar o carregamento, configurar as coisas que foram carregadas e executar o programa quando tudo estiver configurado).fonte
.init
, talvez não, mas provavelmente. Ergo, caso válido.Tudo o que você leu e disse que Init e CleanUp são melhores, também deveria ter lhe explicado o porquê. Artigos que não justificam suas reivindicações não valem a pena ser lidos.
Ter funções separadas de inicialização e desligamento pode facilitar a configuração e a destruição de sistemas, pois você pode escolher em que ordem chamá-los, enquanto os construtores são chamados exatamente quando o objeto é criado e os destruidores chamados quando o objeto é destruído. Quando você tem dependências complexas entre dois objetos, geralmente é necessário que os dois existam antes que eles se estabeleçam - mas isso geralmente é um sinal de mau design em outro lugar.
Alguns idiomas não têm destruidores nos quais você pode confiar, pois a contagem de referências e a coleta de lixo tornam mais difícil saber quando o objeto será destruído. Nessas linguagens, você quase sempre precisa de um método de desligamento / limpeza, e alguns gostam de adicionar o método init para simetria.
fonte
Eu acho que a melhor razão é: permitir o pool.
se você possui Init e CleanUp, quando um objeto é morto, basta chamar CleanUp e enviar o objeto para uma pilha de objetos do mesmo tipo: um 'pool'.
Então, sempre que você precisar de um novo objeto, poderá popular um objeto do pool OU se o pool estiver vazio - muito ruim - você precisará criar um novo. Então você chama Init neste objeto.
Uma boa estratégia é preencher previamente o pool antes do jogo começar com um número 'bom' de objetos, para que você nunca precise criar nenhum objeto em pool durante o jogo.
Se, por outro lado, você usar 'novo' e parar de fazer referência a um objeto quando ele não for útil para você, você criará um lixo que deve ser recuperado em algum momento. Essa lembrança é especialmente ruim para linguagens de thread único como Javascript, onde o coletor de lixo para todo o código quando avalia que precisa recuperar a memória dos objetos que não estão mais em uso. O jogo trava por alguns milissegundos e a experiência de jogo é estragada.
- Você já entendeu -: se você agrupar todos os seus objetos, nenhuma lembrança acontece, portanto, não há mais lentidão aleatória.
Também é muito mais rápido chamar init em um objeto proveniente do pool do que alocar memória + init para um novo objeto.
Mas a melhoria da velocidade tem menos importância, já que muitas vezes a criação de objetos não é um gargalo de desempenho ... Com algumas exceções, como jogos frenéticos, mecanismos de partículas ou mecanismo físico usando intensamente vetores 2D / 3D para seus cálculos. Aqui, a velocidade e a criação de lixo são bastante aprimoradas usando um pool.
Rq: talvez você não precise ter um método CleanUp para seus objetos em pool se o Init () redefinir tudo.
Edit: responder a este post me motivou a finalizar um pequeno artigo que fiz sobre o pool em Javascript .
Você pode encontrá-lo aqui se estiver interessado:
http://gamealchemist.wordpress.com/
fonte
Sua pergunta é revertida ... Historicamente falando, a pergunta mais pertinente é:
Por que é de construção + intialisation conflated , ou seja, por que não fazemos estas etapas separadamente? Certamente isso vai contra o SoC ?
Para C ++, a intenção da RAII é que a aquisição e liberação de recursos sejam vinculadas diretamente ao tempo de vida do objeto, na esperança de que isso garanta a liberação de recursos. Faz isso? Parcialmente. Ele é 100% cumprido no contexto de variáveis automáticas / baseadas em pilha, onde sair do escopo associado automaticamente chama destruidores / libera essas variáveis (daí o qualificador
automatic
). No entanto, para variáveis de heap, esse padrão muito útil se desintegra, pois você ainda é obrigado a ligar explicitamentedelete
para executar o destruidor e, se você esquecer de fazê-lo, ainda será mordido pelo que o RAII tenta resolver; no contexto de variáveis alocadas por heap, o C ++ fornece benefícios limitados sobre o C (delete
vsfree()
), confundindo a construção com a inicialização, o que afeta negativamente em termos do seguinte:A criação de um sistema de objetos para jogos / simulações em C é altamente recomendável, pois esclarecerá bastante as limitações do RAII e outros padrões centrados em OO, através de uma compreensão mais profunda das suposições que C ++ e as linguagens clássicas de OO mais recentes fazem. (lembre-se de que o C ++ começou como um sistema OO integrado em C).
fonte