Megapack: como a Factorio resolveu o problema multijogador de 200 jogadores

Megapack: como a Factorio resolveu o problema multijogador de 200 jogadores
Em maio deste ano, participei como jogador do KatherineOfSky Eventos MMO. Percebi que quando o número de jogadores atinge um determinado número, a cada poucos minutos alguns deles "caem". Felizmente para você (mas não para mim), eu era um desses jogadores toda vezmesmo com uma boa conexão. Tomei isso como um desafio pessoal e comecei a procurar as causas do problema. Após três semanas de depuração, teste e correção, o bug foi finalmente corrigido, mas a jornada não foi tão fácil.

Problemas em jogos multiplayer são muito difíceis de rastrear. Eles geralmente ocorrem em parâmetros de rede muito específicos e em estados de jogo muito específicos (neste caso, mais de 200 jogadores). E mesmo quando um problema pode ser reproduzido, ele não pode ser depurado adequadamente porque a inserção de pontos de interrupção interrompe o jogo, atrapalha os cronômetros e geralmente faz com que a conexão expire devido a um tempo limite. Mas graças à perseverança e a uma ferramenta maravilhosa chamada desajeitado Consegui entender o que está acontecendo.

Resumindo, devido a um bug e implementação incompleta da simulação do estado de atraso, o cliente às vezes se encontrava em uma situação em que precisava enviar um pacote de rede em um ciclo de clock, consistindo em ações de entrada do jogador para selecionar aproximadamente 400 entidades do jogo ( chamamos de "megapacote"). Depois disso, o servidor não apenas precisa receber corretamente todas essas ações de entrada, mas também enviá-las para todos os outros clientes. Se você tiver 200 clientes, isso rapidamente se tornará um problema. O link para o servidor rapidamente fica obstruído, resultando em pacotes perdidos e uma cascata de pacotes solicitados novamente. Adiar as ações de entrada faz com que mais clientes comecem a enviar megapacotes, e sua avalanche fica ainda mais forte. Clientes bem-sucedidos conseguem se recuperar, todo o resto cai.

Megapack: como a Factorio resolveu o problema multijogador de 200 jogadores
O problema era bastante fundamental e demorei 2 semanas para consertá-lo. É bastante técnico, então explicarei os detalhes técnicos suculentos abaixo. Mas antes, você precisa saber que desde a versão 0.17.54, lançada em 4 de junho, diante de problemas temporários de conexão, o multiplayer ficou mais estável, e o delay hide está com muito menos bugs (menos travamento e teletransporte). Além disso, mudei a maneira como os atrasos de combate são ocultados e espero que isso os torne um pouco mais suaves.

Mega Pack Multijogador - Detalhes Técnicos

Simplificando, o multijogador em um jogo funciona assim: todos os clientes simulam o estado do jogo recebendo e enviando apenas a entrada do jogador (chamada de "ações de entrada" Ações de entrada). A principal tarefa do servidor é transferir Ações de entrada e garantir que todos os clientes executem as mesmas ações no mesmo ciclo. Você pode ler mais sobre isso no post. FFF-149.

Uma vez que o servidor tem que tomar decisões sobre quais ações tomar, as ações do jogador seguem aproximadamente o seguinte caminho: ação do jogador -> cliente do jogo -> rede -> servidor -> rede -> cliente do jogo. Isso significa que cada ação do jogador é realizada somente após ele ter feito um percurso de ida e volta pela rede. Por causa disso, o jogo pareceria terrivelmente lento, então quase imediatamente após o aparecimento do multiplayer no jogo, um mecanismo para ocultar atrasos foi introduzido. A ocultação de latência simula a entrada do jogador sem considerar as ações de outros jogadores e a tomada de decisão do servidor.

Megapack: como a Factorio resolveu o problema multijogador de 200 jogadores
Factorio tem um estado de jogo estado do jogo é o estado completo do mapa, jogador, entidades e tudo mais. É simulado de forma determinística em todos os clientes com base nas ações recebidas do servidor. O estado do jogo é sagrado e, se começar a diferir do servidor ou de qualquer outro cliente, ocorrerá a dessincronização.

Mas estado do jogo temos um estado de atrasos Estado de latência. Ele contém um pequeno subconjunto do estado principal. Estado de latência não é sagrado e apenas representa uma imagem de como será o estado do jogo no futuro com base nas entradas do jogador Ações de entrada.

Para fazer isso, mantemos uma cópia dos dados gerados Ações de entrada na fila de espera.

