05 de fev. de 2019

Melhorias no Cálculo de Ping

Olá, pessoal! Sou Bart Hawthorne, Engenheiro Sênior de Rede na equipe de engine aqui da Epic, e quero falar com vocês sobre nosso método para exibir valores de ping no Fortnite, sobre o novo método que testamos no v.7.30 e como ele deu errado, e por fim, sobre nossos futuros planos para aprimorar a precisão do nosso display.

Antes de começar, para aqueles que não estão familiarizados, tenho aqui uma rápida introdução de alguns termos que usarei bastante:

Cliente e servidor: cada console, dispositivo móvel, PC ou Mac é um cliente, e os clientes se conectam a servidores dedicados que hospedam o jogo. O servidor mantém o estado geral do jogo (o local dos jogadores, o status do círculo, o estado dos Baús e muito mais). O servidor também é responsável por atualizar todos os clientes quando há mudanças no estado do jogo.

Pacote: um pacote é um bloco de dados enviado pela rede do cliente para o servidor ou do servidor para um cliente. A comunicação entre clientes e o servidor é feita pelos pacotes. No Fortnite (e no UE4), usamos esses pacotes no nosso método de cálculo do ping.

Quadro: no servidor, um quadro é a frequência em que o servidor realiza todas as suas tarefas — verificar se há pacotes novos, atualizar o estado do jogo e enviar atualizações para os clientes. No cliente, um quadro é a frequência em que a imagem é atualizada na tela. A taxa de quadros, também conhecida como FPS, ou "frames per second" (quadros por segundo), é a quantidade de quadros processados no servidor ou cliente em um segundo. Então quando dizemos que o cliente está sendo executado a 60 FPS, isso significa que cada segundo contém 60 quadros.

Agora que já esclarecemos as coisas, vamos falar sobre ping!

Nosso Antigo Método de Cálculo de Pacote de Ping

Quando a maioria das pessoas pensa em "ping", elas pensam no tempo que demora para um cliente enviar um pacote para o servidor e para que o servidor envie uma resposta, ou vice-versa. Também chamamos isso de tempo resposta, ou RTT. Isso fornece as informações que interessam à maioria dos jogadores — quanto tempo para que eles recebam atualizações do servidor e quanto tempo para que o servidor receba as atualizações deles. Quanto maior o valor de ping do jogador, maiores as chances de ele ver artefatos no jogo, como teleporte de jogadores, eliminações inesperadas e atrasos ao interagir com baús e outros objetos.

Exibimos o ping em milissegundos — e quanto menor, melhor. Se você morar ao lado de um centro de dados que hospeda nossos servidores, você pode ter um ping de um dígito! Porém, se você estiver jogando em um servidor do outro lado do mundo, seu ping será próximo dos 100 ms ou mais.

Dito isso, a comunicação entre os clientes e os servidores é mais complexa do que apenas o tempo de viagem dos pacotes. No Fortnite, geralmente verificamos a chegada de novos pacotes uma vez por quadro próximo do começo tanto no servidor quanto no cliente. Isso significa que há um potencial para atrasos de até um quadro entre o momento em que o pacote chega e o momento em que o cliente ou servidor faz a verificação em casos em que o pacote chega logo após verificarmos a chegada de novos pacotes. Além disso, há o tempo de leitura do pacote e de atualização do estado do jogo com base nos dados contidos no pacote.

Eis um diagrama para ajudar a ilustrar:

As setas pretas do cliente e do servidor representam o tempo. Nesse exemplo, os servidores estão totalmente sincronizados e sendo executados na mesma taxa de quadros. As linhas pretas verticais representam o começo de cada quadro, as marcas azuis indicam as verificações do cliente ou servidor sobre o processamento dos pacotes, e as linhas laranjas indicam o momento em que o cliente ou servidor envia pacotes. As linhas roxas pontilhadas representam um pacote viajando pela Internet.

Eis a sequência de eventos:

  1. O cliente envia um pacote para o servidor.
  2. O servidor recebe o pacote enviado pelo cliente.
  3. O servidor verifica se há outros novos pacotes — esse é o momento em que o servidor nota o pacote enviado pelo cliente. Observe que o pacote já estava parado há quase meio quadro, pois chegou logo depois de o servidor fazer a verificação de chegada de pacotes.
  4. O servidor envia sua resposta ao cliente dizendo que recebeu o pacote.
  5. A resposta do servidor chega ao cliente.
  6. O cliente lê a resposta do servidor.
Só queremos exibir o tempo resposta, representado pelas setas verdes, e subtrair o tempo representado pelas setas vermelhas. Então, fazemos nosso melhor — quando enviamos um pacote do servidor para o cliente, isso inclui o tempo de quadro do servidor, então para um determinado pacote, subtraímos o tempo de quadro daquele servidor e o tempo de quadro do cliente até o momento em que estamos processando o pacote. Nosso cálculo de ping é assim:
Ping = Tempo de viagem do pacote até o servidor + Tempo de viagem do pacote para o cliente - Tempo de quadro do servidor - Tempo de quadro do cliente até o momento

