Pular para o conteúdo principal
Além do SDK

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

user spacethe SDK's own businessyour codemodules: rooms, matchmaking, inventory, leaderboards…primitives: events · RPC · data · groupsthe hub: DI and mounting · the user session · state recovery · message QoS · routingtransport adapters: WebSocket · PlayServ UDP · HTTP
A pilha de camadas: o seu código e os módulos acima da linha; primitivas, hub e transportes abaixo dela

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:

EixoVariação
Formatoorientado a mensagens ou a requisições
Canaiscanal único ou vários canais
Estadocom recuperação do estado da conexão ou sem
ProtocoloTCP 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ívelSignificado
at least oncereenviada até ser confirmada; o receptor tolera duplicatas
at most onceenviada uma vez, sem nova tentativa; a perda é aceitável
exactly oncededuplicada 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:

  1. 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.
  2. 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.