Godot: Estado de C++ en Godot

Creado en 18 jul. 2017  ·  65Comentarios  ·  Fuente: godotengine/godot

En Godot todavía estamos usando C++ 98/03. El objetivo de este problema es tratar de decidir si aún debemos apegarnos a él o si podemos comenzar a usar algunas cosas modernas de C++ (C++11/14/17 e incluso 20).

Cuando se escribió inicialmente Godot, C++03 era la versión más actual y años después se mantuvo ese estilo (versión del estándar, evitación de la biblioteca estándar, etc.) para evitar problemas de compatibilidad en ciertos compiladores (algunas consolas, hasta ahora). que yo sé). Pero debemos revisar estas preocupaciones.

Elementos a considerar (esta lista está destinada a seguir creciendo o descartar algunas entradas; solo una lluvia de ideas al principio):

  • Punteros inteligentes: ¿El mecanismo de puntero/objeto personalizado de Godot es compatible con eso? ¿Necesitaría Godot ser reescrito en gran parte para usarlos? ¿Serían los beneficios mayores que el costo?
  • Mover la semántica: las preguntas que se pueden hacer aquí son más o menos las mismas que para los punteros inteligentes.
  • auto , constexpr , lambdas, etc. : en general, cualquier característica moderna de C++ que facilite la escritura de código y/o brinde alguna posibilidad de optimización sin interrumpir el núcleo actual.
  • Primitivos de multiprocesamiento estándar : C++ ahora proporciona subprocesos estándar, atómicos, barreras de memoria, etc. ¿Tenemos que mantener implementaciones personalizadas y mantenerlas para diferentes plataformas?
  • STL/Boost/otro : ¿Obtendríamos algún beneficio al cambiar de contenedores personalizados a una buena implementación conocida? Des/ventajas en cuanto a mantenimiento, rendimiento, compatibilidad, etc.
  • register , "trucos" insertados, etc. : Cosas añadidas al código para tratar de hacer que el compilador genere un código de mayor rendimiento. Algunos de ellos están obsoletos o no tienen un impacto real, o incluso pueden empeorar el rendimiento. ¿Cuál soltar? ¿Cuál conservar?
  • etc,

Se pueden hacer pequeños cambios antes. Pero, ¿cuándo se deben hacer los grandes cambios (en su caso)? ¿Godot 4.0? ¿O tan pronto como Godot 3.0 se estabilice?

Permítanme invitar a algunas personas explícitamente: @reduz , @punto-, @akien-mga, @karroffel , @bojidar-bg, @BastiaanOlij , @Faless.

discussion

Comentario más útil

Me gustaría ofrecer mis 2 (o 20) centavos como alguien nuevo en Godot y su base de código. Actualmente estoy supervisando y trabajando en el esfuerzo de portar _Battle for Wesnoth_ a Godot. ¡Ahora, la interfaz (el editor y la API de GDScript) es excelente! Aparte de algunas asperezas, hasta ahora nos ha permitido progresar a buen ritmo. Pero también imaginamos que nosotros (el equipo) contribuiríamos con parches para el back-end (el motor) en algún momento. Con ese fin, a principios de esta semana cloné el repositorio git y comencé a hurgar en C++ y, sinceramente... Estoy un poco consternado.

Tengo experiencia en la gestión de una gran base de código C++ en la forma del antiguo motor personalizado de Wesnoth. También comenzó como C++03, pero se modernizó para usar C++11 y características posteriores y ahora es compatible con C++14. Encabecé ese esfuerzo de modernización (a menudo con demasiado celo) y creo que hizo que nuestro código base fuera mucho más legible y fácil de trabajar. De acuerdo, nunca trabajé mucho con un código base puramente C++03; Aprendí C++ usando las características modernas de C++. Pero para mí, la idea de que cosas como auto , range-for y lambdas hacen que su código sea menos legible es simplemente... muy extraño.

Tome auto , es ciertamente posible abusar de él y usarlo en exceso, pero también tiene un montón de usos legítimos. Uno de los lugares más comunes en los que implementamos auto cuando actualizamos el código base de Wesnoth fue en los bucles for usando iteradores. Anteriormente, tendríamos algo como esto:

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

¡Qué desordenado! En los casos en que realmente necesitábamos un iterador, hicimos esto:

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

Sí, ahora no conoce el tipo de iterador explícito, pero casi nunca necesita saberlo. Leí un comentario aquí en algunas publicaciones que dicen que hace que sea más difícil si el contenedor se declara a algunos archivos, pero en realidad, con los editores de código modernos y la inteligencia, eso no es un gran problema.

En la mayoría de los casos, simplemente cambiaríamos a range-for:

for(const auto& foo : container) {}

Mucho más rápido de escribir y más conciso también. No necesita preocuparse por desreferenciar iteradores dentro del bucle o por realizar un seguimiento de los índices. Y está muy claro que está recorriendo todo el contenedor. Con los iteradores, las personas que no están familiarizadas con el código deben verificar dos veces que el ciclo realmente va de principio a fin.

Usar auto en bucles range-for aquí también tiene un beneficio adicional. ¡Hace una cosa menos que debe recordar actualizar si alguna vez cambia el tipo de contenedor! Realmente no puedo entender el argumento de Juan de que estas cosas hacen que tu código sea menos legible o menos fácil de entender. Para mí, es exactamente lo contrario.

En el video del Estado de Godot también mencionó a las lambdas. Nuevamente, ciertamente es posible abusar de ellos, ¡pero también son una herramienta increíblemente útil! Aquí hay un paradigma común que vi en el código base de Wesnoth antes de usar 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);
}

Código largo, desordenado e inflado. Y esto es lo que usamos con 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; });
}

¡Ese es el caso más común! Y sí, sé que también puede implementar operator< para T y que std::sort lo usa de forma predeterminada, pero ilustra mi punto. Nuevamente, tal vez solo haya aprendido y trabajado casi exclusivamente con C ++ moderno, pero creo que ignorar herramientas como auto , range-for y lambdas donde corresponda es dispararse a sí mismo.

Lo cual me lleva a mi siguiente punto. He notado que Godot usa internamente muchos de sus propios tipos personalizados en lugar de los STL. Si su preocupación es la legibilidad del código con respecto a cosas como auto , ¡usar tipos de núcleo personalizados sobre los STL es absolutamente perjudicial! Hace unos días, estaba navegando por el código base y me encontré con un montón de código que se veía así:

container.push_back(T(args));

Ahora, esto es ineficiente. push_back (al menos en términos de std::vector ) toma una referencia constante; por lo tanto, este operador da como resultado una copia innecesaria. Quería hacer un parche para que usaran emplace_back ... pero luego me di cuenta de que todo el código base usaba tipos de contenedores personalizados 😐

Uno de los grandes problemas con el diseño de Wesnoth y uno de los principales factores que contribuyeron a decidirse por un nuevo motor (en este caso, Godot) fue que Wesnoth padecía en gran medida el síndrome de Not Invented Here. Si bien usamos bibliotecas como Boost, nuestra canalización de renderizado era personalizada, nuestro kit de herramientas de interfaz de usuario era personalizado, nuestro lenguaje de secuencias de comandos/datos era personalizado... entiendes la esencia. Antes de comenzar a trabajar en el puerto de Godot, intenté (y fallé) implementar el renderizado acelerado por hardware (hasta este punto habíamos estado usando renderizado de superficie/sprites basado en CPU. En 2019. Sí, lo sé X_X). La API Texture de SDL no tenía compatibilidad con sombreadores y se necesitaba compatibilidad con sombreadores. Al final, decidí que implementar nuestro propio renderizador, mientras fuera posible, impondría una carga de mantenimiento innecesaria en el proyecto en el futuro. Ya teníamos pocos desarrolladores centrales, y encontrar a alguien capaz de escribir OpenGL (o Vulkan si alguna vez necesitábamos dejar OGL) habría sido un dolor innecesario cuando un motor como Godot tiene un renderizador perfectamente bueno y bien mantenido que podríamos usar. en lugar de.

