Kutta no navegador: mesmo código, outro orçamento de frame

O Kutta agora roda dentro de uma aba do navegador. Mesmo código do desktop, GOOS=js GOARCH=wasm, nenhum fork, nenhum arquivo duplicado com uma versão “web” do solver.

Kutta soltando vórtices a 20 graus de ângulo de ataque

O que não é de graça é a velocidade. Um passo do solver custa 6,1 ms no wasm contra 2,3 ms nativo, medido no grid de 360x200 que o app usa, num Mac M4, com o mesmo solver compilado para os dois alvos na mesma máquina. Fator 2,6.

Esse número decide o ritmo da simulação. O ambiente do navegador decide o resto.

Em 60 TPS, cada tick tem 16,7 ms

O build nativo avança três passos do solver por tick. São 6,9 ms, e ainda sobra tempo para a fumaça, o campo de cor e os painéis.

Três passos no wasm dariam 18 ms. Estoura o orçamento do tick antes de desenhar qualquer coisa, e o navegador começa a engasgar. Então o build web anda um passo por tick.

O escoamento evolui mais devagar no relógio de parede. A física por passo é idêntica: o mesmo D2Q9, a mesma colisão BGK, os mesmos bounce-backs. Só o tempo simulado por segundo de tela é menor. Suave e lento lê melhor que rápido e picotado, e para uma ferramenta qualitativa isso não custa nada de verdade.

O default mora em dois arquivos que o build tag separa, substeps_js.go e substeps_other.go:

//go:build js
var substeps = 1
//go:build !js
var substeps = 3

É var, não const, de propósito. A flag -substeps sobrescreve os dois sem recompilar. Isso veio de um PR do johncohn para dar folga em hardware devagar, um Raspberry Pi rodando o Kutta em quiosque, e acabou servindo igualzinho para o navegador. Uma flag que troca velocidade de evolução do escoamento por folga de CPU serve nos dois lados.

Uma aba não escreve em caminho nenhum

O Kutta desktop usa diálogos para abrir e salvar cenas .afoil, importa SVG pelo menu e tem menu nativo. Na aba, abrir ou importar vira drag-and-drop; salvar vira download; o menu nativo sai.

A separação é por constante, não por if de runtime:

//go:build js
const onWeb = true

Constante para o compilador jogar fora o galho que não se aplica, em vez de carregar código de filesystem que nunca vai rodar. Três lugares no app perguntam por onWeb e o resto do código não sabe que existe navegador.

“Salvar” no navegador virou entregar bytes para o navegador baixar: monta um Uint8Array, embrulha num Blob, URL.createObjectURL, cria um <a download>, clica nele por código e remove. A implementação inteira cabe em webfile_js.go.

E aí tem a cicatriz. No Kutta, revogar a object URL logo depois do clique cancelava o download que ainda não tinha começado a ler. O arquivo simplesmente não aparecia, sem erro, sem aviso. A saída foi deixar a URL envelhecer num setTimeout de 60 segundos e morrer sozinha.

js.Global().Call("setTimeout", release, 60000)

Sessenta segundos é chute com folga. Não achei número melhor e não vou fingir que achei.

O tamanho

O kutta.wasm tem 17,5 MB. Servido com gzip cai para 4,25 MB.

É muito. É o runtime do Go inteiro mais o Ebitengine dentro de um download que o visitante faz antes de ver o primeiro pixel. -ldflags="-s -w" tira os símbolos e não muda o jogo. Quem quer wasm pequeno não escreve em Go, escreve em C ou Rust e aceita o resto do custo.

Eu aceito os 4 MB. O que eu ganho do outro lado é a mesma base de código gerando builds para Windows, macOS, Linux e navegador, com um solver escrito uma vez só.

O que isso muda na distribuição

A página do produto passou a rodar o simulador dentro dela. Sem download, sem instalador, sem “confie em mim e execute este .exe”: crgimenes.itch.io/kutta carrega o wasm e o túnel de vento aparece ali.

Os executáveis continuam lá, um arquivo por plataforma, sem instalador. Pague quanto quiser, incluindo zero.

O fonte segue MIT em github.com/crgimenes/kutta, e o build web é uma linha:

GOOS=js GOARCH=wasm go build -trimpath -ldflags="-s -w" -o web/kutta.wasm .

Cesar Gimenes

Última modificação
Projetos:
Tags: