Tulio Paschoalin Leao

Bastidores da versão v0.4.23 do OpenRCT2

· Tulio Paschoalin Leao · 6 min


Esse artigo foi originalmente escrito para o blog do OpenRCT2.


No OpenRCT2 temos lançado uma versão mensalmente desde o início de 2024 e com lançamentos tão frequentes, há sempre uma preocupação de que as pessoas não irão notar alguma mudança, especialmente se elas pularem uma ou duas versões. Os vídeo de “novidades” do Deurklink tem sido de grande ajuda (obrigado!) e mesmo que algumas pessoas não assistam, sabemos que elas eventualmente descobrirão sobre uma nova atração ou funcionalidade apenas jogando ou passando o olho pela lista de mudanças, mas e sobre o que é feito nos bastidores? Vamos mergulhar no que a comunidade tem feito nessa frente!

Redução da invalidação de janelas

Mix tem estado por todo os lugares recentemente (de um modo bom!), majoritariamente trabalhando em atrações e montanhas-russas, mas um dia ele pegou uma vassoura e varreu o código com força. Quando a poeira baixou (e paramos de tossir), estava mais limpo do que jamais tinhas estado.

Tá, vou parar com as analogias, o que acontece é que cada janela está ativamente ouvindo uma série de eventos do jogo para entender se ela precisa tomar alguma ação em resposta. Eis aqui alguns exemplos:

  1. Quando você redimensiona uma janela, ela precisa ser redesenhada, já que seu conteúdo anteriormente considerava dimensões diferentes.
  2. Se a janela de nova atração está aberta e alguma nova atração é inventada, ela deve ser atualizada para mostrar esse item recém descoberto.
  3. Se a aba de finanças está aberta, ela deve fazer uma transmissão em tempo real de cada compra e pagamento que o jogo está registrando conforme progride.
  4. E assim por diante…

A solução mais fácil1 é fazer com que cada janela reaja a tudo, e assim elas sempre estarão atualizadas, mas isso também faz com que o computador processe muito mais do ele deveria, já que o jogo raramente está lançando eventos que afetam todas as janelas simultaneamente em cada quadro. A solução ideal é que cada janela deve ser cuidadosamente projetada e estudada para ser sensível apenas àqueles eventos que lhe são relevantes, mas isso traz duas dificuldades com as quais Mix teve de lidar:

  1. Realmente descobrir o que cada janela precisa. Exemplos: [a] e [b].
  2. Consertar janelas que não deveriam estar reagindo a um dado evento, mas o faziam porque isso “arrumava um problema”2. Exemplos: [c] e [d].

Em um caso, ter a janela de cenários e tematização aberta sem tomar nenhuma ação (como mexer o mouse ou a própria janela) poderia levá-la a ser invalidada até 40 vezes por segundo e agora não invalida nenhuma. São 40 cálculos a menos que estamos jogando para o seu computador, isso não apenas melhora a performance do jogo, mas também torna nosso trabalho de investigar um problema mais fácil, dado que as compilações não otimizadas rodam mais rápido3. Essas melhorias aconteceram em diversas janelas, então os benefícios se acumulam, obrigado Mix!

Prevenir a interpolação desnecessária de entidades

Em animação, há um conceito em inglês chamado tweening (“interpolação”), que pode ser mal explicado como a suavização de um movimento ou transição para que pareça mais natural4. Imagine que você tenha um visitante andando da entrada do parque até a fila de um brinquedo, você não pode simplesmente remover o visitante de uma coordenada e fazê-lo aparecer na próxima, porque não vai parecer uma animação natural de caminhar. Ao mesmo tempo, você não quer ter de escrever o código para cada passo que ele toma, então faz-se aproximações.