Megapack: como a Factorio resolveu o problema multijogador de 200 jogadores
Ou seja, ao final do processo no lado do cliente, a imagem fica mais ou menos assim:

  1. Aplicar Ações de entrada todos os jogadores para estado do jogo a maneira como essas ações de entrada foram recebidas do servidor.
  2. Remova tudo da fila de atraso Ações de entrada, que, segundo o servidor, já foram aplicados a estado do jogo.
  3. Excluir Estado de latência e redefini-lo para que pareça exatamente o mesmo que estado do jogo.
  4. Aplique todas as ações da fila de atraso para Estado de latência.
  5. Baseado em dados estado do jogo и Estado de latência renderizar o jogo para o jogador.

Tudo isso se repete a cada batida.

Muito difícil? Não relaxe, isso não é tudo. Para compensar conexões de Internet não confiáveis, criamos dois mecanismos:

  • Tiques ignorados: quando o servidor decide que Ações de entrada será executado no tacto do jogo, então se ele não recebeu Ações de entrada algum jogador (por exemplo, devido a um atraso maior), ele não esperará, mas informará a este cliente “Não levei em consideração o seu Ações de entrada, tentarei adicioná-los na próxima barra. Isso é feito para que, devido a problemas com a conexão (ou com o computador) de um jogador, a atualização do mapa não fique lenta para todos os outros. Vale a pena notar que Ações de entrada não são ignorados, mas simplesmente adiados.
  • Latência de ida e volta completa: o servidor tenta adivinhar qual é a latência de ida e volta entre o cliente e o servidor para cada cliente. A cada 5 segundos, ele negocia um novo atraso com o cliente conforme necessário (dependendo de como a conexão se comportou no passado) e aumenta ou diminui o atraso de ida e volta de acordo.

Sozinhos, esses mecanismos são bastante simples, mas quando são usados ​​em conjunto (o que muitas vezes acontece com problemas de conexão), a lógica do código se torna difícil de gerenciar e com muitos casos extremos. Além disso, quando esses mecanismos entram em ação, o servidor e a fila de atraso devem implementar corretamente um Ação de entrada intitulado StopMovementInTheNextTick. Graças a isso, em caso de problemas de conexão, o personagem não correrá sozinho (por exemplo, embaixo de um trem).

Agora preciso explicar a você como funciona a seleção de entidades. Um dos tipos aprovados Ação de entrada é uma mudança no estado de seleção de uma entidade. Ele diz a todos sobre qual entidade o jogador passou o mouse. Como você pode ver, esta é uma das ações de entrada mais frequentes enviadas pelos clientes, portanto, para economizar largura de banda, nós a otimizamos para que ocupe o mínimo de espaço possível. Isso é implementado assim: quando cada entidade é selecionada, em vez de armazenar coordenadas de mapa absolutas e de alta precisão, o jogo armazena um deslocamento relativo de baixa precisão da seleção anterior. Isso funciona bem porque a seleção do mouse geralmente acontece muito perto da seleção anterior. Isso dá origem a dois requisitos importantes: Ações de entrada nunca deve ser ignorado e deve ser feito na ordem correta. Esses requisitos são atendidos para estado do jogo. Mas como a tarefa estado de latência em "parecer bom o suficiente" para o jogador, eles não estão satisfeitos no estado de atraso. Estado de latência não leva em conta muitos casos limítrofesassociados a pular relógios e alterar atrasos de transmissão de ida e volta.

Você já pode adivinhar onde isso vai dar. Finalmente, estamos começando a ver as causas do problema do megapacote. A raiz do problema é que a lógica de seleção de entidade depende de Estado de latência, e esse estado nem sempre contém as informações corretas. Assim, o megapacote é gerado assim:

  1. O player está com problemas de conexão.
  2. Os mecanismos para pular ciclos e regular o atraso de transmissão de ida e volta entram em ação.
  3. A fila de estado de atraso não leva em conta esses mecanismos. Isso faz com que algumas ações sejam removidas prematuramente ou executadas na ordem errada, resultando em uma Estado de latência.
  4. O player não tem problema de conexão e simula até 400 ciclos para alcançar o servidor.
  5. A cada ciclo, uma nova ação é gerada e preparada para ser enviada ao servidor, alterando a seleção da entidade.
  6. O cliente envia um megapacote de mais de 400 alterações de seleção de entidade para o servidor (e com outras ações: estado de disparo, estado de caminhada, etc. também sofreram com esse problema).
  7. O servidor recebe 400 ações de entrada. Como não é permitido pular uma única ação de entrada, ele instrui todos os clientes a executar essas ações e as envia pela rede.

A ironia é que um mecanismo projetado para conservar a largura de banda resultou em enormes pacotes de rede.

Resolvemos esse problema corrigindo todos os casos extremos de atualização e o suporte à fila de atraso. Embora tenha demorado bastante, valeu a pena acertar no final, em vez de depender de hacks rápidos.

Fonte: habr.com

Adicionar um comentário