Arquitetura em um jogo de MMO Parte 1
Palia é um game MMO lançado em versão Beta na Steam no final do mês passado. Primeiro jogo da desenvolvedora e distribuidora Singularity6, a própria desenvolvedora publicou um blog post em seu site contando como é a arquitetura do game. Como desenvolvedor atuante na área de eSports aproveito o disclosure que a equipe da Singularity6 fez para trazer uma análise da arquitetura e stack utilizada no game.
Para começar devemos ter em mente que Palia é um game com foco em socializar, apesar de muitos jogadores reclamarem que esse não é bem o forte da experiência. Palia também pode ser jogado em single player oque torna mais desafiante criar mundos compartilhados de maneira que muitos dados se complementem.
Minha análise aqui se baseia em diversos reviews de jogadores que estão jogando o game nesses últimos 10 dias, e em cima delas e do post da própria Singularity6 analiso a arquitetura e stack do game.
Game Engine
Palia usa Unreal Engine como motor de jogo e demonstra uso massivo das ferramentas já inclusas na engine, muito da movimentação parece adaptação do próprio sistema de movimento de personagem já presente na engine. A engine em particular tem um sistema de rede bem robusto e que tende a ser fácil de utilizar para esse tipo de multiplayer, para isso o próprio game é construído em versão cliente e servidor e que opera de maneiras diferentes dependendo do modo de jogo. Toda lógica do jogo é presente em ambos servidor e cliente, onde o player poder atuar como servidor e cliente ao mesmo tempo e os servidores dedicados podem atuar como servidor enquanto os players são somente clientes conectados a ele.
Mas é nessa parte que a Singularity6 peca em algo que para muitos acaba com a experiência social. Cada servidor é limitado a 25 jogadores, é ótimo quando você conhece quem joga com você mas imagine jogar com desconhecidos e só ter 25 pessoas no mapa. A interação ficou quase inexistente para muitos que se aventuraram a jogar com desconhecidos.
Memória RAM
Para quem já desenvolveu games online com Unreal ou já deve ter lido sobre, o consumo de memória RAM nesses games cresce conforme o jogo progride. A engine sofre com vazamentos de memória de dados que ficam alocados e nunca mais são utilizados.
A meu ver a equipe não deu uma solução adequada para esse problema, eles esperam que servidores geolocalizados ajudem com latência e que criem uma rotação onde eventualmente os servidores esvaziem e o servidor reinicie limpando a memória. Muitos games com mapas gigantescos utilizam mecanismos de unload de recursos não utilizados uma vez que o player se encontra muito longe do recurso então descarregamos o recurso liberando espaço na memória para outro recursos serem carregados.
Nos servidores de jogos online unreal e unity por exemplo, o servidor está rodando o game, mas ele não é um player, e para poder conectar esses players ele precisa estar ciente de tudo oque rodeia os players, implementar esse tipo de GC é dificultoso e pode levar a diversos bugs por falhas de implementação. Conforme os mundos dos jogos crescem o problema tende a escalar.
Games de desenvolvedoras famosas implementam técnicas de serialização e deserialização avançadas salvando dados em arquivos, de forma temporal para determinadas localidades não acessadas pelo jogadores depois de algum tempo. E caso esse dados não seja acessado após determinado período ele é apagado. Essa abordagem libera memória RAM mas exige muito mais do servidor para que calcule qual dado deve ser carregado e qual deve ser descarregado, além de que qualquer erro de lógica no processo pode ser fatal.
Serviços
Palia utiliza uma arquitetura de microserviços descrita por eles como "solta". Cada microserviço fica responsável por dados de cada feature quase que indiviualmente. Para comunicação externa aos data centers eles utilizam HTTP e internamente entre os serviços no mesmo data center utilizam gRPC. gRPC é uma ótima tecnologia para comunicação e é uma das principais utilizadas pela gigante Google, vale a pena pelo menos entender como ela funciona se você ainda não sabe.
Usar o gRPC internamente facilita e agiliza a comunicação entre os serviços permitindo que objetos mantenham suas estrutura sem perdas. Já o uso do HTTP externamente, acredito que se deu para permitir que agentes externos se comuniquem com sua interface, visto que o gRPC precisaria de implementação adicional por parte do terceiro que entraria nessa equação.
Rust
Seus micro serviços foram escritos exclusivamente em Rust seguindo uma nova tendência no mundo, e que se mostra ousada, principalmente para uma equipe que desenvolveu um jogo em uma engine usando C++. Rust é uma linguagem robusta não indicada para iniciantes pois demanda muito conhecimento teórico do que ocorre por trás das linhas de código para poder programá-la eficientemente.
Contratar programadores Rust é um desafio para todas equipes que trabalham com a linguagem. Muito poucos profissionais no mercado que realmente sabem usar a linguagem e muitos poucos dispostos a se aventurar a aprender.
Fora isso Rust é performática, segura de todas as maneiras e praticamente impede de todas as maneiras que o programador faça besteira. Mas isso não quer dizer zero bugs, quer dizer que comportamentos indesejados por alocações erradas ou movimentação de memória errada ou até mesmo pedaços de memória deixados para trás são protegidos no momento que o código compila, impedindo que tais erros sejam compilados.
Gerenciamento de Estado
O servidor do Palia trabalha com uma arquitetura de autoridade do servidor, onde o servidor controla todos gerenciamento de estado e o cliente apenas o reproduz ou faz "solicitações" de mudanças, essa arquitetura é fundamental em games, impede trapaceiros de mudarem os dados no cliente local, uma vez que o servidor irá dizer quais com quais dados o cliente trabalhará e também é o responsável por modificá-los. Isso significa mais uso de recursos no servidor? Sim, mas é necessário para o bom funcionamento de um game online aberto.
No layer de persistência de dados eles aplicaram uma abordagem interessante. Uma vez que os dados residem no servidor qualquer erro ou queda leva a perda dos dados. Portanto estes precisam ser salvos em algum mecanismo de persistência, usualmente bancos de dados. A maneira e frequência com que isso é feito é a chave para eficiência. Palia salva as mudanças de dados constantemente utilizando um mecanismo de filas para armazenamento. As demandas em cima do banco de dados são intensas e necessitam um banco robusto e rápido como veremos a frente.
Modelo de Dados
Uma abordagem diferente do uso padrão de um DB foi utilizada aqui. Em bancos relacionais devemos ter uma estrutura de dados definida e validada no banco, nos bancos não relacionais essa estrutura nem sempre é necessária mas é necessária no momento que acessamos os dados, precisamos saber onde e oque acessar.
A equipe de Palia decidiu por uma abordagem que facilitasse o desenvolvimento futuro e também trouxesse uma preocupação menor com a replicação da estrutura de dados em diversos ambientes diferentes. Os dados são salvos na forma de structs C++ serializadas em Blobs e utilizam reflexão para desserializar esses blobs de volta em structs e poderem ser utilizadas em preocupações de estrutura pela equipe de desenvolvimento do jogo.
Nesse ponto o banco de dados virou um armazenamento de dados brutos e sem significado para o banco em si, mas que atende as necessidades do jogo e dos desenvolvedores sem criar trabalho extra com manutenção de estruturas desnecessárias em torno de todos os ambientes.
Parte 2
Esse será um artigo extenso então fique ligado para a parte 2 em breve vou deixar o link aqui.