Godot: Статус C++ в Godot

Созданный на 18 июл. 2017  ·  65Комментарии  ·  Источник: godotengine/godot

В Godot мы по-прежнему используем C++ 98/03. Цель этого выпуска - попытаться решить, должны ли мы все еще придерживаться его или мы можем начать использовать некоторые современные материалы C++ (C++ 11/14/17 и даже 20).

Когда Godot был первоначально написан, C++03 был самой последней версией, а годы спустя этот стиль (версия стандарта, отказ от стандартной библиотеки и т. д.) был сохранен, чтобы избежать проблем совместимости на определенных компиляторах (на некоторых консолях, насколько это возможно). насколько я знаю). Но мы должны перепроверить эти опасения.

Вопросы для рассмотрения (этот список предназначен для дальнейшего расширения или отбрасывания некоторых записей; сначала просто мозговой штурм):

  • Умные указатели: совместим ли с этим механизм пользовательских указателей/объектов Godot? Потребуется ли большая часть Godot переписываться, чтобы использовать их? Будет ли выгода больше, чем затраты?
  • Семантика перемещения: вопросы, которые нужно задать здесь, более или менее такие же, как и для интеллектуальных указателей.
  • auto , constexpr , лямбда-выражения и т . д. : В общем, любая современная функция C++, которая упрощает написание кода и/или дает некоторую возможность оптимизации без нарушения текущего ядра.
  • Стандартные многопроцессорные примитивы : C++ теперь предоставляет стандартную многопоточность, атомарность, барьеры памяти и т. д. Должны ли мы сохранять собственные реализации и поддерживать их для разных платформ?
  • STL/Boost/other : получим ли мы какую-то выгоду, перейдя от пользовательских контейнеров к хорошо известной реализации? Недостатки/преимущества в отношении обслуживания, производительности, совместимости и т. д.
  • register , встраивание «трюков» и т . д. : в код добавлены элементы, призванные заставить компилятор генерировать более производительный код. Некоторые из них либо устарели, либо не имеют реального влияния, либо даже могут ухудшить производительность. Какой сбросить? Что оставить?
  • и т.д,

Небольшие изменения можно было бы сделать заранее. Но когда должны быть сделаны большие изменения (в его случае)? Годо 4.0? Или как только Godot 3.0 станет стабильной?

Пожалуйста, позвольте мне пригласить некоторых людей явно: @reduz , @punto-, @akien-mga, @karroffel , @bojidar-bg, @BastiaanOlij , @Faless.

discussion

Самый полезный комментарий

Я хотел бы предложить свои 2 (или 20) центов как человек, плохо знакомый с Godot и его кодовой базой. В настоящее время я наблюдаю и работаю над переносом _Battle for Wesnoth_ на Godot. Внешний интерфейс (редактор и GDScript API) великолепен! Помимо нескольких шероховатостей, пока что это позволяло нам прогрессировать в хорошем темпе. Но мы также предполагали, что в какой-то момент мы (команда) внесем исправления для серверной части (движка). С этой целью ранее на этой неделе я клонировал репозиторий git и начал ковыряться в C++, и, честно говоря... я немного встревожен.

У меня есть опыт управления большой кодовой базой C++ в виде старого пользовательского движка Wesnoth. Он также начинался как C++03, но был модернизирован для использования C++11 и более поздних функций и теперь совместим с C++14. Я возглавил эти усилия по модернизации (часто слишком усердно), и я чувствую, что это сделало нашу кодовую базу намного более читабельной и с ней стало легче работать. Конечно, я никогда особо не работал с кодовой базой, основанной исключительно на C++03; Я изучил C++, используя современные возможности C++. Но для меня идея о том, что такие вещи, как auto , range-for и лямбда-выражения делают ваш код менее читаемым, просто... очень странная.

Возьмите auto , конечно, можно злоупотреблять и злоупотреблять им, но у него также есть масса законных применений. Одно из наиболее распространенных мест, где мы развертывали auto при обновлении кодовой базы Wesnoth, — это циклы for с использованием итераторов. Раньше у нас было бы что-то вроде этого:

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

Что грязно! В тех случаях, когда нам действительно был нужен итератор, мы делали это:

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

Да, теперь вы не знаете явного типа итератора, но вам почти никогда не нужно это знать. Я прочитал комментарий здесь, в нескольких сообщениях, в котором говорилось, что это усложняет задачу, если контейнер объявлен в нескольких файлах, но на самом деле с современными редакторами кода и intellisense это не большая проблема.

В большинстве случаев вместо этого мы бы просто переключились на range-for:

for(const auto& foo : container) {}

Гораздо быстрее печатать и более лаконично. Вам не нужно беспокоиться о разыменовывании итераторов внутри цикла или об отслеживании индексов. И совершенно ясно, что вы перебираете весь контейнер. С итераторами людям, незнакомым с кодом, необходимо дважды проверить, действительно ли цикл идет от начала до конца.

Использование auto в циклах range-for здесь также имеет дополнительное преимущество. Это делает на одну вещь меньше, которую вам нужно помнить, чтобы обновить, если вы когда-нибудь измените тип контейнера! Я действительно не могу понять аргумент Хуана о том, что эти вещи делают ваш код менее читаемым или менее простым для понимания. Для меня это полная противоположность.

В видео State of Godot он также упомянул лямбды. Опять же, ими, безусловно, можно злоупотреблять, но это также невероятно полезный инструмент! Вот общая парадигма, которую я видел в кодовой базе Wesnoth до использования 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++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; });
}

Это просто самый распространенный случай! И да, я знаю, что вы также можете реализовать operator< для T, и std::sort использует это по умолчанию, но это иллюстрирует мою точку зрения. Опять же, может быть, это просто изучение и почти исключительно работа с современным C++, но я думаю, что игнорирование таких инструментов, как auto , range-for и лямбда-выражения, где это уместно, стреляет себе в ногу.

Что подводит меня к следующему пункту. Я заметил, что внутри Godot используются многие собственные пользовательские типы вместо STL. Если вас беспокоит читабельность кода в отношении таких вещей, как auto , использование пользовательских типов ядра вместо STL абсолютно вредно! Несколько дней назад я просматривал кодовую базу и наткнулся на много кода, который выглядел примерно так:

container.push_back(T(args));

Теперь это неэффективно. push_back (по крайней мере, с точки зрения std::vector ) принимает константную ссылку; поэтому этот оператор приводит к ненужной копии. Я хотел сделать патч, чтобы они использовали emplace_back … но потом я понял, что вся кодовая база использует пользовательские типы контейнеров 😐

Одной из больших проблем конструкции Веснота и одним из основных факторов, повлиявших на решение использовать новый двигатель (в данном случае Годо), было то, что Веснот в значительной степени страдал от синдрома «Не здесь изобретено». Хотя мы использовали такие библиотеки, как Boost, наш конвейер рендеринга был настраиваемым, наш инструментарий пользовательского интерфейса был настраиваемым, наш язык данных/скриптов был настраиваемым... вы поняли суть. Прежде чем мы начали работу над портом Godot, я пытался (и не смог) реализовать аппаратно-ускоренный рендеринг (до этого момента мы использовали рендеринг поверхностей/спрайтов на базе процессора. В 2019 году. Да, я знаю X_X). SDL Texture API не имел поддержки шейдеров, а поддержка шейдеров была необходима. В конце концов я решил, что внедрение нашего собственного рендерера, хотя и возможно, наложит ненужную нагрузку на проект в будущем. У нас уже было несколько основных разработчиков, и найти кого-то, кто мог бы написать OpenGL (или Vulkan, если бы нам когда-нибудь нужно было отказаться от OGL), было бы просто ненужной болью, когда такой движок, как Godot, имеет совершенно хороший, хорошо поддерживаемый рендерер, который мы могли бы использовать. вместо.

