Os usuários ativos do Telegram, especialmente aqueles que se inscreveram em Pavel Durov, provavelmente ouviram algo sobre o fato de o Telegram ter realizado um concurso para desenvolvedores de iOS, Android e JavaScript, bem como para designers, nesses sites da Internet. Apesar do fato de ter sido um evento épico com a distribuição de prêmios sólidos (um dos participantes recebeu US $ 50 mil pelo primeiro lugar, tendo escrito o aplicativo Android mais rápido e fácil), eles de alguma forma escreveram pouco sobre isso, pelo menos no Runet. Meu post de estreia tentará corrigir a situação.

Como sou desenvolvedor desenvolvedor full-stack (para ser exato, desenvolvedor TypeScript), decidi me testar. Manila não é apenas um fundo de prêmios, mas também o próprio formato: não é um concurso de programação em que a abstração e a velocidade do pensamento são importantes. Tudo no complexo era importante aqui: experiência, velocidade de desenvolvimento a médio prazo, gosto nas questões da interface do usuário, conhecimento da ciência da computação como um todo e autocrítica. De acordo com os termos da competição, foi necessário desenvolver uma biblioteca para exibição de gráficos para uma das plataformas: iOS, Android ou Web.

Os desenvolvedores de plataformas diferentes não competiram entre si, e cada plataforma teve seus próprios vencedores. Os principais critérios foram: velocidade do trabalho (inclusive em dispositivos mais antigos), consistência com o design, animação suave e tamanho mínimo do aplicativo. As soluções e bibliotecas já existentes não podiam ser usadas, tudo tinha que ser escrito do zero.
Antes disso, participei de competições para desenvolvedores, nas quais não foram alocadas mais de 5 horas para todas as tarefas, essas horas tiveram que ser gastas em um tremendo estresse. Apesar de o Telegram não exigir tal esforço para concluir a tarefa, é um dos concursos mais difíceis dos quais tive que participar. A tarefa aparentemente descomplicada acabou sendo tão ampla que, se eu fosse paga por isso, seria capaz de cortar esses "gráficos" por meses, tentando encontrar um compromisso entre o desempenho do código e sua harmonia arquitetônica. Ajudou que três (
atual: duas, graças a
vlad2711 pela alteração) foram alocadas para a solução. Alguns dos rivais se despediram especificamente para dedicar mais tempo ao concurso, e eu decidi combinar o desenvolvimento do concurso à noite e nos fins de semana com o trabalho em "
Ontanta ", como sempre.
LONA versus SVG
A questão arquitetônica mais importante que todos nós enfrentamos foi a escolha de uma ferramenta de renderização gráfica. Atualmente, os padrões da web nos oferecem duas abordagens: por meio da geração de svg-graphics on-the-fly e da boa e velha tela. Aqui estão os prós e contras de cada um.
Tela
+ Versatilidade absoluta - com a capacidade de alterar a cor de qualquer pixel na tela, você pode desenhar o que quiser.
+ [Potencial] Alto desempenho - se você pode preparar uma tela, ela pode mostrar um bom desempenho. Seria ótimo usar o webgl, mas seu suporte em smartphones é ruim.
- Todos os cálculos e todas as renderizações manualmente - ao contrário do SVG, onde os pontos intermediários da polilinha podem ser definidos uma vez e, em seguida, você pode manipular a caixa de visualização para mover a “câmera” pelas seções da polilinha, com tela mais complicada: não há “câmeras” aqui apenas coordenadas no canto superior esquerdo; se você precisar "mover" a área de visualização atual do gráfico, deverá recalcular todas as coordenadas de todos os seus pontos em relação à nova posição da área de visualização. Em outras palavras, a caixa de exibição, que em svg está pronta, precisa ser implementada na tela manualmente.
- A animação inteira é manual - com base no parágrafo anterior, todas as animações possíveis são implementadas recalculando as coordenadas, os valores de cor e transparência e redesenhando a cena inteira N-ésimo número de vezes por segundo, e quanto mais vezes era possível recontar e redesenhar a cena, mais suave era a animação.
Svg
+ Desenho simples - basta adicionar as linhas, formas e muito mais ao SVG uma vez, manipulando os parâmetros de janela de visualização, cor e transparência, para fornecer navegação gráfica.
+ Implementação simples de animações - novamente, com base no parágrafo anterior, é suficiente Ne especificar novos valores para a caixa de exibição, cor e número de transparência de vezes por segundo, e a imagem será redesenhada, o navegador cuidará disso. Além disso, não esqueça que as formas e primitivas no SVG podem ser estilizadas em CSS, para que possam ser animadas usando animações CSS3, o que abre as mais amplas possibilidades para obter animações interessantes com o mínimo de esforço.
+ Bom desempenho por padrão - se você pode preencher facilmente algo lento e consumir centenas de recursos com tela, o resultado baseado no SVG sempre parecerá bastante leve, decente e suave.
Mas há um outro lado da moeda.
- Oportunidades modestas de otimização - como não estamos desenhando svg, mas o navegador, é impossível controlar esse processo - se você deseja aumentar o desempenho, por exemplo, armazenando em cache elementos desenhados individualmente, não pode fazê-lo de nenhuma maneira. Provavelmente isso já foi feito pelo navegador, mas não podemos ter certeza até o final.
- Ferramentas limitadas - no SVG, não controlamos mais todos os pixels da tela, mas pensamos e codificamos na estrutura das primitivas vetoriais. No entanto, para esta tarefa, esse é um sinal de menos insignificante, impondo algumas restrições novamente insignificantes no contexto da tarefa de competição.
Eu nunca tive que me preocupar em escolher uma ferramenta, porque tenho um traço de caráter nojento - sou maximalista e costumava usar apenas a minha ferramenta favorita no meu trabalho. Aconteceu que desde meus dias de estudante, quando eu me divertia com o DirectDraw, minha ferramenta favorita era sempre uma tela na qual "faça o que você deseja". E a tela para resolver um problema competitivo realmente acabou sendo boa, mas realmente jogou nas minhas mãos apenas uma de suas vantagens: as mais amplas possibilidades de otimizações, já que o principal critério ainda era o desempenho do aplicativo.
Bom código não é bom
A tarefa é clara: você precisa desenhar pontos na tela no lugar certo e na hora certa. Resta escrever o código. Mais uma vez, foi necessário escolher, desta vez, entre escrever um código compacto produtivo com um "calçado" no estilo processual ou pouco produtivo e ainda mais pouco compacto no meu orientado a objetos favorito. Você provavelmente já adivinhou que eu escolhi a segunda opção, temperando-a com outra das minhas favoritas - o TypeScript.
E essa escolha não foi muito correta. Devido ao uso de abstrações e encapsulamento, nem sempre é possível salvar, transmitir e reutilizar resultados de cálculos intermediários, o que afeta mal o desempenho. E devido ao uso generalizado disso, sem o qual o OOP no JS é impossível, o código é pouco minificado, enquanto o tamanho também importava.
É hora de dar um link para o github:
github.com/native-elements/telechart . Se estiver interessado, recomendo prestar atenção ao histórico de confirmações, pois mantém uma memória de provações de otimização e tentativas malsucedidas de extrair alguns quadros de renderização extras por segundo.
Bem, na competição eu não levei o prêmio. E o problema, como costuma acontecer com os programadores, acabou não sendo experiência insuficiente, inteligência ou velocidade rápidas, mas autocrítica insuficiente: o fato de eu ter conseguido fazer isso funciona e se parece com a imagem, fiquei satisfeito, mas Quanto aos freios de renderização, pensei que fiz tudo o que podia, o resto provavelmente fez o mesmo. Tenho vergonha de falar sobre isso, mas tinha certeza de que ocuparia o primeiro ou o segundo lugar. Na verdade, eu escrevi um programa de frenagem e buggy, não o pior, mas longe do melhor. Quando vi o trabalho de outros desenvolvedores, percebi que não tinha chance e só podia morder meus cotovelos. Se eu fosse imparcial em relação ao meu trabalho, estaria envolvido em produtividade, a parte mais importante da tarefa da concorrência.
Uma das lições mais valiosas da minha vida profissional, das quais não me canso, é que um bom engenheiro, ao contrário de um artista, por exemplo, é obrigado a avaliar objetivamente a qualidade de seu trabalho, descartando a autoconfiança, porque o resultado de seu trabalho não deve agradar apenas aos olhos. mas deve funcionar corretamente e bem.
Esta foi a primeira etapa da competição. Os vencedores foram generosamente recompensados. Para minha alegria indescritível, a história não terminou aí, porque o segundo estágio foi anunciado:
Foi necessário refinar sua arte, em apenas uma semana, implementando tipos adicionais de gráficos. Mostrarei imediatamente o que aconteceu e, abaixo, contarei como aconteceu.
No meu caso, antes de adicionar novas funcionalidades, eu precisava entender o desempenho da antiga. O primeiro problema que resolvi é
Animação de espasmosMesmo se você tiver energia suficiente para produzir 60 quadros por segundo, a animação não será suave se a posição do elemento ou sua transparência não for determinada pelo tempo decorrido desde o início da animação. Isso ocorre devido a intervalos de tempo desiguais entre os ticks: por exemplo, um tick funcionou após 10 ms e o segundo após 40, enquanto no primeiro e segundo ticks o objeto se moveu para a esquerda em 1 pixel - ou seja, sua velocidade de movimento flutua constantemente, visualmente, parece uma "contração". Em outras palavras, você precisa fazer algo errado:
let left = 10, interval = setInterval(() => { left += 1 if (left >= 90) { clearInterval(interval) } }, 10)
E assim:
let left = 10, startLeft = 10, targetLeft = 90, startTime = Date.now(), duration = 1000, interval = setInterval(() => { left = startLeft + (targetLeft - startLeft) * (Date.now() - startTime) / duration if (left >= targetLeft) { left = targetLeft clearInterval(interval) } })
Como existem muitos parâmetros animados no código, filmei uma
classe universal que facilita a tarefa e também adiciona ising à animação. É bastante fácil de usar:
let left = Telemation.create(10, 90, 1000) … drawVerticalLine(left.value)
Então a regra dos 60 fps entra em jogo. Os jogadores de PC vão me entender: para que uma animação pareça perfeita, ela deve ser renderizada a uma velocidade de pelo menos 60 fps. Assim, cada renderização do quadro não deve demorar mais que 1/60 de segundo. Isso requer hardware poderoso e bom código.
Pesquisas posteriores mostraram que
A tela de pintura fica mais lenta se houver elementos html acima da tela .
Inicialmente, usei elementos html "vazios" para implementar o controle sobre a viewport atual:
Esses elementos foram colocados no topo da tela e, apesar de não terem conteúdo, foram usados apenas para rastrear eventos do mouse, como resultado de experimentos que resultaram em uma presença muito reduzida do desempenho da renderização. Removendo-os e complicando a lógica de determinar eventos para controlar um pouco a área de visualização, aumentei a velocidade de renderização do quadro.
Restava puxar a última unha da tampa do caixão da apresentação: eu fiz
Cache de MinimapaAntes disso, as linhas do minimapa eram desenhadas a cada quadro novamente. Essa é uma operação cara, pois exibiu a programação inteira do ano (365 pontos por linha). A solução óbvia, que eu estava com preguiça de implementar desde o início, foi desenhar as linhas do gráfico para o minimapa uma vez, salvar o resultado no cache e usá-lo no futuro. Após essa otimização, o desempenho do aplicativo não é mais embaraçoso.
O que vem depois?
Ainda havia muitas lutas bem-sucedidas e pouco executadas pelo desempenho: tentativas de armazenar em cache os resultados dos cálculos de coordenadas, experimentos com os parâmetros CanvasRenderingContext2D lineJoin (mitra mais rápida), mas eles não são tão interessantes, porque não deram um ganho perceptível de desempenho ou não o deram.
Nos oito dias, gastei cinco na aceleração do código e apenas três na conclusão da nova funcionalidade. Sim, levei apenas três dias para adicionar novos tipos de gráficos, e aqui o OOP acabou sendo muito útil, com isso a base de código aumentou um pouco. Não tive tempo suficiente para concluir a tarefa de bônus (+5 gráficos adicionais). Acredito que naqueles cinco dias que gastei na eliminação das consequências da minha autoconfiança, eu poderia gastar na solução do problema do bônus.
No entanto, meu trabalho rendeu o resultado: 4º lugar e um prêmio de “consolação” de mil dólares:
A propósito, a competição continuou, mas sem mim.
Estou satisfeito com a participação: além de ser apenas uma aventura interessante, tive uma boa experiência profissional e uma lição de vida.
Além disso, usei essa biblioteca no desenvolvimento de nosso timetracker corporativo, sobre o qual também pretendo falar em um futuro próximo.
Para discussão, proponho a seguinte pergunta: por que o Telegram precisa de tudo isso? Acredito que, por dinheiro adequado, o Telegram receberá a melhor biblioteca do mundo por exibir gráficos: o melhor resultado de centenas de tentativas de fazer melhor do que outras. O princípio competitivo permite que você obtenha um nível de qualidade tão alto que ninguém pode fazer por encomenda e sem dinheiro.
E alguns links: