Read this manual in English

Лучшие Практики

Написание кроссплатформенного кода может быть сложным, но есть некоторые способы облегчить его разработку и поддержку. В этом руководстве мы перечислим некоторые способы, с помощью которых мы в Defold работаем с кроссплатформенным нативным кодом и API.

Код Defold

В движке Defold мы используем C++ очень мало. Фактически, большинство кода C-подобна. Мы избегаем шаблонов, за исключением нескольких контейнерных классов, из-за того, что шаблоны увеличивают как время компиляции, так и размер исполняемого файла.

C++ версия

Исходный код Defold собирается с использованием версии C++ по умолчанию каждого компилятора (см. Native Extensions - Best Practices).

Мы избегаем использования последних возможностей или версий C++. В основном потому, что у нас уже есть все необходимое для создания игрового движка. Следить за последними возможностями C++ - трудоемкая задача, и чтобы действительно освоить эти возможности, потребуется много драгоценного времени.

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

Стандартные Библиотеки Шаблонов - STL

Поскольку движок 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 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