Pensamentos sobre C ++ moderno e desenvolvimento de jogos

O novo ano para desenvolvedores de jogos começou com uma onda de críticas que caíram sobre o comitê de padronização de C ++ após a publicação de Reclamações de Aras Prankevichus sobre o Modern C ++ . Surgiu uma pergunta séria: o comitê de padrões realmente perdeu o contato com a realidade, ou é o contrário, e esses desenvolvedores de jogos estão se afastando do resto da comunidade C ++?

Nós oferecemos uma tradução do popular post de Ben Dean, um veterano da indústria de jogos , que trabalhou por um longo tempo na Blizzard, Electronic Arts e Bullfrog como desenvolvedor de C ++ e líder de equipe, no qual ele responde às críticas do ponto de vista de sua própria experiência.
TL; DR: O Comitê de Padronização do C ++ não tem o objetivo oculto de ignorar as necessidades dos desenvolvedores de jogos, e o C ++ "moderno" não se tornará uma linguagem "depurada".
Durante a semana passada , houve uma discussão ativa no Twitter , durante a qual muitos programadores - especialmente aqueles que trabalham no campo do desenvolvimento de jogos - disseram que o atual vetor de desenvolvimento do "C ++ moderno" não atende às suas necessidades . Em particular, do ponto de vista de um desenvolvedor de jogos comum, tudo parece que o desempenho da depuração no idioma é ignorado e a otimização do código se torna esperada e necessária.

Devido ao fato de que em 2019 eu consegui trabalhar na indústria de jogos por mais de 23 anos, tenho minha própria opinião com base em observações sobre esse tópico em relação ao desenvolvimento de jogos, que gostaria de compartilhar. A depuração é importante para os desenvolvedores de jogos e por quê? Quais são os problemas associados a ele?

Para começar - uma pequena digressão na história.

Muitos desenvolvedores de jogos em C ++ trabalham no Microsoft Visual C ++. Historicamente, um enorme mercado de jogos se formou em torno das plataformas Microsoft, e isso afetou a experiência típica de um programador de jogos comum. Nos anos 90 e 2000, a maioria dos jogos foi escrita com essas circunstâncias em mente. Mesmo com o advento de consoles de outros fabricantes e a crescente popularidade de jogos para celular, os ativos de muitos estúdios AAA e de vários programadores de jogos hoje em dia são ferramentas feitas pela Microsoft.

O Visual Studio é sem dúvida o melhor depurador de C ++ do mundo. Além disso, o Visual Studio realmente se destaca mais em termos de depuração de programas - mais do que com seu front-end, back-end, implementação STL ou qualquer outra coisa. Nos últimos cinco anos, a Microsoft fez progressos significativos no desenvolvimento de ferramentas de desenvolvimento em C ++, mas mesmo antes disso, o depurador no Visual Studio sempre foi muito legal. Portanto, quando você está desenvolvendo em um PC com Windows, sempre tem um depurador de classe mundial disponível.

Dado o exposto, vejamos o processo de obtenção de código no qual não haverá bugs; oportunidades que temos do ponto de vista de um programador que não lida com jogos; bem como as limitações enfrentadas pelos desenvolvedores de jogos. Se você reformular o argumento principal em favor do "vetor de desenvolvimento do C ++ moderno", ele será reduzido a tipos, ferramentas e testes. Seguindo esse pensamento, o depurador deve ser a última linha de defesa . Antes de alcançá-lo, temos as seguintes opções.

Oportunidade Nº 1: Tipos


Podemos usar a digitação forte necessária para eliminar classes inteiras de bugs em tempo de compilação. A digitação forte é, sem dúvida, uma oportunidade que a recente evolução do C ++ nos proporcionou; por exemplo, começando com o C ++ 11, conseguimos:

  • extensão significativa de type traits de type traits ;
  • inovações como nullptr e scoped enum com scoped enum para combater a herança C - tipagem fraca;
  • GSL e ferramentas auxiliares;
  • conceitos em C ++ 20.