Я упоминаю об этом, так как думаю, что это хороший пример того, почему реализация всего внутри компании может быть плохой идеей. Да, вы можете немного уменьшить размер своего двоичного файла, не используя стандартную библиотеку, но вы также несете огромную нагрузку на обслуживание. Преобразование этих push_back вызовов в emplace_back — это очень простой плод, который может сделать код чище и производительнее. Но поскольку у вас есть пользовательский тип vector , если вам нужна конструкция на месте, кто-то должен будет реализовать ее вручную. И во всех других пользовательских типах тоже!

Еще большая проблема заключается в том, что это повышает входной барьер. Я просмотрел кодовую базу Godot, ожидая типов C++ STL. Не найдя эти средства, я или кто-либо еще теперь должен точно знать, какие типы предоставляет движок и какой API подходит для каждого из них. Я хочу std::map ? Нет, мне нужно использовать dictionary . Это только усложняет жизнь сопровождающим и усложняет жизнь новым участникам. И действительно, разве код, который выглядит как одно, а на самом деле совсем другое... нечитабелен?

Около года назад, когда мы подошли к выпуску Wesnoth 1.14 и его запуску в Steam, я предпринял проект рефакторинга, чтобы исключить пользовательский тип контейнера, который по сути был std::list , за исключением того, что использование erase() не делало недействительным итераторы. Вот принцип коммита, о котором идет речь . После этого потребовалась дополнительная работа, чтобы все получилось правильно, но в результате код стал намного проще, понятнее и менее непрозрачным.

В заключение, я думаю, что запрет некоторых очень полезных функций C++11 и использование пользовательских типов вместо STL станет помехой для Godot в долгосрочной перспективе. Я знаю, что рефакторинг занимает много времени (поверьте мне, я знаю), но, судя по тому, как обстоят дела сейчас, очень вероятно, что вы в конечном итоге столкнетесь с уловкой-22. Пытаясь избежать недостатков использования STL (больших двоичных размеров и т. д.), вы в конечном итоге будете все труднее и труднее переключаться на более производительный и чистый код в новых стандартах C++. Я уверен, что никто особенно не надеется на реализацию конструкции на месте во всех пользовательских типах. 😬

Я знаю, что мое мнение здесь мало что значит, но я решил высказать свои мысли с точки зрения человека, который работал с большой кодовой базой C++, перешедшей на современный C++ из C++03. Это большая работа, но в долгосрочной перспективе я чувствую, что это того стоит.

Извините за массивную текстовую стену!

Все 65 Комментарий

Это обсуждалось много раз, обычные ответы:

1) Нет желания переносить кодовую базу на что-то выше 03. Фичи того не стоят. Однако для GDNative C++ его вполне можно использовать.
2) Нет желания использовать STL/Boost/Etc. Обоснование всегда было одним и тем же: а) шаблоны Godot делают небольшие вещи, которые они обычно не делают (например, атомарные пересчеты и копирование при записи) б) STL генерирует огромные отладочные символы и отладочные двоичные файлы из-за их чрезвычайно длинных искаженных символов.

Мы держимся так же, как и все.

У меня есть несколько мнений о личных беседах, но я хотел
обсуждение более публичное.

Я не знал, что это так много обсуждалось и уже закрыто.

Вы даже отбрасываете auto, constexpr и тому подобное?

Сбросить "регистрацию"?

Эль 18 июл. 2017 г., 13:54, «Хуан Линиецки», notifications @github.com
описание:

Это обсуждалось много раз, обычные ответы:

  1. Нет необходимости перемещать кодовую базу на что-либо выше 03. Особенности
    не стоит. Однако для GDNative C++ его вполне можно использовать.
  2. Нет желания использовать STL/Boost/Etc. Обоснование всегда было
    то же самое: а) шаблоны Godot делают небольшие вещи, которые они обычно не делают (например, атомарные
    refcounts и копирование при записи) b) STL генерирует огромные отладочные символы и
    двоичные файлы из-за их чрезвычайно длинных искаженных символов

Мы держимся так же, как и все.


Вы получаете это, потому что вы создали тему.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/godotengine/godot/issues/9694#issuecomment-316041201 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/ALQCtipKmepD_1Xw6iRXZ7aGoQlLfiwFks5sPJzqgaJpZM4ObOio
.

Я поднимал этот вопрос, что было бы безопаснее использовать умные указатели, и что этот код страдает от синдрома NIH, но начальство не собирается обновляться, поэтому я предлагаю вам также сдаться.

Вероятно, на обновление кода потребуется много человеко-часов, которые разработчики предпочитают тратить на реализацию новых функций. Я это как-то понимаю - пользователи движка предпочитают получать новые фичи, чем "рефакторинг" (не разбираясь в таких вещах, как технологический долг).

@Marqin Извините, но умные указатели - плохая идея, потому что:

1) единственный полезный - пересчитанный, остальное - ментальная мастурбация на уже имеющиеся в языке возможности.
2) Использование интеллектуальных указателей везде - ужасная идея, вы рискуете иметь циклы ссылок, которые приводят к утечке памяти.
3) у нас уже есть что-то похожее, Ref<> , с дополнительным преимуществом, заключающимся в том, что мы контролируем, как работает подсчет ссылок, поэтому мы можем добавлять специальные случаи для обработки привязок к таким языкам, как C#, с собственным gc

Итак, пожалуйста, прежде чем спорить, что мы решаем вещи по прихоти, просто спросите. Обычно мы принимаем обоснованные решения и делимся процессом со всеми.

Что можно легко сделать, если вы хотите улучшить качество, так это начать писать модульные тесты (для этого добавьте какую-нибудь необязательную работу в scons и просто заставьте разработчиков самостоятельно установить gmock/gtest [чтобы не засорять ./ третье лицо])

остальное - мысленная мастурбация на уже имеющиеся в языке черты

@reduz , как unique_ptr с его явным владением и освобождением памяти RAII уже присутствует в языке?

Также упрощается не только управление памятью, но и многопоточность (например, lock_guard ).

@RandomShaper Я не против обновления в какой-то момент в будущем, но, несмотря на то, что есть много полезных функций, новые версии C ++ имеют тенденцию заставлять программиста писать менее явный код. Это приводит к тому, что код, как правило, становится труднее читать другим (типично при автоматическом злоупотреблении ключевыми словами).

Другое преимущество отказа от использования более продвинутых функций (например, мы не используем исключения и rtti можно отключить с небольшими затратами) заключается в том, что компиляторы создают гораздо меньшие двоичные файлы.

Это нормально. Я уважаю ваше слово. Только я считаю, что обсуждать это — здоровая вещь.

Я знаю, что некоторые крупные компании по производству видеоигр (Ubisoft, IIRC) перенесли большие базы кода на современный C++, поэтому он должен идеально подходить и для Godot. Конечно, как вы указали, это требует работы/времени.

Вот почему мы могли:

  • выбрать разумное подмножество для максимально безболезненной миграции всего кода;
  • или, по крайней мере, выберите разумное подмножество как «одобренное» для нового кода.