Menciono eso porque creo que es un buen ejemplo de por qué implementar todo internamente puede ser una mala idea. Sí, puede reducir un poco el tamaño de su binario al no usar la biblioteca estándar, pero también incurre en una carga de mantenimiento masiva. Convertir esas llamadas push_back a emplace_back es una tarea fácil que puede hacer que el código sea más limpio y de mayor rendimiento. Pero debido a que tiene un tipo personalizado vector , si desea una construcción en el lugar, alguien deberá implementarlo manualmente. ¡Y en todos los demás tipos personalizados también!

Un problema aún mayor es que eleva la barrera de entrada. Miré el código base de Godot esperando tipos STL de C++. No encontrar esos medios que yo o cualquier otra persona ahora necesita saber exactamente qué tipos proporciona el motor y qué API acompaña a cada uno. Quiero std::map ? No, necesito usar dictionary . Simplemente hace la vida más difícil para los mantenedores y complica las cosas para los nuevos contribuyentes. Y realmente, ¿no es el código que parece una cosa pero en realidad es otra... ilegible?

Hace aproximadamente un año, cuando cerrábamos el lanzamiento de la versión 1.14 de Wesnoth y su lanzamiento en Steam, emprendí un proyecto de refactorización para eliminar un tipo de contenedor personalizado que era esencialmente std::list excepto que usar erase() no invalidaba iteradores. Aquí está el compromiso principal en cuestión . Necesitó más trabajo después de eso para hacerlo bien, pero el resultado fue un código mucho más simple, más fácil de entender y menos opaco.

En conclusión, creo que prohibir ciertas características muy útiles de C++11 y apegarse a los tipos personalizados sobre los STL será un obstáculo para Godot a largo plazo. Sé que refactorizar las cosas lleva mucho tiempo (confía en mí, lo sé), pero tal como están las cosas ahora, parece muy probable que termines con un catch-22. Al tratar de evitar las desventajas de usar STL (tamaños binarios más grandes, etc.), terminará haciendo que sea cada vez más difícil cambiar a un código más limpio y de mejor rendimiento en los estándares C++ más nuevos. Estoy seguro de que nadie está ansioso por implementar la construcción in situ en todos los tipos personalizados. 😬

Sé que mi opinión no significa mucho aquí, pero pensé que daría mi opinión desde la perspectiva de alguien que ha trabajado con una gran base de código C++ que pasó a C++ moderno desde C++03. Es mucho trabajo, pero a largo plazo siento que vale la pena.

¡Disculpe el muro de texto masivo!

Todos 65 comentarios

Esto se discutió muchas veces, las respuestas habituales son:

1) No se desea mover la base de código a algo superior a 03. Las características no valen la pena. Sin embargo, para GDNative C++, es perfectamente posible usarlo.
2) No deseo usar STL/Boost/Etc. La razón siempre ha sido la misma: a) Las plantillas de Godot hacen cosas pequeñas que normalmente no hacen (es decir, refcounts atómicos y copia en escritura) b) STL genera enormes símbolos de depuración y binarios de depuración debido a sus símbolos extremadamente largos y destrozados.

Seguimos igual que todo.

Tengo algunas opiniones sobre conversaciones privadas, pero quería hacer el
discusión más pública.

No sabía que ya había sido tan discutido y cerrado.

¿Incluso descarta auto, constexpr y tal?

¿Soltar 'registrarse'?

El 18 jul. 2017 13:54, "Juan Linietsky" [email protected]
escribió:

Esto se discutió muchas veces, las respuestas habituales son:

  1. No se desea mover la base de código a nada superior a 03. Las características son
    no vale la pena. Sin embargo, para GDNative C++, es perfectamente posible usarlo.
  2. No deseo usar STL/Boost/Etc. La razón siempre ha sido la
    mismo: a) Las plantillas de Godot hacen cosas pequeñas que normalmente no hacen (es decir, atómico
    refcounts y copy on write) b) STL genera enormes símbolos de depuración y depuración
    binarios debido a sus símbolos destrozados extremadamente largos

Seguimos igual que todo.


Usted está recibiendo esto porque usted fue el autor del hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/godotengine/godot/issues/9694#issuecomment-316041201 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ALQCtipKmepD_1Xw6iRXZ7aGoQlLfiwFks5sPJzqgaJpZM4ObOio
.

Planteé este problema: sería más seguro usar punteros inteligentes y que este código sufre del síndrome NIH, pero los superiores no tienen intención de actualizar, por lo que le sugiero que también se dé por vencido.

Probablemente se necesitarían muchas horas de trabajo para actualizar el código, que los desarrolladores prefieren gastar en implementar nuevas funciones. De alguna manera entiendo esto: los usuarios del motor prefieren obtener nuevas funciones que "refactorizar" (sin comprender cosas como la deuda tecnológica).

@Marqin Lo siento, pero los punteros inteligentes son una mala idea porque:

1) la única útil es la refutada, el resto es masturbación mental por rasgos ya presentes en la lengua.
2) Usar punteros inteligentes en todas partes es una idea terrible, corre el riesgo de tener ciclos de referencia que pierden memoria
3) ya tenemos algo similar, Ref<> , con la ventaja adicional de que controlamos cómo funciona el conteo de referencias, por lo que podemos agregar casos especiales para manejar enlaces a lenguajes como C#, con su propio gc

Entonces, por favor, antes de argumentar que decidimos las cosas por capricho, solo pregunte. Por lo general, tomamos decisiones informadas y compartimos el proceso con todos.

Lo que se podría hacer fácilmente si desea mejorar la calidad es comenzar a escribir pruebas unitarias (agregue algún trabajo opcional a scons para eso, y simplemente haga que los desarrolladores instalen gmock/gtest ellos mismos [para no obstruir ./thirdparty])

el resto es masturbación mental por rasgos ya presentes en el lenguaje

@reduz , ¿cómo es que unique_ptr con su propiedad explícita y la liberación de memoria RAII ya están presentes en el lenguaje?

Además, no solo la gestión de la memoria, sino también la creación de subprocesos se vuelve más fácil (por ejemplo, lock_guard ).

@RandomShaper No estoy en contra de actualizar en algún momento en el futuro pero, aunque hay muchas características útiles, las versiones más nuevas de C++ tienden a hacer que el programador escriba código menos explícito. Esto da como resultado que el código sea generalmente más difícil de leer por otros (típico con el abuso automático de palabras clave).

La otra ventaja de no usar funciones más avanzadas (p. ej., no usamos excepciones y rtti se puede deshabilitar con poco costo) es que los compiladores producen binarios mucho más pequeños.

Esta bien. Respeto tu palabra. Solo encuentro que discutir sobre esto es algo saludable.

Sé que algunas grandes compañías de videojuegos (Ubisoft, IIRC) han migrado grandes bases de código a C++ moderno, por lo que también debe ser perfectamente adecuado para Godot. Por supuesto, como usted señaló, eso requiere trabajo/tiempo.

Es por eso que podríamos:

  • elija un subconjunto razonable para una migración de todo el código lo más sencilla posible;
  • o al menos elija un subconjunto razonable como "aprobado" para el nuevo código.

En ambos casos, probablemente sea necesario definir un estilo de codificación para no abusar de las funciones, como dijiste que sucede con auto .

Por supuesto, ahora hay cosas con mayor prioridad, pero tal vez cuando 3.0 se estabilice o en algún otro momento en el futuro, sería bueno haberlo decidido ya.

@Marqin Como se mencionó, Godot usa su propio esquema de asignación de memoria para los nodos, lo que tiene más sentido que cualquier cosa proporcionada por las nuevas bibliotecas estándar.

Además, Lock Guard tiene prácticamente 3 líneas de código y ya lo tenemos implementado sin usar C++ 11+.

Nuevamente, ¿por qué deberíamos suponer que cualquier cosa "estándar" 1) debe ser mejor que lo que ya tenemos 2) es mejor si lo usamos de todos modos? .Creo que es una falacia común.

¿Qué sigue, abandonar nuestras funciones de impresión y usar ostream/istream? Cambiar nuestra clase String, que es bastante impresionante, y reemplazarla con std::string o std::wstring?

Las bibliotecas estándar no sirven para todos los propósitos y no funcionan mejor que todo lo demás solo porque son bibliotecas estándar. Solo están ahí para aquellos que los necesitan, y está bien ignorarlos y escribir sus propias implementaciones si tiene una razón válida para hacerlo. Lo hacemos y estamos seguros de ello.

@RandomShaper El problema es que:

1) Es más costo que beneficio.
2) Abrirá la ventana a muchos argumentos sobre cómo es la forma estándar de escribir C++ 11+. Puede valer la pena tenerlos en algún momento, y reescribir gran parte del motor para aprovechar estas características puede ser útil algún día, pero creo que hay cosas mucho más importantes en las que concentrarse.
3) También como se mencionó anteriormente, es posible que, por más genial que suene la migración a una nueva versión de C ++, esto podría resultar en un tamaño binario mucho mayor. En tal caso, puede ser no deseado.

En serio, no vale la pena en este momento, podemos discutirlo nuevamente dentro de unos años.

Lo que creo que tendría sentido es asegurarse de que Godot compile con opciones como --std=c++17, para que pueda traer fácilmente una biblioteca escrita en C++ moderno si lo necesita. Por ejemplo, votaría por la eliminación de la palabra clave de registro de la base de código https://github.com/godotengine/godot/issues/9691

De alguna manera relacionado, recuerdo haber leído en alguna parte que gcc-6.3 no es compatible (google
dice que estaba en https://github.com/godotengine/godot/issues/7703). Me molesta ya que gcc-6.3 es el compilador predeterminado en mi distribución (debian estable). ¿Alguien puede confirmar eso? ¿Y por qué es eso?

@efornara, algunas bibliotecas de terceros ya requieren versiones más nuevas de C ++, y eso está bien porque Scons se encarga de eso con entornos de compilación clonados. Verifique el código de terceros de etc2comp para ver cómo funciona.

@karroffel Gracias, no sabía eso.

Entonces sería una buena característica, pero no es necesario para importar una biblioteca (aunque, si para el código de pegamento necesita incluir más de Godot, es posible que se encuentre con un archivo de encabezado que no se compila).

Por cierto, si alguien necesita hacer algo similar y ha encontrado esta publicación, el archivo correspondiente es: https://github.com/godotengine/godot/blob/master/modules/etc/SCsub . Lo encontré usando grep y parece el único lugar donde se necesita esto en este momento.

No es tan seguro vincular c ++ 11 con código no c ++ 11: http://gcc.gnu.org/wiki/Cxx11AbiCompatibility

@Marqin A menos que malinterprete el enlace, esto en realidad parece respaldar el caso de que Godot _no_ comience a usar componentes de la Biblioteca estándar, sino que se adhiera a componentes personalizados:

El lenguaje C++98 es compatible con ABI con el lenguaje C++11, pero varios lugares en la biblioteca estándar rompen la compatibilidad.

Mezclar idiomas parece bastante seguro (no es agradable, lo admito, y podría dejar de funcionar en el futuro), pero se sabe que mezclar cosas como pares, vectores, listas, etc... causa problemas.

@efornara El enlace se trata de vincular algunas librerías de terceros que usan C++ 11 con Godot que usan C++ 03.

@Marqin Sí, pero la forma en que entiendo el enlace es que puedes hacerlo. Lo que no puedes hacer es, por ejemplo, pasar un std::list<> de godot a la biblioteca de terceros.

@reduz , ¿dónde puedo encontrar documentación de la referencia interna de Godot <>? ¿Dónde puedo encontrar documentación sobre cómo los contenedores internos de Godot difieren de los de STL?

@Marqin Para contenedores no hay mucho:
http://docs.godotengine.org/en/stable/development/cpp/core_types.html#containers
Para Ref<> me di cuenta de que no hay ninguno. Probablemente debería agregarse.
Cualquier sugerencia sobre cómo mejorar el documento para agregar estas cosas es muy bienvenida.

Personalmente, encuentro el nuevo estándar de C++ bastante impresionante, pero no propondría ninguna refactorización interna de Godot porque requeriría demasiados esfuerzos para obtener muy pocas ganancias.

También una de las compatibilidades con versiones anteriores es uno de los sellos distintivos de C ++ por esta misma razón. Pero aún así me gustaría poder usarlo para las nuevas funciones de Godot y en GDNative.
Preferiría que Godot se compilara con soporte C++ moderno por este motivo.

@reduz Como dijiste, C++ 11/14/17 te permite escribir código menos explícito. Aunque esto es un inconveniente para los novatos de C++, es algo bueno para los usuarios avanzados de C++.
En cuanto a "automático", personalmente creo que también es bueno para los usuarios avanzados. No solo puede permitirle evitar escribir tipos una y otra vez cuando no es estrictamente necesario, sino que también puede evitar escribir algunos errores.

FYI, compilé un maestro reciente (godot3) con:

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

en un tramo de Debian (gcc-6.3). De manera molesta, la opción también se configura al compilar archivos C, por lo que se inunda con advertencias si las habilita, pero, aparte de eso, todo salió bien. Incluso la palabra clave de registro no parece causar problemas.

No iría tan lejos como para sugerir que las compilaciones oficiales se compilen de esta manera, pero es bueno saber que la opción está ahí si la necesita en su proyecto. Lo que sugeriría es que cualquier cambio que rompa esto se trate como una regresión.

EDITAR: se corrigieron algunos errores tipográficos, lo que hizo que la declaración sobre las advertencias fuera más clara.

De manera molesta, la opción también se establece al compilar archivos C

¿Probaste con CPPFLAGS ?

@Hinsbart No, no lo hice. Tal vez haya una manera, pero como no conozco muy bien los scons, simplemente hice lo que parecía posible sin retoques:

$ 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

EDITAR: Por cierto, no sé cómo se usa en el sistema de compilación de Godot, pero CPPFLAGS me haría pensar en las opciones del preprocesador. Personalmente, siempre he usado CXXFLAGS para C++.

¿Probaste con CPPFLAGS?

Creo que CPPFLAGS afecta a todos los idiomas que usan el preprocesador, por lo que se incluyen tanto C como C++.

@m4nu3lf puede usar cualquier forma de C++ que desee con GDNative.

En mi experiencia, cualquier oportunidad de eliminar código es una buena oportunidad.

Tal vez podríamos configurar algún tipo de página wiki que documente qué archivos se pueden eliminar y reemplazar con variantes de c++11. Esto probablemente incluiría subprocesos primitivos y demás.

Cambiar por cambiar no es bueno (proverbial koolaid), pero en este caso el proyecto LLVM y muchos otros proyectos FOSS se han movido a favor de algunos de los patrones de sintaxis más claros, es decir, la nueva notación para iterador, pero también descargar la separación de preocupaciones a los respectivos tiempos de ejecución del idioma, porque (seamos honestos) mantener la menor cantidad posible de código específico de la plataforma es ideal para un motor de juego.

El mejor código que escribirás es el código que deshaces. :)

