Advertisement
RazFire

JPSSOCS

Oct 25th, 2019
302
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.87 KB | None | 0 0
  1. -------------------------------
  2. Jump Point: Camino al OCS
  3. -------------------------------
  4.  
  5. Hola, mi nombre es Christopher Bolte, programador principal de motores en Cloud Imperium. Como alguien involucrado en casi todos los pasos del desarrollo de "Object-Container-Streaming", me gustaría, en nombre de todo el equipo, dar una visión general sobre los desafíos técnicos en los que trabajamos a lo largo de los años para ofrecer esta tecnología.
  6. En este artículo, cubriré primero qué es Object-Container-Streaming. Posteriormente, se explican los límites tecnológicos que deben superarse, seguidos de un breve vistazo a cómo se desarrollan características tan grandes y complejas.
  7. Después de eso, el artículo se centrará en las partes individuales y los hitos ya alcanzados que conducen a Object-Container-Streaming.
  8.  
  9. ¿QUÉ ES "SERVER SIDE OBJECT CONTAINER STREAMING"?
  10. --------------------------------------------------
  11. Antes de entrar en detalles técnicos, debemos entender qué es lo que Object-Container-Streaming debe proporcionar al jugador.
  12. En resumen, Object-Container-Streaming es el término general para todas la tecnología que hace posible un vasto universo perfecto, por
  13. que podemos proporcionar un mundo virtual extremadamente grande a través del cuallos jugadores pueden moverse sin ver una pantalla de carga.
  14.  
  15. LÍMITES TECNOLÓGICOS QUE EL OBJECT CONTAINER STREAMING DEBE SUPERAR
  16. -------------------------------------------------------------------
  17. Pero, ¿qué implicaciones tiene ese objetivo para la tecnología del motor?. Ante todo, un videojuego debe actualizar su pantalla al menos 30 veces por segundo para dar la impresión de una experiencia fluida.
  18. Para lograr 30 cuadros por segundo (FPS), el juego debe realizar todos los cálculos necesarios para todo el marco dentro de 0.033
  19. segundos (33 milisegundos). Si no se queda dentro de este límite de tiempo "tartamudeara", ya que la impresión de movimiento fluido se rompe cuando el marco no se actualiza con suficiente frecuencia y el cerebro humano comienza a reconocer imágenes individuales.
  20.  
  21. VELOCIDAD LIMITADA DE TRANSFERENCIA DE RAM Y ARCHIVO
  22. ----------------------------------------------------
  23. Además, una PC tiene una cantidad limitada de "memoria de acceso aleatorio"(RAM). RAM es una memoria muy rápida, que puede archivar 20,000 o más megabyte por segundo (MB/s) en velocidad de transferencia. Para garantizar una experiencia sin "tartamudeos" es necesario que todo el acceso a datos ocurra dentro de la RAM, y no en el disco duro ya que es mucho más lento.
  24.  
  25. Dado que la RAM es limitada y las magnitudes son más pequeñas que todos los datos del juego que queremos proporcionar, debemos , mientras el juego se está ejecutando, cargar datos desde el disco duro y reemplazar otros datos que ya no son necesarios. Y eso
  26. requiere tiempo. La transferencia de solo 10 MB con un disco duro rápido de 500 MB/s requiere 0.2 segundos para cargar los datos, lo que se traduce en ~ 6 cuadros con 30 FPS, lo que resulta en un "tartamudeo" notable.
  27.  
  28. Por lo tanto, todas las transferencias de archivos deben realizarse de manera que no afecten la simulación del juego mientras se juega, para garantizar una fluida experiencia mientras viajas por el universo perfecto.
  29.  
  30. EJECUCIÓN DEL PROGRAMA MÚLTIHILOS
  31. ---------------------------------
  32. Esto nos lleva al multihilo. Cada unidad central de procesamiento (CPU) en los últimos 10 años tienen múltiples núcleos de CPU. Cada núcleo de CPU puede ejecutar y programar las instrucciones independientemente una de la otra. Esto (y otras técnicas que omito para facilitar la comprensión) permite que las computadoras hagan más de una cosa al mismo tiempo. En el caso de Object-Container-Streaming, podemos cargar recursos en paralelo a la lógica del juego, por lo tanto no afecta la velocidad de actualización de fotogramas del juego.
  33.  
  34. Cargar un recurso es más que solo el tiempo de transferencia de archivos. Después de cargar desde el disco duro, los datos del recurso deben inicializarse, algo así puede llevar tiempo y aumenta el tiempo hasta que se pueda usar el recurso. Un problema similar surge cuando descargamos el recurso, ya que entonces necesitamos desinicializar el recurso, que también lleva un tiempo.
  35.  
  36. Pero el peor problema es la comunicación. Un motor de juego tiene varios administradores centrales para ciertos tipos de recursos (como texturas o personajes). Esos gerentes normalmente mantienen una lista de instancias cargadas de su tipo de recurso y proporcionan operaciones sobre ellos. Un ejemplo simplificado sería un administrador de personajes, que mantiene una lista de todos los personajes cargados. Cada cuadro, la simulación del juego le pregunta al administrador de personajes actualizar todos los personajes cargados. Y aquí se vuelve complicado.
  37.  
  38. Si la carga de recursos de nuestro personaje finaliza en paralelo a la actualización del personaje, hay que colocar el personaje recién cargado en la lista del administrador de personajes, para que el personaje reciba actualizaciones en el futuro.
  39. Poner el personaje en la lista lo modifica. Si la simulación del juego está usando esta lista al mismo tiempo para actualizar los personajes, esto puede resultar en una corrupción de datos y bloquear el juego.
  40.  
  41. Por lo tanto, para una correcta ejecución del programa, solo una ruta de ejecución debe acceder a dicho administrador a la vez, por lo tanto, una exclusión mutua debe ser aplicada; Si la simulación del juego está utilizando el administrador, la carga de recursos debe esperar, y viceversa. Y esperar lleva tiempo, que no tenemos debido al límite de tiempo de 0.033 segundos, lo que nos lleva a la principal complejidad de la programación multiproceso: encontrar la cantidad mínima de comunicación necesaria mientras el programa aún se ejecuta correctamente.
  42.  
  43. Si esto no se hace correctamente, la simulación del juego podría esperar la transferencia completa del archivo, lo que nuevamente resultaría en un "tartamudeo" no deseado. O el juego simplemente se bloquea de vez en cuando debido a la corrupción de datos.
  44.  
  45. Por lo tanto, toda la tecnología debe diseñarse en torno a la carga, inicialización y destrucción de recursos concurrentes minimizando la comunicación para no afectar la simulación del juego.
  46.  
  47. DESARROLLO DEL "SERVER SIDE OBJECT CONTAINER STREAMING" EN UN PRODUCTO VIVO
  48. ----------------------------------------------------------------------------------
  49. Object-Container-Streaming ha estado en desarrollo durante varios años.
  50. Algunos pasos eran muy visibles para los jugadores, como el Network-Bind-Culling, otros, como eliminar LUA y reelaborar el código heredado, no tanto.
  51.  
  52. Uno de los principales obstáculos en el desarrollo de todos estos es el hecho de que somos un producto vivo. Tenemos lanzamientos regulares (o no tan regulares en el pasado, algo en lo que mejoramos). También hacemos un desarrollo constante de características para construir el juego. Debido al trabajo de características y lanzamientos, no podemos tener una versión del juego que no funcione durante varios meses. Al mismo tiempo, para Object-Container-Streaming, estamos cambiando las leyes fundamentales contra las cuales se desarrollan esas características del juego.
  53.  
  54. Por lo tanto, para cada paso que damos, debemos observar el impacto en el cronograma y qué función debe reelaborarse. En base a esto, nosotros tenemos que decidir o idear formas de introducir los cambios de Object-Container-Streaming de una manera que permita a los equipos de juego gradualmente adaptarse a esas nuevas leyes mientras se mantiene el juego funcionando.
  55.  
  56. CONCEPTO DE CONTENEDOR DE OBJETO
  57. --------------------------------
  58. Debían implementarse varios pasos antes de que incluso pudiéramos considerar desarrollar Object-Container-Streaming. Hace aproximadamente cinco años, nosotros comenzamos a presentar el concepto de contenedor de objetos. Antes de eso, nuestro motor solo admitía ‘niveles’. Un nivel es una lista de objetos del juego. Un objeto del juego en sí mismo es una colección de recursos que normalmente se conoce como una "entidad". Antes de poder jugar un nivel, todas las entidades deben cargarse en la RAM desde el disco duro e inicializarse, lo que normalmente ocurre detrás de una pantalla de carga.
  59.  
  60. Los contenedores de objetos son un bloque de construcción de nivel. Su concepto es que, en lugar de desarrollar un gran nivel, los creadores de contenido desarrollan Una pequeña sección. El nivel final (o universo en nuestro caso) se compone de muchos Contenedores de Objetos diferentes. Este concepto nos permite dividir el mundo en muchos bloques de construcción más pequeños. Y construir la transmisión además de eso nos permite cargar y descargar esos bloques de construcción en tiempo de ejecución, asegurando que encajamos en el presupuesto de RAM que proporciona el universo perfecto. Si bien los Contenedores de objetos no proporcionaron un impacto notable a los jugadores (ya que durante mucho tiempo, simplemente cargamos todos los contenedores de objetos durante la carga de nivel), fueron un peldaño importante.
  61.  
  62. LUA Y COMPONENTES
  63. -----------------
  64. Otros dos requisitos importantes se iniciaron hace aproximadamente dos años y medio.
  65. Primero, comenzamos a trabajar para eliminar LUA del código del juego. LUA es un lenguaje de script que se utilizó mucho para todo tipo de lógica de entidad dentro del motor. El problema con LUA era que era imposible hacer que los subprocesos múltiples fueran seguros. En otras palabras, mientras usáramos LUA, no podríamos ejecutar la carga de recursos en paralelo a la simulación del juego sin introducir tiempos de espera muy largos debido a la exclusión mutua requerida. Por lo tanto, todo el código LUA fue reemplazado por C++, lo que nos da el control suficiente para evitar tales tiempos de espera.
  66.  
  67. Al mismo tiempo, comenzamos a convertir nuestras entidades de objetos monolíticos más grandes en componentes. Un componente de entidad representa parte del comportamiento específico del juego. Con los componentes, el comportamiento de una entidad se define por los tipos de componentes que tiene. Sin componentes, todo tipo de lógica diferente tiende a entrelazarse en un bloque lógico central monolítico y muy complejo.
  68.  
  69. El uso de componentes nos brinda varias mejoras en el lado de la implementación. Como son partes más pequeñas, es mucho más simple
  70. para que se comuniquen eficientemente con la simulación del juego mientras los cargamos simultáneamente. Además, dividieron la lógica del juego monolítico en partes más manejables, que desempeñaron un papel fundamental al permitir un despliegue parcial de la inicialización de entidades concurrentes.
  71.  
  72. VARIABLES EN SERIE (Serialized-Variables)
  73. -----------------------------------------
  74. Las entidades dentro de una simulación de juego tienen un cierto estado. Algunas situaciones, como la comunicación de red, requieren que almacenemos ese estado en un formato que podamos transferir y luego restaurar el mismo estado en una máquina diferente. Otras situaciones, como las partidas grabadas de Squadron 42, requieren algo similar, excepto que almacenamos los datos en el disco. En términos de programador, el proceso de convertir un estado en una representación que puede almacenarse en un disco/transferirse a la red se denomina "serialización". Por lo tanto, el nombre Serialized-Variables. Este es un concepto en el que tomamos las partes de una entidad y la colocamos en un objeto contenedor especial. Este objeto contenedor proporciona formas de serializar el estado de la entidad. Al hacerlo de esta manera, podemos tener la escritura del código del juego en un estilo uniforme, independientemente de cómo deseamos transferir los datos serializados más adelante (también importante para Meshing del servidor, que se explicará más adelante).
  75.  
  76. CARGA DE RECURSOS DEL MOTOR MULTITHILO
  77. --------------------------------------
  78. Además de los recursos relacionados con el juego (componentes de creación de datos), el motor también admite muchos recursos compartidos, que son compartidos por diferentes componentes e incluso diferentes contenedores de objetos. Esos recursos incluyen objetos como texturas o mallas. El motor ya admite transmisión de malla y textura, que es el proceso de cargar y descargar los datos de la GPU para renderizar mientras se juega el juego.
  79. Pero necesitábamos abordar esto en un nivel superior para Object-Container-Streaming. En el contexto Object-Container-Streaming, también debemos poder cargar el objeto que representa los datos de la GPU de forma paralela y organizada, para que todos los Object-Containers cargados en paralelo puedan compartir los mismos recursos del motor. Todo esto fue un trabajo que se realizó en segundo plano hace unos dos años y medio.
  80.  
  81. TODO LLEGANDO A LA VEZ
  82. ----------------------
  83. El trabajo preliminar anterior requirió mucho trabajo, y la mayor parte no era realmente visible para los jugadores, ya que hacer esos cambios sin un efecto visible era nuestro objetivo (verificar que lo hayamos cambiado correctamente). Pero todo esto fue un trabajo de preparación muy necesario, avanzando la tecnología hacia los primeros efectos visibles cuando se lanzó Network-Bind-Culling al público en Alpha 3.3.
  84.  
  85. CARGA E INICIALIZACIÓN DE COMPONENTES EN PARALELO
  86. -------------------------------------------------
  87. Con todo el trabajo preliminar hecho tenemos:
  88. • Niveles divididos en bloques de construcción (el Contenedor de Objetos)
  89. • Entidades (que constituyen una gran parte del Contenedor de Objetos) implementado sin LUA y como componentes
  90. • Todo el estado de tiempo de ejecución de la entidad organizado en Variables serializadas para facilitar la comunicación del estado a través de la red
  91. • Creación de subprocesos múltiples de recursos del lado del motor (texturas, mallas, personajes, etc.)
  92.  
  93. Los siguientes pasos se basan en esa base. Nuestro primer objetivo era cargar entidades en paralelo a la simulación del juego. Este primer paso no incluía ninguna lógica de alto nivel sobre qué cargar o descargar o cuándo, por lo que era un sistema de transmisión muy tonto. Sin embargo, ya se redujo la "tartamudez" en tiempo de ejecución (por ejemplo, el despegue de una nave ya no requería que ejecutaramos el código de inicialización de las entidades de la nave como parte de la simulación del juego).
  94.  
  95. En ese momento, teníamos aproximadamente 300 a 400 tipos de componentes diferentes. Si hubiéramos intentado ejecutar todos esos en paralelo desde el principio, nos habríamos ahogado en errores. Por lo tanto, tuvimos que desarrollar un sistema que nos permitiera ejecutar gradualmente más y más tipos de componentes en paralelo a la simulación del juego. Cuantos más tipos de componentes hicimos seguros para la carga paralela, menos "tartamudearía" la simulación del juego.
  96.  
  97. La solución que hemos elegido es utilizar fibras. Una Fibra es un estado de ejecución donde podemos controlar exactamente cuándo y dónde queremos ejecutar(carga paralela o simulación de juego). Si bien las fibras pueden ser muy difíciles de usar, proporcionaron exactamente el control que necesitábamos. Con Fibras, podríamos mover la lógica para la carga de recursos entre subprocesos de carga concurrentes y subprocesos de simulación de juegos, dependiendo de si el tipo de componente admite carga paralela o no. Y con eso, fue posible ajustar paso a paso más y más código para que se ejecute en paralelo al tiempo que se garantiza que todos usen (y por lo tanto prueben) el código ya paralelo. Esos cambios se implementaron parcialmente con Alpha 3.2, donde redujeron el "tartamudeo" causado por la carga de recursos de la entidad en tiempo de ejecución, como el despegue de las naves.
  98.  
  99. PREPARACIÓN PARA EL NETWORK BIND CULLING
  100. ----------------------------------------
  101. Network-Bind-Culling es cómo hacemos referencia a Object-Container-Streaming en el cliente. En otras palabras, es el proceso de decidir qué entidades cargar/descargar en cualquier cliente. Decidimos centrarnos solo en el cliente primero, ya que esto nos permitió proporcionar varias mejoras a los jugadores, desarrollar toda la tecnología de manera más incremental y permitirnos resolver ciertos problemas más adelante. Pero incluso solo enfocándonos en el cliente, tuvimos que hacer mucho trabajo de preparación.
  102.  
  103. ACTUALIZADOR DE COMPONENTES
  104. ----------------------------
  105. Queremos decidir qué entidades cargar o descargar en un cliente en función de la distancia y la visibilidad de esa entidad con respecto a un cliente. Por lo tanto, necesitamos esta información. Afortunadamente, ya habíamos desarrollado dicha tecnología para Alpha 3.0. Entity-Component-Update-Scheduler es un sistema diseñado para controlar la actualización de los componentes de la entidad, en función de cómo se ubican espacialmente en relación con el jugador. Al hacer esto, podemos omitir las actualizaciones de componentes que están demasiado lejos (otro beneficio del diseño del componente, podemos hacer actualización por componente). Entonces era natural que Entity-Component-Update-Scheduler proporcionara la misma información para NetworkBind-Culling.
  106.  
  107. JERARQUIA DE PROPIEDAD DE ENTIDAD Y AGREGADO DE ENTIDAD()
  108. -------------------------------------------------------
  109. Otro tema importante a abordar fueron los grupos de entidades dinámicas. Los ObjectContainers dividen la geometría del nivel estático y los objetos en bloques de construcción en el momento del diseño del nivel. Pero nuestro juego también consta de entidades dinámicas, como jugadores, naves(que pueden construirse a partir de múltiples Contenedores de Objetos) o máquinas expendedoras, básicamente, todo lo que puede moverse en el mundo virtual. Además, esas entidades dinámicas pueden combinarse en grupos. Por ejemplo, un jugador está recogiendo un objeto, o una nave está estacionada en otra nave. Y eso tiene implicaciones para la transmisión.
  110.  
  111. En el caso de naves en naves, no queremos transmitir la nave interna antes que la externa, ya que el estado de la interno está parcialmente definido por la nave externa. Por lo tanto, tenemos que rastrear esos grupos dinámicos y cuándo se forman o se disuelven. Este es un concepto que llamamos Jerarquía de propiedad de entidad. En esta jerarquía, hacemos un seguimiento de las entidades que están relacionadas entre sí. Si están relacionados, los tratamos como un grupo: el denominado agregado de entidad.
  112. Partiendo de eso, Network-Bind-Culling funciona en unidades de agregado de entidad, utilizando la información del actualizador de componentes para decidir qué agregado de entidad cargar o descargar en cada cliente.
  113.  
  114. ENTITY-SPAWN-BATCHES Y ENTITY-SNAPSHOTS
  115. ----------------------------------------
  116. Después de rastrear correctamente los agregados de entidad, aún teníamos que desarrollar una forma de generar eficientemente la gran cantidad de entidades dentro de un agregado de manera eficiente en un cliente. Y debemos asegurarnos de que las entidades se generen de manera consistente en los clientes para que terminemos con los mismos agregados de entidad y la misma jerarquía de entidades que en el servidor. Por ejemplo, para asegurarse de que un vehículo se genera con sus armas/propulsores ya conectados, en lugar de hacerlo poco a poco con el tiempo. Para lograr esto, agrupamos las entidades en Entity-Spawn-Batches, que representan un conjunto de entidades que deben generarse juntas y solo activarse cuando se generen todas.
  117.  
  118. Para cada entidad que generamos en una máquina cliente, enviamos una EntitySnapshot desde el servidor que contiene el estado actual de la entidad. Estos son simplemente los valores de las Variables Serializadas que pertenecen a la entidad.
  119. Las ENTITY-SNAPSHOTS también se usan para serializar el estado de la entidad a la persistencia, o para guardar partidas del Squadron42. Debido a que generar entidades de forma asíncrona en el cliente lleva tiempo, un problema que enfrentamos fue que cuando el cliente completó un lote de generación, la ENTITY-SNAPSHOTS podría estar desactualizada, ya que el estado de esas entidades en el servidor puede haber cambiado mientras el cliente los estaba generando.
  120.  
  121. Para solucionar esto, el servidor debe enviar un segundo conjunto de ENTITY-SNAPSHOTS una vez que el cliente ha generado todas las entidades en un lote de generación. Cuando el cliente recibe estas ENTITY-SNAPSHOTS secundarias, puede realizar pequeñas reparaciones en el estado de las entidades y finalmente agregarlas a la simulación del cliente, finalizando la generación de entidades impulsadas por la red.
  122.  
  123. SERIALIZED-VARIABLE-CULLING
  124. ---------------------------
  125. Al final del desarrollo de Alpha 3.0, ya habíamos desarrollado varias partes necesarias, pero no todas se hicieron, e introdujimos planetas y muchas ubicaciones importantes como Levski y Grim HEX. Al mismo tiempo, solo tuvimos la eliminación de actualizaciones del Entity-Component-Scheduler. Si bien ese sistema ayudó con el rendimiento del servidor, los clientes aún tenían que pagar un costo innecesario; todavía no teníamos un sistema para decidir qué cliente requiere qué información. Además de esto, cada componente tuvo que ejecutar su actualización cuando recibió nueva información de red, lo que resultó en un costo de rendimiento medible. Por ejemplo, si en ese momento el Jugador A estaba en Levski, y el Jugador B en Olisar, el Jugador B actualizaría todas sus versiones locales de los NPC en Levski debido a recibir actualizaciones de red para ellos ya que el Jugador A estaba cerca de ellos en el servidor. Pero teníamos todas las herramientas listas para proporcionar la información requerida. En base a eso, decidimos construir un sistema que solo enviaría actualizaciones de red a los clientes si ese cliente está cerca de la entidad agregada actualizada. En cuanto al ejemplo anterior, el Jugador B ya no actualizaría sus NPC en Levski, ya que el servidor sabría que el Jugador B no está cerca.
  126. Implementar esto, bajo el nombre Serialized-Variable-Culling, nos dio una mejora notable en el rendimiento de los clientes. Además, fue la primera prueba de la vida real de ejecutar la simulación de juego de nuestro cliente con solo información parcial de todo el universo. Queríamos enviar esta característica con Alpha 3.0, pero desafiando el heroico esfuerzo del equipo de la red, tuvimos que dejar que esta característica se deslizara en Alpha 3.1.
  127.  
  128. HABILITANDO NETWORK-BIND-CULLING
  129. --------------------------------
  130. Varios años después de que se emprendieron los primeros pasos y múltiples versiones del juego, podríamos cosechar los resultados de todo el trabajo.
  131. Hasta ahora, hemos trabajado:
  132. • Niveles divididos en bloques de construcción (el Contenedor de Objetos)
  133. • Entidades (que constituyen una gran parte de Object-Container) implementadas sin LUA y como componentes
  134. • Todo el estado de tiempo de ejecución de la entidad organizado en Variables serializadas para facilitar comunicación del estado a través de la red
  135. • Creación multiproceso de recursos del lado del motor (texturas, mallas,personajes, etc.)
  136. • Creación multiproceso de la mayoría de los tipos de componentes.
  137. • Información de proximidad entre todas las entidades y jugadores proporcionada por el Actualizador de componentes
  138. • La capacidad de rastrear grupos de entidades dinámicas (Agregados de entidad)
  139. • Formas eficientes de generar agregados de entidad en clientes
  140. • Primer uso en el mundo real de la simulación en el cliente con conocimiento parcial del mundo a través de la eliminación de variables en serie.
  141.  
  142. Utilizando toda la preparación anterior, podríamos desarrollar Network-Bind-Culling.
  143. En ese sistema, en lugar de omitir actualizaciones de red mientras teniamos todas las entidades presentes en cada cliente (como lo hicimos para Serialized-Variable-Culling), nosotros, impulsados ​​por el servidor, cargaremos y descargaremos entidades en el cliente. En otras palabras, Network-Bind-Culling cambia las reglas para que cada cliente solo tenga una vista de un mundo de juego virtual mucho más grande, mientras que con Serialized-Variable-Culling, cada cliente tiene la vista completa pero solo realiza actualizaciones parciales de su mundo virtual local.
  144.  
  145. Esto nos da varias ventajas, la más notable es el rendimiento. Como cada cliente tiene un conjunto de datos mucho más pequeño, cada operación local cuyo costo se ve afectado por el recuento de entidades se vuelve más rápido. También ayuda con otros costos de tiempo de ejecución que no pueden ser eliminados por Serialized-Variable-Culling. Otro beneficio es que el cliente usa menos memoria, ya que solo tiene que mantener lo que ve en RAM y ya no todo el universo. Para muchos clientes, hubo una mejora de rendimiento muy grande cuando lanzamos Network-Bind-Culling al público con Alpha 3.3.
  146.  
  147. Pero el sistema tiene otra ventaja aún más importante: desacoplar al cliente del contenido del universo. Como cada cliente solo tiene
  148. cargado el pequeño conjunto de entidades requerido para su vista local, los clientes ya no se ven afectados por la cantidad de entidades que colocamos en el universo. El rendimiento del cliente ahora solo se ve afectado por la ubicación y el entorno real del cliente (por ejemplo, espacio vacío frente a ciudad llena de gente). Y esto nos da la libertad de colocar todo el contenido que queramos en nuestro mundo virtual sin tener que preocuparnos por los clientes. Excepto que, en este momento, el servidor todavía tiene todo cargado y tiene que pagar el costo de rendimiento. Y aunque tener un mal rendimiento del servidor no afecta la velocidad de fotogramas del cliente, causa sacudidas cuando los objetos se mueven (ya que es muy probable que el cliente y el servidor no estén de acuerdo con la posición de las entidades). Lo cual también es un problema grave, pero algo que queremos un Object-Container-Streaming a nivel de servidor.
  149.  
  150. CREANDO EL SERVER SIDE OBJECT CONTAINER STREAMING
  151. -------------------------------------------------
  152. Con Network-Bind-Culling implementado, el enfoque cambió a implementar Object-Container-Streaming en el servidor.
  153. El concepto básico es que si ningún jugador está cerca de un objeto, podemos "congelar" el estado de ese objeto. Y en lugar de mantener la entidad congelada en la memoria (incurriendo en un costo), podemos serializar (usando Variables Serializadas) su estado y almacenar el estado serializado en una base de datos. Mientras un cliente se está moviendo por el mundo virtual, el servidor actualiza su vista en la base de datos para restaurar las entidades ahora cercanas, así como liberar y almacenar entidades que ya no son necesarias.
  154. Aquí, Server-Object-Container-Streaming y Network-Bind-Culling van de la mano. La base de datos contiene todo el universo en estado congelado.
  155.  
  156. El servidor tiene solo un pequeño subconjunto del universo cargado; en otras palabras, tiene una vista en la base de datos. Luego, el cliente también solo tiene un subconjunto de todas las entidades cargadas en el servidor, que tiene una visión del mundo virtual del servidor. Con este modelo, mantenemos todo lejos de los jugadores en un estado congelado para que esas entidades no afecten el rendimiento o la memoria. También hay algunas excepciones a este modelo, como la simulación del universo de Subsumption, pero están fuera del alcance de este artículo.
  157.  
  158. Cuando se finalice Server-Object-Container-Streaming, tendremos una solución tecnológica para escalar el contenido en el servidor. Esto significa que podemos colocar mucho más contenido en el mundo virtual, mientras que el rendimiento del servidor solo se ve afectado por las áreas donde todos los jugadores están activos (que es un conjunto mucho más pequeño que todo el universo).
  159.  
  160. Pero Server-Object-Container-Streaming también viene con varios problemas adicionales, en los cuales los equipos involucrados están trabajando para entregarlo lo antes posible.
  161.  
  162. ESTADO DEFINIDO
  163. ---------------
  164. Con Network-Bind-Culling, siempre tuvimos una versión autorizada de cada entidad cargada en el servidor. Esto nos permitió algunas soluciones "perezosas", ya que podríamos salir con problemas más pequeños, ya que siempre los corregiríamos después de que la entidad se carga en el cliente.
  165.  
  166. Un ejemplo de esto es teletransportar al jugador. Un teletransporte es un movimiento instantáneo de un lugar a otro en el universo. Este es el peor caso para la transmisión, pero lo tenemos en algunas situaciones, como la reaparición del jugador o cuando utilizamos herramientas de desarrollo. Después de un teletransporte, se debe cargar todo alrededor del jugador. No teníamos ninguna prioridad para el orden en que generamos esas entidades. Esto dio como resultado que los NPC cayeran a través del piso aún no cargado. Con Network-Bind-Culling esto estuvo bien, ya que podríamos depender de que el servidor nos envíe la posición NPC correcta (ya que el piso existe en el servidor). Con Server-Object-Container-Streaming no podemos hacer eso. Como el servidor tiene autoridad, si el NPC se genera antes del piso, el NPC desaparecerá, lo que dará como resultado ciudades aburridas y vacías.
  167.  
  168. Por lo tanto, teníamos que asegurarnos de que siempre engendramos el piso antes que los NPC. Otro problema son los tipos de componentes que solo ejecutamos en el servidor. Anteriormente, no los descargábamos, así que debemos asegurarnos de que restablezcan su estado correctamente a partir de datos serializados.
  169.  
  170. Esos y todos los otros tipos de pequeños problemas son las cosas que tenemos que solucionar antes de que podamos enviar Server-Object-Container-Streaming.
  171.  
  172. ENTITY-STREAMING-MANAGER / STARHASH / STARHASH-RADIXTREE
  173. --------------------------------------------------------
  174. Otro problema surge cuando descargamos todas las entidades y las almacenamos en una base de datos. Necesitamos una forma de realizar búsquedas espaciales en esas entidades para asegurarnos de que solo cargamos las que están cerca de cualquier cliente. Por lo tanto, fue necesario desarrollar un esquema de búsqueda que nos permita almacenar una gran cantidad de entidades con suficiente información espacial. Para esto, adaptamos el algoritmo Geohash (utilizado por todas las aplicaciones de mapas para encontrar lugares alrededor de los usuarios) para nuestras necesidades haciéndolo más grande (nuestro mundo virtual con una solución de 2m necesita más datos que la tierra real) y 3D. Lo llamamos StarHash.
  175.  
  176. Este StarHash nos proporciona una herramienta eficiente para almacenar nuestras entidades de una manera que permite búsquedas eficientes para todas las entidades en un área de espacio utilizando una estructura de datos llamada RadixTree. Entity-Streaming-Manager es entonces la lógica que impulsa las consultas entre StarHash y RadixTree para activar la carga y descarga de entidades en el servidor, en función de las posiciones de todos los clientes conectados.
  177.  
  178. ID DE UBICACIÓN
  179. ---------------
  180. El último problema importante que tuvimos que abordar fue la aparición de lugares. Para generar un jugador, la lógica del juego requiere un SpawnPoint, que también es una entidad. Pero solo cargamos entidades si un jugador está cerca, por lo que necesitamos generar un jugador para cargar el SpawnPoint para generar el jugador. Dado que tampoco es posible excluir SpawnPoints de la transmisión (ya que son parte de otras construcciones más grandes como las estaciones espaciales), tuvimos que encontrar otra solución.
  181. Aquí decidimos un proceso de generación de dos fases. Cuando un jugador se conecta, primero encontramos su ID de ubicación de generación. Una ubicación es un concepto de nivel superior de un punto en el espacio. Entonces, primero cargamos todas las entidades en este punto, que también cargará el SpawnPoint requerido. Luego podemos generar de forma segura al jugador en su SpawnPoint de destino. Por último, la lógica de transmisión cambiará al jugador desde la ID de ubicación para que la vista de la base de datos de ese jugador se mueva con ellos.
  182.  
  183. TRABAJO ACTUALMENTE EN CURSO
  184. ----------------------------
  185. Al momento de escribir este artículo, no todo el trabajo para Server-Object-Container-Streaming está terminado. Hemos implementado el Entity-Streaming-Manager, así como la lógica StarHash. El trabajo de Location-ID está casi terminado y debería finalizar pronto. Debido a eso, Server-Object-Container-Streaming ya se puede utilizar hasta cierto punto. Y hacer eso nos muestra todos los problemas que tenemos con la falta del Estado Definido y todas las áreas que todavía tenemos que arreglar. La mayoría de las áreas principales donde tenemos tales problemas son conocidas y se están trabajando activamente.
  186.  
  187. PRÓXIMOS PASOS
  188. --------------
  189. El trabajo no terminará cuando se entregue la primera iteración de Server-Object-Container-Streaming. Si bien la primera versión debería proporcionarnos una mejor escala de contenido en el servidor, todavía tendremos varias áreas para trabajar.
  190.  
  191. PERSISTENCIA DE LA SESIÓN
  192. -------------------------
  193. Server-Object-Container-Streaming no afecta cómo y qué tipo de datos serializamos para persistencia. Almacenaremos la entidad en estado congelado en una base de datos en proceso. Esto implica que el estado se pierde cuando el servidor falla o se reinicia (además del estado en el que ya persistimos). Por lo tanto, los siguientes pasos son desarrollar una capa de acceso de red eficiente para permitir el almacenamiento de la entidad en una base de datos en una máquina diferente. Cuando implementamos ese paso, el estado del objeto persistirá durante los reinicios y fallas del servidor (hasta que eliminemos la base de datos de persistencia), moviendo todo el juego hacia una experiencia persistente.
  194.  
  195. SERVER MESHING
  196. --------------
  197. Con Server-Object-Container-Streaming, un único servidor es responsable de administrar las vistas de la base de datos de todos los clientes. Por lo tanto, aunque Server-Object-Container-Streaming debería mejorar el rendimiento del servidor (ya que cargamos menos entidades), en última instancia no resolverá el problema de más jugadores por servidor.
  198. Aquí es donde entra Server-Meshing. En este concepto, en lugar de tener un solo servidor que administre todas las vistas, distribuiremos las vistas individuales sobre múltiples servidores. Hacer esto reducirá la carga en cada servidor participante. Cuando colocamos esos servidores en diferentes máquinas, obtenemos una forma agradable y práctica de escalar la cantidad de jugadores.
  199.  
  200. Para implementar Server-Meshing, construiremos sobre lo que estamos construyendo en este momento: las entidades se moverán entre los servidores mediante el uso de código de serialización proporcionado por Serialized-Variables, dependiendo del código de Server-Object-Container-Streaming, para garantizar que podamos restaurar esas entidades movidas correctamente en un servidor diferente.
  201.  
  202. SOPORTE DEL EDITOR
  203. ------------------
  204. Más oculto al público pero muy importante es el editor del juego. El editor es una herramienta de motor personalizada que se utiliza para construir nuestros Object-Containers y colocarlos en el universo. También se usa para probar el contenido recientemente desarrollado mientras se trabaja en él, lo cual es extremadamente importante para desarrollar contenido de buena calidad.
  205. Desafortunadamente, el editor en sí aún no es compatible con la transmisión. Por lo tanto, el creador de contenido puede crear y desarrollar contenido, pero sufre largos tiempos de carga y una mala velocidad de fotogramas. Y esto empeorará a medida que agreguemos más contenido al juego. Por lo tanto, un siguiente paso muy importante es hacer que el editor esté en streaming para brindar a los creadores de contenido los mismos beneficios que les dimos a los clientes (a través de Network-Bind-Culling) y al servidor (a través de Server-Object-Container-Streaming). Pero como el editor tiene su propia lógica adicional además de toda la simulación del juego, solo podemos abordar eso después de hacer Server-Object-Container-Streaming.
  206.  
  207. SOPORTE DEL SQUADRON42
  208. ----------------------
  209. El Squadron42 será el trabajo adicional más fácil. En Squadron42, el cliente también será el servidor. Por lo tanto, ejecutaremos el mismo código que en el servidor Star Citizen. De hecho, ya lo hacemos internamente. Y como Squadron 42 y Star Citizen comparten la misma base de código, las correcciones para Server-Object-Container-Streaming para cualquiera de los productos beneficiarán al otro.
  210.  
  211. PALABRAS DE CIERRE
  212. ------------------
  213. Espero que esta introducción haya brindado una explicación útil del viaje de varios años de “Object-Container-Streaming” y una explicación comprensible de todos los desafíos técnicos que tuvimos que enfrentar y superar durante este viaje. Por favor, también perdóname por omitir la mayoría de los detalles técnicos importantes, pero presentar todo eso convertiría este artículo en un libro.
  214. Y creo que es mejor escribir la tecnología que escribir un libro sobre la tecnología que queremos construir.
  215. Gracias por tomarse el tiempo de leer esto.
  216.  
  217. Atentamente, Christopher Bolte
  218. Programador principal de motores, Cloud Imperium Games
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement