Nós tiramos um tempo para conversar com Ben, um dos nossos programadores de IU que corrigiu o bug do "texto embaralhado" que tem afetado o Path of Exile por muito tempo. Por este ser um bug interessante em si, ele foi gentil o suficiente para escrever um texto póstumo. Veja-o no post!



Após anunciar que finalmente encontramos e corrigimos o infame bug do "texto embaralhado" que os jogadores vinham encontrando nos últimos 6 anos, alguns jogadores expressaram interesse em saber um pouco mais sobre este problema já antigo. Eu sempre me interessei em dar o meu melhor em um post com detalhamento técnico, então aqui vamos nós! O bug já era conhecido por alguns nomes diferentes, sendo referido como texto "embaralhado", "corrompido" ou "escangalhado". Internamente ele era conhecido como sendo "embaralhado", então é assim que vamos chamá-lo.

Posso te dizer que o bug foi introduzido no código base no dia 25 de Abril de 2016 e entrou na produção na 2.3.0 com a liga Prophecy. Ele foi introduzido por alguma reestruturação da engine de texto para dar suporte as então recém apresentadas versões do Xbox do Path of Exile.

Os Sintomas

Sinto que posso dizer que a maioria dos jogadores já encontraram esse bug em algum momento no decorrer dos anos, em sua maioria sendo mostrado após longas sessões de jogo, mas alguns jogadores o encontravam com maior frequência do que outros. Ele poderia afetar qualquer texto no jogo e haviam dois efeitos distintos do bug: Um onde o espaçamento entre os glifos individuais desenhados, eram ou grandes demais ou pequenos demais.



E outro onde os caracteres individuais estavam sendo representados graficamente pelo glifo errado:


Alguns jogadores com olhos de águia notaram que no segundo caso, aplicar um cifrão de substituição poderia restaurar o texto original.

Examplo: "Pevpf`^l D^j^db" -> "Dano Físico", ^ mapeado para "a".

Uma coisa que sempre nos ocorreu como estranho era que letras maiúsculas eram diferentes (ou em alguns casos, não teriam offset nenhum) comparado a letras minúsculas.

A Caçada

O primeiro ticket sobre este problema foi criado no dia 4 de Junho de 2016, criado a partir dos relatórios nos fóruns logo após o lançamento da liga Prophecy. O maior problema era que nunca conseguíamos encontrar uma forma de reproduzir o bug de forma consistente em nossas máquinas, com ele só aparecendo raramente e de forma aleatória. Pelo que ouvi nós só o tivemos na máquina de um programador uma ou duas vezes, o que é muito importante para que conseguíssemos inspecionar o que a memória reteve para conseguir pistas do que aconteceu de errado. Até conseguirmos encontrar os passos de reprodução, o melhor que podíamos fazer era algumas correções especulativas e esperar que o problema parasse de ser reportado. Devido a falta de encontrar qualquer coisa relacionada e ele não ser um problema tão grave, ele foi rebaixado para uma menor importância para que mais tempo pudesse ser dedicado a novos recursos e outras correções.

Vários desenvolvedores (eu incluso) tentaram por si mesmos corrigir o problema durante os anos, tudo isso enquanto mais relatórios de usuários eram adicionados todos os meses para nos lembrar deste difícil problema. Ao aglomerar os relatórios, screenshots e minha própria experiência eu pude perceber que:

  • Ele afetava estilos de fontes individuais (combinação de tamanho, status itálico/negrito), ao invés de textos em particular ou entradas.
  • Ele não parecia ser uma geração de textura, corrompimento ou problema relacionado ao atlas, já que nenhum dos glifos nunca pareceram cortados ao meio. As raras vezes que ele aconteceu conosco na máquina de um programador também confirmaram isso.
  • Sair do jogo não resolvia o problema na maioria das vezes, era necessário fechar e abrir o cliente.
  • Notei que nunca tivemos um relato disso acontecendo no Xbox, PlayStation ou MacOS, o que acabou me ajudando a afunilar a maioria até uma área em particular da engine de texto.

Por volta do lançamento da Scourge, notei que o bug pareceu começar a ser reportado com um pouco mais frequência e comecei a experienciar mais o bug em minhas próprias sessões de jogo. Gravei a maioria destas ocorrências, coletei imagens dos jogadores e comecei a construir alguns palpites, mas ainda não conseguia encontrar os passos de reprodução diferentes de "jogar muito o jogo". Há algumas semanas atrás eu tive um pouco de espaço entre minhas atividades e decidi fazer outra tentativa séria, gastando alguns dias tentando adivinhar meu caminho pela engine de texto para ler tudo e entender todos os detalhes.

