SaaS, IaaS, PaaS, plataforma de comunicação como serviço, videoconferências como serviço, e quanto aos jogos em nuvem como serviço? Já houve várias tentativas de criar jogos em nuvem, como o Stadia, recentemente lançado pela Google. Stadia , mas outros podem usar o WebRTC da mesma forma?
Thanh Nguyen decidiu investigar essa possibilidade em seu projeto de código aberto, CloudRetro. CloudRetro é baseado no Pion, biblioteca WebRTC escrita em Go (agradecimentos a do grupo de desenvolvedores do Pion pela ajuda na preparação deste artigo). Neste artigo, Thanh faz uma visão geral da arquitetura de seu projeto e também compartilha o que aprendeu e quais desafios enfrentou durante o trabalho.
Introdução
No ano passado, quando a Google anunciou o Stadia, eu simplesmente fiquei impressionado. A ideia é tão única e inovadora que eu continuamente me perguntava como isso era possível com as tecnologias atuais. O desejo de entender melhor o assunto me levou a criar minha própria versão de um jogo em nuvem de código aberto. O resultado foi simplesmente fantástico. Abaixo, gostaria de compartilhar o processo de trabalho sobre meu projeto de um ano .
TLDR: uma versão resumida dos principais pontos
Por que os jogos em nuvem são o futuro
Acredito que Cloud Gaming logo se tornará a nova geração não apenas de jogos, mas também de outros campos da informática. Jogos em nuvem representam o auge do modelo cliente/servidor. Esse modelo maximiza o controle do backend e minimiza o trabalho do frontend, colocando a lógica do jogo em um servidor remoto e transmitindo imagens/áudio para o cliente. O servidor realiza o processamento pesado, portanto o cliente não depende mais das limitações de hardware.
O Google Stadia, em essência, permite jogar (ou seja, games de alto nível) em uma interface como o YouTube. A mesma metodologia pode ser aplicada a outros aplicativos pesados offline, como sistemas operacionais ou design gráfico 2D/3D, etc., permitindo que os executemos de forma estável em dispositivos com especificações técnicas baixas em várias plataformas.

O futuro dessa tecnologia: imagine se o Microsoft Windows 10 funcionasse no navegador Chrome?
Jogos em nuvem são tecnicamente complexos
O gaming é uma das raras áreas em que uma rápida reação do usuário é constantemente necessária. Se ocasionalmente encontramos um atraso de 2 segundos ao clicar na página, isso é aceitável. As transmissões de vídeo ao vivo geralmente têm um atraso de alguns segundos, mas ainda assim oferecem um uso suficientemente confortável. No entanto, se o jogo frequentemente demora 500 ms, jogar se torna impossível. Nosso objetivo é alcançar uma latência extremamente baixa, para que a diferença entre a entrada e a mídia seja a menor possível. Portanto, a abordagem tradicional para transmissões de vídeo não se aplica aqui.

Modelo geral de jogo em nuvem
Projeto de código aberto CloudRetro
Decidi criar um protótipo de jogo em nuvem para testar se isso é possível com restrições de rede tão rígidas. Para a prova de conceito, escolhi Golang, pois é a linguagem com a qual estou mais familiarizado e se adequa bem a essa implementação por muitas razões, como se mostrou mais tarde. Go é simples e se desenvolve rapidamente; os canais em Go são ótimos para gerenciar a multitarefa.
Projeto – serviço de jogos em nuvem de código aberto para jogos retro. O objetivo do projeto é trazer uma experiência de jogo mais confortável para os tradicionais jogos retro e adicionar um modo multiplayer.
Você pode saber mais sobre o projeto aqui: .
Funcionalidade do CloudRetro
Para demonstrar todo o poder dos jogos em nuvem, o CloudRetro utiliza jogos retro. Isso permite obter uma variedade de experiências de jogo exclusivas.
- Portabilidade do jogo
- Reprodução instantânea ao abrir a página; downloads e instalações não são necessários
- Funciona em navegadores móveis, portanto, nenhum software é necessário para a execução
- As sessões de jogo podem ser compartilhadas em vários dispositivos e armazenadas na nuvem para o próximo login
- O jogo pode ser transmitido, ou pode ser jogado instantaneamente por vários usuários:
- Crowdplay tipo TwitchPlayPokemon, apenas mais multiplataforma e mais em tempo real
- Jogos offline online. Muitos usuários podem jogar sem configurar a rede. Em Samurai Shodown agora dois jogadores podem jogar pela rede CloudRetro

Versão demo de um jogo multiplayer online em vários dispositivosInfraestrutura
Requisitos e stack tecnológico
Abaixo está uma lista de requisitos que estabeleci antes do início do projeto.
1. Um jogador
Este requisito pode parecer menos importante e óbvio aqui, mas é uma das minhas principais conclusões, permitindo que os jogos na nuvem se distanciem dos serviços de streaming tradicionais. Se nos concentrarmos em jogos para um único jogador, podemos nos livrar de um servidor centralizado ou CDN, pois não precisamos fazer streaming para um grande público. Em vez de carregar streams em um servidor massivo ou transmitir pacotes para um servidor WebSocket centralizado, os streams de serviço são entregues diretamente ao usuário através de uma conexão ponto a ponto usando WebRTC.2. Stream de mídia com baixa latência
Ao ler sobre o Stadia, frequentemente encontro em alguns artigos menções ao WebRTC. Eu percebi que o WebRTC é uma tecnologia excepcional e se encaixa perfeitamente no uso em jogos na nuvem. O WebRTC é um projeto que fornece comunicação em tempo real para navegadores da web e aplicativos móveis através de uma API simples. Ele proporciona uma conexão ponto a ponto, otimizada para mídia, com codecs padrão embutidos, como VP8 e H264.Eu priorizei garantir o máximo conforto para os usuários em vez de manter uma alta qualidade gráfica. No algoritmo, algumas perdas são aceitáveis. O Google Stadia possui um passo adicional para reduzir o tamanho da imagem no servidor, e os quadros são escalonados para uma qualidade superior antes de serem enviados para os nós ponto a ponto.
3. Infraestrutura distribuída com roteamento geográfico
Independentemente de quão otimizado seja o algoritmo de compressão e o codec, a rede continua sendo um fator decisivo que mais contribui para a latência. A arquitetura deve ter um mecanismo de emparelhamento para o servidor mais próximo do usuário, a fim de reduzir o tempo de ida e volta (RTT). A arquitetura deve ter 1 coordenador e vários servidores de streaming distribuídos pelo mundo: Oeste dos EUA, Leste dos EUA, Europa, Cingapura, China. Todos os servidores de streaming devem ser completamente isolados. O sistema pode ajustar sua distribuição quando um servidor ingressa ou sai da rede. Assim, em caso de grande tráfego, a adição de servidores adicionais permite a escalabilidade horizontal.4. Compatibilidade do navegador
Os jogos em nuvem brilham quando exigem o mínimo dos usuários. Isso significa que é possível executá-los no navegador. Navegadores ajudam a tornar a experiência de jogo o mais confortável possível para os usuários, livrando-os da instalação de software e hardware. Eles também ajudam a garantir a compatibilidade entre plataformas para versões móveis e desktop. Felizmente, o WebRTC é bem suportado em vários navegadores.5. Separação clara entre a interface de jogo e o serviço
Eu vejo o serviço de jogos em nuvem como uma plataforma. Cada um deve ter a possibilidade de conectar o que quiser à plataforma. No momento, integrei com o serviço de jogos em nuvem, porque o LibRetro oferece uma bela interface de emulador de jogos para jogos retro, como SNES, GBA, PS.6. Salas para multiplayer, crowd play e link profundo com o jogo
CloudRetro suporta muitos novos estilos de jogo, como CrowdPlay e Multiplayer Online para jogos retro. Se vários usuários abrirem o mesmo link profundo em computadores diferentes, eles verão o mesmo jogo em execução e até poderão se juntar a ele.Além disso, os estados dos jogos são armazenados na nuvem. Isso permite que os usuários continuem jogando a qualquer momento em qualquer outro dispositivo.
7. Escalabilidade horizontal
Assim como qualquer SAAS atualmente, os jogos em nuvem devem ser projetados para serem escaláveis horizontalmente. A estrutura 'coordenador-trabalhador' permite adicionar mais trabalhadores para atender a um tráfego maior.8. Sem dependência de uma única nuvem
A infraestrutura do CloudRetro é hospedada em vários provedores de nuvem (Digital Ocean, Alibaba, provedor personalizado) para diferentes regiões. Eu ativo a execução em um contêiner Docker para a infraestrutura e configuro os parâmetros de rede usando um script Bash, para evitar a dependência de um único provedor de nuvem. Combinando isso com a travessia NAT no WebRTC, conseguimos obter flexibilidade para implantar o CloudRetro em qualquer plataforma de nuvem e até mesmo em máquinas de qualquer usuário.Design arquitetônico
Trabalhador: (ou servidor de streaming mencionado acima) multiplica jogos, inicia o pipeline de codificação e transmite a mídia codificada para os usuários. As instâncias do trabalhador são distribuídas globalmente, e cada trabalhador pode processar várias sessões de usuário simultaneamente.
Coordenador: é responsável por emparelhar um novo usuário com o trabalhador mais apropriado para o streaming. O coordenador interage com os trabalhadores via WebSocket.
Armazenamento dos estados do jogo: um armazenamento remoto central para todos os estados do jogo. Este armazenamento fornece funções essenciais, como, por exemplo, salvar/carregar remotamente.