Alguns de vocês podem não gostar da metaprogramação de modelos; outros podem não gostar do estilo de codificação que usa o auto quase universalmente. Independentemente dessas preferências, o principal motivo para usar os estilos listados em C ++ é claramente traçado aqui - este é o desejo de ajudar o compilador para que, por sua vez, possa nos ajudar, usando ao mesmo tempo o que sabe melhor: o sistema de tipos.

Se falamos sobre programação de jogos, a digitação forte aqui é um amplo campo de pesquisa, e é ativamente usada por programadores de jogos familiares a mim, interessados ​​em melhorar suas habilidades no uso de C ++ na prática. Duas coisas importantes são preocupantes aqui: o efeito no tempo de compilação e o efeito na legibilidade do código.

Francamente, você pode facilmente ignorar o tempo de compilação - mas apenas com a condição de ser um programador de uma empresa muito grande que não joga jogos e tem uma infraestrutura interna estabelecida e um poder de computação sem fim para compilar qualquer código que você possa escrever . Essas grandes empresas estão preocupadas com o custo da compilação - portanto, usam módulos - mas, como regra, isso não causa problemas para desenvolvedores individuais. Ao mesmo tempo, para a maioria dos programadores de jogos, esse não é o caso. Os desenvolvedores independentes não têm fazendas para construir; Os desenvolvedores de jogos AAA geralmente usam algo como Incredibuild , mas, como podem trabalhar facilmente com uma base de código com 10 anos ou mais, o processo de compilação ainda pode levar de 15 a 20 minutos.

Podemos discutir sobre o custo relativo da adição de hardware versus o custo do tempo do programador, e concordo com a opinião de que o hardware é mais barato:

  • O hardware é uma despesa real real que será alocada ao orçamento do trimestre atual, em contraste com as despesas não tão tangíveis em tempo / contratação / semelhantes, que serão alocadas por um período mais longo. As pessoas não lidam bem com a decisão em favor de tal compromisso, e as empresas são construídas especialmente para otimizar o lucro a curto prazo.
  • A infraestrutura requer suporte e quase ninguém entra na indústria de jogos para se tornar um engenheiro de lançamento. Comparado a outras áreas em que o C ++ é usado, o salário dos desenvolvedores de jogos não é tão alto - e os engenheiros que não são de jogos são pagos ainda menos aqui.

Também se pode especular sobre o fato de que o tempo de compilação nunca deveria ter atingido tal estado; e novamente eu concordo com você. O preço disso é vigilância constante - novamente, proveniente de um engenheiro de lançamento - e, idealmente, alguma ferramenta automatizada que permite acompanhar as alterações no tempo necessário para construir a construção. Felizmente, com o advento dos sistemas de CI, esse objetivo pode ser alcançado muito mais facilmente hoje.

Oportunidade No. 2: Ferramentas


Devemos usar o máximo de ferramentas disponíveis - avisos, análises estáticas, desinfetantes, ferramentas de análise dinâmica, criadores de perfil e outros.

Minha experiência é que os desenvolvedores de jogos usam essas ferramentas sempre que possível, mas aqui a indústria como um todo tem vários problemas:

  • Essas ferramentas tendem a funcionar melhor em plataformas que não são da Microsoft - e, como mencionado anteriormente, esse não é um cenário típico de desenvolvimento de jogos.
  • A maioria dessas ferramentas visa trabalhar com C ++ "padrão". Eles CStaticVector suportam std::vector , mas não minha classe auto-escrita CStaticVector de um mecanismo hipotético. Obviamente, culpar as ferramentas é inútil, mas essa ainda é uma das barreiras ao uso que os desenvolvedores precisam superar.
  • Criar e manter uma cadeia de ICs que executará todas essas ferramentas requer a presença de engenheiros de lançamento - e, como mencionado anteriormente, contratar pessoas para trabalhos de engenharia que não estejam diretamente relacionados a jogos é um problema sistêmico para a indústria de jogos.

Portanto, como essas ferramentas funcionam tão bem com C ++ padrão, por que os desenvolvedores de jogos não usam STL?