В обоих случаях, вероятно, потребуется определить стиль кодирования, чтобы не злоупотреблять функциями, как вы сказали, это происходит с auto .

Конечно, сейчас есть вещи с более высоким приоритетом, но, может быть, когда 3.0 станет стабильной или в какой-то другой момент в будущем, было бы неплохо уже решить.

@Marqin Как уже упоминалось, Godot использует собственную схему распределения памяти для узлов, что имеет больше смысла, чем что-либо, предоставляемое новыми стандартными библиотеками.

Кроме того, защита блокировки состоит практически из 3 строк кода, и мы уже реализовали ее без использования C++11+.

Опять же, почему мы должны предполагать, что что-то «стандартное» 1) должно быть лучше того, что у нас уже есть 2) лучше, если мы все равно его используем? .Я думаю, что это распространенная ошибка.

Что дальше, отказаться от наших функций печати и использовать ostream/istream? Измените наш класс String, что довольно круто, и замените его гораздо более увечным std::string или std::wstring?

Стандартные библиотеки не служат всем целям и не работают лучше всего остального только потому, что они являются стандартными библиотеками. Они существуют только для тех, кто в них нуждается, и можно игнорировать их и писать свои собственные реализации, если у вас есть веская причина для этого. Мы делаем, и мы уверены в этом.

@RandomShaper Проблема в том, что:

1) Это больше затрат, чем пользы.
2) Откроет окно для множества аргументов о том, как выглядит стандартный способ написания C++11+. Возможно, в какой-то момент их стоит иметь, и переписать большую часть движка, чтобы воспользоваться этими функциями, может быть полезно однажды, но я думаю, что есть гораздо более важные вещи, на которых следует сосредоточиться.
3) Также, как упоминалось ранее, возможно, что, как ни круто звучит перенос на новую версию C++, это может привести к гораздо большему размеру двоичного файла. В таком случае это может быть нежелательно.

На данный момент это серьезно не стоит, мы можем обсудить это снова через несколько лет.

Я думаю, что имеет смысл убедиться, что Godot компилируется с такими параметрами, как --std=c++17, чтобы вы могли легко добавить библиотеку, написанную на современном C++, если вам это нужно. Например, я бы проголосовал за удаление ключевого слова register из кодовой базы https://github.com/godotengine/godot/issues/9691 .

Как-то связано, я помню, где-то читал, что gcc-6.3 не поддерживается (google
говорит, что это было в https://github.com/godotengine/godot/issues/7703). Меня это беспокоит, так как gcc-6.3 является компилятором по умолчанию в моем дистрибутиве (стабильная версия Debian). Кто-нибудь может это подтвердить? И почему так?

@efornara для некоторых сторонних библиотек уже требуются более новые версии C ++, и это нормально, потому что Scons позаботится об этом с помощью клонированных сред сборки. Проверьте сторонний код etc2comp, чтобы увидеть, как он работает.

@karroffel Спасибо, я этого не знал.

Тогда это была бы просто хорошая функция, но она не нужна для импорта библиотеки (хотя, если для связующего кода вам нужно включить больше godot, вы можете наткнуться на заголовочный файл, который не компилируется).

Кстати, если кому-то нужно сделать что-то подобное и нашел этот пост, соответствующий файл: https://github.com/godotengine/godot/blob/master/modules/etc/SCsub . Я нашел его с помощью grep, и похоже, что это единственное место, где это необходимо на данный момент.

Не так безопасно связывать С++ 11 с кодом, отличным от С++ 11 - http://gcc.gnu.org/wiki/Cxx11AbiCompatibility

@Marqin Если я неправильно понимаю ссылку, на самом деле это, похоже, поддерживает случай, когда godot _not_ начинает использовать компоненты из стандартной библиотеки, а вместо этого придерживается пользовательских компонентов:

Язык C++98 ABI-совместим с языком C++11, но несколько мест в стандартной библиотеке нарушают совместимость.

Смешивание языков выглядит довольно безопасным (нехорошо, я признаю, и это может перестать работать в будущем), но известно, что смешивание таких вещей, как пары, векторы, списки и т. д., вызывает проблемы.

@efornara Ссылка посвящена связыванию некоторых сторонних библиотек, использующих C++11, с Godot, использующих C++03.

@Marqin Да, но насколько я понимаю ссылку, вы можете это сделать. Чего вы не можете сделать, так это, например, передать std::list<> из godot в стороннюю библиотеку.

@reduz, где я могу найти документацию по внутренней ссылке Godot<>? Где я могу найти документацию о том, чем внутренние контейнеры Godot отличаются от контейнеров в STL?

@Marqin Для контейнеров не так много:
http://docs.godotengine.org/en/stable/development/cpp/core_types.html#containers
Для Ref<> я понял, что их нет. Наверное надо добавить.
Любые предложения о том, как улучшить документ, чтобы добавить эти вещи, очень приветствуются.

Лично я нахожу новый стандарт C++ довольно крутым, но я бы не стал предлагать какой-либо внутренний рефакторинг Godot, потому что это потребовало бы слишком много усилий для получения слишком малой выгоды.

Также именно по этой причине обратная совместимость является одной из отличительных черт C++. Но все же я хотел бы иметь возможность использовать его для новых функций Godot и в GDNative.
По этой причине я бы предпочел, чтобы Godot был скомпилирован с поддержкой современного C++.

@reduz Как вы сказали, C ++ 11/14/17 позволяет писать менее явный код. Несмотря на то, что это недостаток для новичков в C++, это хорошо для опытных пользователей C++.
Что касается «авто», я лично думаю, что это хорошо и для опытных пользователей. Это может не только позволить вам избежать ввода типов снова и снова, когда это не является строго необходимым, но также может избежать ввода некоторых ошибок.

К вашему сведению, я скомпилировал недавний мастер (godot3) с помощью:

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

на дебианском участке (gcc-6.3). Досадно, что эта опция также устанавливается при компиляции файлов C, так что если вы их включите, вас завалят предупреждениями, но, кроме этого, все прошло гладко. Даже ключевое слово register, кажется, не вызывает проблем.

Я бы не стал предлагать, чтобы официальные сборки компилировались таким образом, но приятно знать, что такая опция есть, если она вам нужна в вашем проекте. Я бы предложил, чтобы любые изменения, нарушающие это, рассматривались как регресс.

РЕДАКТИРОВАТЬ: Исправлены некоторые опечатки, что сделало заявление о предупреждениях более понятным.

Досадно, что этот параметр также устанавливается при компиляции файлов C.

Вы пробовали с CPPFLAGS ?

@ Хинсбарт Нет, не знал. Может быть, есть способ, но, поскольку я не очень хорошо знаю scons, я просто пошел с тем, что казалось возможным, не возясь:

$ 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

РЕДАКТИРОВАТЬ: Кстати, я не знаю, как это используется в системе сборки godot, но CPPFLAGS заставил бы меня подумать о вариантах препроцессора. Лично я всегда использовал CXXFLAGS для C++.

Вы пробовали с CPPFLAGS?

Я думаю, что CPPFLAGS влияет на все языки, использующие препроцессор, поэтому включены как C, так и C++.

@ m4nu3lf вы можете использовать любую форму C ++ с GDNative.

По моему опыту, любая возможность удалить код — это хорошая возможность.

Возможно, мы могли бы создать какую-то вики-страницу, документирующую, какие файлы можно удалить и заменить вариантами С++ 11. Это, вероятно, будет включать примитивы потоков и тому подобное.

Изменение ради изменения — нехорошо (пресловутый koolaid), но в данном случае проект LLVM и многие другие проекты FOSS перешли к некоторым более четким синтаксическим шаблонам, т. е. более новой нотации for-iterator, но также перекладывая задачи на соответствующие языковые среды выполнения, потому что (давайте будем честными) поддержка как можно меньшего кода, специфичного для платформы, идеальна для игрового движка.

Лучший код, который вы когда-либо писали, — это код, который вы не написали. :)

