Muy bien, entonces me doy cuenta de que esta pregunta es bastante antigua, pero sigo pensando que vale la pena responder, porque desafortunadamente el Sr. Le Blanc, y lo digo con todo respeto, tiene un montón de DOD mal. De hecho, la frase "Después de que hayas hecho tu diseño, te haces la siguiente pregunta: ¿cómo puedo organizar todos los datos que he diseñado en una sola gota grande?" es tan opuesto a lo que DOD intenta hacer que es casi una parodia, aunque el resto de la respuesta va más al grano. Sin embargo, sin falta de respeto, el Sr. Le Blanc es obviamente un miembro muy entendido y servicial de esta comunidad.
Entonces, ¿de qué se trata el DOD? Obviamente, se trata de rendimiento, pero no es solo eso. También se trata de un código bien diseñado que sea legible, fácil de entender e incluso reutilizable. Ahora el diseño orientado a objetos tiene que ver con el diseño de código y datos para encajar en "objetos" virtuales encapsulados. Cada objeto es una entidad separada con variables para las propiedades que el objeto puede tener y métodos para actuar sobre sí mismo u otros objetos en el mundo. La ventaja del diseño OO es que es fácil modelar mentalmente su código en objetos porque el mundo entero (real) que nos rodea parece funcionar de la misma manera. Objetos con propiedades que pueden interactuar entre sí.
Ahora el problema es que la CPU en su computadora funciona de una manera completamente diferente. Funciona mejor cuando dejas que haga las mismas cosas una y otra vez. ¿Porqué es eso? Por una pequeña cosa llamada caché. Acceder a la RAM en una computadora moderna puede tomar 100 o 200 ciclos de CPU (¡y la CPU tiene que esperar todo ese tiempo!), Que es demasiado larga. Entonces, hay una pequeña porción de memoria en la CPU a la que se puede acceder muy rápidamente, la memoria caché. El problema es que solo hay unas pocas tapas MB. Así que cada vez que necesita datos que no estaban en la memoria caché, aún necesita recorrer el largo camino hasta la memoria RAM. No es solo así para los datos, lo mismo ocurre con el código. Intentar ejecutar una función que no está en la caché de instrucciones causará una pérdida mientras el código se carga desde la RAM.
Volver a la programación OO. Los objetos son grandes, pero la mayoría de las funciones solo necesitan una pequeña parte de esos datos, por lo que estamos desperdiciando el caché cargando datos innecesarios. Los métodos llaman a otros métodos que llaman a otros métodos, agotando su caché de instrucciones. Aún así, a menudo hacemos muchas de las mismas cosas una y otra vez. Tomemos una bala de un juego, por ejemplo. En una implementación ingenua, cada bala podría ser un objeto separado. Puede haber una clase de administrador de bala. Llama a la primera función de actualización de bala. Actualiza la posición 3D usando la dirección/velocidad. Esto hace que muchos otros datos del objeto se carguen en la memoria caché. A continuación, llamamos a la clase World Manager para verificar una colisión con otros objetos. Esto carga muchas otras cosas en la memoria caché, tal vez incluso hace que el código de la clase original de administrador de viñetas se elimine de la memoria caché de instrucciones. Ahora volvemos a la actualización de bala, no hubo colisión, así que volvemos al administrador de bala. Es posible que necesite cargar algún código de nuevo. A continuación, actualización bala # 2. Esto carga una gran cantidad de datos en el caché, llamadas al mundo ... etc. Por lo tanto, en esta situación hipotética, tenemos 2 puestos para cargar código y digamos 2 puestos para cargar datos. Eso es al menos 400 ciclos desperdiciados, por 1 viñeta, y no hemos tenido balas que golpeen a otra cosa en cuenta. Ahora una CPU funciona a 3+ GHz por lo que no vamos a notar una sola bala, pero ¿y si tenemos 100 balas? O incluso más?
Así que este es el lugar donde hay una historia. Sí, hay algunos casos en los que solo tienes acceso a objetos, clases de administrador, acceso a archivos, etc. Pero, con mayor frecuencia, hay muchos casos similares. El diseño orientado a objetos ingenuo o incluso no ingenuo provocará muchos problemas. Así que ingrese el diseño orientado a los datos. La clave del DOD es modelar su código alrededor de sus datos, y no al revés como con OO-design. Esto comienza en las primeras etapas del diseño. Primero no diseña su código OO y luego lo optimiza. Comienza por enumerar y examinar sus datos y pensar cómo quiere modificarlos (voy a llegar a un ejemplo práctico en un momento).Una vez que sepa cómo su código va a modificar los datos, puede diseñarlos de manera que sean lo más eficientes posible para procesarlos. Ahora puede pensar que esto solo puede llevar a una sopa horrible de código y datos en todas partes, pero ese solo es el caso si lo diseña mal (el diseño incorrecto es igual de fácil con la programación OO). Si lo diseña bien, el código y los datos se pueden diseñar cuidadosamente alrededor de una funcionalidad específica, lo que permite un código muy legible e incluso muy reutilizable.
Volvamos a nuestras viñetas. En lugar de crear una clase para cada viñeta, solo conservamos el administrador de viñetas. Cada bala tiene una posición y una velocidad. La posición de cada bala debe actualizarse. Cada bala debe tener un control de colisión y todas las balas que han golpeado algo deben tomar alguna acción en consecuencia. Entonces, al echar un vistazo a esta descripción, puedo diseñar todo este sistema de una manera mucho mejor. Pongamos las posiciones de todas las viñetas en una matriz/vector. Pongamos la velocidad de todas las balas en una matriz/vector. Ahora comencemos iterando entre esas dos matrices y actualizando cada valor de posición con su velocidad correspondiente. Ahora, todos los datos cargados en el caché de datos son datos que vamos a usar. Incluso podemos poner un comando de precarga inteligente para precargar previamente algunos datos de la matriz, de modo que los datos estén en caché cuando lleguemos a ellos. A continuación, comprobación de colisión. No voy a entrar en detalles aquí, pero pueden imaginarse cómo la actualización de todas las viñetas puede ayudar. También tenga en cuenta que si hay una colisión, no vamos a llamar a una nueva función o hacer algo. Solo mantenemos un vector con todas las viñetas que tuvieron colisión y cuando se realiza la verificación de colisión, podemos actualizarlas una tras otra. ¿Ve cómo pasamos de tener acceso a la memoria a casi ninguno al disponer nuestros datos de manera diferente? ¿Notó también cómo nuestro código y nuestros datos, aunque ya no están diseñados de manera OO, todavía son fáciles de entender y fáciles de reutilizar?
Para volver al "donde hay uno, hay muchos". Al diseñar el código OO, piensas en un objeto, el prototipo/clase. Una bala tiene una velocidad, una bala tiene una posición, una bala moverá cada cuadro por su velocidad, una bala puede golpear algo, etc. Cuando pienses en eso, pensarás en una clase, con velocidad, posición y función de actualización que mueve la bala y comprueba la colisión. Sin embargo, cuando tiene varios objetos, debe pensar en todos ellos. Las balas tienen posiciones, velocidad. Algunas balas pueden tener colisión. ¿Ves que ya no estamos pensando en un objeto individual? Estamos pensando en todos ellos y estamos diseñando código de manera muy diferente ahora.
Espero que esto ayude a responder su segunda parte de la pregunta. No se trata de si necesitas actualizar a cada enemigo o no, se trata de la forma más eficiente de actualizarlos. Y aunque diseñar solo a tus enemigos usando DOD puede no ayudar a obtener mucho rendimiento, diseñando todo el juego alrededor de estos principios (¡solo cuando corresponda!) Puede generar una gran cantidad de mejoras en el rendimiento.
En la primera parte de la pregunta, hay otros ejemplos de DOD. Lo siento, pero no tengo tanto allí. Sin embargo, hay un muy buen ejemplo, me encontré con esto hace algún tiempo, una serie sobre diseño orientado a datos de un árbol de comportamiento por Bjoern Knafla: http://bjoernknafla.com/data-oriented-behavior-tree-overview Es probable que desee comenzar en el primero de la serie de 4, los enlaces están en el artículo en sí. Espero que esto ayude, a pesar de la vieja pregunta. O tal vez algún otro usuario de SO encuentre esta pregunta y use esta respuesta.
El enlace parece estar roto, pero supongo que es el que publicó en [altdevblogaday] (http://www.altdevblogaday.com/2011/07/09/data-oriented-behavior-tree-overview/) también. Gracias por una muy buena respuesta! – falstro
Gran explicación. Hay una cosa que no obtuve. Tener un vector para cada propiedad (posición, movimiento, ...) ¿cómo están conectados? Imagine que una bala colisiona, ¿cómo sé qué elemento debo eliminar de todos los vectores? – danijar
Podrían estar conectados por una identificación. Entonces, en lugar de un puntero a un objeto de viñeta, tiene una identificación que la clase BulletManager puede traducir al índice correcto. Digamos que quieres destruir/eliminar una viñeta con id # 9001, llamar a la función de eliminación de BulletManager con esa identificación, BulletManager busca # 9001 en su índice y encuentra que apunta al índice de matriz 42. Entonces puede, en todos sus datos arrays (en nuestro caso posición y velocidad) reemplazan los datos en el índice 42 con los datos en el último índice, actualicen la tabla de id para reflejar que el ID que apuntaba al último índice apunta ahora al índice 42 y el # 9001 se ha ido – Mart