Por onde começar a resposta a esta pergunta? Talvez, da próxima excursão à história do desenvolvimento de jogos:

  • Até o início dos anos 90, não confiávamos nos compiladores C, por isso escrevíamos jogos em assembler.
  • Desde o início até meados dos anos 90, começamos a confiar nos compiladores C, mas ainda não confiávamos nos compiladores C ++. Nosso código era C, que usava comentários no estilo C ++, e não precisávamos mais escrever typedefs para nossas estruturas o tempo todo.
  • Por volta de 2000, a revolução do C ++ ocorreu no mundo do desenvolvimento de jogos. Era uma era de padrões de design e grandes hierarquias de classe . Naquela época, o suporte da STL aos consoles deixava muito a desejar, e os consoles dominavam o mundo na época. No PS2, estamos sempre presos ao GCC 2.95.
  • Por volta de 2010, foram lançadas mais duas revoluções. A dor do uso de grandes hierarquias de classe estimulou o desenvolvimento de uma abordagem componente ao código. Essa mudança continua sua evolução hoje na forma de arquiteturas de sistema de componentes de entidade. De mãos dadas com isso estava a segunda revolução - uma tentativa de tirar proveito das arquiteturas de multiprocessadores.

Durante essas mudanças de paradigma, as próprias plataformas de desenvolvimento de jogos estavam constantemente mudando e mudando seriamente. A memória segmentada deu lugar a um espaço de endereço plano. As plataformas se tornaram multiprocessadores, simétricas e não muito. Os desenvolvedores de jogos, acostumados a trabalhar com arquiteturas da Intel, tiveram que se acostumar com o MIPS (Playstation), depois com um hardware especial com CPUs heterogêneas (PS2), depois com o PowerPC (XBox 360), e com uma heterogeneidade ainda maior (PS3) ... A nova plataforma possui novos recursos de desempenho para processadores, memória e unidades. Se você queria alcançar o desempenho ideal, foi forçado a reescrever seu código antigo, e muitos e frequentemente. Não vou nem mencionar o quanto os jogos foram influenciados pelo surgimento e crescimento da popularidade da Internet, bem como pelas restrições que os proprietários de plataformas impuseram aos desenvolvedores.

Historicamente, as implementações de STL em plataformas de jogos são insatisfatórias. Não é segredo que os contêineres da STL não são adequados para jogos. Se você empurrar o desenvolvedor do jogo contra a parede, talvez ele admita que std::string esteja bem, e std::vector seja uma opção padrão razoável. Mas todos os contêineres contidos no STL têm um problema de controle de alocação e inicialização. Em muitos jogos, você precisa se preocupar com as limitações de memória para várias tarefas - e nos objetos para os quais você provavelmente terá que alocar memória dinamicamente durante o jogo, os alocadores de laje ou arena são frequentemente usados. Tempo constante amortizado não é um bom resultado, uma vez que a alocação é potencialmente uma das coisas mais "caras" que podem acontecer durante a execução do programa, e eu não quero pular um quadro apenas porque aconteceu quando eu não esperava. . Como desenvolvedor de jogos, preciso gerenciar meus requisitos de memória com antecedência.

Uma história semelhante é obtida para outras dependências em geral. Os desenvolvedores de jogos querem saber o que cada ciclo do processador leva, onde, quando e por que cada byte de memória é responsável, e também onde e quando cada encadeamento de execução é controlado. Até recentemente, os compiladores da Microsoft alteravam a ABI a cada atualização - portanto, se você tivesse muitas dependências, reconstruir todas elas poderia ser um processo doloroso. Os desenvolvedores de jogos geralmente preferem pequenas dependências fáceis de integrar, fazem apenas uma coisa e fazem bem - de preferência com uma API estilo C - e são usadas por muitas empresas, estão disponíveis ao público ou têm uma licença gratuita que não requer uma indicação do autor. SQLite e zlib são bons exemplos de quais desenvolvedores de jogos preferem usar.