Por curiosidad, ¿es posible escribir código que se compile tanto con C++ antiguo como con C++ nuevo? ¿Hay cambios significativos importantes entre las versiones de C++?

Por curiosidad, ¿es posible escribir código que se compile tanto con C++ antiguo como con C++ nuevo? ¿Hay cambios significativos importantes entre las versiones de C++?

El C++ más nuevo es retrocompatible con el C++ más antiguo en el 99,99... % de los casos (solo las cosas que no deberían haberse usado o que estaban mal definidas se definieron como no admitidas pero, en general, solo generarán advertencias de compilación, pero aún funcionan hoy en día) .

Sin embargo, el C++ más nuevo tiene funciones importantes , y obviamente no funcionarán en versiones anteriores de C++, y esas funciones no son solo funciones de usabilidad como macros variables y auto y lambdas, sino también funciones de eficiencia como move ing y rvalue referencias y, por supuesto, macros variadas, así como de seguridad, como los punteros de propiedad más nuevos (especialmente con las bibliotecas C++ Core), entre muchos otros.

Teniendo en cuenta que incluso Visual Studio admite C++ 17 moderno ahora, realmente no hay razón para no usar versiones más nuevas.

queria comentar Pude compilar godot con el indicador C++17 y el único problema fue uno de los elementos de terceros que usaban auto_ptr (que se eliminó de C++17 debido a lo malo que es)

El problema no es que no se compile en c++17, el problema es que las personas que quieren usar las características de c++17, o peor aún, comienzan relaciones públicas usando esas... solo para descubrir que el proyecto está en c ++03.

La falta de lambdas por sí sola es un problema ENORME que no veo aquí.
Varias veces, Godot tiene que almacenar una lista temporal de datos, para luego iterar sobre eso.
En cada uno de esos casos, se puede hacer como una estructura "for_each" o similar que itera por sí misma, lo que simplifica enormemente el código, reduce el uso de memoria y mejora el rendimiento gracias a que la lambda se integra/optimiza. (y esto es C++ 11, usado en todas partes). Exactamente lo mismo para todos los bucles generales en la lista enlazada.

Los operadores de movimiento también permitirían mejores estructuras de datos, que podrían reemplazar las actuales absolutamente terribles por algunas mejor optimizadas.

Los tipos de datos de cadena (como "mystring"_hs) o similares, podrían brindarnos una buena manera de distribuir cadenas hash en todo el código, lo que hace que la verificación de cadenas (muy común en scripts y códigos de eventos) sea mucho más rápida.

Incluso si no se usa std::thread (creo que es una abstracción bastante terrible), la biblioteca atómica std es increíble y extremadamente útil. estándar::atómicoy los gustos.

Y ni siquiera hablemos de lo mucho que obligar a las personas a usar C++ 03 daña el proyecto en sí mismo, cuando las personas no pueden integrar fácilmente las bibliotecas modernas, porque Godot es como el único proyecto C++ de código abierto no abandonado en una versión tan antigua de C++ ( por lo que sé)

Personalmente, estoy de acuerdo con ser conservador y no ir al último estándar absoluto de C++, pero creo que algo como C++11 con algunas características examinadas de C++14 es lo que funcionaría mejor y mejoraría significativamente a Godot. C ++ 11-14 es lo suficientemente bueno para Unreal Engine, que se adapta a ps4, xbox, switch, pc, pc de gama baja, android, IOS y webassembly HTML5. No veo por qué Godot debería limitarse cuando admite una cantidad mucho menor de plataformas.

Los tipos de datos de cadena (como "mystring"_hs) o similares, podrían brindarnos una buena manera de distribuir cadenas hash en todo el código, lo que hace que la verificación de cadenas (muy común en scripts y códigos de eventos) sea mucho más rápida.

Además, te permite usarlos en cosas como interruptores. Como hice un tipo Atom hace años para reemplazar una implementación de cadena de peso ligero:
https://github.com/OvermindDL1/OverECS/blob/master/StringAtom.hpp

Tiene una versión de 32 bits (5 caracteres como máximo, o 6 si recodificas un poco la tabla) y una versión de 64 bits (10 o 12 caracteres como máximo, dependiendo de si eliges una codificación estricta o no). Es completamente reversible, ocurre en tiempo de compilación o dinámicamente en tiempo de ejecución en cualquier dirección, totalmente utilizable en interruptores, etc... etc... Ejemplo de uso de ese archivo:

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

Al interactuar con el código LUA, solo usé cadenas y realicé la conversión en los límites. Ese archivo no es la versión "más reciente", agregué funciones para hacer las conversiones sin asignación de memoria al volver de un entero a una cadena (es sin asignación en cadena-> entero independientemente, ya que se ejecuta en tiempo de compilación) . Es trivial hacer un filtro de Visual Studio o GDB que invierta la codificación cuando se muestra el tipo Atom64/Atom32/cualquiera que sea (que es solo un número entero debajo) para que pueda ver el tipo de cadena en lugar de algún valor hash extraño.

Pero cosas como esta son extremadamente útiles, especialmente en el código sensible al rendimiento y para hacer que el código sea fácil de leer, y esto solo requiere C++ 11, ni siquiera algo más nuevo.

Como mínimo, diría que C++ 14 debería ser el estándar de Godot. C++17 estaría bien (algunas mejoras de rendimiento muy útiles en algún código nuevo), pero C++14 es un mínimo universal ahora. Pero, por supuesto, dado que gcc/clang/VisualStudio y cualquier otra cosa son compatibles con C++17 ahora (e incluso grandes partes de C++20), C++17 también parece bueno. Probablemente seguiría optando por C++ 14 para 'por si acaso'.

@OvermindDL1 Esa cosa de Atom es increíble, me encanta. Definitivamente encajaría muy bien con Godot, donde está haciendo exactamente eso muchas veces.

Esa cosa de Atom es increíble, me encanta. Definitivamente encajaría muy bien con Godot, donde está haciendo exactamente eso muchas veces.

@vblanco20-1 Bueno, si Godot lo quiere, son libres de absorber el código. Este (y su cadena predecesora de peso mosca) ha tenido un uso prolongado en mi antiguo motor C++. Fue muy útil para las etiquetas de eventos pequeños, solo cadenas cortas para usar como "claves" en las cosas, y tan trivialmente fácil de mover hacia adelante y hacia atrás en Lua/LuaJit, además los filtros del depurador fueron de gran ayuda.

La falta de lambdas por sí sola es un problema ENORME que no veo aquí.

¿Soy el único que piensa que las lambdas dificultan la lectura del código en la mayoría de los casos?

@Faless : No, no eres el único, creo que las lambdas son difíciles de leer es una de las razones por las que Akien no ha mejorado la versión de c ++.

Lambdas son solo pequeñas funciones en línea. No sé lo que comentas "difícil de leer" en ellos. Pero permiten cosas como el algoritmo Ordenar donde envía la función de comparación, o el resto de la biblioteca de algoritmos estándar. También le permiten ahorrar memoria y mejorar significativamente el rendimiento gracias a la eliminación de la necesidad de matrices temporales (lo que ocurre varias veces en el código fuente con el octree y otros sistemas)

Y con la biblioteca de algoritmos std, esa es la forma más fácil de multiproceso de un programa que puede obtener, simplemente a través de paralelo para, clasificación paralela y acumulación paralela.

Es una lástima que la gente los descarte como "raros", cuando pueden mejorar tanto la calidad del código, el rendimiento y la reutilización.

