Digamos que meu jogo tenha um monstro que o kamikaze possa explodir no jogador. Vamos escolher um nome para esse monstro aleatoriamente: um Creeper. Portanto, a Creeper
classe tem um método que se parece com isso:
void Creeper::kamikaze() {
EventSystem::postEvent(ENTITY_DEATH, this);
Explosion* e = new Explosion;
e->setLocation(this->location());
this->world->addEntity(e);
}
Os eventos não estão na fila, eles são despachados imediatamente. Isso faz com que o Creeper
objeto seja excluído em algum lugar dentro da chamada para postEvent
. Algo assim:
void World::handleEvent(int type, void* context) {
if(type == ENTITY_DEATH){
Entity* ent = dynamic_cast<Entity*>(context);
removeEntity(ent);
delete ent;
}
}
Como o Creeper
objeto é excluído enquanto o kamikaze
método ainda está em execução, ele falha quando tenta acessar this->location()
.
Uma solução é enfileirar os eventos em um buffer e enviá-los mais tarde. Essa é a solução comum em jogos em C ++? Parece um pouco complicado, mas isso pode ser apenas por causa da minha experiência com outros idiomas com diferentes práticas de gerenciamento de memória.
Em C ++, existe uma solução geral melhor para esse problema em que um objeto se exclui acidentalmente de dentro de um de seus métodos?
fonte
autorelease
Objective-C, onde as exclusões são suspensas até "apenas um pouco".Respostas:
Não apague
this
Mesmo implicitamente.
- Sempre -
A exclusão de um objeto enquanto uma de suas funções membro ainda está na pilha está implorando por problemas. Qualquer arquitetura de código que resulte em que isso aconteça ("acidentalmente" ou não) é objetivamente ruim , perigosa e deve ser refatorada imediatamente . Nesse caso, se seu monstro puder chamar 'World :: handleEvent', não exclua, em nenhuma circunstância, o monstro dentro dessa função!
(Nesta situação específica, minha abordagem usual é fazer com que o monstro coloque uma bandeira 'morta' em si mesma e faça com que o objeto Mundo - ou algo parecido - teste essa bandeira 'morta' uma vez por quadro, removendo esses objetos. da lista de objetos no mundo e excluí-lo ou devolvê-lo a um pool de monstros ou o que for apropriado.Neste momento, o mundo também envia notificações sobre a exclusão, para que outros objetos no mundo saibam que o monstro tem parou de existir e pode soltar qualquer ponteiro que possa estar segurando.O mundo faz isso em um momento seguro, quando sabe que nenhum objeto está sendo processado no momento, para que você não precise se preocupar com o desenrolar da pilha a um ponto onde o ponteiro 'this' aponta para a memória liberada.)
fonte
Em vez de enfileirar eventos em um buffer, enfileire exclusões em um buffer. A exclusão tardia tem o potencial de simplificar massivamente a lógica; você pode realmente liberar memória no final ou no início de um quadro quando não sabe que nada de interessante está acontecendo com seus objetos e excluir de qualquer lugar.
fonte
NSAutoreleasePool
Objective-C nessa situação. Pode ter que fazer umDeletionPool
com modelos C ++ ou algo assim.Em vez de permitir que o mundo lide com a exclusão, você pode permitir que a instância de outra classe sirva como um balde para armazenar todas as entidades excluídas. Essa instância específica deve escutar
ENTITY_DEATH
eventos e manipulá-los de modo que os enfileire. EmWorld
seguida, é possível iterar essas instâncias e executar operações pós-morte após a renderização do quadro e 'limpar' esse bucket, que por sua vez executaria a exclusão real das instâncias das entidades.Um exemplo dessa classe seria assim: http://ideone.com/7Upza
fonte
World
classe.Eu sugeriria a implementação de uma fábrica usada para todas as alocações de objetos do jogo. Então, em vez de chamar de novo, você diria à fábrica para criar algo para você.
Por exemplo
Sempre que você deseja excluir um objeto, a fábrica empurra o objeto em um buffer que é limpo no próximo quadro. Destruição atrasada é muito importante na maioria dos cenários.
Considere também enviar todas as mensagens com um atraso de um quadro. Existem apenas algumas exceções para as quais você precisa enviar imediatamente, que a grande maioria dos casos, no entanto, apenas
fonte
Você pode implementar a memória gerenciada em C ++, para que, quando
ENTITY_DEATH
chamado, tudo o que acontece é que o número de suas referências seja reduzido em um.Mais tarde, como o @John sugeriu no início de cada quadro, você pode verificar quais entidades são inúteis (aquelas com zero referências) e excluí-las. Por exemplo, você pode usar
boost::shared_ptr<T>
( documentado aqui ) ou se estiver usando C ++ 11 (VC2010)std::tr1::shared_ptr<T>
fonte
std::shared_ptr<T>
relatórios não técnicos! - Você precisará especificar um deleter personalizado, caso contrário, ele também excluirá o objeto imediatamente quando a contagem de referência chegar a zero.Use pool e, na verdade, não exclua objetos. Em vez disso, altere a estrutura de dados em que estão registrados. Por exemplo, para renderizar objetos, há um objeto de cena e todas as entidades de alguma forma registradas para renderização, detecção de colisão etc. Em vez de excluir o objeto, desanexe-o da cena e insira-o em um conjunto de objetos mortos. Este método não apenas evita problemas de memória (como um objeto que se apaga), mas também pode acelerar o jogo se você usar o pool corretamente.
fonte
O que fizemos em um jogo foi usar o posicionamento new
o eventPool era apenas uma grande matriz de memória que foi dividida e o ponteiro para cada segmento foi armazenado. Portanto, o assign () retornaria o endereço de um bloco de memória livre. No eventPool, a memória era tratada como uma pilha; portanto, após todos os eventos terem sido enviados, redefiníamos o ponteiro da pilha de volta ao início da matriz.
Por causa do funcionamento do nosso sistema de eventos, não precisamos chamar os destruidores dos eventos. Portanto, o pool simplesmente registraria o bloco de memória como livre e o alocaria.
Isso nos deu uma enorme velocidade.
Além disso ... Na verdade, usamos pools de memória para todos os objetos alocados dinamicamente no desenvolvimento, pois era uma ótima maneira de encontrar vazamentos de memória, se ainda houvesse algum objeto nos pools quando o jogo terminava (normalmente), provavelmente havia um vazamento de mem.
fonte