Godot: Status do C++ em Godot

Criado em 18 jul. 2017  ·  65Comentários  ·  Fonte: godotengine/godot

Em Godot ainda estamos usando C++ 98/03. O objetivo desta edição é tentar decidir se ainda devemos cumpri-lo ou podemos começar a usar algumas coisas modernas de C++ (C++ 11/14/17 e até 20).

Quando Godot foi escrito inicialmente, C++03 era a versão mais atual e anos depois esse estilo (versão do padrão, evitando a biblioteca padrão, etc.) como eu sei). Mas devemos rever essas preocupações.

Itens a serem considerados (esta lista destina-se a continuar crescendo ou ter algumas entradas descartadas; apenas um brainstorming no início):

  • Ponteiros inteligentes: O mecanismo de ponteiro/objeto personalizado Godot é compatível com isso? Godot precisaria ser reescrito em grande parte para usá-los? Os benefícios seriam maiores que o custo?
  • Mover semântica: As perguntas a serem feitas aqui são mais ou menos as mesmas que para ponteiros inteligentes.
  • auto , constexpr , lambdas, etc. : Em geral, qualquer recurso C++ moderno que facilite a escrita de código e/ou forneça alguma chance de otimização sem interromper o núcleo atual.
  • Primitivas de multiprocessamento padrão : C++ agora fornece threading padrão, atomics, barreiras de memória, etc. Temos que manter implementações personalizadas e mantê-las para diferentes plataformas?
  • STL/Boost/other : Teríamos algum benefício ao mudar de contêineres personalizados para uma implementação bem conhecida? Des/vantagens em relação à manutenção, desempenho, compatibilidade, etc.
  • register , "truques" embutidos, etc. : Coisas adicionadas ao código para tentar fazer o compilador gerar código com mais desempenho. Alguns deles estão obsoletos ou não têm impacto real, ou podem até piorar o desempenho. Qual largar? Qual manter?
  • etc,

Pequenas mudanças podem ser feitas antecipadamente. Mas quando grandes mudanças (no seu caso) devem ser feitas? Godot 4.0? Ou assim que Godot 3.0 se tornar estável?

Por favor, deixe-me convidar algumas pessoas explicitamente: @reduz , @punto-, @akien-mga, @karroffel , @bojidar-bg, @BastiaanOlij , @Faless.

discussion

Comentários muito úteis

Eu gostaria de oferecer meus 2 (ou 20) centavos como alguém novo para Godot e sua base de código. Atualmente estou supervisionando e trabalhando no esforço de portar _Battle for Wesnoth_ para Godot. Agora, o front-end (o editor e a API GDScript) é ótimo! Além de algumas arestas, até agora nos permitiu progredir em um bom ritmo. Mas também imaginamos que nós (a equipe) contribuiríamos com patches para o back-end (o mecanismo) em algum momento. Para esse fim, no início desta semana eu clonei o repositório git e comecei a bisbilhotar o C++, e honestamente... estou um pouco desanimado.

Eu tenho experiência no gerenciamento de uma grande base de código C++ na forma do antigo mecanismo personalizado de Wesnoth. Ele também começou como C++03, mas foi modernizado para usar C++11 e recursos posteriores e agora é compatível com C++14. Liderei esse esforço de modernização (muitas vezes com um pouco de zelo) e sinto que tornou nossa base de código muito mais legível e fácil de trabalhar. Concedido, eu nunca trabalhei extensivamente com uma base de código puramente C++03; Aprendi C++ usando recursos modernos de C++. Mas para mim, a ideia de que coisas como auto , range-for e lambdas tornam seu código menos legível é apenas... muito estranha mesmo.

Pegue auto é certamente possível abusar e usar demais, mas também tem uma tonelada de usos legítimos. Um dos lugares mais comuns em que implantamos auto quando atualizamos a base de código Wesnoth foi em loops for usando iteradores. Antes teríamos algo assim:

for(std::vector<T>::iterator foo = container.begin(); foo != container.end(); ++foo) {}

O que é bagunçado! Nos casos em que realmente precisávamos de um iterador, fizemos isso:

for(auto foo = container.begin(); foo != container.end(); ++foo) {}

Sim, agora você não conhece o tipo de iterador explícito, mas quase nunca precisa saber disso. Eu li um comentário aqui em alguns posts dizendo que fica mais difícil se o contêiner for declarado alguns arquivos ausentes, mas realmente, com editores de código modernos e intellisense, isso não é um grande problema.

Na maioria dos casos, basta alternar para range-for:

for(const auto& foo : container) {}

Muito mais rápido de digitar e mais conciso também. Você não precisa se preocupar em desreferenciar iteradores dentro do loop ou em acompanhar os índices. E fica bem claro que você está fazendo um loop sobre todo o contêiner. Com iteradores, as pessoas não familiarizadas com o código precisam verificar novamente se o loop está realmente indo do começo ao fim.

Usar auto em loops range-for aqui também tem um benefício adicional. Faz uma coisa a menos que você precisa se lembrar de atualizar se você alterar o tipo do contêiner! Eu realmente não consigo entender o argumento de Juan de que essas coisas tornam seu código menos legível ou menos fácil de entender. Para mim, é exatamente o oposto.

No vídeo do Estado de Godot, ele também mencionou lambdas. Novamente, certamente é possível abusar deles, mas eles também são uma ferramenta incrivelmente útil! Aqui está um paradigma comum que vi na base de código de Wesnoth antes de usar o C++ 11:

struct sort_helper {
    operator()(const T& a, const T& B) {
        return a < b;
    }
}

void init() const {
    std::vector<T> foo;
    foo.push_back(T(1));
    foo.push_back(T(2));
    foo.push_back(T(3));

    std::sort(foo.begin(), foo.end(), sort_helper);
}

Longo, confuso, código inchado. E aqui está o que usamos com o C++11:

void init() const {
    std::vector<T> foo {
        T(1),
        T(2),
        T(3),
    };

    std::sort(foo.begin(), foo.end(), [](const T& a, const T& b) { return a < b; });
}

Esse é apenas o caso mais comum! E sim, eu sei que você também pode implementar operator< para T e que std::sort usa isso por padrão, mas ilustra meu ponto. Novamente, talvez seja apenas ter aprendido e trabalhado quase exclusivamente com o C++ moderno, mas acho que desconsiderar ferramentas como auto , range-for e lambdas quando apropriado é dar um tiro no pé.

O que me leva ao meu próximo ponto. Percebi que Godot usa internamente muitos de seus próprios tipos personalizados em vez dos STL. Se sua preocupação é a legibilidade do código em relação a coisas como auto , usar tipos de núcleo personalizados sobre os STL é absolutamente prejudicial! Alguns dias atrás, eu estava navegando pela base de código e me deparei com um monte de código que se parecia com isso:

container.push_back(T(args));

Agora, isso é ineficiente. push_back (pelo menos em termos de std::vector ) recebe uma referência const; portanto, este operador resulta em uma cópia desnecessária. Eu queria fazer um patch para fazê-los usar emplace_back ... mas então percebi que toda a base de código estava usando tipos de contêiner personalizados 😐

Um dos grandes problemas com o design de Wesnoth e um dos principais fatores que contribuíram para a decisão de usar um novo motor (neste caso, Godot) foi que Wesnoth sofria da síndrome de Não Inventado Aqui em grande medida. Embora usássemos bibliotecas como Boost, nosso pipeline de renderização era personalizado, nosso kit de ferramentas de interface do usuário era personalizado, nossa linguagem de dados/script era personalizada... Antes de começarmos a trabalhar na porta Godot, tentei (e falhei) implementar renderização acelerada por hardware (até este ponto, estávamos usando renderização de superfície/sprite baseada em CPU. Em 2019. Sim, eu conheço X_X). A API Texture do SDL não tinha suporte a shader, e era necessário suporte a shader. No final, decidi que implementar nosso próprio renderizador, embora possível, imporia uma carga de manutenção desnecessária ao projeto no futuro. Nós já tínhamos poucos desenvolvedores principais, e encontrar alguém capaz de escrever OpenGL (ou Vulkan se precisássemos abandonar o OGL) teria sido uma dor desnecessária quando um mecanismo como Godot tem um renderizador perfeitamente bom e bem mantido que poderíamos usar em vez de.

Trago isso à tona porque acho que é um bom exemplo de por que implementar tudo internamente pode ser uma má ideia. Sim, você pode reduzir um pouco o tamanho do seu binário por não usar a biblioteca padrão, mas também incorre em uma enorme carga de manutenção. Converter essas chamadas push_back para emplace_back é uma fruta muito fácil que pode tornar o código mais limpo e com melhor desempenho. Mas como você tem um tipo vector personalizado, se você quiser uma construção no local, alguém precisará implementá-la manualmente. E em todos os outros tipos personalizados também!

Um problema ainda maior é que aumenta a barreira à entrada. Eu olhei para a base de código Godot esperando tipos C++ STL. Não encontrar esses meios que eu ou qualquer outra pessoa agora precisa aprender exatamente quais tipos o mecanismo fornece e qual API acompanha cada um. Eu quero std::map ? Não, eu preciso usar dictionary . Isso só dificulta a vida dos mantenedores e complica as coisas para os novos contribuidores. E realmente, não é um código que parece uma coisa, mas na verdade é outra... ilegível?

Cerca de um ano atrás, quando nos aproximamos da versão 1.14 de Wesnoth e do lançamento no Steam, realizei um projeto de refatoração para eliminar um tipo de contêiner personalizado que era essencialmente std::list exceto que o uso erase() não invalidava iteradores. Aqui está o princípio do commit em questão . Ele precisava de mais trabalho depois disso para ficar certo, mas o resultado foi um código muito mais simples, mais fácil de entender e menos opaco.

Concluindo, acho que proibir certos recursos muito úteis do C++11 e manter os tipos personalizados em vez dos STL será um obstáculo para Godot a longo prazo. Eu sei que refatorar as coisas leva muito tempo (confie em mim, eu sei), mas do jeito que as coisas estão agora parece muito provável que você acabe com um catch-22. Ao tentar evitar as desvantagens de usar o STL (tamanhos binários maiores, etc.), você acabará tornando cada vez mais difícil mudar para um código mais limpo e de melhor desempenho em padrões C++ mais recentes. Tenho certeza de que ninguém está particularmente ansioso para implementar a construção no local em todos os tipos personalizados. 😬

Eu sei que minha opinião não significa muito aqui, mas imaginei que daria meus pensamentos da perspectiva de alguém que trabalhou com uma grande base de código C++ que mudou para o C++ moderno do C++03. É muito trabalho, mas a longo prazo eu sinto que vale a pena.

Desculpe o textwall enorme!

Todos 65 comentários

Isso foi discutido muitas vezes, as respostas usuais são:

1) Não é desejável mover a base de código para algo acima de 03. Os recursos não valem a pena. Para GDNative C++, é perfeitamente possível usá-lo.
2) Não deseja usar STL/Boost/Etc. A lógica sempre foi a mesma: a) Modelos Godot fazem pequenas coisas que normalmente não fazem (ou seja, refcounts atômicas e cópia na gravação) b) STL gera símbolos de depuração enormes e binários de depuração devido a seus símbolos desfigurados extremamente longos

Continuamos do mesmo jeito que tudo é.

Recebi algumas opiniões sobre conversas particulares, mas queria fazer o
discussão mais pública.

Não sabia que já tinha sido tão discutido e fechado.

Você descarta mesmo auto, constexpr e afins?

Soltar 'registrar'?

El 18 jul. 13h54 de 2017, "Juan Linietsky" [email protected]
escreveu:

Isso foi discutido muitas vezes, as respostas usuais são:

  1. Não deseja mover a base de código para qualquer coisa acima de 03. Os recursos são
    não vale a pena. Para GDNative C++, é perfeitamente possível usá-lo.
  2. Nenhum desejo de usar STL/Boost/Etc. A justificativa sempre foi a
    mesmo: a) Os modelos Godot fazem pequenas coisas que normalmente não fazem (ou seja,
    refcounts e copy on write) b) STL gera símbolos de depuração enormes e depura
    binários devido aos seus símbolos mutilados extremamente longos

Continuamos do mesmo jeito que tudo é.


Você está recebendo isso porque foi o autor do tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/godotengine/godot/issues/9694#issuecomment-316041201 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/ALQCtipKmepD_1Xw6iRXZ7aGoQlLfiwFks5sPJzqgaJpZM4ObOio
.

Eu levantei essa questão, seria mais seguro usar ponteiros inteligentes e que esse código sofre da síndrome NIH, mas os superiores não têm intenção de atualizar, então sugiro que você também desista.

Provavelmente levaria muitas horas de trabalho para atualizar o código, que os desenvolvedores preferem gastar na implementação de novos recursos. De alguma forma, eu entendo isso - os usuários do mecanismo preferem obter novos recursos do que "refactoring" (sem entender coisas como dívida tecnológica).

@Marqin Desculpe, mas ponteiros inteligentes são uma má ideia porque:

1) o único útil é o refcontado, o resto é masturbação mental para características já presentes na linguagem.
2) Usar ponteiros inteligentes em todos os lugares é uma péssima ideia, você corre o risco de ter ciclos de referência que vazam memória
3) já temos algo semelhante, Ref<> , com a vantagem adicional de controlarmos como funciona a contagem referencial, para que possamos adicionar casos especiais para lidar com vinculações a linguagens como C#, com seu próprio gc

Então, antes de argumentar que decidimos as coisas por capricho, pergunte. Geralmente tomamos decisões informadas e compartilhamos o processo com todos.

O que pode ser feito facilmente se você quiser melhorar a qualidade é começar a escrever testes de unidade (adicione algum trabalho opcional aos scons para isso e apenas faça o dev instalar o gmock/gtest [para não entupir ./thirdparty])

o resto é masturbação mental para características já presentes na linguagem

@reduz , como é o unique_ptr com sua propriedade explícita e liberação de memória RAII já presente na linguagem?

Além disso, não apenas o gerenciamento de memória, mas também o encadeamento fica mais fácil (por exemplo lock_guard ).

@RandomShaper Não sou contra a atualização em algum momento no futuro, mas, embora existam muitos recursos úteis, as versões mais recentes do C++ tendem a fazer o programador escrever código menos explícito. Isso resulta em código geralmente mais difícil de ler por outras pessoas (típico com abuso automático de palavras-chave).

A outra vantagem de não usar recursos mais avançados (por exemplo, não usamos exceções e o rtti pode ser desabilitado com pouco custo) é que os compiladores produzem binários muito menores.

Isso é bom. Respeito sua palavra. Só que eu acho que discutir sobre isso é uma coisa saudável.

Eu sei que algumas grandes empresas de videogames (Ubisoft, IIRC) migraram grandes bases de código para C++ moderno, então deve ser perfeitamente adequado para Godot também. Claro, como você apontou, isso requer trabalho/tempo.

Por isso poderíamos:

  • escolha um subconjunto razoável para uma migração de todo o código o mais simples possível;
  • ou pelo menos escolha um subconjunto razoável como "aprovado" para o novo código.

Em ambos os casos, um estilo de codificação provavelmente precisaria ser definido para não abusar dos recursos, como você disse que acontece com auto .

Claro, agora há coisas com maior prioridade, mas talvez quando o 3.0 se tornar estável ou em algum outro momento no futuro, seria bom já ter decidido.

@Marqin Como mencionado, Godot usa seu próprio esquema de alocação de memória para nós, o que faz mais sentido do que qualquer coisa fornecida pelas novas bibliotecas padrão.

Além disso, o lock guard é praticamente 3 linhas de código e já o implementamos sem usar o C++ 11+

Novamente, por que deveríamos supor que qualquer coisa "padrão" 1) precisa ser melhor do que o que já temos 2) é melhor se a usarmos de qualquer maneira? .Acho que é uma falacia comum.

O que vem a seguir, descartar nossas funções de impressão e usar ostream/istream? Altere nossa classe String, que é muito legal, e substitua-a pela muito mais aleijada std::string ou std::wstring?

As bibliotecas padrão não servem a todos os propósitos e não funcionam melhor do que todo o resto apenas porque são bibliotecas padrão. Eles estão lá apenas para aqueles que precisam deles, e não há problema em ignorá-los e escrever suas próprias implementações se você tiver uma razão válida para fazê-lo. Nós fazemos e estamos confiantes sobre isso.

@RandomShaper O problema é que:

1) É mais custo do que benefício.
2) Abrirá a janela para muitos argumentos sobre como é a maneira padrão de escrever C++11+. Pode valer a pena tê-los em algum momento, e reescrever grande parte do mecanismo para aproveitar esses recursos pode ser útil um dia, mas acho que há coisas muito mais importantes para focar.
3) Também como mencionado anteriormente, pode ser possível que, por mais legal que pareça portar para uma nova versão C++, isso possa resultar em um tamanho binário muito maior. Nesse caso, pode ser indesejado.

Não vale a pena no momento, podemos discutir novamente em alguns anos

O que eu acho que faria sentido é garantir que Godot compile com opções como --std=c++17, para que você possa facilmente trazer uma biblioteca escrita em C++ moderno, se precisar. Por exemplo, eu votaria pela remoção da palavra-chave register da base de código https://github.com/godotengine/godot/issues/9691

De alguma forma relacionado, lembro de ter lido em algum lugar que o gcc-6.3 não é suportado (google
diz que estava em https://github.com/godotengine/godot/issues/7703 ). Isso me incomoda, pois o gcc-6.3 é o compilador padrão na minha distribuição (debian estável). Alguém pode confirmar isso? E por que isto?

@efornara algumas bibliotecas de terceiros já exigem versões C++ mais recentes, e tudo bem porque o Scons cuida disso com ambientes de compilação clonados. Verifique o código de terceiros etc2comp para ver como funciona.

@karroffel Obrigado, eu não sabia disso.

Seria apenas um bom recurso, mas não é necessário para importar uma biblioteca (embora, se para o código de cola você precisar incluir mais godot, poderá encontrar um arquivo de cabeçalho que não compila).

Aliás, se alguém precisar fazer algo parecido e encontrou este post, o arquivo relevante é: https://github.com/godotengine/godot/blob/master/modules/etc/SCsub . Eu o encontrei usando grep e parece o único lugar onde isso é necessário no momento.

Não é tão seguro vincular c++11 com código não-c++11 - http://gcc.gnu.org/wiki/Cxx11AbiCompatibility

@Marqin A menos que eu entenda mal o link, isso realmente parece apoiar o caso de godot _not_ começando a usar componentes da Biblioteca Padrão, mas aderindo a componentes personalizados:

A linguagem C++98 é compatível com ABI com a linguagem C++11, mas vários lugares na Biblioteca Padrão quebram a compatibilidade.

Misturar idiomas parece bastante seguro (não é legal, eu admito, e pode parar de funcionar no futuro), mas misturar coisas como pares, vetores, listas, etc... é conhecido por causar problemas.

@efornara O link é sobre como vincular algumas bibliotecas de terceiros que usam C++11 com Godot que usam C++03.

@Marqin Sim, mas do jeito que eu entendo o link é que você pode fazer isso. O que você não pode fazer é, por exemplo, passar um std::list<> do godot para a biblioteca de terceiros.

@reduz onde posso encontrar a documentação da Ref interna do Godot<>? Onde posso encontrar documentação sobre como os contêineres internos Godot diferem daqueles em STL?

@Marqin Para contêineres não há muito:
http://docs.godotengine.org/en/stable/development/cpp/core_types.html#containers
Para Ref<> percebi que não existe. Provavelmente deve ser adicionado.
Qualquer sugestão de como melhorar o doc para adicionar essas coisas é muito bem-vinda.

Pessoalmente, acho o novo padrão C++ bastante impressionante, mas não proporia nenhuma refatoração interna Godot porque exigiria muito esforço para pouco ganho.

Além disso, uma das compatibilidades com versões anteriores é uma das marcas registradas do C++ por esse motivo exato. Mas ainda gostaria de poder usá-lo para novos recursos Godot e no GDNative.
Eu preferiria que Godot fosse compilado com suporte C++ moderno por esse motivo.

@reduz Como você disse, o C++ 14/11/17 permite escrever código menos explícito. Mesmo que isso seja uma desvantagem para iniciantes em C++, é uma coisa boa para usuários avançados de C++.
Quanto ao "auto", eu pessoalmente acho que é bom para usuários avançados também. Ele pode não apenas evitar digitar tipos repetidamente quando não for estritamente necessário, mas também evitar digitar alguns bugs.

FYI, eu compilei um mestre recente (godot3) com:

scons platform=x11 target=debug tools=yes builtin_openssl=true CCFLAGS=-std=c++17

em um trecho debian (gcc-6.3). Irritantemente, a opção também é definida ao compilar arquivos C, então você é inundado com avisos se os habilitar, mas, além disso, tudo correu bem. Mesmo a palavra-chave register não parece causar problemas.

Eu não iria tão longe a ponto de sugerir que as compilações oficiais fossem compiladas dessa forma, mas é bom saber que a opção está lá se você precisar dela em seu projeto. O que eu sugiro é que quaisquer mudanças que quebrem isso sejam tratadas como uma regressão.

EDIT: Corrigido alguns erros de digitação, tornando a declaração sobre avisos mais clara.

Irritantemente, a opção também é definida ao compilar arquivos C

Você tentou com CPPFLAGS ?

@Hinsbart Não, não fiz. Talvez haja uma maneira, mas como não conheço muito bem os scons, simplesmente fui com o que parecia possível sem mexer:

$ scons platform=x11 builtin_openssl=true -h | grep FLAGS
CCFLAGS: Custom flags for the C and C++ compilers
CFLAGS: Custom flags for the C compiler
LINKFLAGS: Custom flags for the linker

EDIT: A propósito, não sei como é usado no sistema de compilação godot, mas CPPFLAGS me faria pensar nas opções do prepocessor. Pessoalmente, sempre usei CXXFLAGS para C++.

Você tentou com CPPFLAGS?

Acho que CPPFLAGS afeta todas as linguagens que usam o pré-processador, portanto, C e C++ estão incluídos.

@ m4nu3lf você pode usar qualquer forma de C++ que desejar com o GDNative.

Na minha experiência, qualquer oportunidade de excluir código é uma boa oportunidade.

Talvez possamos configurar algum tipo de página wiki documentando quais arquivos podem ser excluídos e substituídos por variantes do c++ 11. Isso provavelmente incluiria primitivos de encadeamento e tal.

Mudar por mudar não é bom (provérbio koolaid), mas neste caso o projeto LLVM e muitos outros projetos FOSS avançaram em favor de alguns dos padrões de sintaxe mais claros, ou seja, a notação for-iterator mais recente, mas também descarregar a separação de preocupações para os respectivos tempos de execução da linguagem, porque (sejamos honestos) manter o mínimo possível de código específico de plataforma é ideal para um mecanismo de jogo.

O melhor código que você escreverá é o código que você des-escreve. :)

Por curiosidade, é possível escrever código que compile com C++ mais antigo e C++ mais recente? Existem mudanças significativas entre as versões do C++?

Por curiosidade, é possível escrever código que compile com C++ mais antigo e C++ mais recente? Existem mudanças significativas entre as versões do C++?

O C++ mais recente é compatível com o C++ mais antigo em 99,99...% dos casos (apenas as coisas que não deveriam ter sido usadas ou foram mal definidas foram definidas como não mais suportadas, mas em geral isso só causará avisos de compilação, mas ainda funciona hoje em dia) .

No entanto, o C++ mais recente possui recursos significativos , e eles obviamente não funcionarão em versões mais antigas do C++, e esses recursos não são apenas recursos de usabilidade como macros variádicas e auto e lambdas, mas também recursos de eficiência como move ing e rvalue referências e, é claro, macros variádicas, bem como outras para segurança, como os ponteiros de propriedade mais recentes (especialmente com as bibliotecas C++ Core) entre muitos outros.

Considerando que até mesmo o Visual Studio oferece suporte ao C++ 17 moderno agora, não há realmente nenhuma razão para não usar versões mais recentes.

Eu queria comentar. Consegui compilar godot com o sinalizador C++ 17 com o único problema sendo um dos itens third_party que usava auto_ptr (que é removido do C++ 17 devido ao quão ruim é)

O problema não é que ele não compila em c++17, o problema são as pessoas que querem usar recursos de c++17, ou pior ainda, iniciar PRs usando esses... só para descobrir que o projeto está em c ++03.

A falta de lambdas por si só é um problema ENORME que eu não vejo aqui.
Várias vezes, godot tem que armazenar uma lista temporal de dados, para então iterar sobre isso.
Em cada um desses casos, isso pode ser feito como um "for_each" ou estrutura semelhante que itera por si só, simplificando muito o código, diminuindo o uso de memória e melhorando o desempenho graças ao lambda sendo incorporado/otimizado. (e isso é C++11, usado em todos os lugares). A mesma coisa exata para todos os loops gerais na lista vinculada.

Os operadores de movimentação também permitiriam estruturas de dados melhores, que poderiam substituir as atuais absolutamente terríveis por algumas mais otimizadas.

Tipos de dados de string (como "mystring"_hs) ou semelhantes, podem nos dar uma boa maneira de espalhar strings com hash em todo o código, tornando a verificação de string (muito comum em script e código de evento) muito mais rápida.

Mesmo que std::thread não seja usado (eu acho que é uma abstração bastante terrível), a biblioteca std atomics é incrível e extremamente útil. std::atômicoe os gostos.

E nem vamos falar sobre o quanto forçar as pessoas a C++03 prejudica o próprio projeto, quando as pessoas não podem facilmente integrar bibliotecas modernas, porque godot é como o único projeto C++ de código aberto não abandonado em uma versão tão antiga do C++ ( até onde sei)

Pessoalmente, concordo em ser conservador e não ir para o mais recente padrão C++ absoluto, mas acho que algo como C++11 com alguns recursos verificados do C++14 é o que funcionaria melhor e melhoraria significativamente Godot. O C++ 11-14 é bom o suficiente para o Unreal Engine, que porta para ps4, xbox, switch, pc, pc de baixo custo, android, IOS e HTML5 webassembly. Não vejo por que o godot deve se limitar quando suporta uma quantidade muito menor de plataformas.

Tipos de dados de string (como "mystring"_hs) ou semelhantes, podem nos dar uma boa maneira de espalhar strings com hash em todo o código, tornando a verificação de string (muito comum em script e código de evento) muito mais rápida.

Além disso, permite usá-los em coisas como interruptores. Como eu fiz um tipo Atom anos atrás para substituir uma implementação de string flyweight:
https://github.com/OvermindDL1/OverECS/blob/master/StringAtom.hpp

Ele tem uma versão de 32 bits (máximo de 5 caracteres ou 6 se você recodificar um pouco a tabela) e uma versão de 64 bits (máximo de 10 ou 12 caracteres, dependendo da escolha da codificação rígida ou não). É totalmente reversível, acontece em tempo de compilação ou dinamicamente em tempo de execução em qualquer direção, totalmente utilizável em switch's, etc... etc... Exemplo de uso desse arquivo:

switch(blah) {
  case "UPDATE"_atom64: ... pass
  case "PHYSUPDATE"_atom64: ... pass
  ...
}

Ao interagir com o código LUA, usei apenas strings e realizei a conversão nos limites. Esse arquivo não é a versão "mais recente", adicionei funções para fazer as conversões sem alocação de memória ao voltar de inteiro para string (é menos alocado em string-> inteiro independentemente, pois é executado em tempo de compilação) . É trivial fazer um filtro do Visual Studio ou GDB que reverta a codificação ao exibir o Atom64/Atom32/qualquer tipo também (que é apenas um número inteiro abaixo) para que você possa ver o tipo de string em vez de algum valor de hash estranho.

Mas coisas como essa são extremamente úteis, especialmente em código sensível ao desempenho e para tornar o código fácil de ler, e isso só exigia C++ 11, nem mesmo algo mais novo.

No mínimo, eu diria que C++ 14 deveria ser o padrão Godot. C++17 seria bom (algumas melhorias de desempenho muito úteis em algum código novo), mas C++14 é um mínimo universal agora. Mas é claro que gcc/clang/VisualStudio e qualquer outra coisa suportam C++17 bem agora (e até grandes pedaços de C++20), C++17 também parece bom. Eu provavelmente ainda optaria pelo C++ 14 para 'just-in-case'.

@OvermindDL1 Essa coisa do Atom é incrível, adorei. Definitivamente se encaixaria muito bem com godot, onde está fazendo exatamente isso muitas vezes.

Essa coisa do Atom é incrível, adorei. Definitivamente se encaixaria muito bem com godot, onde está fazendo exatamente isso muitas vezes.

@vblanco20-1 Bem, se Godot quiser, eles são livres para absorver o código. Ela (e sua antecessora string flyweight) teve um longo uso no meu antigo mecanismo C++. Era tão útil para pequenas tags de evento, apenas strings curtas para usar como 'chaves' nas coisas, e tão trivialmente fácil de mover para trás e para frente em Lua/LuaJit, além dos filtros do depurador serem uma grande ajuda.

A falta de lambdas por si só é um problema ENORME que eu não vejo aqui.

Eu sou o único que acha que lambdas torna o código difícil de ler na maioria dos casos?

@Faless : Não, você não é o único, acho que lambdas sendo difíceis de ler é uma das razões pelas quais Akien não atualizou a versão c++.

Lambdas são apenas pequenas funções embutidas. Eu não sei o que você comenta "difícil de ler" neles. Mas eles permitem coisas como o algoritmo Sort onde você envia a função de comparação, ou o resto da biblioteca de algoritmos std. Eles também permitem economizar memória e melhorar significativamente o desempenho graças à remoção da necessidade de matrizes temporais (o que acontece várias vezes no código-fonte com o octree e outros sistemas)

E com a biblioteca de algoritmos std, essa é a maneira mais fácil de multithread de um programa que você pode obter, apenas através de parallel for, ordenação paralela e acumulação paralela.

É uma pena que as pessoas os considerem "estranhos", quando eles podem melhorar tanto a qualidade do código, o desempenho e a reutilização.

Exemplo real de algo que já implementei:

//old linked list iteration
while (scenario->instances.first()) {
            instance_set_scenario(scenario->instances.first()->self()->self, RID());
        }
//new (just abstracts above)
scenario->instances.for_each([]( RID& item  ){
    instance_set_scenario(item, RID());
});

Há também este outro caso, repetido muitas vezes