Arquitetura de alto nível do CloudRetroCenário do usuário
Quando um novo usuário abre o CloudRetro nos passos 1 e 2 mostrados na figura abaixo, o coordenador junto com a lista de trabalhadores disponíveis é chamado para a primeira página. Em seguida, no passo 3, o cliente calcula as latências para todos os candidatos usando uma solicitação HTTP ping. Esta lista de latências é então enviada de volta ao coordenador, para que ele possa determinar o trabalhador mais apropriado para atender o usuário. No passo 4 abaixo, o jogo é criado. Uma conexão de streaming WebRTC é estabelecida entre o usuário e o trabalhador designado.

Cenário do usuário após receber acessoO que há dentro do trabalhador
Os pipelines de jogos e streaming são armazenados dentro do trabalhador de forma isolada e trocam informações lá por meio de uma interface. Atualmente, essa comunicação é realizada pela transmissão de dados na memória através de no mesmo processo. O próximo objetivo é a segregação, ou seja, a execução independente do jogo em outro processo.

Interação entre componentes do trabalhadorComponentes principais:
- WebRTC: componente do cliente, que recebe a entrada do usuário e transmite a mídia codificada do servidor.
- Emulador de jogo: componente de jogo. Graças à biblioteca Libretro, o sistema é capaz de executar o jogo dentro do mesmo processo e interceptar internamente a mídia e o fluxo de entrada.
- Frames dentro do jogo são capturados e enviados ao codificador.
- Codificador de imagem/audio: pipeline de codificação que recebe frames de mídia, os codifica em segundo plano e produz imagens/audio codificados.
Implementação
O CloudRetro depende do WebRTC como sua tecnologia central; portanto, antes de me aprofundar nos detalhes da implementação em Golang, decidi falar sobre o próprio WebRTC. É uma tecnologia incrível que me ajudou bastante a alcançar uma latência de transmissão de dados de apenas uma fração de segundo.
WebRTC
WebRTC foi projetado para fornecer conexões peer-to-peer de alta qualidade em aplicativos móveis nativos e navegadores por meio de APIs simples.
NAT Traversal
O WebRTC é conhecido por sua funcionalidade de NAT Traversal. Ele é voltado para comunicação peer-to-peer. Seu objetivo é encontrar a rota direta mais adequada, evitando gateways NAT e firewalls para comunicação peer-to-peer através de um processo chamado . Durante esse processo, a API do WebRTC localiza seu endereço IP público usando servidores STUN e o redireciona para um servidor de retransmissão (), quando uma conexão direta não pode ser estabelecida.
No entanto, o CloudRetro não utiliza completamente essa capacidade. Suas conexões peer-to-peer não existem entre os usuários, mas entre os usuários e os servidores em nuvem. A parte do servidor do modelo tem menos restrições para a comunicação direta do que um dispositivo de usuário comum. Isso permite a abertura prévia de portas de entrada ou o uso de endereços IP públicos diretamente, já que o servidor não está atrás de um NAT.
Eu já quis transformar o projeto em uma plataforma de distribuição de jogos para Cloud Gaming. A ideia era permitir que criadores de jogos oferecessem jogos e recursos de streaming, enquanto os usuários interagissem diretamente com os provedores. De uma maneira descentralizada, o CloudRetro é apenas um meio de conectar recursos de streaming de terceiros aos usuários, tornando-o mais escalável quando não depende mais de hospedagem. O papel do NAT Traversal do WebRTC é muito importante para facilitar a inicialização da conexão peer-to-peer com recursos de streaming de terceiros, simplificando a conexão do criador à rede.
Compressão de vídeo
A compressão de vídeo é uma parte essencial do pipeline, que contribui significativamente para a fluidez do fluxo. Embora não seja necessário conhecer todos os detalhes da codificação de vídeo em VP8/H264, entender o conceito ajuda a lidar com as configurações de taxa de vídeo, depurar comportamentos inesperados e ajustar a latência.
A compressão de vídeo para serviços de streaming é uma tarefa complexa, pois o algoritmo deve garantir que o tempo total de codificação + tempo de transmissão pela rede + tempo de decodificação seja o menor possível. Além disso, o processo de codificação deve ser contínuo e consistente. Algumas concessões no processo de codificação não são aplicáveis – por exemplo, não podemos preferir um longo tempo de codificação a um tamanho de arquivo menor e um tempo de decodificação, ou utilizar uma compressão inconsistente.
A ideia da compressão de vídeo é eliminar bits desnecessários de informação, mantendo um nível aceitável de precisão para os usuários. Além de codificar quadros de imagem estáticos individuais, o algoritmo obtém informações sobre o quadro atual a partir do anterior e do próximo, de forma que apenas suas diferenças são enviadas. Como pode ser visto no exemplo com o Pacman, apenas os pontos diferenciais são transmitidos.