Из любопытства, возможно ли написать код, который компилируется как с более старым C++, так и с более новым C++? Существуют ли существенные критические изменения между версиями C++?

Из любопытства, возможно ли написать код, который компилируется как с более старым C++, так и с более новым C++? Существуют ли существенные критические изменения между версиями C++?

Более новый C++ обратно совместим со старым C++ в 99,99...% случаев (только те вещи, которые не должны были использоваться или были неправильно определены, были определены как неподдерживаемые, но в целом это вызовет только предупреждения при компиляции, но все еще работает в настоящее время) .

Однако более новый C++ имеет важные функции, и они, очевидно, не будут работать в более старых версиях C++, и эти функции — это не только функции удобства использования, такие как вариативные макросы и auto и лямбда-выражения, но и функции повышения эффективности, такие как move Ссылки

Учитывая, что даже Visual Studio теперь поддерживает современный C++17, нет никаких причин не использовать более новые версии.

Я хотел прокомментировать. Мне удалось скомпилировать godot с флагом С++ 17, единственная проблема заключалась в том, что один из сторонних материалов использовал auto_ptr (который удален из С++ 17 из-за того, насколько он плох)

Проблема не в том, что он не компилируется в С++ 17, проблема в том, что люди хотят использовать возможности С++ 17 или, что еще хуже, начинают PR, используя их... только для того, чтобы обнаружить, что проект находится на С ++03.

Одно только отсутствие лямбда-выражений — ОГРОМНАЯ проблема, которую я здесь не вижу.
Несколько раз godot должен сохранять временной список данных, чтобы затем повторять его.
В каждом из этих случаев это можно сделать как «for_each» или аналогичную структуру, которая повторяется сама по себе, значительно упрощая код, снижая использование памяти и повышая производительность благодаря встраиванию/оптимизации лямбды. (а это C++11, используется повсеместно). То же самое для всех общих циклов в связанном списке.

Операторы перемещения также позволили бы улучшить структуры данных, которые могли бы заменить нынешние абсолютно ужасные на более оптимизированные.

Строковые типы данных (такие как "mystring"_hs) или подобные могут дать нам хороший способ распространять хешированные строки по всему коду, делая проверку строк (очень распространенную в скриптах и ​​коде событий) намного быстрее.

Даже если std::thread не используется (я сам думаю, что это довольно ужасная абстракция), библиотека std atomics замечательна и чрезвычайно полезна. станд::атомныйи лайки.

И давайте даже не будем говорить о том, насколько принуждение людей к C++03 вредит самому проекту, когда люди не могут легко интегрировать современные библиотеки сами, потому что godot — это как бы единственный незаброшенный проект C++ с открытым исходным кодом на такой старой версии C++ ( насколько я знаю)

Лично я согласен быть консервативным и не переходить на самый последний стандарт C++, но я думаю, что что-то вроде C++11 с некоторыми проверенными функциями C++14 будет работать лучше всего и значительно улучшит Godot. C++ 11-14 достаточно хорош для Unreal Engine, который портируется на PS4, Xbox, Switch, ПК, бюджетный ПК, Android, IOS и веб-сборку HTML5. Я не понимаю, почему godot должен ограничивать себя, если он поддерживает гораздо меньшее количество платформ.

Строковые типы данных (такие как "mystring"_hs) или подобные могут дать нам хороший способ распространять хешированные строки по всему коду, делая проверку строк (очень распространенную в скриптах и ​​коде событий) намного быстрее.

Кроме того, это позволяет использовать их в таких вещах, как переключатели. Как я сделал тип Atom несколько лет назад, чтобы заменить реализацию легковесной строки:
https://github.com/OvermindDL1/OverECS/blob/master/StringAtom.hpp

Он имеет как 32-битную (максимум 5 символов или 6, если вы немного перекодируете таблицу), так и 64-битную версию (максимум 10 или 12 символов, в зависимости от того, выбрана ли жесткая кодировка или нет). Это полностью обратимо, происходит во время компиляции или динамически во время выполнения в любом направлении, полностью используется в переключателях и т. д. и т. д. Пример использования этого файла:

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

При взаимодействии с кодом LUA я просто использовал строки и выполнял преобразование на границах. Этот файл не является «последней» его версией, я добавил функции для выполнения преобразований без выделения памяти при переходе от целого числа к строке (независимо от того, что он не выделяет строку-> целое число, так как он запускается во время компиляции) . Тривиально создать фильтр Visual Studio или GDB, который меняет кодировку при отображении типа Atom64/Atom32/любого (который внизу просто целое число), чтобы вы могли видеть строковый тип вместо какого-то странного хешированного значения.

Но такие вещи чрезвычайно полезны, особенно в коде, чувствительном к производительности, и для облегчения чтения кода, а для этого требовался только C++11, даже ничего новее.

По крайней мере, я бы сказал, что C++14 должен быть стандартом Godot. С++ 17 было бы неплохо (несколько очень полезных улучшений производительности в каком-то новом коде), но С++ 14 сейчас является скорее универсальным минимумом. Но, конечно, поскольку gcc/clang/VisualStudio и все остальное теперь отлично поддерживает C++17 (и даже большие куски C++20), C++17 тоже кажется хорошим. Я бы, наверное, все же выбрал С++ 14 «на всякий случай».

@OvermindDL1 Эта штука с Атомом потрясающая, обожаю ее. Это определенно очень хорошо подходит для godot, где он делает то же самое довольно много раз.

Эта штука с Атомом потрясающая, обожаю ее. Это определенно очень хорошо подходит для godot, где он делает то же самое довольно много раз.

@ vblanco20-1 Что ж, если Годо хочет этого, они могут свободно поглощать код. Она (и ее предшественница, строка легковеса) давно использовалась в моем старом движке C++. Это было так полезно для небольших тегов событий, просто коротких строк для использования в качестве «ключей» к вещам, и так тривиально легко перемещаться вперед и назад по Lua/ LuaJit , плюс фильтры отладчика очень помогли.

Одно только отсутствие лямбда-выражений — ОГРОМНАЯ проблема, которую я здесь не вижу.

Я единственный, кто думает, что лямбда-выражения в большинстве случаев затрудняют чтение кода?

@Faless : Нет, вы не единственный, я думаю, что лямбды трудно читать, это одна из причин, по которой Akien не повысил версию C ++.

Лямбды — это просто небольшие встроенные функции. Я не знаю, что вы комментируете о них «трудно читать». Но они позволяют такие вещи, как алгоритм сортировки, куда вы отправляете функцию сравнения, или остальную часть библиотеки стандартных алгоритмов. Они также позволяют вам экономить память и значительно повышать производительность благодаря устранению необходимости во временных массивах (что происходит несколько раз в исходном коде с октодеревом и другими системами).