Para o valor exibido, fazemos a média do valor calculado dos pacotes ao longo de vários segundos para produzir um valor mais consistente.

Entretanto, na prática, o valor não é tão preciso quanto gostaríamos. Ele não leva em conta o tempo de espera entre a chegada dos pacotes e seu processamento, então o ping exibido geralmente é alto demais. O problema é que a maioria das plataformas não dispõe de uma maneira de descobrir precisamente o momento de chegada de um pacote, então não sabemos exatamente quanto tempo subtrair.

Nosso Método de Cálculo de Pacote de Ping no v.7.30

No v.7.30, tivemos uma ideia e decidimos testar algo novo — e se usássemos apenas os pacotes com menor tempo acumulado somando a viagem do cliente para o servidor e a resposta? A teoria é que os pacotes com menor tempo seriam aqueles processados imediatamente, o que nos permitiria ignorar os tempos de espera porque seriam praticamente nulos. No fim, usamos os pacotes mais rápidos (25%) para nosso cálculo de valor de ping pois parecia um bom equilíbrio entre escolher os pacotes mais rápidos e ter dados suficientes para obter um valor realista.

O que deu errado?

Depois do lançamento do v.7.30, notamos vários relatos de jogadores com ping baixos de apenas um dígito, às vezes até 0, quando deveriam ser mais altos. Se isso estivesse correto, estaríamos enviando pacotes mais rápido do que a velocidade da luz, e, apesar do UE4 ser incrível, não está tão avançado assim! Então claramente tem algo errado.

Nosso cálculo de ping é parte do problema. Ei-lo de novo para referência:
Ping = Tempo de viagem do pacote até o servidor + Tempo de viagem do pacote para o cliente - Tempo de quadro do servidor - Tempo de quadro do cliente até o momento

O problema é subtrair o tempo de quadro do servidor. Considerando apenas os pacotes mais rápidos, estamos subtraindo demais — se processamos um pacote no momento em que chega ao servidor, pode levar apenas alguns milissegundos para analisar seu conteúdo e enviar uma resposta. O tempo total de quadro do servidor pode ser 5 vezes maior!

Eis um diagrama dessa atividade:

Esse é um cliente cuja taxa de quadros é muito mais alta que a do servidor. Como estamos considerando apenas os pacotes verdes, eles estão chegando logo antes de o servidor fazer a verificação de chegada desses pacotes. Nesse caso, o servidor está sendo executado mais rápido que sua taxa de quadros fixa, então ele entra em suspensão (não faz nada por um tempo) no começo do quadro antes de fazer uma verificação de chegada de novos pacotes.

A linha vermelha é o que estamos subtraindo da taxa de quadros do servidor, mas o correto seria a linha laranja. Isso é claramente tempo demais, o que explica por que alguns servidores começaram a exibir 0 de ping.

Isso continua sendo um problema mesmo usando nosso antigo método, em que consideramos uma variedade mais ampla de pacotes. Porém, temos uma chance maior de considerar pacotes que esperaram mais tempo para serem processados, cancelando os pacotes em que subtraímos tempo demais.

Planos futuros

Temos planos para melhorias em futuros lançamentos para que o valor exibido de ping seja mais preciso. Eu disse o seguinte acima:

"O problema é que a maioria das plataformas não dispõe de uma maneira de descobrir precisamente o momento de chegada de um pacote, então não sabemos quanto tempo subtrair."


Por sorte, o Linux dispõe! Já que nossos servidores são executados exclusivamente no Linux, podemos mudar o cálculo de ping para:

Ping = Tempo de viagem do pacote até o servidor + Tempo de viagem do pacote para o cliente - Tempo entre a chegada do pacote no servidor e o envio da resposta - Tempo de quadro do cliente até o momento.


Isso remove a adivinhação que tentávamos fazer ao subtrair o tempo de quadro do servidor. Prevemos que isso fornecerá valores muito mais precisos, mas ainda precisamos fazer muitos testes.

Também estamos analisando verificar a chegada de pacotes no cliente mais frequentemente, o que nos permitiria remover a parte "tempo de quadro do cliente até o momento" da equação.

Esperamos que a combinação dessas duas mudanças nos dê uma exibição de valor de ping muito mais preciso.

Conclusão

Espero que essa explicação sobre como calculamos o ping e como esperamos aprimorá-lo tenha sido útil. Quando exibimos esse tipo de informação, é imprescindível que ela seja precisa — exibir valores incorretos pode ser pior do que não exibir informação alguma. É importante que os jogadores saibam se um erro de disparo ou a sensação de que o jogo está com atraso é por causa do ping ruim para que possam corrigi-lo.

Ficarei de olho nos comentários em busca de perguntas e ficarei feliz em respondê-las assim que puder. Obrigado por ler!