¿Cómo mantener una estructura de datos sincronizada a través de una red?

12

Contexto

En el juego en el que estoy trabajando (una especie de aventura gráfica de apuntar y hacer clic), casi todo lo que sucede en el mundo del juego está controlado por un administrador de acciones que está estructurado un poco como:

ingrese la descripción de la imagen aquí

Entonces, por ejemplo, si el resultado de examinar un objeto hace que el personaje diga hola, camine un poco y luego se siente, simplemente engendro el siguiente código:

var actionGroup = actionManager.CreateGroup();
actionGroup.Add(new TalkAction("Guybrush", "Hello there!");
actionGroup.Add(new WalkAction("Guybrush", new Vector2(300, 300));
actionGroup.Add(new SetAnimationAction("Guybrush", "Sit"));

Esto crea un nuevo grupo de acción (una línea completa en la imagen de arriba) y lo agrega al administrador. Todos los grupos se ejecutan en paralelo, pero las acciones dentro de cada grupo se encadenan para que el segundo solo comience después de que termine el primero. Cuando finaliza la última acción en un grupo, el grupo se destruye.

Problema

Ahora necesito replicar esta información en una red, de modo que en una sesión multijugador, todos los jugadores vean lo mismo. Serializar las acciones individuales no es el problema. Pero soy un principiante absoluto cuando se trata de redes y tengo algunas preguntas. Creo que, por simplicidad en esta discusión, podemos abstraer el componente del administrador de acciones para que sea simplemente:

var actionManager = new List<List<string>>();

¿Cómo debo proceder para mantener sincronizados los contenidos de la estructura de datos anterior entre todos los jugadores?

Además de la pregunta central, también tengo algunas otras preocupaciones relacionadas con ella (es decir, todas las posibles implicaciones del mismo problema anterior):

  • Si utilizo una arquitectura de servidor / cliente (con uno de los jugadores actuando como servidor y como cliente), y uno de los clientes ha generado un grupo de acciones, en caso de que las agregue directamente al gerente, o solo envíe una solicitud al servidor, que a su vez ordenará a cada cliente agregar ese grupo ?
  • ¿Qué pasa con las pérdidas de paquetes y similares? El juego es determinista, pero creo que cualquier discrepancia en la secuencia de acciones ejecutadas en un cliente podría conducir a estados inconsistentes del mundo. ¿Cómo me protejo contra ese tipo de problema ?
  • ¿Qué sucede si agrego demasiadas acciones a la vez, eso no causará problemas para la conexión? ¿Alguna forma de aliviar eso?
David Gouveia
fuente
55
Obligatorio "lea esto primero" enlace gafferongames.com/networking-for-game-programmers que no es muy técnico a pesar de tener algún código, pero es extenso y explica sus opciones bastante bien. Además, lo pondrá en igualdad de condiciones con todos los demás que hayan leído ese enlace y ese es un lugar feliz.
Patrick Hughes
@PatrickHughes ¡Gracias por el enlace! Definitivamente lo leeré todo.
David Gouveia
Hay algunos paquetes que manejan este tipo de cosas por usted. No sé cuán eficiente o rápido es, pero NowJS ( nowjs.com ) es un ejemplo interesante. Desde el punto de vista de los desarrolladores, simplemente ha compartido estructuras de datos entre el cliente y el servidor. Cambiar uno, el otro cambia.
Tim Holt

Respuestas:

9

Si utilizo una arquitectura de servidor / cliente (con uno de los jugadores actuando como servidor y como cliente), y uno de los clientes ha generado un grupo de acciones, en caso de que las agregue directamente al gerente, o solo envíe una solicitud al servidor, que a su vez ordenará a cada cliente agregar ese grupo?

Tiene tres opciones en este caso, dos de las cuales ya ha mencionado. La elección de qué opción tomar depende de una variedad de factores de los que solo usted puede ser el mejor juez:

1: El cliente genera un grupo de acciones que las agrega directamente y luego las envía al servidor. El servidor lo acepta sin ningún control.

* Los beneficios de este enfoque son que, en lo que respecta al cliente, sus propias acciones les proporcionan una respuesta instantánea independiente del retraso de la red. La desventaja es que los mensajes del cliente son aceptados por el servidor como un hecho (que pueden no serlo) *

2: El cliente envía una solicitud al servidor, que a su vez difunde la solicitud a todos, incluido el cliente que originó la solicitud.

Los beneficios de este enfoque son que el servidor ahora tiene el control de todo lo que sucede en el juego que alivia la mayoría de los problemas con la actividad ilegal (aquí, ilegal puede variar desde que el cliente corta un muro hasta realizar un movimiento que ahora es ilegal porque el servidor tiene más información que el cliente no tenía cuando hizo un movimiento en particular)

3: El cliente genera un grupo de acciones, las agrega directamente y luego las envía al servidor. El servidor procesa la solicitud, ejecuta sus propias verificaciones de las acciones para asegurarse de que no sean ilegales y luego transmite el movimiento a todos los jugadores, incluido el cliente.

Esto toma lo mejor de 1 y 2 a costa de un poco más de requisitos de ancho de banda. Los beneficios son comentarios instantáneos al cliente para sus propios movimientos y si los movimientos que realiza el cliente son ilegales, el servidor toma las medidas apropiadas (corrige al cliente con fuerza).

¿Qué pasa con las pérdidas de paquetes y similares? El juego es determinista, pero creo que cualquier discrepancia en la secuencia de acciones ejecutadas en un cliente podría conducir a estados inconsistentes del mundo. ¿Cómo me protejo contra ese tipo de problema?

La pérdida de paquetes y el retraso extremo es un problema difícil de solucionar . Se emplean diferentes soluciones (ninguna de ellas perfectas) para lidiar con el problema dependiendo del tipo de juego (por ejemplo, ajuste de cuentas). Asumiré que tu juego no tiene que sincronizar la física.

Una de las primeras cosas que debe hacer es sellar todos sus movimientos. Su sistema debería tenerlos en cuenta y ejecutar los movimientos en consecuencia (tal vez el servidor tiene esta responsabilidad y luego negar movimientos ilegales). Al implementar este sistema, asuma la latencia 0 (prueba localmente).

0 latencia, por supuesto, no es una buena suposición, incluso en las redes locales, así que ahora que todos los movimientos respetan el tiempo, ¿en qué hora confía? Ahí es donde entra en juego la cuestión de la sincronización horaria. Muchos artículos / documentos en línea lo guiarán para resolver este problema.

¿Qué sucede si agrego demasiadas acciones a la vez, eso no causará problemas para la conexión? ¿Alguna forma de aliviar eso?

Primero lo obvio. Nunca envíe cadenas u objetos serializados. No envíes mensajes tan rápido como el juego se esté actualizando. Envíelos en trozos (por ejemplo, 10 veces por segundo). Habrá un error (especialmente un error de redondeo) que el servidor / cliente necesitará corregir los errores de vez en cuando (así que cada segundo, el servidor envía todo [todo = todos los datos que son importantes para mantener las cosas en su juego en el correcto Estado] que el cliente, a continuación se ajusta a y / o tiene en cuenta para corregir su estado).EDITAR: Uno de mis colegas mencionó que si está utilizando UDP [que debería, si tiene muchos datos], entonces no hay pedidos de red. Enviar más de 10 paquetes por segundo aumenta los cambios de los paquetes que llegan fuera de servicio. Veré si puedo citar esa afirmación. En cuanto a los paquetes fuera de servicio, puede descartarlos o agregarlos a la cola de mensajes / movimiento de su sistema, que está diseñada para manejar cambios en el pasado para corregir el estado actual

Debe tener un sistema de mensajería que se base en algo que ocupe muy poco espacio. Si tiene que enviar algo que solo puede tener dos valores, envíe solo un bit. Si sabe que solo puede tener 256 movimientos, envíe un personaje sin firmar. Hay varios trucos de giro de bits para empaquetar los mensajes con la mayor precisión posible.

Lo más probable es que su sistema de mensajería utilice muchas enumeraciones para permitirle extraer fácilmente movimientos de un mensaje entrante (sugeriría echar un vistazo a RakNet y ejemplos allí si no entiende lo que quiero decir aquí).

Estoy seguro de que ya sabe que no puede solucionar los problemas que surgen con la latencia alta y la pérdida de paquetes, pero puede hacer que sus efectos sean menos pronunciados. ¡Buena suerte!

EDITAR: el comentario de Patrick Hughes anterior con el enlace hace que esta respuesta sea incompleta y específica en el mejor de los casos a la pregunta de OP. En su lugar, recomendaría leer los temas vinculados.

Samaursa
fuente
Muchas gracias por la respuesta detallada, que cubre casi todas mis preocupaciones. Solo una pregunta con respecto a la parte donde dices so every second, the server sends everything ... which the client then snaps to. ¿Puedo enviar una gran cantidad de información como esta a la vez? ¿Qué es un tamaño máximo razonable?
David Gouveia
Un máximo razonable será un número que no presente retraso:) ... Sé que no estabas buscando esa respuesta, pero no quiero anotar un número porque no estoy familiarizado con tu juego.
Samaursa
Además, no existe una regla estricta de que debe enviar una gran parte, solo lo di como ejemplo.
Samaursa
@Samaursa - No estaría de acuerdo con el comentario "Si está utilizando UDP [que debería, si tiene muchos datos]". Como son nuevos en la programación de red, TCP se encarga de la mayoría de las cosas difíciles por usted a costa de que sea mucho más lento. En su caso, usaría TCP hasta que se convirtiera en un cuello de botella. En ese momento, tendrían una mejor comprensión de cómo su aplicación está utilizando la red, por lo que la implementación de las cosas UDP sería más fácil.
Chris
@Chris: No estaré de acuerdo :) ... la decisión entre usar TCP y UDP es como (imo) la decisión entre elegir Python y C ++ para el desarrollo. En teoría, puede sonar bien, pero en la práctica será difícil simplemente cambiar (de nuevo, eso es IMO).
Samaursa