Programación orientada a datos
La Programación Orientada a Datos es un enfoque poderoso para obtener el máximo 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 multiplayer, ya que facilita y acelera el acceso a los datos que necesitan sincronizarse entre jugadores.
La Programación Orientada a Datos te anima a pensar en todo en tu escena como datos que deben copiarse y mutarse a través de los distintos systems. El beneficio principal de este enfoque es optimizar la velocidad con la que los datos pueden leerse desde la memoria, que a menudo es el cuello de botella principal al ejecutar aplicaciones y juegos modernos.
Debido a esta notable mejora en el rendimiento, gran parte de la industria del desarrollo de juegos ha ido adoptando este enfoque en los últimos años.
El SDK de Decentraland ejecuta scenes en JavaScript, y una desventaja de este lenguaje es que no ofrece control sobre la asignación de memoria. El engine que ejecuta Decentraland, sin embargo, usa C#, y se beneficia mucho al seguir principios orientados a datos. El SDK y el engine están constantemente enviando mensajes entre sí. Para hacer esta comunicación lo más eficiente posible, tiene sentido mantener las estructuras de datos en ambos lados lo más parecidas 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 construcciones del mundo real: objetos. Cada uno de estos objetos puede contener tanto datos como funcionalidad. Las aplicaciones que usan este enfoque a menudo son fáciles de planificar conceptualmente, pero también más ineficientes de ejecutar.
En la Programación Orientada a Datos, los datos no se estructuran en torno a objetos; se estructuran para optimizar su facilidad de acceso. Las construcciones del mundo real que representan los datos no influyen 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 component forma parte de una colección estructurada de datos. Los components pertenecen a una entity por referencia, pero los datos no se estructuran alrededor de la entity; los datos se estructuran como una colección de components similares. Por ejemplo, todos Transform components en una escena son iguales. Uno de estos transforms podría pertenecer al modelo de tu edificio principal, otro a un vaso que es hijo de una mesa. Los systems en la escena entonces procesan la lista de Transform components uno por uno, sin hacer distinciones. Todos los Transform components tienen los mismos campos y sufren 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 component "isOpen" que contiene un valor booleano. Si "isOpen" es true la puerta debería estar abierta; si es false la puerta debería estar cerrada. Si un jugador hace clic en una puerta, debería alternar el estado, y otros jugadores también deberían verla alternar. Mientras tu escena procesa un cambio en el estado de una puerta y lo sincroniza con otros jugadores, la escena realmente no se preocupa por lo que "isOpen" representa. Todo el conjunto de components es solo una colección de booleanos que necesitan sincronizarse con otros jugadores. Un sistema separado en tu escena puede entonces encargarse de emparejar 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 pueden necesitar algo de tiempo para familiarizarse con él; te animamos a que explores y juegues con ejemplos para obtener una mejor sensación.
Por qué funciona
Para entender por qué la programación orientada a datos hace una gran diferencia, necesitamos echar un vistazo al hardware.
Cuando el procesador necesita obtener datos de la memoria, trae un fragmento completo de datos a la caché, incluidos datos que simplemente están escritos en la memoria junto al 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á tu código.
Imagina que la memoria de la máquina es un almacén literal, donde los datos se guardan en muchas cajas apiladas. Cada vez que necesitamos un cierto dato, tenemos que pedir a una carretilla elevadora que vaya a buscar la caja donde se encuentra ese dato y la traiga al mostrador para inspección. Ese mostrador solo puede acomodar un par de cajas a la vez, así que no puedes mantener muchos datos cerca.
La carretilla tarda mucho en ir hasta el fondo del almacén y traernos una caja. Si los datos que quieres de esas cajas están dispersos, eso significará pedirle a la carretilla que haga muchos viajes. La mayoría del tiempo, estarás esperando junto al mostrador a que llegue la siguiente caja que solicitaste.
Puedes evitar gran parte de ese tiempo perdido si apilas esas cajas inteligentemente y organizas los datos de modo que las cosas que probablemente necesites al mismo tiempo estén mayormente agrupadas. Si agrupas los datos de forma inteligente, a menudo descubrirás que lo siguiente que necesitas ya está en una de las cajas en el mostrador. Podrás pasar directamente a ello sin molestar al operador de la carretilla.
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á obteniendo el valor del booleano particular que describe el estado de esa puerta, sino que está trayendo un montón de otros datos que pueden o no ser relevantes.
Supongamos que tu escena tiene un system 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 diferentes fragmentos relevantes de información. Tal vez 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 traer una nueva "caja" de datos por cada una de las doce puertas. Y, por supuesto, todo este proceso debe repetirse 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 es porque 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 encajan estos datos 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 son mucho mejor que 12.
Comprobar el estado de cada puerta en cada frame parece 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 ordenados cuidadosamente, tu escena puede ejecutar procesos como estos sobre muchas entities y aun así funcionar realmente rápido.
Última actualización