Написание кроссплатформенного кода может быть сложным, но есть некоторые способы облегчить его разработку и поддержку. В этом руководстве мы перечислим некоторые способы, с помощью которых мы в Defold работаем с кроссплатформенным нативным кодом и API.
В движке Defold мы используем C++ очень мало. Фактически, большинство кода C-подобна. Мы избегаем шаблонов, за исключением нескольких контейнерных классов, из-за того, что шаблоны увеличивают как время компиляции, так и размер исполняемого файла.
Исходный код Defold собирается с использованием версии C++ по умолчанию каждого компилятора (см. Native Extensions - Best Practices).
Мы избегаем использования последних возможностей или версий C++. В основном потому, что у нас уже есть все необходимое для создания игрового движка. Следить за последними возможностями C++ - трудоемкая задача, и чтобы действительно освоить эти возможности, потребуется много драгоценного времени.
Для разработчиков расширений это также является дополнительным преимуществом, поскольку мы поддерживаем стабильный ABI. Также стоит отметить, что использование последних возможностей C++ может помешать компиляции кода на разных платформах из-за различной поддержки.
Поскольку движок Defold не использует никакого кода STL, за исключением некоторых алгоритмов и математики (std::sort, std::upper_bound и т.д.), но вы можете использовать STL в своем расширении.
Опять же, имейте в виду, что несовместимость ABI может помешать вам при использовании вашего расширения в сочетании с другими расширениями или библиотеками сторонних производителей
Избегая (сильно шаблонизированных) библиотек STL, мы также улучшаем время сборки и, что более важно, размер исполняемого файла.
В движке Defold мы используем const char*
вместо std::string
.
std::string
- распространенный подводный камень при смешивании различных версий C++ или версий компилятора: вы получите несоответствие ABI.
Для нас лучше использовать const char*
и несколько вспомогательных функций.
По возможности используйте ключ static
для функций, локальных для вашего блока компиляции. Это позволяет компилятору выполнять некоторые оптимизации, что может как улучшить производительность, так и уменьшить размер исполняемого файла.
При выборе сторонней библиотеки для использования (независимо от языка) мы учитываем, по крайней мере, следующие моменты:
Всегда убеждайтесь, что у вас есть доступ к зависимостям. Например, если вы зависите от чего-то на GitHub, ничто не помешает тому, что репозиторий будет удален, или внезапно изменит направление или владельца. Вы можете уменьшить этот риск, сделав форк репозитория и используя свой форк вместо изначального проекта.
Код из этой библиотеки будет внедрен в вашу игру, поэтому убедитесь, что библиотека делает то, что должна делать, и ничего больше!
При создании расширения есть несколько вещей, которые помогают как в его разработке, так и в поддержании.
Должен быть только один Lua API и одна его реализация. Это значительно облегчает одинаковое поведение для всех платформ.
Если рассматриваемая платформа не поддерживает расширение, мы рекомендуем просто не регистрировать модуль Lua вообще. Таким образом, вы можете обнаружить поддержку, проверив наличие nil:
if myextension ~= nil then
myextension.do_something()
end
Вот структура папок, которую мы часто используем для наших расширений.
/root
/input
/main -- Все файлы для актуального примера проекта
/...
/myextension -- Фактическая корневая папка расширения
ext.manifest
/include -- Внешние включения, используемые другими расширениями
/libs
/<platform> -- Внешние библиотеки для всех поддерживаемых платформ
/src
myextension.cpp -- Lua API расширения и функции жизненного цикла расширения
Также содержимое общих реализаций ваших функций Lua API.
myextension_private.h -- Ваш внутренний API, который будет реализован на каждой платформе (например, `myextension_Init` и т.д.)
myextension.mm -- Если для iOS/macOS необходимы нативные вызовы. Реализует `myextension_Init` и т.д. для iOS/macOS
myextension_android.cpp -- Если для Android необходимы вызовы JNI. Реализуется `myextension_Init` и т.д. для Android
/java
/<platform> -- Любые java-файлы, необходимые для Android
/res -- Любые ресурсы, необходимые для платформы
/external
README.md -- Заметки/скрипты о том, как собрать или упаковать любые внешние библиотеки
/bundleres -- Ресурсы, для которых должны быть созданы связки (см. game.project и параметр [bundle_resources setting]([physics scale setting])(/ru/manuals/project-settings/#project))
/<platform>
game.project
game.appmanifest -- Любая дополнительная информация о конфигурации приложения
Обратите внимание, что myextension.mm
и myextension_android.cpp
нужны только в том случае, если вы делаете специфические нативные вызовы для данной платформы.
В некоторых местах мы используем архитектуру платформы в качестве имени папки, чтобы знать, какие файлы использовать при компиляции/комплектации приложения. Они имеют такую форму:
<architecture>-<platform>
Текущий список таков:
arm64-ios, armv7-ios, x86_64-ios, arm64-android, armv7-android, x86_64-linux, x86_64-osx, x86_64-win32, x86-win32
Например, поместите библиотеки для конкретной платформы в раздел:
/libs
/arm64-ios
/libFoo.a
/arm64-android
/libFoo.a
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB