Sunstone Apps
← Voltar para o blog

Performance em React Native

Migrando um tabuleiro Sudoku em React Native para Skia sem apagar o renderer antigo

Um tabuleiro Sudoku lento não pede automaticamente uma reescrita. O caminho mais seguro foi adicionar um segundo renderer e manter o contrato do jogo intacto.

Publicado: 7 min de leitura
Imagem de prévia OpenGraph deste artigo. Migrando um tabuleiro Sudoku em React Native para Skia sem apagar o renderer antigo

Um tabuleiro Sudoku é mais caro do que parece. Oitenta e uma células não são muita coisa para uma engine de jogo, mas em React Native cada célula pode virar várias views, textos, overlays animados, bordas, áreas de toque e notas.

A restrição no nosso app de Sudoku não era “trocar tudo por Skia”. O objetivo era mais estreito: descobrir se o tabuleiro ficava mais rápido em dispositivos lentos mantendo o renderer atual disponível.

Mantenha o contrato do jogo estável

A tela atual já tinha uma fronteira útil. GameScreen é dona do estado do jogo, célula selecionada, notas, eventos de score e regras de entrada. O tabuleiro recebe props simples: valores atuais, givens originais, notas, solução, seleção, presets de animação e um callback onPressCell.

Essa fronteira tornou o experimento mais seguro. A versão Skia pode receber os mesmos dados da versão React Native. Nenhuma regra de jogo, persistência, pontuação ou modal precisa saber qual renderer está ativo.

Coloque Skia atrás de config, não em setting do usuário

A escolha de renderer é infraestrutura de runtime, não preferência do jogador. Adicionamos uma flag tipada:

rendering:
  boardRenderer: skia

O ambiente local pode optar por Skia, enquanto test e production continuam no renderer existente até o smoke visual e o comparativo de performance justificarem a troca.

Preserve acessibilidade e testes

Um canvas puro é fácil de desenhar e difícil de testar. O compromisso foi deixar o Skia desenhar o tabuleiro enquanto o React Native mantém uma área de toque transparente por célula.

Essas hitboxes preservam:

  • Entrada por toque.
  • accessibilityLabel por célula.
  • Seletores de E2E web.
  • Espelhos de texto para asserts que conferem valores das células.

Essa camada tem custo, mas evita quebrar o produto enquanto o renderer ainda é experimental.

Meça antes de chamar o renderer de vitória

A primeira versão Skia provou o caminho de migração, mas não o caso de performance. Em um Redmi Note 7 lento, selecionar uma célula ficou pior no renderer Skia inicial do que no renderer React Native porque a implementação ainda tinha muitas partes React por célula e a seleção reconstruía tabuleiro demais.

O experimento melhor foi um segundo renderer: um canvas Skia para o tabuleiro e uma área transparente única, no nível do tabuleiro, para hit testing. A partir daí, o estado de seleção saiu da tela pai e o desenho no Skia foi separado em camadas estáveis: células, valores, notas, bordas e linhas ficaram parados enquanto seleção e highlights de notas atualizavam separadamente.

Isso mudou o resultado de “Skia é mais lento” para “esta arquitetura Skia é mais lenta”. No nosso probe interno de latência, o spike de superfície única levou a seleção de célula no Note 7 para cerca de 149ms de mediana depois da separação em camadas. Ainda não é instantâneo, mas finalmente ficou bom o bastante para continuar investigando.

Porte o comportamento das animações, não a lógica de negócio

A fila de animação existente já emite presets para reveal do tabuleiro, erros, unidades completas, pontos e auto-complete. O renderer Skia mapeia esses presets para transforms e overlays no canvas em vez de criar um segundo sistema de animação.

A regra fica simples: eventos de jogo continuam na camada de jogo; renderers só decidem como visualizar.

A lição

Skia não é uma correção mágica para toda tela lenta em React Native. Ele ajuda mais quando existe uma superfície visual clara que pode ser desenhada como canvas sem mover lógica de aplicação para dentro do renderer.

Para esse tabuleiro Sudoku, o padrão prático é:

  • Manter o renderer React Native atual.
  • Adicionar um renderer Skia com as mesmas props.
  • Selecionar por config.
  • Preservar input e acessibilidade na fronteira React Native.
  • Comparar performance antes de enviar para produção.

Isso é menos dramático do que uma reescrita, mas dá ao time um caminho de rollback e dados reais.

Mantenha o renderer antigo útil