No OpenRCT2 aplicamos o tweening a todas as pessoinhas e veículos, que são as entidades que mais se movem, mas então o Matt descobriu que estávamos fazendo isso mesmo quando não era necessário:

  1. Quando o jogo estava com o menor zoom - afinal, para que suavizar o movimento de algo que mal se vê? Seria como abrir um mapa no nível dos continentes, mas ainda gastar recursos desenhando todas as estradas de uma dada cidade, mesmo que seja impossível de ver.
  2. Quando a entidade não está visível - ao contrário do caso acima, onde se você forçar a vista pode conseguir enxergar5, dessa vez a entidade nem na tela está, então porque se importar em animá-la de modo suave?

Assim como com a redução da invalidação das janelas, aqui tudo diz respeito a evitar executar código que não é necessário. Obrigado Matt!

Limpeza do código das Pessoinhas

No jogo original, muito do código era compartilhado entre diferentes entidades, por questão de restrições de espaço, mas isso não tornava o código muito legível, nem de fácil manutenção e entendimento. Um exemplo disso é o código de Peep6, que dava voltas e escolhia diferentes caminhos dependendo se era um visitante ou um funcionário, mas sempre começava no mesmo lugar, porque no fim, são todos pessoas, né?7

Um pequeno exemplo é verificar em cada pessoinha se ele é um visitante ou um funcionário, porque se for o primeiro, temos de atualizar seus pensamentos. Com a separação feita pelo Matt agora ficou mais claro qual rotina pertence a qual e o jogo precisa fazer bem menos verificações. No futuro, isso também nos permitirá reescrever esse código sabendo exatamente se estamos afetando apenas as rotinas dos funcionários ou dos visitantes.

O custo invisível da abstração do std::shared_ptr abstraction

A última é para os meus colegas nerds de C++ que estiverem por aí: sabe-se que a gerência de memória sempre foi apontada como umas fraquezas da linguagem8, até que o C++ 11 apareceu e trouxe os ponteiros inteligentes. Então ganhamos a capacidade de criar objetos e deixar que o compilador fizesse o código para administrar a vida dele, o que foi ótimo, certo? Sim, na maioria das vezes, mas acontece que essas abstrações não são uma solução universal e podem introduzir degradações de performance, que é o que o Matt e Mix descobriram.

O diff da mudança é grande porque mudamos objetos muito fundamentais como o UIContext para serem retornados por referência, já que sabemos exatamente como e quando eles são criados e deletados, como aqui:

1- std::shared_ptr<IAudioContext> GetAudioContext() override
2+ IAudioContext& GetAudioContext() override
3  {
4-     return _audioContext;
5+     return *_audioContext;
6  }

Então tivemos de mudar centenas de usos dos operadores -> par . :

1- GetContext()->GetUiContext()->SetKeysPressed(key, scancode);
2+ GetContext()->GetUiContext().SetKeysPressed(key, scancode);

Ao contrário dos outros tópicos desse artigo, aqui praticamente não mudamos como a lógica do código estava sendo executada, só mudamos a maneira como os objetos estavam sendo armazenados e recuperados, o que rendeu um aumento de quase 5 quadros por segundo na maquina do Matt.

Por hoje é só, espero que consiga ver todas essas diferenças na versão que vem logo aí, v0.4.23!


  1. E a mais preguiçosa também, mas não nos culpe, existem MUITAS janelas e eventos no OpenRCT2. ↩︎

  2. O correto seria dizer que camuflava um problema, já que a janela estar num estado inválido por causa de alguma mudança que não deveria afetá-la não é o esperado. ↩︎

  3. E para os preguiçosos que investigam problemas com “cout” como eu, não será mais tão impensável adicionar mensagens no código de janelas, porque antes elas eram impossíveis de acompanhar. ↩︎

  4. Eu mesmo não sabia o que era e tive de perguntar ao Matt. ↩︎

  5. Talvez não, mas vamos assumir que você conseguiria. ↩︎

  6. Peep sendo a maneira que chamamos, em inglês, as pessoinhas do jogo. ↩︎

  7. Até quando manualmente pegamos visitantes chatos e os tacamos na água! ↩︎

  8. Curiosamente, também é uma de suas fortalezas, para alguns. ↩︎

#código-aberto #c++ #openrct2

Responda a este post por email ↪