Além disso, a indústria de jogos em C ++ tem um histórico rico de pacientes com a síndrome "Não inventado aqui". Isso era de se esperar da indústria, que começou com entusiastas solteiros que criavam suas próprias coisas com equipamentos completamente novos e não tinham outras opções. A indústria de jogos, entre outras coisas, é a única em que os programadores são indicados nos créditos em nenhuma ordem específica. Escrever uma variedade de coisas é divertido e ajuda sua carreira! É muito melhor construir algo próprio do que comprar pronto! E, como estamos tão preocupados com o desempenho, podemos adaptar nossa solução de forma que ela seja adequada especificamente para o nosso projeto - em vez de usar uma solução generalizada que desperdice recursos disponíveis sem pensar. A hostilidade ao Boost é o principal exemplo de como esse pensamento se manifesta no desenvolvimento de jogos. Trabalhei em projetos da seguinte maneira:

  • Para começar, para resolver um problema específico, conectamos uma biblioteca do Boost ao projeto.
  • Tudo funciona muito bem. Quando você precisa atualizar, ocorre um pouco de dificuldade, mas não mais do que na atualização de qualquer outra dependência.
  • Outro jogo quer usar nosso código, mas o obstáculo é que usamos o Boost - apesar de nossa experiência com o Boost ter corrido muito bem.
  • Removemos o código usando o Boost, mas agora estamos diante de um novo problema: precisamos resolver o problema que a biblioteca do Boost, em vez da nossa, resolveu antes.
  • Essencialmente, copiamos as partes do código Boost necessárias em nossos próprios namespaces.
  • Mais tarde, inevitavelmente, de tempos em tempos, nos deparamos com o fato de que precisamos de funcionalidades adicionais que já estariam no código original se não o tivéssemos jogado fora. Mas agora somos os proprietários desse código, por isso temos que continuar a apoiá-lo.

Não gostamos de algo enorme que esteja tentando fazer muitas coisas ao mesmo tempo ou que possa afetar o tempo de compilação - e isso é bastante razoável. O que as pessoas cometem erros repetidamente é que se opõem a aceitar a suposta dor hoje - enquanto, por causa dessa decisão, enfrentarão uma dor muito real e muito maior com o apoio de algo à custa de alguém. o orçamento que eles terão que experimentar nos próximos três anos. Infelizmente, a presença de evidências na forma de jogos que usam com sucesso um prato de STL e Boost, de forma alguma pode afetar a psicologia humana e convencer os desenvolvedores de jogos.

Por todos esses motivos, muitas empresas de jogos criaram suas próprias bibliotecas que cobrem o que a STL faz - e ainda mais - enquanto suportam casos de uso específicos de jogos. Algumas grandes empresas de jogos conseguiram dominar o desenvolvimento de sua própria substituição STL , compatível com a API, quase completamente compatível, o que implicou custos enormes para apoiar este projeto.

É aconselhável encontrar uma alternativa aprimorada ao std::map ou aplicar uma otimização de buffer pequeno no std::vector . É muito menos aceitável estar condenado a suportar suas próprias implementações de algorithms ou type traits , o que fará pouco bem. Quanto a mim, é lamentável que, para a maioria dos desenvolvedores, STLs sejam apenas contêineres. Como ao aprender STL no início, eles aprendem exatamente que, falando em STL, a maioria implica std::vector - embora na verdade eles devam pensar em std::find_if .

Oportunidade No. 3: Testes


Argumenta-se que testes extensivos devem ser realizados, TDD e / ou BDD devem abranger todo o código que pode ser coberto, e os erros devem ser resolvidos escrevendo novos testes.

Portanto, vamos discutir o tópico de teste.

A julgar pela minha experiência, o teste automatizado praticamente não é usado na indústria de jogos. Porque

1. Como a correção não é tão importante, mas não há especificação real


Como jovem programador da indústria de jogos, rapidamente me livrei da ideia de que deveria me esforçar para simular algo de forma realista. Os jogos são fumaça e espelhos e a busca por caminhos curtos. Ninguém se importa com o quão realista é a sua simulação; o principal é que seja divertido. Quando você não tem outra especificação além de "o jogo deve parecer certo", o assunto do teste está ausente. Graças a bugs, a jogabilidade pode melhorar ainda mais. Muitas vezes, um bug entra no lançamento e até ganha o amor dos usuários ( lembre-se do mesmo Gandhi do Civilization ). Jogos são diferentes de outras áreas que usam C ++; aqui, a falta de correção não leva ao fato de que alguém acaba perdendo suas economias.

