Esta página ainda não foi traduzida: mostrando a versão em inglês.
Por baixo dos panos: transporte e hub
Uma referência de arquitetura, não uma interface que você chama. Nada nesta página aparece na API com que você programa: não há socket, canal, envelope nem retentativa para você gerenciar. Conceitos principais dá nome às camadas; o mecanismo é descrito só aqui, para que um arquiteto possa conferir o que o SDK faz com uma conexão perdida, um módulo desativado ou uma mensagem que precisa chegar exatamente uma vez.
A pilha de camadas
Cinco camadas, de cima para baixo: espaço do usuário, módulos, primitivas, o hub e, abaixo dele, os adaptadores de transporte. As duas de cima formam o espaço do usuário; tudo o que fica abaixo é assunto interno do SDK.
- O espaço do usuário é o seu código. Ele enxerga módulos, e o vocabulário para aí.
- Os módulos são a camada aplicada: salas, matchmaking, inventário, rankings. Eles formam um grafo, não uma árvore, e é isso que o hub precisa resolver quando um deles é desligado (Herança e composição descreve essa estrutura em si).
- As primitivas são a primeira implementação, aquela que tudo referencia: dados, eventos, RPC, grupos. Um módulo é um conjunto nomeado de primitivas mais as próprias regras.
- O hub é o controlador: injeção de dependências, montagem de módulos, a sessão do usuário, recuperação de estado, qualidade de serviço das mensagens e o roteamento de cada mensagem recebida para o módulo montado para ela.
- Os transportes são adaptadores para um protocolo. Existem vários, e o hub trata todos da mesma forma.
Transportes são adaptadores
Vai haver mais de um transporte, e eles diferem em pontos que, sem essa camada, vazariam para todos os módulos:
| Eixo | Variação |
|---|---|
| Formato | orientado a mensagens ou a requisições |
| Canais | canal único ou vários canais |
| Estado | com recuperação do estado da conexão ou sem |
| Protocolo | TCP ou UDP |
Hoje isso significa WebSocket, o transporte UDP da PlayServ e HTTP simples. Cada um é um adaptador que esconde os próprios detalhes de implementação, e todos expõem a mesma coisa para cima: uma sessão de transporte. O hub guarda uma sessão, nunca um socket, então nada acima do adaptador precisa pensar na interface de rede.
Uma sessão de transporte por vez
Um canal de transporte e uma sessão de transporte por vez. Rodar um transporte de backend para os rankings enquanto um transporte de cliente mestre carrega a sessão ao vivo está fora do escopo, e nenhuma API depende de haver vários canais abertos. Até que uma versão futura diga o contrário, o contrato é a sessão única.
O hub esconde o transporte por completo
Para baixo, o hub fala a interface de transporte. Para cima, oferece estado, eventos e a sessão do usuário. Nem o código dos módulos nem o do jogo conseguem saber qual transporte está por baixo, como o hub agrupou uma chamada num lote ou o que ele fez para voltar a um estado consistente depois de uma interrupção.
- A sessão do usuário pertence ao hub, não a um módulo. Reconexão, retomada e recuperação de estado acontecem uma única vez, no hub, para tudo o que está montado nele.
- A QoS das mensagens pertence ao hub, não ao módulo de dados. Envelopes, retentativas e empacotamento são mecânica do hub.
QoS das mensagens: três níveis
Um módulo declara só a garantia de entrega de que precisa:
| Nível | Significado |
|---|---|
at least once | reenviada até ser confirmada; o receptor tolera duplicatas |
at most once | enviada uma vez, sem nova tentativa; a perda é aceitável |
exactly once | deduplicada e confirmada; o nível mais caro, usado onde é obrigatório |
Como a garantia é cumprida é assunto do hub — não do módulo, nem seu.
DI, montagem e módulos desativados
O hub instancia os módulos e os monta — na raiz ou num namespace —, resolvendo as dependências de cada módulo em relação às primitivas e aos outros módulos. Um build que não precisa de um módulo não o monta. A montagem usa namespaces, e um segundo módulo que tente ocupar um ponto de montagem já em uso é rejeitado no momento da montagem: a composição falha ali, nunca na primeira chamada ao módulo.
Como os módulos formam um grafo, desligar um deles tem consequências mais adiante, e o hub segue exatamente um de dois caminhos:
- Desativar a cadeia dependente. Todo módulo que precisa do que falta também é desligado, e as interfaces dele deixam de existir em vez de falhar.
- Declarar funcionalidade degradada. Os dependentes continuam montados e anunciam o que não conseguem mais fazer.
Não existe terceiro caminho: um módulo montado nunca descarta em silêncio as operações que não consegue mais executar, então uma dependência desativada é sempre observável.
O que fica acima da linha
Todas as promessas das páginas de módulo se cumprem acima dela: alterar uma entidade é a operação de rede, um hook é uma função tipada, entrar numa sala é uma chamada só. Os nomes das camadas podem chegar até você — Conceitos principais aponta para cá —, mas nada abaixo da linha é uma chamada que o seu código faz. Essas camadas existem para que as promessas de cima sobrevivam a uma troca de transporte.
O que você vai encontrar — o contexto de entrega em que os seus handlers rodam, quando um handle termina e a implementação em memória usada nos testes — está uma página acima: Threads, ciclo de vida e testes.