A Correção

Enquanto imerso no código da engine de texto, finalmente encontrei a seguinte função:

SCRIPT_CACHE* ShapingEngineUniscribe::GetFontScriptCache( const Resources::Font& font )
{
    const auto font_resource = font.GetResource()->GetPointer();
    // `font_script_caches` here is a map of `const FontResource*` to `SCRIPT_CACHE` values
    auto it = font_script_caches.find( font_resource );
    if( it == std::end( font_script_caches ) )
        it = font_script_caches.emplace( std::make_pair( font_resource, nullptr ) ).first;
    return &it->second;
}

Para não programadores: esta função pega uma referência de uma fonte em particular e usa seu local na memória como chave (valor lookup) para um objeto de informação SCRIPT_CACHE, criando uma nova entrada caso ela não exista. A função então retorna um apontador para o objeto SCRIPT_CACHE, que permite que o convocador da função modifique o SCRIPT_CACHE armazenado ao invés de uma cópia que não teria estas mudanças persistindo no mapa 'font_script_caches'.
O objeto SCRIPT_CACHE aqui é um objeto de arquivo opaco usado pela biblioteca Windows Uniscribe (que nós só usamos para versões do Windows do cliente). A documentação do Uniscribe não nos dá nenhuma informação sobre o que exatamente é armazenado por ele, ele só diz que a aplicação precisa manter um destes para cada "estilo de caractere" utilizado. A partir do embaralhamento dos efeitos do texto podemos inferir que ela é utilizada no espaçamento e mapeamento dos caracteres das texturas dos glifos ao menos.

Logo de cara essa função parece estar fazendo algo completamente racional, o que provavelmente é o porque este problema nunca foi notado por todos estes anos. Você só consegue ver o problema uma vez que entende que os recursos de fonte só podem ser descarregados pelo gerenciador de recursos uma vez que eles não estejam mais em uso. O bug então ocorre quando outra fonte (diferentes tipos de fontes, estilos e/ou tamanhos) são carregados pelo gerenciador de recursos no mesmo local exato da memória, fazendo com que a nova fonte reutilize o antigo SCRIPT_CACHE.
Uma vez que encontrei isso, fiz alguns testes para confirmar minha que minha teoria estava correta e este era o problema.

Forçando todas as fontes a usaram o mesmo cache de script produzia imediatamente isso ao iniciar o jogo:


Yohoo! Todos os sintomas foram mostrados, o que também era confirmação de que estes efeitos eram do mesmo problema e não de problemas separados. A partir disso eu consegui reproduzir o bug naturalmente ao carregar e descarregar propositalmente quantas fontes fossem possíveis, até que você tivesse uma nova fonte ocupando o local de memória de uma antiga:


Agora que sabemos qual é o problema, havia algumas formas de corrigi-lo: Você poderia mover o SCRIPT_CACHE para pertencer ao objeto Resource::Font, deletar o antigo SCRIPT_CACHE sempre que a fonte fosse descarregada, ou trocar o valor lookup do endereço de memória para, ao invés, ser baseado no typeface, tamanho e estilo da fonte, o que é o que torna a fonte única. Todas estas opções funcionam mas cada uma delas tem seus próprios prós e contras e deveriam ser pesados baseado em como se encaixam em sistemas maiores.

Resumo

A causa do bug em si não é muito interessante, perceber que os endereços de memória podem e são reutilizados, você precisa ter cuidado se/quando usar apontadores como chaves. Este bug ainda permanecerá na minha memória, porém, devido aos sintomas particularmente estranhos, sendo bem chato de rastrear e até mesmo por sua notoriedade ter persistido por todos estes anos. Eu quase sentirei falta dele, de certa forma, já que é um "grande mistério" a menos para meu cérebro pensar. Acho que só preciso encontrar meu próximo mistério para me fascinar!

Obrigado a todos que relataram este problema e outros problemas no decorrer dos anos! O desenvolvimento de softwares e debugging em particular pode ser estranho e as menores coisas podem produzir bugs muito esquisitos. Relatos detalhados de bugs são sempre muito valiosos para construir a imagem do que poderia estar acontecendo e nos ajuda a reproduzir o problema sozinhos, o que então nos leva a desenvolver e testar correções ao invés de ficar tateando no escuro.


R.I.P T l e e v
Postado por 
em
Grinding Gear Games

Reportar Post do Fórum

Reportar Conta:

Tipo de Reporte

Informação Adicional