//old. Note the 1024 array, wich is hard size, and is wasting memory

int culled = 0;
Instance *cull[1024];
culled = scenario->octree.cull_aabb(p_aabb, cull, 1024);
for (int i = 0; i < culled; i++) {

    Instance *instance = cull[i];
    ERR_CONTINUE(!instance);
    if (instance->object_ID == 0)
        continue;

    instances.push_back(instance->object_ID);
}

//NEW. not implemented yet. 0 memory usage, can be inlined, and doesnt have a maximum size. 
//Its also shorter and will be faster in absolutely every case compared to old version.

scenario->octree.for_each_inside_aabb(p_aabb, [](Instance* instance){       
    ERR_CONTINUE(!instance);
    if (instance->object_ID == 0)
        continue;
    instances.push_back(instance->object_ID);
});

Exemplo real de algo que já implementei:

Não sei qual é o ganho aqui...

É uma pena que as pessoas os considerem "estranhos", quando eles podem melhorar tanto a qualidade do código, o desempenho e a reutilização.

@vblanco20-1 bem, vou tentar me explicar.
As pessoas geralmente acabam escrevendo coisas como:

my_arr.push(
    [par1, par2, par3]{
      somefunc(par1, par2, par3);
    }
);

e depois, em alguns outros lugares:

func = my_arr.front();
func();

O que, quando você lê, não dá nenhuma pista sobre qual função é executada, nem o que procurar. Pode ser bom se estiver no mesmo arquivo, mas o que acontece se você passar essa matriz por vários arquivos é que todo o código se torna ilegível.

@Faless seu exemplo é um exemplo do pior que você pode fazer com lambdas (e esse tipo de uso definitivamente deve ser banido).

Usar lambdas "across time" não é um bom uso deles, pois eles terão que alocar, e eles também se tornam um grande perigo, pois as variáveis ​​capturadas podem deixar de ser válidas no momento em que você executa o lambda (por exemplo, capturando um ponteiro e, em seguida, excluindo o objeto antes de chamar o lambda, o ponteiro capturado ficará pendurado). Estou realmente defendendo lambdas para seu uso ao lado de estruturas de dados e algoritmos, onde você os usa "instantaneamente".