Ejemplo real de algo que ya implementé:

//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());
});

También está este otro caso, repetido demasiadas veces.

//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);
});

Ejemplo real de algo que ya implementé:

No estoy seguro de cuál es la ganancia aquí...

Es una lástima que la gente los descarte como "raros", cuando pueden mejorar tanto la calidad del código, el rendimiento y la reutilización.

@vblanco20-1 bueno, intentaré explicarme.
La gente suele terminar escribiendo cosas como:

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

y luego, en algunos otros lugares:

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

Lo cual, cuando lo lees, no te da ninguna pista sobre qué función se ejecuta, ni qué buscar. Podría estar bien si está en el mismo archivo, pero lo que sucede si pasa esa matriz a través de varios archivos es que todo el código se vuelve ilegible.

@Faless, su ejemplo es un ejemplo de lo peor que puede hacer con lambdas (y ese tipo de uso definitivamente debería prohibirse).

Usar lambdas "a lo largo del tiempo" no es un buen uso de ellas, ya que tendrán que asignarse, y también se convierten en un gran peligro, ya que las variables capturadas pueden dejar de ser válidas en el momento en que ejecuta la lambda (por ejemplo, capturando un puntero y luego eliminando el objeto antes de llamar a la lambda, el puntero capturado colgará). Realmente solo estoy defendiendo las lambdas para su uso junto con estructuras de datos y algoritmos, donde los usa "instantáneamente".

En el ejemplo de for_each, for_each es inmune a los cambios en la estructura de datos interna (por ejemplo, si reordena un poco sus variables), permite estructuras de datos más "opacas" (lo que permite que un desarrollador pueda "distribuir fácilmente " cambie de una estructura de datos a otra para probar cuál podría funcionar mejor. Al adoptar eso, puede implementar estructuras de datos mucho más complicadas que funcionan de forma opaca, mientras mantiene la capa de "uso" fácil de usar.

También reduce el texto estándar y hace más "claro" lo que realmente está haciendo el código (iterando la lista vinculada). Simplemente deshacerse de " first()->self()->self " es una mejora en sí mismo.

Tenga en cuenta que los beneficios de esto se acumulan con más uso, ya que podrá tener cosas como "unordered_for_each" que itera los nodos en el orden en que están en la memoria (a través del asignador, o si la lista vinculada se almacena en la parte superior una matriz) o "reverse_for_each". Incluso cosas como "buscar" o "ordenar". (No recomiendo tanto los algoritmos std::, al menos hasta C++ 20, cuando se fusionan los rangos. Es mucho mejor implementar sus propios algoritmos como parte de su estructura de datos)

Las Lambdas fueron básicamente lo primero que Epic Games permitió de C++11 a Unreal Engine, junto con su propia biblioteca de algoritmos como Ordenar, Particionar, Filtrar, Eliminar todo, Buscar, etc. Desde entonces, se usan con bastante frecuencia en el código fuente, tanto en código de motor y código de juego

Genial, al menos estamos de acuerdo en que las lamdbas no son un santo grial de
programación, y ya definimos una regla sobre cómo no usarlos.

Investigaré más sobre actuaciones.

El sábado 8 de diciembre de 2018 a las 17:06 vblanco20-1 < [email protected] escribió:

@Faless https://github.com/Faless su ejemplo es un ejemplo de la
lo peor que puedes hacer con lambdas (y ese tipo de uso definitivamente debería ser
prohibido).

Usar lambdas "a lo largo del tiempo" no es un buen uso de ellas, ya que tendrán
asignar, y también se convierten en un gran peligro, ya que las variables capturadas
dejaría de ser válido en el momento en que ejecute la lambda (por ejemplo,
capturar un puntero y luego eliminar el objeto antes de llamar a la lambda,
el puntero capturado colgará). Realmente solo estoy defendiendo lambdas para
su uso junto con estructuras de datos y algoritmos, donde los use
"instantáneamente".

En el ejemplo for_each, for_each es inmune a los cambios en el
estructura de datos (por ejemplo, si reordena un poco sus variables), permite
más estructuras de datos "opacas" (lo que permite que un desarrollador pueda
cambio "fácil" de una estructura de datos a otra para probar cuál
podría funcionar mejor. Al adoptar eso, puede implementar mucho más
estructuras de datos complicadas que funcionan de forma opaca, manteniendo el "uso"
capa fácil de usar.

También reduce el texto estándar y deja más "claro" cuál es el código.
realmente haciendo (iterando la lista enlazada). Solo deshaciéndome del "
first()->self()->self " es una mejora en sí mismo.

Tenga en cuenta que los beneficios de esto en realidad se acumulan con más uso, ya que
podrá tener cosas como "unordered_for_each" que itera
los nodos en el orden en que están en la memoria (a través del asignador, o si el
lista enlazada se almacena en la parte superior de una matriz), o "reverse_for_each". Incluso las cosas
como "buscar" o "ordenar". (No recomiendo tanto los algoritmos std::algorithms, en
al menos hasta C++ 20 cuando los rangos se fusionan. implementando sus propios algoritmos
como parte de su estructura de datos es mucho mejor de usar)

Las Lambdas fueron básicamente lo primero que Epic Games permitió de C++11 a
unreal engine, junto con su propia biblioteca de algoritmos como Sort,
Particionar, filtrar, eliminar todo, buscar, etc. Desde entonces, son bastante frecuentes
utilizado en el código fuente, tanto en el código del motor como en el código del juego


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/godotengine/godot/issues/9694#issuecomment-445474001 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABnBbvBTxThAh0v8AfFCdGsSv2HFnEz6ks5u2_GKgaJpZM4ObOio
.

Características de C++11 que mejorarían la capacidad de mantenimiento del código base:

  • override y final
  • Bucle for basado en rango
  • nullptr
  • Enumeraciones fuertemente tipadas
  • explicit palabra clave

Características de C++11 que mejorarían la calidad de vida:

  • Soporte de ángulo recto (no más Vector<Vector> > )

[[nodiscard]] (C++17 ?) ver aquí

No creo que debamos adoptar una versión moderna por usar cosas nuevas o porque hay 1 o 2 funciones que se pueden usar. No propondría usar algo más allá de C++ 11 porque el beneficio no vale la pena.
A medida que C++ evoluciona, el stl crece cada vez más y se agregan cientos de líneas al proyecto durante la etapa de preprocesador. En la mayoría de los casos tiene un impacto notable en el rendimiento.

En la mayoría de los casos tiene un impacto notable en el rendimiento.

¿Absolutamente no debería? El código que no se ejecuta no debería tener un impacto en el rendimiento a menos que tenga un error de compilación más o menos, y en el lanzamiento no debería estar en los archivos binarios finales. En muchos casos, solo tener la semántica de movimiento adecuada aumenta el rendimiento, en algunas áreas significativamente, y aunque ese es un ejemplo más antiguo, las directivas actualizadas también pueden tener mejoras en el rendimiento, entre otras características.

@ OvermindDL1 Sí, así es como debería ser en teoría.
Mira esto: https://twitter.com/zeuxcg/status/1085781851568914432
Recientemente he visto varios casos como este en las redes sociales. No es tan "abstracciones de costo cero" como debería.

@lupoDharkael Ese hilo de twitter (una vez que finalmente se cargó, Dios mío, twitter es una interfaz horrible... Sigo olvidándolo...) solo habla de la velocidad de tiempo de compilación porque math.h en libstdc++ es más grande en modo C++17 (donde libc++ no tiene ese problema) debido a una mayor cantidad de sobrecargas y funciones para una mejor velocidad de tiempo de ejecución, por lo que cualquier compilación que incorpore math.h tiene un mayor tiempo de compilación de aproximadamente 300 ms si usa esa stdlib anterior específica en lugar de la stdlib más nueva. No dice nada sobre el rendimiento del tiempo de ejecución (del cual solo he visto que es más rápido en los modos superiores de C ++, dependiendo de las funciones utilizadas, velocidad idéntica en el peor de los casos). Entonces, en cuanto a It's not as "zero cost abstractions" as it should. , ¿todavía no estoy seguro de a qué se refiere? ¿Tiene algún enlace a informes reales de problemas de rendimiento en tiempo de ejecución, ya que el hilo que vinculó parecía no tener nada que ver con eso en absoluto, ya que solo se trataba de un aumento de 300 ms en el tiempo de compilación al compilar con math.h en el stdlib anterior (estoy no estoy seguro de ver el problema de un aumento plano de 300 ms en el tiempo de compilación de un objeto compilado de todos modos)?

Investigaré más al respecto.
Entiendo que el costo tiene una razón, pero los tiempos de compilación más grandes son realmente una anti-función para mí. Solo tengo una computadora portátil y trabajar en funciones para el motor toma su tiempo porque tengo que esperar el proceso de compilación + enlace después de cada cambio. aumentar eso aún más sin un beneficio realmente justificado, solo por usar versiones más nuevas, no es una buena idea.

Solo tengo una computadora portátil y trabajar en funciones para el motor toma su tiempo porque tengo que esperar el proceso de compilación + enlace después de cada cambio.

Si tiene instalado ccache, se dedicará mucho tiempo en el proceso de compilación incremental a la vinculación. Podría ahorrarse un segundo más o menos usando gold en lugar de ld para vincular, y es posible optimizarlo aún más cambiando a lld .

Oh, definitivamente, no puedo decir lo suficiente tanto para ccache como para ninja como generador de respaldo (si usa cmake o algo así, por ejemplo), ¡ambos ahorran mucho tiempo!

Además, las compilaciones de Unity pueden ser sorprendentemente sorprendentes, ahí es donde crea un nuevo archivo unity.cpp más o menos que solo incluye todos los archivos cpp en el proyecto, aunque, de manera realista, generalmente tiene un archivo CPP de Unity por 'módulo' de un proyecto, por lo que solo tiene una docena más o menos para mantener baja la memoria, a cambio de esa memoria adicional en el momento de la compilación, compila y vincula mucho más rápido. Sin embargo, son menos útiles para la reconstrucción incremental y más útiles para las versiones de lanzamiento.

Para agregar uno a la pila:
static_assert

Por ejemplo, la unión SpatialMaterial::MaterialKey asume que la estructura tiene el mismo tamaño que uint64_t, pero no se afirma en ninguna parte, que yo sepa.

Quería poner un static_assert(sizeof(MaterialKey) == sizeof(uint64_t)) allí, pero no pude.

La otra cosa es usar unique_ptr para garantizar una limpieza adecuada en la destrucción sin tener que escribir demasiado texto repetitivo manual y sin usar un conteo de referencias innecesario.

Me gustaría ofrecer mis 2 (o 20) centavos como alguien nuevo en Godot y su base de código. Actualmente estoy supervisando y trabajando en el esfuerzo de portar _Battle for Wesnoth_ a Godot. ¡Ahora, la interfaz (el editor y la API de GDScript) es excelente! Aparte de algunas asperezas, hasta ahora nos ha permitido progresar a buen ritmo. Pero también imaginamos que nosotros (el equipo) contribuiríamos con parches para el back-end (el motor) en algún momento. Con ese fin, a principios de esta semana cloné el repositorio git y comencé a hurgar en C++ y, sinceramente... Estoy un poco consternado.

Tengo experiencia en la gestión de una gran base de código C++ en la forma del antiguo motor personalizado de Wesnoth. También comenzó como C++03, pero se modernizó para usar C++11 y características posteriores y ahora es compatible con C++14. Encabecé ese esfuerzo de modernización (a menudo con demasiado celo) y creo que hizo que nuestro código base fuera mucho más legible y fácil de trabajar. De acuerdo, nunca trabajé mucho con un código base puramente C++03; Aprendí C++ usando las características modernas de C++. Pero para mí, la idea de que cosas como auto , range-for y lambdas hacen que su código sea menos legible es simplemente... muy extraño.

Tome auto , es ciertamente posible abusar de él y usarlo en exceso, pero también tiene un montón de usos legítimos. Uno de los lugares más comunes en los que implementamos auto cuando actualizamos el código base de Wesnoth fue en los bucles for usando iteradores. Anteriormente, tendríamos algo como esto:

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

¡Qué desordenado! En los casos en que realmente necesitábamos un iterador, hicimos esto:

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

Sí, ahora no conoce el tipo de iterador explícito, pero casi nunca necesita saberlo. Leí un comentario aquí en algunas publicaciones que dicen que hace que sea más difícil si el contenedor se declara a algunos archivos, pero en realidad, con los editores de código modernos y la inteligencia, eso no es un gran problema.

En la mayoría de los casos, simplemente cambiaríamos a range-for:

for(const auto& foo : container) {}

Mucho más rápido de escribir y más conciso también. No necesita preocuparse por desreferenciar iteradores dentro del bucle o por realizar un seguimiento de los índices. Y está muy claro que está recorriendo todo el contenedor. Con los iteradores, las personas que no están familiarizadas con el código deben verificar dos veces que el ciclo realmente va de principio a fin.

Usar auto en bucles range-for aquí también tiene un beneficio adicional. ¡Hace una cosa menos que debe recordar actualizar si alguna vez cambia el tipo de contenedor! Realmente no puedo entender el argumento de Juan de que estas cosas hacen que tu código sea menos legible o menos fácil de entender. Para mí, es exactamente lo contrario.

En el video del Estado de Godot también mencionó a las lambdas. Nuevamente, ciertamente es posible abusar de ellos, ¡pero también son una herramienta increíblemente útil! Aquí hay un paradigma común que vi en el código base de Wesnoth antes de usar 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);
}

Código largo, desordenado e inflado. Y esto es lo que usamos con 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; });
}

¡Ese es el caso más común! Y sí, sé que también puede implementar operator< para T y que std::sort lo usa de forma predeterminada, pero ilustra mi punto. Nuevamente, tal vez solo haya aprendido y trabajado casi exclusivamente con C ++ moderno, pero creo que ignorar herramientas como auto , range-for y lambdas donde corresponda es dispararse a sí mismo.

Lo cual me lleva a mi siguiente punto. He notado que Godot usa internamente muchos de sus propios tipos personalizados en lugar de los STL. Si su preocupación es la legibilidad del código con respecto a cosas como auto , ¡usar tipos de núcleo personalizados sobre los STL es absolutamente perjudicial! Hace unos días, estaba navegando por el código base y me encontré con un montón de código que se veía así:

container.push_back(T(args));

Ahora, esto es ineficiente. push_back (al menos en términos de std::vector ) toma una referencia constante; por lo tanto, este operador da como resultado una copia innecesaria. Quería hacer un parche para que usaran emplace_back ... pero luego me di cuenta de que todo el código base usaba tipos de contenedores personalizados 😐

Uno de los grandes problemas con el diseño de Wesnoth y uno de los principales factores que contribuyeron a decidirse por un nuevo motor (en este caso, Godot) fue que Wesnoth padecía en gran medida el síndrome de Not Invented Here. Si bien usamos bibliotecas como Boost, nuestra canalización de renderizado era personalizada, nuestro kit de herramientas de interfaz de usuario era personalizado, nuestro lenguaje de secuencias de comandos/datos era personalizado... entiendes la esencia. Antes de comenzar a trabajar en el puerto de Godot, intenté (y fallé) implementar el renderizado acelerado por hardware (hasta este punto habíamos estado usando renderizado de superficie/sprites basado en CPU. En 2019. Sí, lo sé X_X). La API Texture de SDL no tenía compatibilidad con sombreadores y se necesitaba compatibilidad con sombreadores. Al final, decidí que implementar nuestro propio renderizador, mientras fuera posible, impondría una carga de mantenimiento innecesaria en el proyecto en el futuro. Ya teníamos pocos desarrolladores centrales, y encontrar a alguien capaz de escribir OpenGL (o Vulkan si alguna vez necesitábamos dejar OGL) habría sido un dolor innecesario cuando un motor como Godot tiene un renderizador perfectamente bueno y bien mantenido que podríamos usar. en lugar de.

Menciono eso porque creo que es un buen ejemplo de por qué implementar todo internamente puede ser una mala idea. Sí, puede reducir un poco el tamaño de su binario al no usar la biblioteca estándar, pero también incurre en una carga de mantenimiento masiva. Convertir esas llamadas push_back a emplace_back es una tarea fácil que puede hacer que el código sea más limpio y de mayor rendimiento. Pero debido a que tiene un tipo personalizado vector , si desea una construcción en el lugar, alguien deberá implementarlo manualmente. ¡Y en todos los demás tipos personalizados también!

Un problema aún mayor es que eleva la barrera de entrada. Miré el código base de Godot esperando tipos STL de C++. No encontrar esos medios que yo o cualquier otra persona ahora necesita saber exactamente qué tipos proporciona el motor y qué API acompaña a cada uno. Quiero std::map ? No, necesito usar dictionary . Simplemente hace la vida más difícil para los mantenedores y complica las cosas para los nuevos contribuyentes. Y realmente, ¿no es el código que parece una cosa pero en realidad es otra... ilegible?

Hace aproximadamente un año, cuando cerrábamos el lanzamiento de la versión 1.14 de Wesnoth y su lanzamiento en Steam, emprendí un proyecto de refactorización para eliminar un tipo de contenedor personalizado que era esencialmente std::list excepto que usar erase() no invalidaba iteradores. Aquí está el compromiso principal en cuestión . Necesitó más trabajo después de eso para hacerlo bien, pero el resultado fue un código mucho más simple, más fácil de entender y menos opaco.

En conclusión, creo que prohibir ciertas características muy útiles de C++11 y apegarse a los tipos personalizados sobre los STL será un obstáculo para Godot a largo plazo. Sé que refactorizar las cosas lleva mucho tiempo (confía en mí, lo sé), pero tal como están las cosas ahora, parece muy probable que termines con un catch-22. Al tratar de evitar las desventajas de usar STL (tamaños binarios más grandes, etc.), terminará haciendo que sea cada vez más difícil cambiar a un código más limpio y de mejor rendimiento en los estándares C++ más nuevos. Estoy seguro de que nadie está ansioso por implementar la construcción in situ en todos los tipos personalizados. 😬

Sé que mi opinión no significa mucho aquí, pero pensé que daría mi opinión desde la perspectiva de alguien que ha trabajado con una gran base de código C++ que pasó a C++ moderno desde C++03. Es mucho trabajo, pero a largo plazo siento que vale la pena.

¡Disculpe el muro de texto masivo!

@Vultraz Totalmente de acuerdo.

Si bien no tengo (casi ninguna) experiencia con C++, después de trabajar un tiempo con GDNative + C++, comparto su opinión.

Hace solo unos días publiqué en reddit el hilo " ¿Contribuir a Godot es una buena forma de aprender C++? ":

No lo recomendaría. Según mi experiencia trabajando con GDNative y C++, reinventan la rueda al menos varias veces (colecciones personalizadas y punteros). Estas clases personalizadas no están documentadas (al menos desde GDNative C++ enlaces/encabezados POV, salvo una guía que condujo a un proyecto de demostración sin compilación la última vez que lo intenté, no hay documentación en el código [encabezados, enlaces] ni en documentos oficiales) y tienen un comportamiento confuso/poco intuitivo (p. ej., envolver alguna clase de motor tarde en Ref provoca bloqueos aleatorios, aunque Ref intuitivamente debería ser transparente y, por lo tanto, no debería cambiar el comportamiento de una clase que se está envolviendo).

Tampoco soy partidario de preferir el texto estándar menos legible de la OMI en lugar de usar las nuevas características de C++. Me gusta Haskell (y sus bibliotecas), su brevedad, no complacer a los principiantes para no paralizar el lenguaje para los usuarios avanzados.

Debido a estas decisiones/valores, dudo que alguna vez contribuya al motor. (Es divertido, porque los desarrolladores del motor afirman que la razón detrás de estas decisiones es fomentar las contribuciones. En mi caso, tuvo un efecto totalmente opuesto).

@Vultraz un escrito bien articulado, gran lectura, gracias por eso. Es bueno escuchar una perspectiva como esa.

Soy un programador de C++ al viejo estilo y como tal no he tenido muchos problemas para entender el código fuente de Godot, lo he encontrado bien estructurado y muy de mi agrado. Creo que haber podido agregar soporte de realidad virtual al núcleo de este motor con relativa rapidez mientras tenía muy poca experiencia previa con el código base es un testimonio de cuán legible y comprensible es este motor. Puede que sea de la vieja escuela en algunos aspectos, pero siempre me sorprende lo bien que se construyen ciertas partes. Sí, hay partes que necesitan modernización para tener menos memoria y más rendimiento, pero en general, me ha impresionado.

Cuando escucho a la gente hablar sobre la sintaxis más nueva en C++, a menudo me siento viejo y realmente me pregunto de qué se trata todo este alboroto. Pero entiendo que cuando aprendes C++ más moderno y luego decides contribuir con Godot, es raro que parezca reinventar la rueda. Pero al menos para la gente como yo, estamos tan acostumbrados a ver implementadas clases como estas, estamos impresionados por lo bien que se implementan la mayoría aquí :)

Ahora que todo ha dicho, hay tantas maneras diferentes de ver este problema que no es gracioso. Desde los orígenes de Godot que preceden a la nueva sintaxis de C ++ hasta lo que los desarrolladores principales se sienten más cómodos hasta las limitaciones impuestas por la naturaleza multiplataforma de Godot y los dispositivos (algunos no públicos) en los que puede (o podría) ejecutarse. No creo que haya un bien o un mal en esto, es a lo que estás acostumbrado y de dónde vienes, y la pregunta sobre si la curva de aprendizaje adicional para aprender cómo funciona Godot supera los beneficios de refactorizar un código grande. base.

No se si lo vieron pero Juan dio una charla en GDC que fue puesta en linea: https://www.youtube.com/watch?v=C0szslgA8VY
Durante la sesión de preguntas y respuestas hacia el final, habla un poco sobre la toma de decisiones al respecto.
Es un buen reloj.

En cuanto a las reacciones anteriores sobre GDNative, chicos comunes, GDNative es una adición reciente que satisface una necesidad particular, no es indicativo de cómo está estructurado y construido el producto principal.
Intente crear un módulo (https://docs.godotengine.org/en/3.1/development/cpp/custom_modules_in_cpp.html), sí, eso requiere compilar sus cambios en el producto principal, que es el problema que GDNative intenta resolver, pero eso da una mejor comprensión de cómo está estructurado realmente el motor. Muchos de los argumentos mencionados anteriormente son deficiencias de GDNative, no de Godot en sí.
Me encantaría una solución de módulo dinámico que me permita crear módulos de la misma manera que estamos creando módulos estáticos, tal vez algún día eso sea posible, pero hasta entonces, GDNative es suficiente.

@Vultraz @mnn De hecho, puedo ver el punto sobre los contenedores STL, pero simplemente porque algunas implementaciones (MSVC, en su mayoría) tienen un rendimiento terriblemente lento en el modo de depuración. Pero uno podría simplemente usar STL de EA y ser bueno (el suyo es rápido y portátil).

Además: personalmente encontré que la falta de RAII es muy angustiosa. La cantidad de limpieza manual que se realiza innecesariamente en todo el código es extraña, ya que RAII no es nuevo en C++11.

En cuanto a las reacciones anteriores sobre GDNative, chicos comunes, GDNative es una adición reciente que satisface una necesidad particular, no es indicativo de cómo está estructurado y construido el producto principal.

Por supuesto, eso es cierto, pero GDNative no debería ser más fácil de usar que el propio motor, ya que GDNative se usa para crear "scripts", por lo que está dirigido a una audiencia que se espera que tenga habilidades de C++ aún más bajas que aquellos dispuestos a sumergirse en las partes internas de ¿el motor?

Debido a estas decisiones/valores, dudo que alguna vez contribuya al motor. (Es divertido, porque los desarrolladores del motor afirman que la razón detrás de estas decisiones es fomentar las contribuciones. En mi caso, tuvo un efecto totalmente opuesto).

Soy un novato de C ++ (alrededor de dos meses trabajando 2 días a la semana en C ++), por lo que debería haber sido el grupo demográfico objetivo que se benefició de esta decisión. Encuentro que los contenedores de Godot carecen de funcionalidad básica (en comparación con stl, que no es demasiado rico en sí mismo), no creo que los contenedores estén relacionados con GDNative, pero podría estar equivocado. Estoy haciendo todo lo posible para evitar los contenedores Godot, porque siempre es doloroso trabajar con ellos. Pensé que el comportamiento inconsistente e inesperado de Ref era responsabilidad del motor, pero supongo que estoy equivocado.

Me doy cuenta de que mi experiencia en programación es probablemente bastante inusual: años de trabajo profesional en JS/TS, años en Scala, proyectos de pasatiempos más pequeños en Haskell (unas pocas miles de líneas, que en realidad no son tan pequeñas considerando lo conciso que es Haskell) muchas veces escribo código en C++ que en Haskell sería al menos 5 veces más corto y más legible). Me pregunto si soy el único que se siente desalentado por el uso de tecnología arcaica demasiado detallada.

@mnn , GDNative se creó para permitir la creación de módulos basados ​​en C como bibliotecas dinámicas para que no tuviera que volver a compilar todo el motor. Además de eso, se crearon varios enlaces de lenguaje para que pudieras escribir módulos en C++, python, rust, etc. que nuevamente no requerían compilar todo el motor.

El otro objetivo de esto era que los desarrolladores pudieran crear módulos en los que solo entreguen el módulo en sí y lo usen con una construcción estable del motor. Muchos módulos se han vuelto obsoletos porque no reciben mantenimiento pero están vinculados a una versión específica del motor.

Así que sí, ha hecho que sea mucho más fácil y simple crear módulos desde una perspectiva de compilación e implementación. Pero debido a su enfoque tiene sus limitaciones. Cuando se mantiene dentro de esas limitaciones, escribir código C++ en GDNative puede seguir siendo simple, ya que el tema para el que está creando un módulo es simple.

Intente romper con esas limitaciones y tendrá dolores de cabeza. Mi dolor de cabeza actual es tratar de implementar la lógica OpenGL mientras todo está encapsulado dentro de la arquitectura del servidor visual y no está realmente disponible dentro de GDNative de la manera que necesito que esté. Pero ese es más un factor de mi deseo de hacer algo con GDNative para el que nunca fue diseñado.
Cuando lo usa para lo que fue diseñado, puede hacer algunas cosas realmente geniales con él.

Tenga en cuenta también que GDNative se diseñó como una forma de reescribir el código GDScript que debe tener un mayor rendimiento. Como resultado, no puede acceder a nada dentro del motor al que GDScript tampoco tenga acceso (como OpenGL)

¿Qué pasa con el último soporte coroutine, es decir, co_await()? Desde una perspectiva de procgen, esto es ENORME.

La compatibilidad con Coroutine es parte del estándar C++20, que aún no se ha finalizado, para que conste.

Permítanme intervenir, actualmente estoy escribiendo herramientas avanzadas de pincel / csg (piense en el editor de martillo de origen) para el Editor espacial 3D y esta charla sobre no permitir el modo automático es realmente confusa para mí. A veces _incluso el uso de auto puede ser explícito_. Considera lo siguiente:

Estoy dentro de SpatialEditorViewportContainer y quiero obtener SpatialEditor, que está tres elementos por encima de la jerarquía principal (antes de que alguien señale que hay mejores formas de acceder a SpatialEditor desde el contenedor de ventana gráfica, tenga en cuenta que comencé a mirar este código base ayer)

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

Como puede ver, el downcast dinámico _ya establece explícitamente el tipo de SP_. Sin auto, tendría que escribir basura redundante como:

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

¡Por favor, por el amor de Dios, permita el uso de automóviles!

Sobre el tema de qué estándar de C ++ usar: las características propuestas de reflexión y metaclase de C ++ 20 y más allá serían realmente útiles para reducir el desorden de macros como

GDCLASS(SpatialEditorViewportContainer, Container);

Además, me gustaría repetir que estas restricciones me hacen dudar de mi decisión de contribuir. Alrededor de 2012, aprendí por mi cuenta C ++ 11 y no poder usar este estándar me parece una bofetada. C++11 y C++03 son lenguajes completamente diferentes, y la mayor parte de la reputación de que C++ es difícil de aprender, difícil de leer y difícil de escribir es culpa de C++03 (o 98 más bien). No usar al menos C++ 11 o 14 es _perjudicial_ para el mantenimiento y la legibilidad. Crecí con C ++ 11 y el líder del proyecto evidentemente no (cuando comenzó a trabajar en Godot en 2007, yo tenía 12 años), así que supongo que esto es más un caso de preferencia y síndrome del pato bebé. Siento que no usar C ++ 11 es solo para consolar a las personas que están acostumbradas a la vieja escuela (también conocido como terrible) C ++, a expensas de personas como yo que se tomaron el tiempo para aprender C ++ moderno.

A medida que pasa el tiempo, más y más programadores junior como yo se criarán en C++ 11 moderno y más allá y encontrarán que el hecho de que el proyecto esté para siempre atascado en un lenguaje que ni siquiera tiene lambdas es bastante desalentador.

En resumen: ¡C++ 11 o busto!

Para ser claros, no abogo por el uso de STL. Hacer rodar tus propios contenedores está bien, pero rechazar características como lambdas y auto parece una estupidez.

Cerraré la cámara de eco por ahora, ya que no tiene sentido discutir más aquí. Ya discutimos hace meses qué tipo de características de C++ 11 y/o algunas versiones posteriores planeamos usar.

Solo estamos esperando que @hpvb tenga tiempo para finalizar las pautas que está escribiendo en función de nuestro consenso, y luego podemos discutir un poco más sobre esas pautas una vez que se publiquen. Hasta entonces, esto no es constructivo.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

testman42 picture testman42  ·  3Comentarios

Spooner picture Spooner  ·  3Comentarios

n-pigeon picture n-pigeon  ·  3Comentarios

RebelliousX picture RebelliousX  ·  3Comentarios

timoschwarzer picture timoschwarzer  ·  3Comentarios