BTYN

Performance

← Índice

Os limites contra os quais você realmente joga

     
Banda por cliente ~50 KB/s antes do throttling Passar disso atrasa toda a replicação — personagens, física, propriedades — não só os seus pacotes
Overhead por chamada de remote ~9 bytes 100 mensagens pequenas por frame são ~900 bytes só de cabeçalho
Taxa de requisição do cliente ~500/s, compartilhada entre remotes do mesmo tipo Spam de remote é throttlado antes de você perceber
Payload unreliable descartado acima do limite Sem erro. O pacote simplesmente não chega
Taxa segura de envio degrada acima de ~60 Hz Batching por frame não é opcional

Os 50 KB/s são compartilhados com a replicação de personagem e física do próprio motor. Só uma parte é sua para gastar.

O que o BTYN faz a respeito

Dois remotes, batch por frame

Todo pacote de um frame vai em um envio só. O overhead de cabeçalho é pago duas vezes por frame em vez de uma vez por mensagem, e tráfego normal de gameplay deixa de conseguir alcançar o limite de taxa de requisição.

Offsets constantes

Um pacote de tamanho fixo compila para uma linha reta, sem cursor e sem dispatch de tipo:

local function writeAttack(b: buffer, o: number, v: Attack, refs: { Instance }): number
    local w1 = 0
    if v.crit then w1 += 1 end
    if v.stun then w1 += 2 end
    buffer.writeu8(b, o, w1)
    buffer.writeu32(b, o + 1, v.target)
    buffer.writeu8(b, o + 5, v.combo)
    return 6
end

É isso que um compilador compra. Um schema em runtime precisa percorrer uma tabela e ramificar pelo tipo de cada campo em toda chamada; aqui não sobrou nada para percorrer.

Booleanos empacotados

Todo booleano do pacote divide um bitfield na frente. 32 flags custam 4 bytes e um writeu32. É o caso onde a distância entre schema compilado e interpretado é maior.

Uma checagem de limites por pacote

Os tamanhos são conhecidos, então um pacote fixo é validado uma vez na entrada em vez de antes de cada campo. Mais rápido e mais seguro do que não checar.

Broadcast serializado uma vez

all, list e except codificam o pacote uma única vez num buffer de rascunho e fazem buffer.copy para o batch de cada destinatário. Quarenta jogadores custam uma serialização e quarenta memcpys.

Uma alocação por flush

Os streams são reaproveitados entre frames e crescem dobrando. A única alocação no caminho de envio é o buffer de tamanho exato entregue ao remote, uma vez por flush.

Delta encoding e interest management

Cobertos no guia da API. Dois efeitos, e o segundo é maior: enviar só o que mudou, e só para quem importa.

Técnicas que importam mais do que qualquer biblioteca

Em ordem aproximada de impacto. Uma biblioteca otimiza o caso genérico; estas tratam do seu caso, e ganham por mais.

1. Não envie. O pacote mais barato é o que não existe. Replique só o que está no raio de relevância do jogador, envie por mudança em vez de por timer, e deixe o cliente derivar o que der para derivar.

2. Baixe a taxa. Muita coisa rodando a 60 Hz fica idêntica a 10 Hz com interpolação no cliente. Movimento de NPC é o clássico: seis atualizações por segundo, interpoladas, são indistinguíveis de sessenta na prática.

3. Quantize. Posição raramente precisa de f64. Ângulos cabem em um ou dois bytes. Vida cabe num u8 se o máximo for 100. Use fixed(min, max, bytes) e angle em vez de ir de f32 por reflexo.

4. Bitpacking. 32 booleanos cabem num u32 — o BTYN faz isso por você. Enums viram um byte.

5. Delta encoding. Envie o que mudou, atrás de uma máscara. É o que os channels são.

6. Opcodes, nunca strings. Nunca mande "FireballCast". O BTYN atribui um opcode numérico a cada pacote em tempo de compilação; você nunca vê um nome no fio.

7. Cuidado com Instance. Referências são caras e chegam nil quando quem recebe ainda não fez streaming do objeto. Prefira entity.

Medindo

Confie no seu jogo mais do que no benchmark de qualquer um, incluindo esta página. Use o network profiler do MicroProfiler e o painel de estatísticas de rede, com contagem realista de jogadores, e olhe três coisas:

O --check te diz o que o compilador sabe estaticamente:

lune run cli/main -- net.btyn --check
  [9    ] Muzzle         unreliable     server -> client  18 B

  largest unreliable packet: 19 B of the 800 B cap

Existe um benchmark headless do codec no repositório:

lune run bench/run
  fixed, 3 fields             7 B   encode     8.0M/s   decode    10.9M/s
  32 booleans                 4 B   encode     1.3M/s   decode     1.6M/s
  quantised vec3 + unit      18 B   encode     3.2M/s   decode     3.4M/s
  dynamic string              6 B   encode     4.0M/s   decode     7.1M/s

Ele mede CPU e tamanho no fio dos codecs gerados, com payloads que variam a cada iteração. E de propósito não se apresenta como comparação entre bibliotecas — isso exigiria as outras bibliotecas e um place de verdade para rodar. Repare nos 4 bytes para 32 booleanos; é esse o número que interessa naquela linha.

Sobre benchmarks

Benchmark de networking neste ecossistema é excepcionalmente fácil de errar. Um resultado bastante divulgado uma vez mostrou uma biblioteca enviando quase nada, porque ela fazia XOR contra um frame anterior idêntico — o payload era constante, então os deltas eram todos zero. Estava medindo um bug.

Se for medir:


Próximo: segurança · compilador e editor · referência do schema · API