No exemplo for_each, o for_each é imune a mudanças na estrutura de dados interna (por exemplo, se você reordenar um pouco suas variáveis), ele permite estruturas de dados mais "opacas" (o que permite que um dev seja capaz de "facilmente-ish" " mude de uma estrutura de dados para outra para testar qual pode funcionar melhor. Ao adotar isso, você pode implementar estruturas de dados muito mais complicadas que funcionam de forma opaca, mantendo a camada de "uso" fácil de usar.

Também está reduzindo o clichê e tornando mais "claro" o que o código está realmente fazendo (iterando a lista vinculada). Apenas se livrar do " first()->self()->self " é uma melhoria em si mesmo.

Tenha em mente que os benefícios disso realmente se acumulam com mais uso, pois você poderá ter coisas como "unordered_for_each", que itera os nós na ordem em que estão na memória (através do alocador ou se a lista vinculada estiver armazenada no topo uma matriz), ou "reverse_for_each". Mesmo coisas como "encontrar" ou "classificar". (Eu não recomendo muito o std::algorithms, pelo menos até C++ 20 quando os Ranges são mesclados. implementar seus próprios algoritmos como parte de sua estrutura de dados é muito melhor de usar)

Lambdas foram basicamente a primeira coisa que a Epic Games permitiu do C++ 11 no motor irreal, juntamente com sua própria biblioteca de algoritmos como Sort, Partition, Filter, RemoveAll, Find, etc. no código do motor e no código do jogo

Legal, pelo menos concordamos que lambdbas não são um santo Graal de
programação, e já definimos uma regra de como não usá-los.

Vou pesquisar mais sobre performances.

No sábado, 8 de dezembro de 2018, 17:06 vblanco20-1 < [email protected] escreveu:

@Faless https://github.com/Faless seu exemplo é um exemplo do
pior que você pode fazer com lambdas (e esse tipo de uso definitivamente deve ser
banido).

Usar lambdas "através do tempo" não é um bom uso deles, pois eles terão
alocar, e eles também se tornam um grande perigo, pois as variáveis ​​capturadas
deixaria de ser válido no momento em que você executasse o lambda (por exemplo,
capturando um ponteiro e, em seguida, excluindo o objeto antes de chamar o lambda,
o ponteiro capturado irá oscilar). Eu só estou realmente defendendo lambdas para
seu uso ao lado de estruturas de dados e algoritmos, onde você os usa
"imediatamente".

No exemplo for_each, o for_each é imune a mudanças no
estrutura de dados (por exemplo, se você reordenar um pouco suas variáveis), ela permite
estruturas de dados mais "opacas" (o que permite que um dev seja capaz de
mudança "facilmente" de uma estrutura de dados para outra para testar qual
pode funcionar melhor. Ao abraçar isso, você pode implementar muito mais
estruturas de dados complicadas que funcionam de forma opaca, mantendo o "uso"
camada fácil de usar.

Também está reduzindo o clichê e tornando mais "claro" o que é o código
realmente fazendo (iterando a lista encadeada). Basta se livrar do "
first()->self()->self " é uma melhoria em si mesmo.

Tenha em mente que os benefícios disso realmente se acumulam com mais uso, pois
você poderá ter coisas como "unordered_for_each" que itera
os nós na ordem em que estão na memória (através do alocador, ou se o
lista encadeada é armazenada no topo de uma matriz), ou "reverse_for_each". Até as coisas
como "localizar" ou "classificar". (Eu não recomendo muito o std::algorithms, no
pelo menos até C++ 20 quando os intervalos são mesclados. implementando seus próprios algoritmos
como parte de sua estrutura de dados é muito melhor usar)

Lambdas foram basicamente a primeira coisa que a Epic Games permitiu do C++11 para
motor irreal, juntamente com sua própria biblioteca de algoritmos como Sort,
Partition, Filter, RemoveAll, Find, etc.
usado no código-fonte, tanto no código do mecanismo quanto no código do jogo


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/godotengine/godot/issues/9694#issuecomment-445474001 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/ABnBbvBTxThAh0v8AfFCdGsSv2HFnEz6ks5u2_GKgaJpZM4ObOio
.

Recursos do C++ 11 que melhorariam a capacidade de manutenção da base de código:

  • override e final
  • Loop for baseado em intervalo
  • nullptr
  • Enumerações fortemente tipadas
  • explicit palavra-chave

Recursos do C++11 que melhorariam a qualidade de vida:

  • Colchete de ângulo reto (não mais Vector<Vector> > )

[[nodiscard]] (C++17?) veja aqui

Não acho que devemos adotar uma versão moderna para usar coisas novas ou porque existem 1 ou 2 recursos que podem ser usados. Eu não proporia usar algo além do C++ 11 porque o benefício não vale a pena.
À medida que o C++ evolui, o stl cresce cada vez mais e centenas de linhas são adicionadas ao projeto durante o estágio de pré-processamento. Na maioria dos casos, tem um impacto notável no desempenho.

Na maioria dos casos, tem um impacto notável no desempenho.

Absolutamente não deveria? O código que não é executado não deve ter impacto no desempenho, a menos que você tenha um bug do compilador ou algo assim, e no lançamento não deve estar nos binários finais? Em muitos casos, apenas a semântica de movimento adequada aumenta o desempenho, em algumas áreas significativamente, e embora seja um exemplo mais antigo, as diretivas atualizadas também podem ter aumentos de desempenho entre outros recursos.

@OvermindDL1 Sim, é assim que deveria ser em teoria.
Confira: https://twitter.com/zeuxcg/status/1085781851568914432
Eu vi vários casos como este nas mídias sociais recentemente. Não é como "abstrações de custo zero" como deveria.

@lupoDharkael Esse tópico do twitter (uma vez carregado, o twitter é uma interface horrível ... eu continuo esquecendo isso ...) só fala sobre a velocidade de compilação porque math.h em libstdc++ é maior no modo C++ 17 (onde libc++ não tem esse problema) devido a uma maior quantidade de sobrecargas e recursos para melhor velocidade de execução, de modo que qualquer compilação que traz math.h tem um tempo de compilação aumentado de cerca de 300ms se estiver usando esse stdlib mais antigo específico em vez do stdlib mais recente. Ele não diz nada sobre o desempenho do tempo de execução (do qual eu só vi ser mais rápido em modos C++ mais altos, dependendo dos recursos usados, velocidade idêntica nos piores casos). Então, quanto a It's not as "zero cost abstractions" as it should. , ainda não tenho certeza do que você está referenciando? Você tem algum link para relatórios reais de problemas de desempenho em tempo de execução, pois o encadeamento que você vinculou parecia não ter nada a ver com isso, pois era apenas um aumento de 300 ms no tempo de compilação ao compilar com math.h no stdlib mais antigo (estou não tenho certeza se vejo o problema de um aumento plano de 300ms no tempo de compilação de um objeto compilado de qualquer maneira)?

Vou investigar mais a respeito.
Eu entendo que o custo tem uma razão, mas tempos de compilação maiores são realmente um anti-recurso para mim. Eu só tenho um laptop e trabalhar em recursos para o mecanismo leva seu tempo porque o tempo que tenho que esperar pelo processo de compilação + link após cada alteração. aumentar ainda mais sem um benefício realmente justificado, apenas por usar uma versão mais recente não é uma boa ideia.

Eu só tenho um laptop e trabalhar em recursos para o mecanismo leva seu tempo porque o tempo que tenho que esperar pelo processo de compilação + link após cada alteração.

Se você tiver o ccache instalado, muito tempo no processo de compilação incremental será gasto na vinculação. Você pode economizar um segundo usando gold em vez de ld para vincular, e é provável que seja possível otimizá-lo ainda mais mudando para lld .

Ah, definitivamente, não posso dizer o suficiente para ccache e ninja como o construtor de apoio (se estiver usando cmake ou algo assim, por exemplo), ambos economizam muito tempo!

Além disso, as compilações de unidade podem ser surpreendentemente surpreendentes, é aí que você cria um novo unity.cpp que inclui apenas todos os arquivos cpp do projeto, embora, na realidade, você geralmente tenha um arquivo cpp de unidade por 'módulo' de um projeto para que você tenha apenas uma dúzia ou mais para manter a memória baixa, em troca dessa memória extra em tempo de compilação, ela compila e vincula muito mais rápido. Esses são menos úteis para reconstrução incremental e mais úteis para compilações de lançamento.

Para adicionar um à pilha:
static_assert

Por exemplo, a união SpatialMaterial::MaterialKey assume que o struct tem o mesmo tamanho que o uint64_t, mas não é afirmado em nenhum lugar até onde eu saiba.

Queria colocar um static_assert(sizeof(MaterialKey) == sizeof(uint64_t)) ali, mas não conseguiu.

A outra coisa é usar unique_ptr para garantir a limpeza adequada na destruição sem ter que escrever muito clichê manual e sem usar a contagem de referências desnecessária.

Eu gostaria de oferecer meus 2 (ou 20) centavos como alguém novo para Godot e sua base de código. Atualmente estou supervisionando e trabalhando no esforço de portar _Battle for Wesnoth_ para Godot. Agora, o front-end (o editor e a API GDScript) é ótimo! Além de algumas arestas, até agora nos permitiu progredir em um bom ritmo. Mas também imaginamos que nós (a equipe) contribuiríamos com patches para o back-end (o mecanismo) em algum momento. Para esse fim, no início desta semana eu clonei o repositório git e comecei a bisbilhotar o C++, e honestamente... estou um pouco desanimado.

Eu tenho experiência no gerenciamento de uma grande base de código C++ na forma do antigo mecanismo personalizado de Wesnoth. Ele também começou como C++03, mas foi modernizado para usar C++11 e recursos posteriores e agora é compatível com C++14. Liderei esse esforço de modernização (muitas vezes com um pouco de zelo) e sinto que tornou nossa base de código muito mais legível e fácil de trabalhar. Concedido, eu nunca trabalhei extensivamente com uma base de código puramente C++03; Aprendi C++ usando recursos modernos de C++. Mas para mim, a ideia de que coisas como auto , range-for e lambdas tornam seu código menos legível é apenas... muito estranha mesmo.

Pegue auto é certamente possível abusar e usar demais, mas também tem uma tonelada de usos legítimos. Um dos lugares mais comuns em que implantamos auto quando atualizamos a base de código Wesnoth foi em loops for usando iteradores. Antes teríamos algo assim:

for(std::vector<T>::iterator foo = container.begin(); foo != container.end(); ++foo) {}

O que é bagunçado! Nos casos em que realmente precisávamos de um iterador, fizemos isso:

for(auto foo = container.begin(); foo != container.end(); ++foo) {}

Sim, agora você não conhece o tipo de iterador explícito, mas quase nunca precisa saber disso. Eu li um comentário aqui em alguns posts dizendo que fica mais difícil se o contêiner for declarado alguns arquivos ausentes, mas realmente, com editores de código modernos e intellisense, isso não é um grande problema.

Na maioria dos casos, basta alternar para range-for:

for(const auto& foo : container) {}

Muito mais rápido de digitar e mais conciso também. Você não precisa se preocupar em desreferenciar iteradores dentro do loop ou em acompanhar os índices. E fica bem claro que você está fazendo um loop sobre todo o contêiner. Com iteradores, as pessoas não familiarizadas com o código precisam verificar novamente se o loop está realmente indo do começo ao fim.

Usar auto em loops range-for aqui também tem um benefício adicional. Faz uma coisa a menos que você precisa se lembrar de atualizar se você alterar o tipo do contêiner! Eu realmente não consigo entender o argumento de Juan de que essas coisas tornam seu código menos legível ou menos fácil de entender. Para mim, é exatamente o oposto.

No vídeo do Estado de Godot, ele também mencionou lambdas. Novamente, certamente é possível abusar deles, mas eles também são uma ferramenta incrivelmente útil! Aqui está um paradigma comum que vi na base de código de Wesnoth antes de usar o C++ 11:

struct sort_helper {
    operator()(const T& a, const T& B) {
        return a < b;
    }
}

void init() const {
    std::vector<T> foo;
    foo.push_back(T(1));
    foo.push_back(T(2));
    foo.push_back(T(3));

    std::sort(foo.begin(), foo.end(), sort_helper);
}

Longo, confuso, código inchado. E aqui está o que usamos com o C++11:

void init() const {
    std::vector<T> foo {
        T(1),
        T(2),
        T(3),
    };

    std::sort(foo.begin(), foo.end(), [](const T& a, const T& b) { return a < b; });
}

Esse é apenas o caso mais comum! E sim, eu sei que você também pode implementar operator< para T e que std::sort usa isso por padrão, mas ilustra meu ponto. Novamente, talvez seja apenas ter aprendido e trabalhado quase exclusivamente com o C++ moderno, mas acho que desconsiderar ferramentas como auto , range-for e lambdas quando apropriado é dar um tiro no pé.

O que me leva ao meu próximo ponto. Percebi que Godot usa internamente muitos de seus próprios tipos personalizados em vez dos STL. Se sua preocupação é a legibilidade do código em relação a coisas como auto , usar tipos de núcleo personalizados sobre os STL é absolutamente prejudicial! Alguns dias atrás, eu estava navegando pela base de código e me deparei com um monte de código que se parecia com isso:

container.push_back(T(args));

Agora, isso é ineficiente. push_back (pelo menos em termos de std::vector ) recebe uma referência const; portanto, este operador resulta em uma cópia desnecessária. Eu queria fazer um patch para fazê-los usar emplace_back ... mas então percebi que toda a base de código estava usando tipos de contêiner personalizados 😐

Um dos grandes problemas com o design de Wesnoth e um dos principais fatores que contribuíram para a decisão de usar um novo motor (neste caso, Godot) foi que Wesnoth sofria da síndrome de Não Inventado Aqui em grande medida. Embora usássemos bibliotecas como Boost, nosso pipeline de renderização era personalizado, nosso kit de ferramentas de interface do usuário era personalizado, nossa linguagem de dados/script era personalizada... Antes de começarmos a trabalhar na porta Godot, tentei (e falhei) implementar renderização acelerada por hardware (até este ponto, estávamos usando renderização de superfície/sprite baseada em CPU. Em 2019. Sim, eu conheço X_X). A API Texture do SDL não tinha suporte a shader, e era necessário suporte a shader. No final, decidi que implementar nosso próprio renderizador, embora possível, imporia uma carga de manutenção desnecessária ao projeto no futuro. Nós já tínhamos poucos desenvolvedores principais, e encontrar alguém capaz de escrever OpenGL (ou Vulkan se precisássemos abandonar o OGL) teria sido uma dor desnecessária quando um mecanismo como Godot tem um renderizador perfeitamente bom e bem mantido que poderíamos usar em vez de.

Trago isso à tona porque acho que é um bom exemplo de por que implementar tudo internamente pode ser uma má ideia. Sim, você pode reduzir um pouco o tamanho do seu binário por não usar a biblioteca padrão, mas também incorre em uma enorme carga de manutenção. Converter essas chamadas push_back para emplace_back é uma fruta muito fácil que pode tornar o código mais limpo e com melhor desempenho. Mas como você tem um tipo vector personalizado, se você quiser uma construção no local, alguém precisará implementá-la manualmente. E em todos os outros tipos personalizados também!

Um problema ainda maior é que aumenta a barreira à entrada. Eu olhei para a base de código Godot esperando tipos C++ STL. Não encontrar esses meios que eu ou qualquer outra pessoa agora precisa aprender exatamente quais tipos o mecanismo fornece e qual API acompanha cada um. Eu quero std::map ? Não, eu preciso usar dictionary . Isso só dificulta a vida dos mantenedores e complica as coisas para os novos contribuidores. E realmente, não é um código que parece uma coisa, mas na verdade é outra... ilegível?

Cerca de um ano atrás, quando nos aproximamos da versão 1.14 de Wesnoth e do lançamento no Steam, realizei um projeto de refatoração para eliminar um tipo de contêiner personalizado que era essencialmente std::list exceto que o uso erase() não invalidava iteradores. Aqui está o princípio do commit em questão . Ele precisava de mais trabalho depois disso para ficar certo, mas o resultado foi um código muito mais simples, mais fácil de entender e menos opaco.

Concluindo, acho que proibir certos recursos muito úteis do C++11 e manter os tipos personalizados em vez dos STL será um obstáculo para Godot a longo prazo. Eu sei que refatorar as coisas leva muito tempo (confie em mim, eu sei), mas do jeito que as coisas estão agora parece muito provável que você acabe com um catch-22. Ao tentar evitar as desvantagens de usar o STL (tamanhos binários maiores, etc.), você acabará tornando cada vez mais difícil mudar para um código mais limpo e de melhor desempenho em padrões C++ mais recentes. Tenho certeza de que ninguém está particularmente ansioso para implementar a construção no local em todos os tipos personalizados. 😬

Eu sei que minha opinião não significa muito aqui, mas imaginei que daria meus pensamentos da perspectiva de alguém que trabalhou com uma grande base de código C++ que mudou para o C++ moderno do C++03. É muito trabalho, mas a longo prazo eu sinto que vale a pena.

Desculpe o textwall enorme!

@Vultraz Concordo plenamente.

Embora eu não tenha (quase nenhuma) experiência com C++, depois de trabalhar algum tempo com GDNative + C++ compartilho sua opinião.

Apenas alguns dias atrás eu postei no reddit no tópico " Contribuir para Godot é uma boa maneira de aprender C++? ":

Eu não recomendaria. Da minha experiência em trabalhar com GDNative e C++, eles reinventam a roda pelo menos várias vezes (coleções personalizadas e ponteiros). Essas classes personalizadas não estão documentadas (pelo menos de ligações/cabeçalhos GDNative C++ POV - exceto um guia que leva a um projeto de demonstração não compilado da última vez que tentei, não há documentação no código [cabeçalhos, ligações] nem em documentos oficiais) e tem comportamento confuso/não intuitivo (por exemplo, agrupar alguma classe de mecanismo no final de Ref causa travamentos aleatórios, embora Ref intuitivamente deva ser transparente e, portanto, não deve alterar o comportamento de uma classe que está sendo agrupada).

Também não sou fã de preferir o clichê menos legível da IMO em vez de usar novos recursos C++. Eu gosto de Haskell (e suas bibliotecas), sua sucinta, não agradar aos iniciantes para não prejudicar a linguagem para usuários avançados.

Por causa dessas decisões/valores, duvido que algum dia possa contribuir para o motor. (É engraçado, porque os desenvolvedores de mecanismos afirmam que a razão por trás dessas decisões é incentivar contribuições. No meu caso, teve um efeito totalmente oposto.)

@Vultraz uma redação bem articulada, ótima leitura, obrigado por isso. É bom ouvir uma perspectiva como essa.

Sou um programador C++ estilo antigo e como tal não tive muitos problemas em entender o código fonte Godot, achei bem estruturado e muito do meu agrado. Acho que ter sido capaz de adicionar suporte VR ao núcleo desse mecanismo de forma relativamente rápida, tendo muito pouca experiência pré-existente com a base de código, é uma prova de quão legível e compreensível é esse mecanismo. Pode ser da velha escola em alguns aspectos, mas estou continuamente surpreso com o quão bem certas partes são construídas. Sim, há partes que precisam de modernização para consumir menos memória e ter mais desempenho, mas como um todo, fiquei impressionado com isso.

Quando ouço as pessoas falando sobre a sintaxe mais recente em C++, geralmente me sinto velho e realmente me pergunto sobre o motivo de tanto barulho. Mas eu meio que entendo que quando você aprende C++ mais moderno e decide contribuir com Godot, é estranho que pareça reinventar a roda. Mas para pessoas como eu, pelo menos, estamos tão acostumados a ver classes como essas implementadas, estamos impressionados com o quão bem a maioria é implementada aqui :)

Agora que tudo dito, há tantas maneiras diferentes de olhar para este problema não é engraçado. Desde as origens do Godot anterior à sintaxe C++ mais recente até o que os desenvolvedores principais se sentem mais à vontade até as limitações impostas pela natureza multiplataforma do Godot e os dispositivos (alguns não públicos) nos quais ele pode (/poderia) ser executado. Eu não sinto que há certo ou errado nisso, é o que você está acostumado e de onde você está vindo, e a questão sobre se a curva de aprendizado extra aprendendo como Godot funciona supera os benefícios de refatorar um código grande base.

Não sei se você assistiu, mas Juan fez uma palestra na GDC que foi colocada online: https://www.youtube.com/watch?v=C0szslgA8VY
Durante as perguntas e respostas no final, ele fala um pouco sobre a tomada de decisão em torno disso.
É um bom relógio.

Quanto às reações acima no GDNative, caras comuns, o GDNative é uma adição recente que atende a uma necessidade específica, não é indicativo de como o produto principal é estruturado e construído.
Tente construir um módulo (https://docs.godotengine.org/en/3.1/development/cpp/custom_modules_in_cpp.html), sim, isso requer a compilação de suas alterações no produto principal, que é o problema que a GDNative tenta resolver, mas isso dá você uma compreensão muito melhor de como o motor realmente está estruturado. Muitos dos argumentos apresentados acima são deficiências da GDNative, não da própria Godot.
Eu adoraria uma solução de módulo dinâmico que me permitisse construir módulos da mesma forma que estamos construindo módulos estáticos, talvez algum dia isso seja possível, mas até então GDNative é suficiente.

@Vultraz @mnn Na verdade, posso ver o ponto sobre os contêineres STL, mas puramente porque algumas implementações (MSVC, principalmente) têm um desempenho terrivelmente lento no modo de depuração. Mas pode-se simplesmente usar o STL da EA e ser bom (o deles é rápido e portátil).

Além disso: eu pessoalmente achei a falta de RAII mais angustiante. A quantidade de limpeza manual que é executada desnecessariamente em todo o código é estranha, já que o RAII não é novo no C++11.

Quanto às reações acima no GDNative, caras comuns, o GDNative é uma adição recente que atende a uma necessidade específica, não é indicativo de como o produto principal é estruturado e construído.

Claro que isso é verdade, mas não deveria ser o GDNative mais amigável do que o próprio mecanismo, já que o GDNative é usado para criar "scripts", tão direcionados a um público que deve ter habilidades de C++ ainda mais baixas que aqueles dispostos a mergulhar em internos de o motor?

Por causa dessas decisões/valores, duvido que algum dia contribua para o motor. (É engraçado, porque os desenvolvedores de mecanismos afirmam que a razão por trás dessas decisões é incentivar contribuições. No meu caso, teve um efeito totalmente oposto.)

Eu sou um noob C++ (cerca de dois meses trabalhando 2 dias por semana em C++), então eu deveria ter sido o alvo demográfico lucrando com essa decisão. Acho que os contêineres Godot carecem de funcionalidade básica (comparado ao stl, que não é muito rico), não acho que os contêineres sejam relacionados à GDNative, mas posso estar enganado. Estou fazendo o meu melhor evitando contêineres Godot, porque eles são sempre difíceis de trabalhar. Eu pensei que o comportamento inconsistente e inesperado do Ref era responsabilidade do motor, mas acho que estou errado.

Percebo que minha experiência em programação é provavelmente bastante incomum - anos de trabalho profissional em JS/TS, ano em Scala, projetos de hobby menores em Haskell (alguns milhares de linhas, o que não é tão pequeno considerando o quão conciso Haskell é - muitas vezes escrevo código em C++ que em Haskell seria pelo menos 5 vezes menor e mais legível). Eu me pergunto se eu sou o único a ser desencorajado pelo uso de tecnologia arcaica excessivamente detalhada.

@mnn , o GDNative foi criado para permitir a criação de módulos baseados em C como bibliotecas dinâmicas para que você não precise recompilar todo o mecanismo. Além disso, várias vinculações de linguagem foram criadas para que você pudesse escrever módulos em C++, python, rust, etc. que novamente não exigia a compilação de todo o mecanismo.

O outro objetivo disso era que os desenvolvedores pudessem criar módulos onde você apenas entregasse o próprio módulo e o usasse com uma construção estável do mecanismo. Muitos módulos foram extintos porque não estão sendo mantidos ainda estão vinculados a uma versão específica do mecanismo.

Então, sim, ficou muito mais fácil e simples criar módulos de uma perspectiva de compilação e implantação. Mas devido à sua abordagem tem suas limitações. Quando você permanece dentro dessas limitações, escrever código C++ no GDNative pode permanecer simples, pois o assunto para o qual você está construindo um módulo é simples.

Tente sair dessas limitações e você terá dores de cabeça. Minha dor de cabeça atual é tentar implementar a lógica OpenGL enquanto tudo isso está encapsulado dentro da arquitetura do servidor visual e não está realmente disponível dentro do GDNative da maneira que eu preciso que seja. Mas isso é mais um fator de eu querer fazer algo com o GDNative para o qual nunca foi projetado.
Quando você o usa para o que foi projetado, pode fazer coisas muito legais com ele.

Observe também que o GDNative foi concebido como uma maneira de reescrever o código GDScript que precisa ter mais desempenho. Como resultado, você não pode acessar nada dentro do mecanismo que o GDScript também não tenha acesso (como OpenGL)

E quanto ao suporte de corrotina mais recente, ou seja, co_await()? De uma perspectiva progenitora, isso é ENORME.

O suporte a coroutine faz parte do padrão C++ 20, que ainda não foi finalizado, para registro.

Permita-me entrar em contato, atualmente estou escrevendo ferramentas brush/csg avançadas (pense no editor de martelo de origem) para o Editor Espacial 3D e essa conversa sobre não permitir o auto é realmente confusa para mim. Às vezes _mesmo o uso de auto pode ser explícito_. Considere o seguinte:

Estou dentro do SpatialEditorViewportContainer e quero obter o SpatialEditor, que está três itens acima da hierarquia pai (antes que alguém aponte que existem maneiras melhores de acessar o SpatialEditor a partir do contêiner da viewport, considere que comecei a olhar para esta base de código ontem)

auto sp = Object::cast_to<SpatialEditor>(get_parent()->get_parent()->get_parent());

Como você pode ver, o downcast dinâmico _já indica explicitamente o tipo de SP_. Sem auto, eu teria que escrever lixo redundante como:

SpatialEditor sp = Object::cast_to<SpatialEditor>(get_parent()->get_parent()->get_parent());

Por favor, pelo amor de Deus, permita o uso de auto!

Sobre o tópico de qual padrão C++ usar: Os recursos propostos de reflexão e metaclasse do C++ 20 e além seriam realmente úteis para reduzir a desordem de macros, como

GDCLASS(SpatialEditorViewportContainer, Container);

Além disso, gostaria de repetir que essas restrições me fazem realmente duvidar da minha decisão de contribuir. Por volta de 2012, aprendi sozinho C++11 e não poder usar esse padrão parece um tapa na cara. C++11 e C++03 são linguagens completamente diferentes, e a maior parte da reputação de que C++ é difícil de aprender, ler e escrever é culpa do C++03 (ou melhor, 98). Não usar pelo menos C++ 11 ou 14 é _prejudicial_ para manutenção e legibilidade. Eu cresci com C++11 e o líder do projeto evidentemente não (quando ele começou a trabalhar no Godot em 2007, eu tinha 12 anos), então acho que isso é mais um caso de preferência e síndrome do pato bebê. Eu sinto que não usar o C++11 é apenas para confortar as pessoas que estão acostumadas com o C++ da velha escola (também conhecido como terrível), às custas de pessoas como eu, que tiveram tempo para aprender o C++ moderno.

Com o passar do tempo, mais e mais programadores juniores como eu serão criados no C++ 11 moderno e além e acharão o fato de que o projeto está para sempre preso em uma linguagem que nem sequer tem lambdas para ser bastante desencorajador.

Resumindo: C++11 ou fracasso!

Para ser claro, não defendo o uso de STL. Rolar seus próprios contêineres é bom, mas rejeitar recursos como lambdas e auto parece bobo.

Vou fechar a câmara de eco por enquanto, pois é inútil discutir mais aqui. Já discutimos meses atrás que tipo de recursos do C++11 e/ou algumas versões posteriores planejamos usar.

Estamos apenas esperando @hpvb ter tempo para finalizar as diretrizes que ele está escrevendo com base em nosso consenso, e então podemos discutir um pouco mais sobre essas diretrizes assim que forem publicadas. Até então, isso não é construtivo.

Esta página foi útil?
0 / 5 - 0 avaliações