А с библиотекой стандартных алгоритмов это самый простой способ сделать программу многопоточной, которую вы можете получить, просто используя параллельную сортировку, параллельную сортировку и параллельное накопление.

Это абсолютный позор, когда люди списывают их как «странные», когда они могут так сильно улучшить качество кода, производительность и возможность повторного использования.

Фактический пример того, что я уже реализовал:

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

Есть еще один случай, повторяющийся слишком много раз.

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

Фактический пример того, что я уже реализовал:

Не знаю, в чем здесь выигрыш...

Это абсолютный позор, когда люди списывают их как «странные», когда они могут так сильно улучшить качество кода, производительность и возможность повторного использования.

@ vblanco20-1 хорошо, я попытаюсь объясниться.
Люди обычно заканчивают тем, что пишут что-то вроде:

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

а затем, в некоторых других местах:

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

Который, когда вы его читаете, не дает вам ни малейшего представления о том, какая функция выполняется и что искать. Это может быть хорошо, если он находится в одном и том же файле, но что произойдет, если вы передадите этот массив по нескольким файлам, так это то, что весь код станет нечитаемым.

@Faless ваш пример - это пример худшего, что вы можете сделать с лямбда-выражениями (и такое использование определенно должно быть запрещено).

Использование лямбда-выражений «во времени» не является хорошим их применением, так как их придется выделять, и они также представляют собой огромную опасность, поскольку захваченные переменные могут перестать быть действительными к моменту выполнения лямбда-выражения (например, захват указатель, а затем удалив объект перед вызовом лямбды, захваченный указатель будет болтаться). Я действительно защищаю лямбды только за их использование вместе со структурами данных и алгоритмами, где вы используете их «мгновенно».

В примере с for_each for_each невосприимчив к изменениям во внутренней структуре данных (например, если вы немного переупорядочиваете его переменные), он допускает более «непрозрачные» структуры данных (что позволяет разработчику «легко вносить изменения»). " переходите от одной структуры данных к другой, чтобы проверить, какая из них может работать лучше. Приняв это, вы можете реализовать гораздо более сложные структуры данных, которые работают непрозрачно, сохраняя при этом простой в использовании слой "use".

Это также уменьшает шаблон и делает более «ясным» то, что на самом деле делает код (итерация связанного списка). Простое избавление от " first()->self()->self " само по себе является улучшением.

Имейте в виду, что преимущества этого на самом деле накапливаются при большем использовании, так как вы сможете иметь такие вещи, как «unordered_for_each», который выполняет итерацию узлов в том порядке, в котором они находятся в памяти (через распределитель или если связанный список хранится сверху). массив) или "reverse_for_each". Даже такие вещи, как «найти» или «сортировать». (Я не очень рекомендую std::algorithms, по крайней мере, до C++20, когда диапазоны объединяются. Гораздо лучше использовать собственные алгоритмы как часть вашей структуры данных)

По сути, лямбда-выражения были первым, что Epic Games допустили из C++11 в нереальный движок, наряду со своей собственной библиотекой алгоритмов, таких как Sort, Partition, Filter, RemoveAll, Find и т. д. С тех пор они довольно часто используются в исходном коде, как в коде движка и коде геймплея

Круто, по крайней мере, мы согласны с тем, что лямбды — это не какой-то святой Грааль
программирование, и мы уже определили одно правило, как их не использовать.

Я сделаю больше исследований о выступлениях.

Сб, 8 декабря 2018 г., 17:06 vblanco20-1 < [email protected] написал:

@Faless https://github.com/Faless ваш пример является примером
худшее, что вы можете сделать с лямбда-выражениями (и такое использование определенно должно быть
запрещены).

Использование лямбда-выражений «через время» не является хорошим их применением, так как они будут иметь
выделить, и они также становятся огромной опасностью, так как захваченные переменные
перестанет быть действительным к тому времени, когда вы выполните лямбду (например,
захват указателя, а затем удаление объекта перед вызовом лямбды,
захваченный указатель будет болтаться). Я действительно защищаю лямбды только для
их использование вместе со структурами данных и алгоритмами, где вы их используете
"немедленно".