O renderer antigo não deve ser tratado como código morto enquanto a versão Skia ainda está sendo provada. Ele é a baseline. Ele mostra se uma nova lentidão pertence ao Skia, ao estado compartilhado do jogo, às animações ou às condições do aparelho. Removê-lo cedo demais torna qualquer número de performance mais difícil de interpretar.

Uma boa migração de renderer mantém as duas implementações atrás da mesma fronteira. A tela passa estado do tabuleiro, seleção, notas, erros, destaques e callbacks por um componente host único. Esse host escolhe o renderer por config. O restante do jogo não deveria se importar com a estratégia de desenho ativa.

Essa fronteira também deixa rollback menos dramático. Se um device beta mostra bug visual, uma config remota ou de ambiente pode voltar o app para o renderer React Native enquanto o caminho Skia é corrigido.

O que o Skia deve e não deve possuir

Skia é excelente para desenhar um tabuleiro denso, mas não precisa ser dono de toda interação. Para Sudoku, a divisão mais segura é deixar o Skia desenhar a superfície visual enquanto o React Native mantém alvos acessíveis e comportamento de input testável. Isso preserva semântica para leitor de tela, seletores E2E e handlers conhecidos.

Essa não é a arquitetura gráfica mais pura, mas é uma arquitetura prática de app. Um jogo mobile ainda precisa de acessibilidade, analytics, haptics, ads, persistência e navegação em volta do tabuleiro. O renderer é uma parte do produto, não o produto inteiro.

Critérios de aceite para a migração

Antes de chamar um renderer de aceito, compare com a baseline em devices reais:

  • latência de toque até seleção em aparelho fraco;
  • correção visual depois de notas, erros, destaques e vitória;
  • alvos e labels de acessibilidade;
  • estabilidade E2E para interação no tabuleiro;
  • caminho de rollback por config.

Um renderer que é mais rápido só no simulador não é necessariamente melhor. Um renderer um pouco mais lento, mas estável, configurável e isolado, ainda pode ser uma boa decisão para beta.

Meça depois que a UI estabiliza

Benchmarks de renderer são fáceis de contaminar. Se um toque dispara seleção, animação, mudança de score, escrita de persistência e analytics ao mesmo tempo, o número talvez descreva a tela inteira em vez do renderer. Isso ainda pode ser útil, mas precisa ser rotulado honestamente.

Para trabalho específico de renderer, isole o cenário tanto quanto for prático. Use o mesmo puzzle, as mesmas células selecionadas, o mesmo modo de animação, o mesmo device e o mesmo tipo de build. Se outro sistema precisa continuar ativo, mantenha-o ativo nas medições antes e depois.

O objetivo não é pureza de laboratório. É evitar parabenizar um renderer por uma melhora que veio de desligar outra coisa.

A migração deve aumentar opcionalidade

Uma migração de renderer não é bem-sucedida apenas quando o novo renderer vence todos os benchmarks. Ela é bem-sucedida quando o app ganha uma forma mais segura de escolher. Se o renderer Skia é melhor em um telefone fraco, mas pior em um tablet específico, config deve permitir resposta sem reescrever lógica de jogo.

Essa opcionalidade vale muito durante beta. Testers reais usam aparelhos que o time não possui, com refresh rates, comportamento de GPU, escala de fonte e ajustes de acessibilidade difíceis de prever. Manter a escolha de renderer atrás de config transforma esses relatos em decisões de produto, não emergências.

Não esconda acessibilidade atrás de performance

O atalho tentador é mover todo input para o canvas. Isso pode deixar a camada de desenho mais limpa, mas arrisca perder acessibilidade de plataforma e hooks de teste. Um tabuleiro Sudoku não é só pixels; cada célula é um alvo com significado.

Manter hitboxes React Native acima ou ao lado da camada desenhada é menos glamouroso, mas preserva o contrato com tecnologias assistivas e testes E2E. Trabalho de performance que remove acessibilidade em silêncio não é otimização. É uma regressão com frames melhores.

Rollout precisa ser reversível

Uma flag de renderer só é útil se o app consegue enviar os dois caminhos de verdade. Mantenha contratos de dados compartilhados estáveis, evite regras de jogo específicas de renderer e deixe o valor da config fácil de inspecionar em diagnósticos. Se um relato beta diz que o tabuleiro quebrou, o time deve saber qual renderer produziu aquilo.

Conclusão

Uma migração para Skia é mais segura quando aumenta opções em vez de apagar o caminho antigo. Mantenha o contrato estável, preserve acessibilidade, meça em devices reais e transforme rollback em decisão de configuração, não em reescrita.