Comparação de quadros de vídeo usando o exemplo do PacmanCompressão de áudio
De maneira semelhante, o algoritmo de compressão de áudio descarta dados que não podem ser percebidos pelo ser humano. O Opus é atualmente o codec de áudio com o melhor desempenho. Ele foi projetado para transmitir ondas sonoras através de um protocolo de datagrama ordenado, como o RTP (Protocolo de Transporte em Tempo Real). Sua latência é menor que a do mp3 e aac, e sua qualidade é superior. A latência geralmente é de cerca de 5~66,5 ms.
Pion, WebRTC em Golang
é um projeto de código aberto que traz WebRTC para o Golang. Em vez de simplesmente envolver bibliotecas nativas C++ do WebRTC, o Pion é uma implementação nativa do WebRTC em Golang, com melhor desempenho, integração com Go e controle de versões nos protocolos WebRTC.
A biblioteca também oferece streaming de dados com uma ampla gama de excelentes módulos embutidos, com uma latência de menos de um segundo. Ela possui sua própria implementação de STUN, DTLS, SCTP, etc., e algumas experimentações com QUIC e WebAssembly. Essa biblioteca open source é realmente uma boa fonte de aprendizado com documentação excelente, implementação de protocolos de rede e exemplos interessantes.
A comunidade Pion, liderada por um criador muito apaixonado, é bastante ativa, com muitas discussões de qualidade sobre WebRTC. Se você está interessado nessa tecnologia, junte-se a – você aprenderá muito.
Escrevendo CloudRetro em Golang

Implementação de um worker em GoCanais Go em ação
Graças ao design elegante dos canais Go, problemas de streaming de eventos e paralelismo são significativamente simplificados. Como mostrado no diagrama, vários componentes operam em paralelo em diferentes GoRoutines. Cada componente gerencia seu próprio estado e se comunica através de canais. A afirmação seletiva do Golang força o processamento de um evento atômico por vez a cada momento do jogo (game tick). Isso significa que, para esse design, não é necessária nenhuma blokagem. Por exemplo, ao salvar, é necessário um snapshot completo do estado do jogo. Esse estado deve permanecer contínuo, realizando entradas até que o salvamento seja concluído. Durante cada game tick, o backend pode processar apenas a operação de salvamento ou entrada, tornando o processo seguro para múltiplas threads.
func (e *gameEmulator) gameUpdate() { for { select { case <-e.saveOperation: e.saveGameState() case key := <-e.input: e.updateGameState(key) case <-e.done: e.close() return } } }Fan-in / Fan-out
Esse padrão Golang se adapta perfeitamente ao meu caso de uso CrowdPlay e Múltiplos Jogadores. Seguindo esse padrão, todas as entradas dos usuários em uma sala são integradas em um canal de entrada central. A mídia do jogo é então distribuída para todos os usuários em uma única sala. Isso nos permite compartilhar o estado do jogo entre várias sessões de jogo de diferentes usuários.

Sincronização entre diferentes sessõesDesvantagens do Golang
O Golang não é perfeito. O canal é lento. Comparado ao bloqueio, o canal Go é simplesmente uma maneira mais fácil de lidar com eventos paralelos e de fluxo, mas o canal não oferece o melhor desempenho. Sob o canal, há uma lógica de bloqueio complexa. Portanto, fiz algumas alterações na implementação, reaplicando bloqueios e valores atômicos ao substituir canais para otimizar o desempenho.
Além disso, o coletor de lixo no Golang é não gerenciado, o que às vezes provoca pausas longas e suspeitas. Isso afeta significativamente o desempenho de um aplicativo de fluxo em tempo real.
CGO
O projeto utiliza uma biblioteca existente de código aberto VP8/H264 em Golang para compressão de mídia e Libretro para emuladores de jogos. Todas essas bibliotecas são, na verdade, wrappers da biblioteca C em Go usando . Algumas das desvantagens estão listadas em . Os problemas que enfrentei:
- impossibilidade de capturar falhas no CGO, mesmo com o uso do Golang RecoveryCrash;
- impossibilidade de identificar o gargalo de desempenho quando não conseguimos detectar problemas detalhados no CGO.
Conclusão
Alcancei meu objetivo – entendi os serviços de jogos em nuvem e criei uma plataforma que ajuda a jogar jogos retro nostálgicos com meus amigos online. A criação deste projeto seria impossível sem a biblioteca Pion e o apoio da comunidade Pion. Estou extremamente grato pelo seu intenso desenvolvimento. As APIs simples fornecidas pelo WebRTC e Pion permitiram uma integração suave. Minha primeira prova de conceito foi lançada na mesma semana, mesmo sem saber com antecedência sobre a conexão peer-to-peer (P2P).
Apesar da facilidade de integração, streaming P2P é de fato uma área muito complexa na ciência da computação. Tem que lidar com a complexidade de arquiteturas de rede que existem há anos, como IP e NAT para estabelecer uma sessão peer-to-peer. Durante o trabalho neste projeto, acumulei muitos conhecimentos valiosos sobre redes e otimização de desempenho, por isso recomendo a todos que tentem construir produtos P2P usando WebRTC.
O CloudRetro atende todos os cenários de uso que eu esperava, do ponto de vista de um jogador retro. No entanto, acredito que há muitas áreas no projeto que posso melhorar, como tornar a rede mais confiável e eficiente, garantir uma melhor qualidade gráfica dos jogos ou a possibilidade de compartilhar jogos entre os usuários. Estou trabalhando arduamente nisso. Por favor, acompanhe e apoie-o se você gostar.
Fonte: habr.com