В примере for_each for_each невосприимчив к изменениям во внутреннем
структуру данных (например, если немного переупорядочить ее переменные), это позволяет
более «непрозрачные» структуры данных (что позволяет разработчику
«легкий» переход от одной структуры данных к другой, чтобы проверить, какая из них
может работать лучше. Принимая это, вы можете реализовать гораздо больше
сложные структуры данных, которые работают непрозрачно, сохраняя при этом «использование»
слой прост в использовании.

Это также уменьшает шаблон и делает более «ясным» код.
фактически делает (итерирует связанный список). Просто избавиться от "
first()->self()->self " является улучшением самого себя.

Имейте в виду, что преимущества этого на самом деле накапливаются при большем использовании, так как
вы сможете иметь такие вещи, как «unordered_for_each», которые повторяются
узлы в том порядке, в котором они находятся в памяти (через распределитель или, если
связанный список хранится поверх массива) или "reverse_for_each". Даже вещи
например, «найти» или «сортировать». (Я не очень рекомендую std::algorithms, т.к.
по крайней мере, до С++ 20, когда диапазоны объединяются. реализация собственных алгоритмов
как часть вашей структуры данных гораздо лучше использовать)

По сути, лямбда-выражения были первым, что Epic Games допустила из C++11 в
нереальный движок, наряду с собственной библиотекой алгоритмов, таких как Sort,
Partition, Filter, RemoveAll, Find и т.д. С тех пор они довольно часто
используется в исходном коде, как в коде движка, так и в коде игрового процесса


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/godotengine/godot/issues/9694#issuecomment-445474001 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/ABnBbvBTxThAh0v8AfFCdGsSv2HFnEz6ks5u2_GKgaJpZM4ObOio
.

Возможности C++11, которые улучшат ремонтопригодность кодовой базы:

  • override и final
  • Цикл for на основе диапазона
  • nullptr
  • Строго типизированные перечисления
  • ключевое слово explicit

Возможности C++11, которые улучшат качество жизни:

  • Угловая скобка (не более Vector<Vector> > )

[[nodiscard]] (C++17?) см. здесь

Я не думаю, что мы должны принимать современную версию ради использования новых вещей или потому, что есть 1 или 2 функции, которые можно использовать. Я бы не предложил использовать что-то помимо С++ 11, потому что выгода того не стоит.
По мере развития C++ stl становится все больше и больше, и на этапе препроцессора в проект добавляются сотни строк. В большинстве случаев это оказывает заметное влияние на производительность.

В большинстве случаев это оказывает заметное влияние на производительность.

Совсем не должно? Код, который не выполняется, не должен влиять на производительность, если у вас нет ошибки компилятора или около того, и в выпуске он вообще не должен быть в окончательных двоичных файлах? Во многих случаях просто наличие правильной семантики перемещения само по себе значительно увеличивает производительность, в некоторых областях, и, хотя это более старый пример, обновленные директивы также могут иметь повышение производительности среди других функций.

@OvermindDL1 Да, теоретически так и должно быть.
Проверьте это: https://twitter.com/zeuxcg/status/1085781851568914432
В последнее время я видел различные случаи, подобные этому, в социальных сетях. Это не такие «абстракции с нулевой стоимостью», как следовало бы.

@lupoDharkael В этой ветке твиттера (после того, как она наконец загрузилась, о боже, вау, твиттер - ужасный интерфейс ... я все время забываю об этом ...) говорится только о скорости компиляции, потому что math.h в libstdc ++ больше в режиме C ++ 17 (где libc++ не имеет этой проблемы) из-за большего количества перегрузок и функций для повышения скорости выполнения, поэтому любой компилятор, который добавляет math.h, имеет увеличенное время компиляции примерно на 300 мс при использовании этой конкретной старой stdlib вместо более новой stdlib. В нем ничего не говорится о производительности во время выполнения (которую я видел только быстрее в более высоких режимах С++, в зависимости от используемых функций, одинаковая скорость в худших случаях). Итак, что касается It's not as "zero cost abstractions" as it should. , я все еще не уверен, на что вы ссылаетесь? Есть ли у вас какие-либо ссылки на фактические отчеты о проблемах с производительностью во время выполнения, поскольку связанный с вами поток, похоже, не имеет к этому никакого отношения, поскольку время компиляции увеличивается всего на 300 мс при компиляции с помощью math.h в более старой stdlib (я не уверен, что я все равно вижу проблему увеличения времени компиляции скомпилированного объекта на 300 мс)?

Я буду исследовать больше об этом.
Я понимаю, что у стоимости есть причина, но большее время компиляции для меня действительно анти-функция. У меня есть только ноутбук, и работа над функциями для движка требует времени, потому что мне приходится ждать процесса компиляции + компоновки после каждого изменения. увеличивать это еще больше без действительно оправданной выгоды, просто ради использования более новых версий - не очень хорошая идея.

У меня есть только ноутбук, и работа над функциями для движка требует времени, потому что мне приходится ждать процесса компиляции + компоновки после каждого изменения.

Если у вас установлен ccache, много времени в процессе инкрементной сборки будет потрачено на связывание. Вы могли бы сэкономить секунду или около того, используя gold вместо ld для ссылки, и, вероятно, можно оптимизировать его еще больше, переключившись на lld .

О, определенно, я не могу сказать, что достаточно хорош как для ccache, так и для ниндзя в качестве вспомогательного компоновщика (например, если вы используете cmake или что-то в этом роде), они оба экономят так много времени!

Кроме того, сборки Unity могут быть на удивление удивительными, именно здесь вы создаете новый unity.cpp или около того файл, который просто включает в себя все файлы cpp в проекте, хотя на самом деле у вас обычно есть файл unity cpp для каждого «модуля». проекта, поэтому у вас есть только дюжина или около того, чтобы сохранить память, в обмен на эту дополнительную память во время компиляции он компилируется и компонуется намного быстрее. Однако они менее полезны для инкрементной перестройки и более полезны для выпускных сборок.

Чтобы добавить один в кучу:
static_assert

Например, объединение SpatialMaterial::MaterialKey предполагает, что структура имеет тот же размер, что и uint64_t, но, насколько я могу судить, это нигде не утверждается.

Хотел влепить туда static_assert(sizeof(MaterialKey) == sizeof(uint64_t)) , но не смог.

Другой момент — использование unique_ptr для обеспечения правильной очистки при уничтожении без необходимости писать слишком много ручного шаблонного кода и без использования ненужного подсчета ссылок.

Я хотел бы предложить свои 2 (или 20) центов как человек, плохо знакомый с Godot и его кодовой базой. В настоящее время я наблюдаю и работаю над переносом _Battle for Wesnoth_ на Godot. Внешний интерфейс (редактор и GDScript API) великолепен! Помимо нескольких шероховатостей, пока что это позволяло нам прогрессировать в хорошем темпе. Но мы также предполагали, что в какой-то момент мы (команда) внесем исправления для серверной части (движка). С этой целью ранее на этой неделе я клонировал репозиторий git и начал ковыряться в C++, и, честно говоря... я немного встревожен.

У меня есть опыт управления большой кодовой базой C++ в виде старого пользовательского движка Wesnoth. Он также начинался как C++03, но был модернизирован для использования C++11 и более поздних функций и теперь совместим с C++14. Я возглавил эти усилия по модернизации (часто слишком усердно), и я чувствую, что это сделало нашу кодовую базу намного более читабельной и с ней стало легче работать. Конечно, я никогда особо не работал с кодовой базой, основанной исключительно на C++03; Я изучил C++, используя современные возможности C++. Но для меня идея о том, что такие вещи, как auto , range-for и лямбда-выражения делают ваш код менее читаемым, просто... очень странная.

Возьмите auto , конечно, можно злоупотреблять и злоупотреблять им, но у него также есть масса законных применений. Одно из наиболее распространенных мест, где мы развертывали auto при обновлении кодовой базы Wesnoth, — это циклы for с использованием итераторов. Раньше у нас было бы что-то вроде этого:

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

Что грязно! В тех случаях, когда нам действительно был нужен итератор, мы делали это:

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

Да, теперь вы не знаете явного типа итератора, но вам почти никогда не нужно это знать. Я прочитал комментарий здесь, в нескольких сообщениях, в котором говорилось, что это усложняет задачу, если контейнер объявлен в нескольких файлах, но на самом деле с современными редакторами кода и intellisense это не большая проблема.

В большинстве случаев вместо этого мы бы просто переключились на range-for:

for(const auto& foo : container) {}

Гораздо быстрее печатать и более лаконично. Вам не нужно беспокоиться о разыменовывании итераторов внутри цикла или об отслеживании индексов. И совершенно ясно, что вы перебираете весь контейнер. С итераторами людям, незнакомым с кодом, необходимо дважды проверить, действительно ли цикл идет от начала до конца.

Использование auto в циклах range-for здесь также имеет дополнительное преимущество. Это делает на одну вещь меньше, которую вам нужно помнить, чтобы обновить, если вы когда-нибудь измените тип контейнера! Я действительно не могу понять аргумент Хуана о том, что эти вещи делают ваш код менее читаемым или менее простым для понимания. Для меня это полная противоположность.

В видео State of Godot он также упомянул лямбды. Опять же, ими, безусловно, можно злоупотреблять, но это также невероятно полезный инструмент! Вот общая парадигма, которую я видел в кодовой базе Wesnoth до использования 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++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; });
}

Это просто самый распространенный случай! И да, я знаю, что вы также можете реализовать operator< для T, и std::sort использует это по умолчанию, но это иллюстрирует мою точку зрения. Опять же, может быть, это просто изучение и почти исключительно работа с современным C++, но я думаю, что игнорирование таких инструментов, как auto , range-for и лямбда-выражения, где это уместно, стреляет себе в ногу.

Что подводит меня к следующему пункту. Я заметил, что внутри Godot используются многие собственные пользовательские типы вместо STL. Если вас беспокоит читабельность кода в отношении таких вещей, как auto , использование пользовательских типов ядра вместо STL абсолютно вредно! Несколько дней назад я просматривал кодовую базу и наткнулся на много кода, который выглядел примерно так:

container.push_back(T(args));

Теперь это неэффективно. push_back (по крайней мере, с точки зрения std::vector ) принимает константную ссылку; поэтому этот оператор приводит к ненужной копии. Я хотел сделать патч, чтобы они использовали emplace_back … но потом я понял, что вся кодовая база использует пользовательские типы контейнеров 😐

