Programación orientada a datos
La Programación Orientada a Datos es un enfoque poderoso que aprovecha al máximo el rendimiento.
La Programación Orientada a Datos es un enfoque poderoso de programación que resulta en grandes mejoras de rendimiento. Se centra en tratar datos como el elemento central, todo lo demás se organiza en torno a ello, ya sea para acceder o modificar esos datos. Este enfoque también es muy amigable para el modo multijugador, ya que facilita y acelera el acceso a los datos que deben sincronizarse entre jugadores.
La Programación Orientada a Datos te anima a pensar en todo en tu escena como datos que necesitan ser copiados y mutados a través de los diversos sistemas. El principal beneficio de este enfoque es optimizar la velocidad a la que los datos pueden leerse desde la memoria, que a menudo es el principal cuello de botella al ejecutar aplicaciones y juegos modernos.
Debido a esta notable mejora en el rendimiento, gran parte de la industria de creación de juegos se ha ido inclinando hacia la adopción de este enfoque en los últimos años.
El SDK de Decentraland ejecuta escenas en JavaScript, y una desventaja de este lenguaje es que no ofrece control sobre la asignación de memoria. Sin embargo, el motor que ejecuta Decentraland utiliza C#, que se beneficia mucho al seguir principios orientados a datos. El SDK y el motor están constantemente enviando mensajes entre sí. Para que esta comunicación sea lo más eficiente posible, tiene sentido mantener las estructuras de datos en ambos lados lo más similares posible, para evitar tener que reorganizar constantemente esos datos.
Cómo se ve
La Programación Orientada a Datos es diferente de la Programación Orientada a Objetos, un enfoque con el que muchos desarrolladores están familiarizados actualmente. En la Programación Orientada a Objetos, el código se estructura siguiendo abstracciones que intentan replicar constructos del mundo real: objetos. Cada uno de estos objetos puede contener tanto datos como funcionalidad. Las aplicaciones que usan este enfoque suelen ser fáciles de planificar conceptualmente, pero también más ineficientes en ejecución.
En la Programación Orientada a Datos, los datos no se estructuran alrededor de objetos; se estructuran para optimizar su facilidad de acceso. Los constructos del mundo real que los datos representan no intervienen en los distintos flujos que mutan esos datos.
El modelo Entity Component System (ECS) sobre el que está construido el SDK de Decentraland es muy compatible con el enfoque de Programación Orientada a Datos. Cada componente forma parte de una colección estructurada de datos. Los componentes pertenecen a una entidad por referencia, pero los datos no se estructuran alrededor de la entidad; los datos se estructuran como una colección de componentes similares. Por ejemplo, todos Transform los componentes en una escena son iguales. Uno de estos Transform podría pertenecer al modelo de tu edificio principal, otro a un vidrio que es hijo de una mesa. Los sistemas en la escena luego procesan la lista de Transform componentes uno por uno, sin hacer distinciones. Todos los Transform componentes tienen los mismos campos y pasan por las mismas comprobaciones.

Imagina una escena que tiene una docena de puertas que pueden estar abiertas o cerradas. Puedes representar el estado de todas estas puertas como un simple componente "isOpen" que contiene un valor booleano. Si "isOpen" es true la puerta debe estar abierta; si es false la puerta debe estar cerrada. Si un jugador hace clic en una puerta, esta debería alternar su estado, y los demás jugadores también deberían verlo alternar. Mientras tu escena está procesando un cambio en el estado de una puerta y sincronizándolo con otros jugadores, la escena realmente no se preocupa por lo que "isOpen" representa. Todo el conjunto de componentes es solo una colección de booleanos que necesitan sincronizarse con otros jugadores. Un sistema separado en tu escena puede entonces encargarse de hacer coincidir regularmente el estado de cada "isOpen" con la rotación correspondiente de su puerta.
La Programación Orientada a Datos no es necesariamente más difícil, pero es un enfoque diferente que necesita ser aprendido y adoptado. Los desarrolladores que no están acostumbrados a este enfoque podrían necesitar algo de tiempo para familiarizarse; te animamos a que explores y juegues con ejemplos para cogerle mejor la mano.
Por qué funciona
Para entender por qué la programación orientada a datos marca una gran diferencia, necesitamos echar un vistazo al hardware.
Cuando el procesador necesita recuperar datos de la memoria, obtiene un bloque entero de datos en la caché, incluyendo datos que simplemente están escritos en la memoria justo al lado del valor que queríamos. Cuanto más pueda tu código aprovechar los datos que ya están en caché, más rápido se ejecutará.
Imagina que la memoria de la máquina es un almacén literal, donde los datos se guardan en muchas cajas apiladas. Siempre que necesitamos un cierto dato, debemos llamar a un montacargas para que vaya a buscar la caja donde se encuentra ese dato y la traiga al mostrador para su inspección. Ese mostrador solo puede acomodar un par de cajas a la vez, por lo que no puedes mantener mucho dato disponible.
El montacargas tarda mucho en ir al fondo del almacén y traernos una caja. Si los datos que quieres de esas cajas están dispersos, eso significará pedir al montacargas que haga muchos viajes. La mayor parte del tiempo estarás esperando en el mostrador, sin hacer nada, a que llegue la próxima caja que solicitaste.
Puedes evitar gran parte de ese tiempo perdido si apilas las cajas de forma inteligente y empaquetas los datos para que las cosas que probablemente necesites al mismo tiempo estén mayormente agrupadas. Si agrupas los datos inteligentemente, a menudo descubrirás que lo siguiente que necesitas ya está en una de las cajas del mostrador. Podrás empezar a trabajar de inmediato sin molestar al operador del montacargas.
Por ejemplo, si tienes una escena que necesita obtener el estado de una puerta para comprobar si está abierta o cerrada, el hardware no solo está recuperando el valor del booleano particular que describe el estado de esa puerta, está recuperando muchos otros datos que pueden o no ser relevantes.
Supongamos que tu escena tiene un sistema que necesita actualizar el estado abierto/cerrado de una docena de puertas, una vez por frame. Si tu código está organizado siguiendo un enfoque de Programación Orientada a Objetos, no hay forma de saber cómo pueden estar agrupados los distintos bits de información relevantes. Quizá una "caja" de nuestro almacén contiene el estado "isOpen" de la puerta A y también contiene la textura de la puerta A y el audio que reproduce al abrirse. Podrías tener que hacer un viaje para buscar una nueva "caja" de datos por cada una de las doce puertas. Y, por supuesto, todo este proceso tiene que ocurrir de nuevo en cada frame. Así que son 360 (30 x 12) viajes metafóricos al fondo del almacén por segundo, incluso cuando ninguna de las puertas ha cambiado de estado.
Si tu código sigue un enfoque orientado a datos, por otro lado, es muy probable que esos 12 booleanos estén en la misma caja. Esto se debe a que todos esos booleanos forman parte de un único array que se registró en la memoria de una vez. Nunca estás organizando explícitamente cómo encaja este dato en la memoria, así que en el peor de los casos el array podría terminar dividido en dos cajas. Pero incluso en ese peor escenario, 2 viajes es mucho mejor que 12.
Comprobar el estado de cada puerta en cada frame suena como mucho trabajo, pero en realidad es súper rápido si todos los datos ya están en la caché de memoria. Si tus datos están bien organizados, tu escena puede ejecutar procesos como estos sobre muchas entidades y aun así funcionar muy rápido.
Última actualización