Digamos que mi juego tiene un monstruo que puede explotar kamikaze en el jugador. Vamos a elegir un nombre para este monstruo al azar: un Creeper. Entonces, la Creeper
clase tiene un método que se parece a esto:
void Creeper::kamikaze() {
EventSystem::postEvent(ENTITY_DEATH, this);
Explosion* e = new Explosion;
e->setLocation(this->location());
this->world->addEntity(e);
}
Los eventos no están en cola, se envían de inmediato. Esto hace que el Creeper
objeto se elimine en algún lugar dentro de la llamada a postEvent
. Algo como esto:
void World::handleEvent(int type, void* context) {
if(type == ENTITY_DEATH){
Entity* ent = dynamic_cast<Entity*>(context);
removeEntity(ent);
delete ent;
}
}
Debido a que el Creeper
objeto se elimina mientras el kamikaze
método aún se está ejecutando, se bloqueará cuando intente acceder this->location()
.
Una solución es poner los eventos en cola en un búfer y enviarlos más tarde. ¿Es esa la solución común en los juegos de C ++? Se siente como un truco, pero eso podría deberse a mi experiencia con otros idiomas con diferentes prácticas de administración de memoria.
En C ++, ¿existe una mejor solución general para este problema en el que un objeto se elimina accidentalmente de uno de sus métodos?
fuente
autorelease
en Objective-C, donde las eliminaciones se detienen hasta por "solo un poco".Respuestas:
No borrar
this
Incluso implícitamente.
- Nunca -
Eliminar un objeto mientras una de sus funciones miembro todavía está en la pila es pedir problemas. Cualquier arquitectura de código que resulte en que eso suceda ("accidentalmente" o no) es objetivamente mala , es peligrosa y debe ser refactorizada de inmediato . En este caso, si se le permitirá a su monstruo llamar a 'World :: handleEvent', ¡en ningún caso elimine el monstruo dentro de esa función!
(En esta situación específica, mi enfoque habitual es hacer que el monstruo coloque una bandera 'muerta' sobre sí mismo y hacer que el objeto Mundial, o algo así, pruebe esa bandera 'muerta' una vez por cuadro, eliminando esos objetos de su lista de objetos en el mundo, y ya sea eliminándolo o devolviéndolo a un grupo de monstruos o lo que sea apropiado. En este momento, el mundo también envía notificaciones sobre la eliminación, para que otros objetos en el mundo sepan que el monstruo tiene dejó de existir, y puede soltar cualquier puntero que pueda estar reteniendo. El mundo hace esto en un momento seguro, cuando sabe que actualmente no se están procesando objetos, por lo que no tiene que preocuparse de que la pila se desenrolle en un punto donde el puntero 'this' apunta a la memoria liberada).
fuente
En lugar de poner en cola eventos en un búfer, ponga en cola las eliminaciones en un búfer. La eliminación retrasada tiene el potencial de simplificar masivamente la lógica; en realidad puede liberar memoria al final o al principio de un cuadro cuando sabe que nada interesante le está sucediendo a sus objetos, y eliminarlos desde cualquier lugar.
fuente
NSAutoreleasePool
sería un Objective-C en esta situación. Puede que tenga que hacer unDeletionPool
con plantillas de C ++ o algo así.En lugar de permitir que el mundo maneje la eliminación, puede permitir que la instancia de otra clase sirva como un depósito para almacenar todas las entidades eliminadas. Esta instancia particular debe escuchar los
ENTITY_DEATH
eventos y manejarlos de manera que los ponga en cola. LaWorld
entonces iterar sobre estos casos y puede realizar operaciones de post-muerte después de la trama se ha prestado y 'claro' este cubo, que a su vez realizar la eliminación real de instancias de entidades.Un ejemplo de tal clase sería así: http://ideone.com/7Upza
fuente
World
clase.Sugeriría implementar una fábrica que se use para todas las asignaciones de objetos del juego en el juego. Entonces, en lugar de llamarlo nuevo, le diría a la fábrica que cree algo para usted.
Por ejemplo
Cada vez que desea eliminar un objeto, la fábrica empuja el objeto en un búfer que se borra el siguiente cuadro. La destrucción retrasada es muy importante en la mayoría de los escenarios.
También considere enviar todos los mensajes con un retraso de un cuadro. Solo hay un par de excepciones en las que debe enviar de inmediato, la gran mayoría de los casos, sin embargo, solo
fuente
Puede implementar memoria administrada en C ++ usted mismo, de modo que cuando
ENTITY_DEATH
se llama, todo lo que sucede es que el número de referencias se reduce en uno.Más tarde, como sugirió @John al comienzo de cada cuadro, puede verificar qué entidades son inútiles (aquellas con cero referencias) y eliminarlas. Por ejemplo, puede usar
boost::shared_ptr<T>
( documentado aquí ) o si está usando C ++ 11 (VC2010)std::tr1::shared_ptr<T>
fuente
std::shared_ptr<T>
, no informes técnicos! - Deberá especificar un eliminador personalizado; de lo contrario, también eliminará el objeto inmediatamente cuando el recuento de referencia llegue a cero.Use la agrupación y en realidad no elimine objetos. En cambio, cambie la estructura de datos en la que están registrados. Por ejemplo, para renderizar objetos hay un objeto de escena y todas las entidades están registradas de alguna manera para renderizar, detectar colisiones, etc. En lugar de eliminar el objeto, sepárelo de la escena e insértelo en un grupo de objetos muertos. Este método no solo evitará problemas de memoria (como que un objeto se elimine a sí mismo) sino que también puede acelerar su juego si usa el grupo correctamente.
fuente
Lo que hicimos en un juego fue usar colocación nueva
EventPool era solo una gran variedad de memoria dividida, y el puntero a cada segmento estaba almacenado. Entonces alloc () devolvería la dirección de un bloque de memoria libre. En nuestro grupo de eventos, la memoria se trató como una pila, por lo que después de enviar todos los eventos, simplemente restablecimos el puntero de la pila al inicio de la matriz.
Debido a la forma en que funcionaba nuestro sistema de eventos, no necesitábamos llamar a los destructores por las noches. Por lo tanto, el grupo simplemente registraría el bloque de memoria como libre y lo asignaría.
Esto nos dio una gran velocidad.
Además ... En realidad, utilizamos grupos de memoria para todos los objetos objetados asignados dinámicamente en el desarrollo, ya que era una excelente manera de encontrar fugas de memoria, si quedaban objetos en los grupos cuando el juego salía (normalmente), entonces era probable que hubiera una fuga de mem.
fuente