Одной из больших проблем конструкции Веснота и одним из основных факторов, повлиявших на решение использовать новый двигатель (в данном случае Годо), было то, что Веснот в значительной степени страдал от синдрома «Не здесь изобретено». Хотя мы использовали такие библиотеки, как Boost, наш конвейер рендеринга был настраиваемым, наш инструментарий пользовательского интерфейса был настраиваемым, наш язык данных/скриптов был настраиваемым... вы поняли суть. Прежде чем мы начали работу над портом Godot, я пытался (и не смог) реализовать аппаратно-ускоренный рендеринг (до этого момента мы использовали рендеринг поверхностей/спрайтов на базе процессора. В 2019 году. Да, я знаю X_X). SDL Texture API не имел поддержки шейдеров, а поддержка шейдеров была необходима. В конце концов я решил, что внедрение нашего собственного рендерера, хотя и возможно, наложит ненужную нагрузку на проект в будущем. У нас уже было несколько основных разработчиков, и найти кого-то, кто мог бы написать OpenGL (или Vulkan, если бы нам когда-нибудь нужно было отказаться от OGL), было бы просто ненужной болью, когда такой движок, как Godot, имеет совершенно хороший, хорошо поддерживаемый рендерер, который мы могли бы использовать. вместо.

Я упоминаю об этом, так как думаю, что это хороший пример того, почему реализация всего внутри компании может быть плохой идеей. Да, вы можете немного уменьшить размер своего двоичного файла, не используя стандартную библиотеку, но вы также несете огромную нагрузку на обслуживание. Преобразование этих push_back вызовов в emplace_back — это очень простой плод, который может сделать код чище и производительнее. Но поскольку у вас есть пользовательский тип vector , если вам нужна конструкция на месте, кто-то должен будет реализовать ее вручную. И во всех других пользовательских типах тоже!

Еще большая проблема заключается в том, что это повышает входной барьер. Я просмотрел кодовую базу Godot, ожидая типов C++ STL. Не найдя эти средства, я или кто-либо еще теперь должен точно знать, какие типы предоставляет движок и какой API подходит для каждого из них. Я хочу std::map ? Нет, мне нужно использовать dictionary . Это только усложняет жизнь сопровождающим и усложняет жизнь новым участникам. И действительно, разве код, который выглядит как одно, а на самом деле совсем другое... нечитабелен?

Около года назад, когда мы подошли к выпуску Wesnoth 1.14 и его запуску в Steam, я предпринял проект рефакторинга, чтобы исключить пользовательский тип контейнера, который по сути был std::list , за исключением того, что использование erase() не делало недействительным итераторы. Вот принцип коммита, о котором идет речь . После этого потребовалась дополнительная работа, чтобы все получилось правильно, но в результате код стал намного проще, понятнее и менее непрозрачным.

В заключение, я думаю, что запрет некоторых очень полезных функций C++11 и использование пользовательских типов вместо STL станет помехой для Godot в долгосрочной перспективе. Я знаю, что рефакторинг занимает много времени (поверьте мне, я знаю), но, судя по тому, как обстоят дела сейчас, очень вероятно, что вы в конечном итоге столкнетесь с уловкой-22. Пытаясь избежать недостатков использования STL (больших двоичных размеров и т. д.), вы в конечном итоге будете все труднее и труднее переключаться на более производительный и чистый код в новых стандартах C++. Я уверен, что никто особенно не надеется на реализацию конструкции на месте во всех пользовательских типах. 😬

Я знаю, что мое мнение здесь мало что значит, но я решил высказать свои мысли с точки зрения человека, который работал с большой кодовой базой C++, перешедшей на современный C++ из C++03. Это большая работа, но в долгосрочной перспективе я чувствую, что это того стоит.

Извините за массивную текстовую стену!

@Vultraz Полностью согласен.

Хотя у меня нет (почти никакого) опыта работы с C++, после некоторого времени работы с GDNative + C++ я разделяю ваше мнение.

Всего несколько дней назад я написал на Reddit в ветке « Является ли участие в Godot хорошим способом изучения C++? »:

Я бы не рекомендовал это. По моему опыту работы с GDNative и C++, они как минимум несколько раз изобретают велосипед (кастомные коллекции и указатели). Эти пользовательские классы недокументированы (по крайней мере, из привязок/заголовков GDNative C++ POV - за исключением одного руководства, которое привело к некомпилируемому демонстрационному проекту в последний раз, когда я пытался, нет документации ни в коде [заголовки, привязки], ни в официальных документах) и имеют запутанное/неинтуитивное поведение (например, обертывание некоторого класса движка в конце Ref вызывает случайные сбои, даже если Ref интуитивно должен быть прозрачным и, следовательно, не должен изменять поведение класса, который обертывается).

Я также не сторонник предпочитать менее читаемый шаблон IMO вместо использования новых функций C++. Мне нравится Haskell (и его библиотеки), его лаконичность, не потворствование новичкам, чтобы не калечить язык для продвинутых пользователей.

Из-за этих решений/ценностей я сомневаюсь, что когда-либо буду вносить свой вклад в движок. (Это забавно, потому что разработчики движка заявляют, что причина этих решений заключается в поощрении вкладов. В моем случае это имело совершенно противоположный эффект.)

@Vultraz хорошо сформулированное описание, отличное чтение, спасибо за это. Приятно слышать такую ​​точку зрения.

Я программист на С++ старого стиля, и поэтому у меня не было особых проблем с пониманием исходного кода Godot, я нашел его хорошо структурированным и очень мне нравящимся. Я думаю, что возможность добавить поддержку VR в ядро ​​этого движка относительно быстро, имея очень небольшой предыдущий опыт работы с кодовой базой, является свидетельством того, насколько читабелен и понятен этот движок. В некотором смысле это может быть старая школа, но я постоянно удивляюсь тому, насколько хорошо сделаны определенные части. Да, есть части, которые нуждаются в модернизации, чтобы быть менее требовательными к памяти и более производительными, но в целом это меня впечатлило.

Когда я слышу, как люди говорят о новом синтаксисе в C++, часто я просто чувствую себя старым и действительно задаюсь вопросом, из-за чего вся эта суета. Но я как бы понимаю, что когда вы изучаете более современный C++, а затем решаете внести свой вклад в Godot, кажется странным, что он заново изобретает колесо. Но, по крайней мере, для таких людей, как я, мы настолько привыкли к реализации подобных классов, что просто впечатлены тем, насколько хорошо здесь реализовано большинство :)

Теперь, когда все сказано, есть так много разных способов взглянуть на эту проблему, что это не смешно. От истоков Godot, предшествовавшего более новому синтаксису C++, до того, что основные разработчики чувствуют себя как дома, до ограничений, налагаемых кросс-платформенным характером Godot и устройствами (некоторые из которых не являются общедоступными), на которых он может (/ мог бы) работать. Я не чувствую, что в этом есть правильное или неправильное, это то, к чему вы привыкли и откуда вы пришли, и вопрос о том, перевешивает ли дополнительная кривая обучения тому, как работает Godot, преимущества рефакторинга большого кода. база.

Я не знаю, смотрели ли вы это, но Хуан провел выступление на GDC, которое было размещено в Интернете: https://www.youtube.com/watch?v=C0szslgA8VY .
Во время вопросов и ответов ближе к концу он немного рассказывает о принятии решений по этому поводу.
Это хорошие часы.