2. Porque é difícil


Obviamente, você gostaria de produzir testes automatizados sempre que puder. Isso pode ser feito para alguns subsistemas para os quais existem resultados finais claramente definidos. O teste de unidade na indústria de jogos, é claro, está presente, mas, como regra, é limitado ao código de baixo nível - os análogos do STL mencionados anteriormente, procedimentos de conversão de strings, métodos de mecanismo físico, etc. Os casos em que a seção executável do código tem resultados previsíveis geralmente são testados por testes de unidade, embora o TDD não seja usado aqui - já que os programadores de jogos preferem simplificar suas vidas, e não vice-versa. Mas como você testa o código da jogabilidade (veja o ponto um)? Assim que você vai além do teste de unidade, encontra imediatamente outra razão pela qual o teste de jogos é tão difícil.

3. Porque o conteúdo está envolvido


O teste de sistemas não triviais provavelmente incluirá o fornecimento de conteúdo com o qual ele será implementado. A maioria dos engenheiros não é muito boa em produzir esse conteúdo por conta própria; portanto, para obter um teste significativo, você precisará atrair alguém com as habilidades certas para criar o conteúdo. Depois disso, você encontrará o problema de medir o que obtém na saída - afinal, isso não é mais uma linha ou um número, mas uma imagem na tela ou um som que muda com o tempo.

4. Porque nós não praticamos


O teste de unidade é uma função para a qual conheço as possíveis entradas e saídas. No entanto, a jogabilidade é um comportamento imprevisível, que evolui dinamicamente, e eu não sei como esse fenômeno pode ser testado adequadamente. O que posso testar - se, é claro, obtenho permissão do meu gerente para dedicar tempo suficiente a isso - isto é, por exemplo, desempenho ou recursos de alto nível, como um matchmaking, que posso analisar. Esse trabalho de infraestrutura pode ser empolgante para alguns programadores de jogos, mas para a maioria simplesmente não é interessante e, além disso, requer aprovação e suporte do proprietário da carteira. Como programador de jogos, nunca tenho a oportunidade de praticar a escrita de testes de alto nível.

5. Como a [empresa] não vê a necessidade de testes automatizados


Nosso principal objetivo é lançar o jogo. Vivemos em uma era da indústria que avança com hits que aproveitam ao máximo seu dinheiro no primeiro mês de vendas, quando os custos de marketing desses hits são maximizados. O ciclo de vida dos consoles nos ensinou que o código, em qualquer caso, não permanecerá por muito tempo. Se estamos trabalhando em um jogo online, provavelmente teremos tempo adicional para testar a correspondência ou a carga do servidor. Como para o lançamento do jogo, precisamos que seu desempenho esteja em ordem, devemos pelo menos realizar testes de desempenho, mas não devemos automatizar esse processo. Para o gerenciamento na indústria de jogos, o teste automatizado nada mais é do que um desperdício de tempo e dinheiro. Para sua implementação, é necessário contratar engenheiros experientes que realizarão o trabalho, cujo resultado será quase imperceptível. O mesmo tempo pode ser gasto no desenvolvimento de novos recursos. No curto prazo, é muito mais lucrativo usar a equipe de controle de qualidade para testar o jogo, o que nos leva ao próximo ponto.

6. Porque, em geral, o teste em jogos é classificado como atividade de segunda categoria


Adoro bons profissionais de controle de qualidade. Para mim, eles valem seu peso em ouro. Eles sabem como melhorar seu jogo quebrando-o de uma maneira que nunca teria passado pela sua cabeça. Eles são especialistas especializados em sua jogabilidade naquele aspecto em que você não entende e quase nunca entende. Eles são melhores do que uma equipe de compiladores super capazes para ajudá-lo a fazer tudo certo. Fico feliz por ter tido a chance de trabalhar com vários especialistas maravilhosos em controle de qualidade ao longo dos anos do meu trabalho.

Eu quase sempre tive que lutar para garantir que eles ficassem no meu time.

AAA-, , QA — , . , . , .

, , , . «» , , QA , , , .

. , . QA-, , API , « ». , , .

. , , , — .

. . , , « .» « Y.». QA- — , , .

, , ; , — , , — QA , , , , QA .

Nas melhores equipes com as quais trabalhei, insistimos que nossas equipes tivessem seus próprios engenheiros de controle de qualidade que trabalhariam conosco. No entanto, eles não perderam sua objetividade ou desejo de alcançar um resultado melhor. Eles tiveram o prazer de receber ajuda de programadores para escrever testes automatizados. O que certamente não duvido é que seria útil para a indústria de jogos fazer automação com mais frequência.

Desempenho de depuração


— , API , , ( ) — , .

, , C++.

, . , , , , , . , - , , .

Em outras palavras, queremos ter uma depuração produtiva, porque, para detectar bugs, geralmente precisamos executar aplicativos com conjuntos de dados suficientemente grandes e representativos. Mas, de fato, quando chegamos a esse ponto, o depurador geralmente se torna uma ferramenta muito rude de usar - independentemente de ser produtiva ou não. Obviamente, definir pontos de interrupção nos dados ( pontos de interrupção de dados ) , , — , , ? , , , , , , , , , ( soak testing )?

Traço dois, podemos confiar apenas no depurador. Nesse caso, fazemos o que sempre fizemos. Estamos tentando isolar o problema, fazer com que isso aconteça com mais frequência; adicionamos registro e analisamos nosso programa através dele; ajustamos temporizadores e configurações de fluxo; usamos pesquisa binária de construção; estudamos despejos de núcleo e logs de falhas; tentamos reproduzir o problema reduzindo o conteúdo ao mínimo; refletimos sobre o que pode estar causando o problema e o discutimos.

Muitas vezes, até chegarmos à verdadeira causa do acidente, teremos tempo para consertar algumas outras coisas. Em outras palavras, resolvemos problemas e, no final, usar um depurador é apenas uma pequena parte desse processo. Então, sim, a velocidade de depuração é uma boa adição, mas sua falta não nos impede de continuar sendo engenheiros. Ainda precisamos de outras habilidades, como a capacidade de analisar dumps principais e ler o assembler otimizado.

« ++» , . ; , ; , . « C++» , — , , STL _ _, STL . , STL « », ; , , , .

, , « C++» — , . — , .

, C++ , . , . , . copy elision ( ) , . . , , NRVO , , . , C++ .

: ++


, C++, .

1.


Supondo que você ainda escreva código C ++, você pode simplesmente continuar usando a linguagem da mesma maneira que antes. Não é necessário começar a usar novos recursos, se você não quiser fazer isso. Quase tudo o que você usa agora continuará a ser suportado - e, ao fazer isso, nos próximos anos você continuará a colher os benefícios de melhorar o compilador.

Essa é uma estratégia de comportamento completamente apropriada para quem trabalha por conta própria ou com uma equipe de pessoas afins. O C ++ 98, junto com alguns recursos mais recentes, ainda é um bom ajuste para a criação de jogos.

, , , . , C++-, «» C++. — , C C++98. , , , – , . ?

2.


, GDC, CppCon , , . ; ; . , , — , .

C++ . , SG14, SG7, SG15 — , — pode ser encontrado em isocpp.org . O comitê não tem planos secretos - de fato, você realmente acha que mais de 200 programadores podem concordar com uma agenda comum? Aqui, mesmo os "chefes" do comitê frequentemente deixam de "empurrar" suas idéias.

Se você deseja que sua opinião seja ouvida, comece a falar onde sua opinião pode ser ouvida, e não no Twitter ou no Reddit. Por favor, siga este conselho - estou ansioso por nossa discussão.

Source: https://habr.com/ru/post/pt435036/


All Articles