Что касается реакции выше на GDNative, обычные ребята, GDNative — это недавнее дополнение, которое удовлетворяет конкретную потребность, это не свидетельствует о том, как структурирован и построен основной продукт.
Попробуйте создать модуль (https://docs.godotengine.org/en/3.1/development/cpp/custom_modules_in_cpp.html), да, это требует компиляции ваших изменений в основной продукт, что является проблемой, которую GDNative пытается решить, но это дает вы гораздо лучше понимаете, как на самом деле устроен двигатель. Многие из аргументов, приведенных выше, являются недостатками GDNative, а не самой Godot.
Мне бы хотелось иметь решение для динамических модулей, которое позволит мне создавать модули так же, как мы создаем статические модули, возможно, когда-нибудь это станет возможным, но до тех пор достаточно GDNative.

@Vultraz @mnn Я действительно вижу смысл контейнеров STL, но исключительно потому, что некоторые реализации (в основном MSVC) имеют ужасно низкую производительность в режиме отладки. Но можно было бы просто использовать STL от EA и быть хорошим (у них быстро и портативно).

Кроме того: лично меня больше всего огорчило отсутствие RAII . Количество ручной очистки, которая бесполезно выполняется по всему коду, является странным, поскольку RAII не является новым для C++11.

Что касается реакции выше на GDNative, обычные ребята, GDNative — это недавнее дополнение, которое удовлетворяет конкретную потребность, это не свидетельствует о том, как структурирован и построен основной продукт.

Это, конечно, верно, но GDNative не должен быть более удобным для пользователя, чем сам движок, поскольку GDNative используется для создания «скриптов», поэтому он нацелен на аудиторию, которая, как ожидается, будет иметь еще более низкие навыки C++, чем те, кто хочет погрузиться во внутренности двигатель?

Из-за этих решений/ценностей я сомневаюсь, что когда-либо буду вносить свой вклад в движок. (Это забавно, потому что разработчики движка заявляют, что причина этих решений заключается в поощрении вкладов. В моем случае это имело совершенно противоположный эффект.)

Я новичок в C++ (около двух месяцев работаю 2 дня в неделю на C++), поэтому я должен был стать целевой демографической группой, извлекшей выгоду из этого решения. Я считаю, что контейнерам Godot не хватает базовой функциональности (по сравнению с stl, который сам по себе не слишком богат), я не думаю, что контейнеры связаны с GDNative, но я могу ошибаться. Я изо всех сил стараюсь избегать контейнеров Годо, потому что с ними всегда тяжело работать. Я думал, что за непоследовательное и неожиданное поведение Ref отвечает движок, но, похоже, я ошибался.

Я понимаю, что мой опыт программирования, вероятно, довольно необычен - годы профессиональной работы в JS/TS, год в Scala, небольшие хобби-проекты в Haskell (несколько тысяч строк, что на самом деле не так уж и мало, учитывая, насколько лаконичен Haskell - много раз я пишу код на C++, который на Haskell был бы как минимум в 5 раз короче и читабельнее). Интересно, я единственный, кого обескураживает использование архаичных чрезмерно многословных технологий.

@mnn GDNative был создан, чтобы позволить создавать модули на основе C в виде динамических библиотек, поэтому вам не нужно было перекомпилировать весь движок. Кроме того, было создано несколько языковых привязок, чтобы вы могли писать модули на C++, python, rust и т. д., что опять же не требовало компиляции всего движка.

Другая цель заключалась в том, чтобы разработчики могли создавать модули, где вы просто доставляете сам модуль и просто используете его со стабильной сборкой движка. Многие модули устарели, потому что они не поддерживаются, но привязаны к конкретной версии движка.

Так что да, стало намного проще создавать модули с точки зрения компиляции и развертывания. Но из-за своего подхода он имеет свои ограничения. Когда вы остаетесь в рамках этих ограничений, написание кода C++ в GDNative может оставаться простым, поскольку предмет, для которого вы создаете модуль, прост.

Попробуйте вырваться из этих ограничений, и вас ждут головные боли. Моя текущая головная боль связана с попыткой реализовать логику OpenGL, в то время как все это инкапсулировано внутри архитектуры визуального сервера и на самом деле недоступно внутри GDNative так, как мне нужно. Но это больше связано с тем, что я хочу сделать что-то с GDNative, для чего он никогда не был разработан.
Когда вы используете его для того, для чего он был разработан, вы можете делать с ним действительно крутые вещи.

Также обратите внимание, что GDNative задумывался как способ переписать код GDScript, который должен быть более производительным. В результате вы не можете получить доступ к чему-либо внутри движка, к чему GDScript также не имеет доступа (например, OpenGL).

Как насчет последней поддержки сопрограмм, т.е. co_await()? С точки зрения procgen это ОГРОМНО.

Поддержка Coroutine является частью стандарта C++20, который еще не доработан.

Позвольте мне вмешаться, в настоящее время я пишу расширенные (думаю, исходный редактор Hammer) инструменты кисти / csg для 3D Spatial Editor, и этот разговор о том, что нельзя разрешить авто, действительно сбивает меня с толку. Иногда _даже использование auto может быть явным_. Рассмотрим следующее:

Я нахожусь внутри SpatialEditorViewportContainer и хочу получить SpatialEditor, который находится на три элемента выше родительской иерархии (прежде чем кто-либо укажет, что есть лучшие способы доступа к SpatialEditor из контейнера области просмотра, учтите, что я начал смотреть на эту кодовую базу вчерашний день)

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

Как видите, динамическое приведение вниз _уже явно указывает тип SP_. Без auto мне пришлось бы писать избыточный мусор вроде:

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

Пожалуйста, ради бога, разрешите использование авто!

На тему того, какой стандарт С++ использовать: предлагаемые функции отражения и метакласса С++ 20 и более поздних версий были бы действительно полезны для уменьшения беспорядка макросов, такого как

GDCLASS(SpatialEditorViewportContainer, Container);

Кроме того, я хотел бы повторить, что эти ограничения заставляют меня сомневаться в моем решении вообще внести свой вклад. Примерно в 2012 году я самостоятельно выучил C++11, и неспособность использовать этот стандарт кажется мне пощечиной. С++ 11 и С++ 03 — это совершенно разные языки, и большая часть репутации того, что С++ трудно изучать, трудно читать и трудно писать, является ошибкой С++ 03 (или, скорее, 98). Неиспользование по крайней мере C++11 или 14 _вредно_ для удобства сопровождения и читабельности. Я вырос с C++11, а руководитель проекта, очевидно, нет (когда он начал работать над godot в 2007 году, мне было 12 лет), так что я думаю, что это скорее случай предпочтения и синдром утенка. Мне кажется, что отказ от использования C++11 — это просто утешение людей, привыкших к старой школе (она же ужасная) C++, за счет таких людей, как я, которые потратили время на изучение современного C++.

Со временем все больше и больше младших программистов, таких как я, будут воспитаны на современном С++ 11 и выше и обнаружат, что тот факт, что проект навсегда застрял на языке, в котором нет даже лямбда-выражений, обескураживает.

Короче говоря: C++11 или бюст!

Чтобы было ясно, я не сторонник использования STL. Создание собственных контейнеров — это хорошо, но отказ от таких функций, как лямбда-выражения и авто, кажется глупым.

Я пока закрою эхо-камеру, так как бессмысленно обсуждать это здесь. Несколько месяцев назад мы уже обсуждали, какие функции C++11 и/или некоторых более поздних версий мы планируем использовать.

Мы просто ждем, когда @hpvb успеет доработать рекомендации, которые он пишет, на основе нашего консенсуса, и мы сможем обсудить эти рекомендации, как только они будут опубликованы. Пока это неконструктивно.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги