Нативные расширения
Нативные расширения позволяют подключающему проекту добавлять код на C++ в FOnline, не перенося игровую логику в репозиторий переиспользуемого движка. Они компилируются из исходного кода в составе общей сборки, проходят через тот же конвейер метаданных и кодогенерации и линкуются с библиотеками выбранных ролей движка.
Это руководство описывает архитектуру, разработку и проверку. Точные актуальные объявления приведены в сгенерированных справочниках ролей, хуков, правил биндинга и в канонической JSON-модели.
Статус контракта
Интерфейс нативных расширений имеет статус experimental и привязан к ревизии. Движок документирует компоновку исходников и поведение сгенерированных биндингов для зафиксированной ревизии, но не обещает бинарную совместимость расширения, собранного для одной ревизии, с библиотеками среды выполнения от другой.
Движок отвечает за:
- распределение исходников по ролям через
AddEngineSourcesи их обнаружение; - участие каждого зарегистрированного исходника в обработке метаданных и кодогенерации;
- поддерживаемые хуки движка и сгенерированные реализации по умолчанию;
- соглашения о пространстве имён движка, указателях, nullable-типах и экспорте в скрипты;
- связи базовых библиотек и линковки для пяти ролей.
Подключающий проект отвечает за:
- реализацию и состояние расширения;
- сторонние библиотеки, пути включения, определения компилятора и доступность на платформах;
- настройки, персистентность, миграции базы данных, учётные данные и внешние сервисы;
- содержимое пакетов, подписание, развёртывание и политику совместимости релизов;
- проектные тесты и документацию видимого игроку поведения.
FO_NATIVE_SCRIPTING выбирает скриптовый бэкенд. Это не переключатель проектных нативных расширений, и проверять по нему доступность расширения нельзя.
Проверенные исходные пути
BuildTools/NativeExtensionInterface.jsonBuildTools/Init.cmakeBuildTools/cmake/ProjectInterface.jsonBuildTools/cmake/helpers/Build.cmakeBuildTools/cmake/helpers/Options.cmakeBuildTools/cmake/stages/EngineSources.cmakeBuildTools/cmake/stages/Codegen.cmakeBuildTools/cmake/stages/CoreLibs.cmakeBuildTools/codegen.py- места вызова хуков движка в
Source/Applications/,Source/Frontend/,Source/Common/,Source/Client/,Source/Server/иSource/Tools/ Examples/MinimalProject/CMakeLists.txtExamples/MinimalProject/StarterServerExtension.cppExamples/NativeExtensionSample/CMakeLists.txtExamples/NativeExtensionSample/SourceExt/ServerExtension.cpp
Компоновка сборки
Регистрируйте проектные исходники после стадии ThirdParty и до точки входа EngineSources:
StartProjectGeneration()
RegisterProjectOptions()
AddThirdPartyLibraries()
AddEngineSources(
COMMON SourceExt/CommonExtension.cpp
SERVER SourceExt/ServerExtension.cpp
CLIENT SourceExt/ClientExtension.cpp
MAPPER SourceExt/MapperExtension.cpp
BAKER SourceExt/BakerExtension.cpp
TESTS SourceExt/Test_ProjectExtension.cpp)
RegisterEngineSources()
SetupCodeGeneration()
Пути и шаблоны разрешаются относительно корня вклада подключающего проекта. Аргументы задаются парами «роль/путь»; нечётное число аргументов приводит к ошибке конфигурации. Текущий helper не отклоняет неизвестный token роли: он создаёт список FO_<ROLE>_SOURCE и всё равно добавляет файл во входы metadata, но ни одна цель Engine не использует этот список, если роль не входит в шесть описанных ниже. Используемых ролей исходников EDITOR, ANIMATION_VIEWER и PARTICLE_VIEWER нет. Mapper, оба специализированных просмотрщика, Baker и ASCompiler линкуются с BakerLib, поэтому переиспользуемая поддержка авторинга и запекания обычно относится к BAKER; код, действительно общий для всех приложений, относится к COMMON. Проектные модули трансляции с тестами Catch2 относятся к TESTS: эта роль добавляет их непосредственно во включённые исполняемые файлы unit-тестов и покрытия, но не в runtime-библиотеки.
Каждый найденный файл добавляется как в список исходников своей роли, так и в FO_SOURCE_META_FILES. Заголовок, зарегистрированный как COMMON, также попадает во входные данные генерации общих заголовков. Регистрируйте только файлы, которые должна анализировать кодогенерация: вендорное дерево исходников должно быть отдельной библиотечной целью, а не широким шаблоном расширения.
Выбор роли
Выбирайте самую узкую роль, которой принадлежит поведение:
| Потребность | Роль | Следствие |
|---|---|---|
| Общепроцессное поведение, конфигурация или приложение, используемые несколькими приложениями | COMMON |
Компилируется в CommonLib; не допускайте зависимостей только для клиента или сервера. |
| Авторитетная логика, персистентность, серверная сеть, серверные скриптовые методы | SERVER |
Компилируется в ServerLib; недоступно клиентским скриптам и бинарным файлам. |
| Рендеринг, ввод, клиентская сеть, клиентские скриптовые методы | CLIENT |
Компилируется в ClientLib; Mapper также получает клиентские регистрации через ClientLib. |
| Автоматизация только для Mapper или его скриптовые методы | MAPPER |
Компилируется в MapperLib. |
| Пользовательские запекатели ресурсов и средства авторинга для Mapper, просмотрщиков и ASCompiler | BAKER |
Компилируется в BakerLib; BAKER не является целью экспорта в скрипты. |
| Проектные модули трансляции с регрессиями Catch2 | TESTS |
Компилируется непосредственно во включённые исполняемые файлы unit-тестов и покрытия; runtime- и скриптовых целей нет. |
Не используйте COMMON только ради устранения ошибки линковки символа. Перенесите зависимость в принадлежащую ей роль или отделите небольшой общий интерфейс от реализаций для конкретных ролей.
Форматы игровых систем, принадлежащие проекту
Нативные расширения могут реализовывать полноценные форматы игровых систем, не превращая эти форматы в возможности движка. Обычная проектная система с авторскими данными может использовать:
COMMONдля парсера, общих записей, экспортируемых скриптовых типов или реестра;BAKERдля проверки синтаксиса, валидации с учётом метаданных и генерации ресурсов;SERVER,CLIENTили проектные скрипты для авторитетного поведения среды выполнения;- проектный редактор, средство аудита, фикстуры и игровые тесты для подтверждения авторинга и поведения.
Регистрация через AddEngineSources даёт интеграцию со сборкой, метаданными, кодогенерацией и линковкой. Она не передаёт FOnline владение API, форматом, совместимостью, безопасностью или документацией. Подключающий проект обязан описать собственные парсер, запекатель, сгенерированные результаты, потребителей среды выполнения, уровни проверки и политику миграции.
Не добавляйте руководство по формату движка, пока переиспользуемая реализация и тесты не находятся в этом репозитории. Если одна система нужна нескольким играм, но не подходит для ядра движка, публикуйте версионируемый сопутствующий репозиторий с точным диапазоном совместимости с движком и собственным минимальным примером.
Подключения и пространства имён
Начинайте с Common.h, затем подключайте минимальный заголовок роли, необходимый реализации:
#include "Common.h"
#include "Server.h"
FO_USING_NAMESPACE();
FO_BEGIN_NAMESPACE
///@ ExportMethod
FO_SCRIPT_API int32_t Server_Game_ProjectValue(ptr<ServerEngine> server);
FO_END_NAMESPACE
int32_t FO_NAMESPACE Server_Game_ProjectValue(ptr<ServerEngine> server)
{
ignore_unused(server);
return 1;
}
Объявления метаданных должны компилироваться как с включённым, так и с выключенным пространством имён движка. Размещайте объявления внутри FO_BEGIN_NAMESPACE / FO_END_NAMESPACE, а определения квалифицируйте через FO_NAMESPACE.
Экспорты FO_SCRIPT_API являются границей кодогенерации и намеренно не начинаются с FO_TRACE_ZONE(Script). Обычные неэкспортируемые проектные функции C++ следуют стандартному соглашению движка о трассировке стека.
Экспорт в скрипты и метаданные
Зарегистрированные файлы расширения разбираются вместе с метаданными движка. Проектный код может использовать поддерживаемые объявления ///@, включая ExportMethod, ExportEvent, ExportRefType, ExportSettings и EngineHook, соблюдая те же правила парсера и nullable-типов, что и объявления движка.
Роль исходника CMake и цель метаданных связаны, но не взаимозаменяемы:
- файл
SERVERобычно объявляет экспортыServer_*; - файл
CLIENTобычно объявляет экспортыClient_*, и эти регистрации также доступны сборкам Mapper; - файл
MAPPERобъявляет экспорты только для Mapper; - экспорты
COMMONрегистрируются на каждой применимой стороне; BAKERможет реализовывать хуки запекателя, но не является цельюExportMethod.
Для заимствований дескрипторов движка используйте ptr<T> / nptr<T>. Кодогенерация отклоняет необёрнутые сырые указатели на дескрипторы. Значения аргументов по умолчанию, nullable-семантика, владение и сторона среды выполнения должны совпадать со сгенерированным скриптовым объявлением. После любого изменения нативных метаданных пересоберите проект и перезапеките ресурсы; не переносите сгенерированные файлы регистрации между ревизиями движка.
Удалённые вызовы проекта остаются проектными скриптовыми метаданными и используют запечённый каталог, описанный в разделе Удалённые вызовы. Они не являются символами нативного расширения.
Хуки движка
Хуки представляют собой необязательные именованные точки входа C++. Объявите хук с ///@ EngineHook и точной сигнатурой из сгенерированного справочника хуков в файле, зарегистрированном в принадлежащей ему роли. Обнаружив объявление, кодогенератор исключает реализацию этого хука по умолчанию из GenericCode-Common.gen.cpp.
Если проект не объявляет хук, кодогенерация создаёт его документированную реализацию по умолчанию. Наличие большинства хуков участвует в сгенерированном состоянии совместимости; текущее исключение — ApplicationShutdownHook. Набор имён хуков закрыт: неизвестное имя приводит к ошибке кодогенерации.
Реализуйте каждый хук ровно один раз. Несколько объявлений при едином решении о генерации реализации по умолчанию могут привести к дублирующим определениям или неразрешённым символам. Держите объявление рядом с исходником реализации и не помещайте обычные комментарии между тегом ///@ и объявлением.
Тела хуков выполняются на границах жизненного цикла или политики. Они обязаны сохранять инварианты движка и следовать контракту исключений владеющей подсистемы. В частности:
- хуки завершения вызываются через защищённые пути остановки и должны освобождать ресурсы без исключений;
- хуки видимости выполняются в авторитетных серверных путях и не должны вводить несинхронизированное изменяемое глобальное состояние;
- хуки конфигурации выполняются при разборе настроек и должны возвращать документированный признак изменения;
- настройка запекателей должна добавлять только запрошенные проектные запекатели и правильно сохранять владение общим контекстом запекания.
Состояние и время жизни
Предпочитайте состояние, принадлежащее экземпляру движка или доступному из него проектному менеджеру. Данные клиентского или серверного расширения можно присоединить через пользовательский слот владения движка с владеющим указателем движка и явным удалителем. Это изолирует параллельные тестовые экземпляры и задаёт детерминированного владельца при остановке.
Не используйте изменяемые статические переменные уровня файла для реестров, сессий, кэшей или состояния расширения на экземпляр движка. В одном процессе может работать несколько экземпляров движка. Общепроцессный сервис допустим только тогда, когда его семантика действительно общепроцессная, хуки жизненного цикла владеют инициализацией и остановкой, а тесты могут его изолировать или отключить.
Проектный слушатель AiControl — показательный пример расширения, чувствительного ко времени жизни: сокетный поток может разбирать и копировать простые значения, но публиковать наблюдения и извлекать команды должен владеющий клиентский цикл. До освобождения состояния экземпляра движка прекратите приём соединений, закройте сокеты, разбудите и присоедините поток, завершите принятые незаконченные команды ошибкой и отмените регистрацию обратных вызовов. Переиспользуемый конверт и политика безопасности описаны в протоколе AiControl; наблюдения игры, действия, средства MCP, условие компиляции и тесты среды выполнения остаются во владении проекта.
Используйте словарь указателей движка:
ptr<T>/nptr<T>для заимствованных объектов движка;unique_*/refcount_*и вспомогательные средстваshared_ptrдвижка для владения;- сырые указатели только на документированных границах ABI ОС или SDK с немедленным оборачиванием при входе.
Зависимости и платформы
AddEngineSources не выводит зависимости автоматически. Подключающий проект должен добавить библиотеки, каталоги включения, определения компилятора, сгенерированные заголовки и платформенные фреймворки до построения базовых библиотек и приложений. Держите сторонние исходники в отдельной цели и направляйте её только через самый узкий потребляемый список FO_*_LIBS текущей ревизии; проектные зависимости описывают полный процесс выбора, происхождения, CMake-интеграции, ABI, упаковки и обновления.
Для платформенно-зависимых расширений нужен явный контракт доступности:
- оградите настоящую реализацию платформенными макросами движка или проекта;
- предоставьте компилируемую заглушку неподдерживаемого режима, если общий скриптовый или нативный символ должен сохраняться;
- явно отклоняйте неподдерживаемое использование во время выполнения вместо имитации успеха;
- синхронизируйте содержимое пакетов и внешние библиотеки времени выполнения со скомпилированной возможностью;
- проверяйте как минимум по одному пути сборки с включённой и выключенной возможностью.
Никогда не помещайте учётные данные, ключи API, материалы подписания или закрытые URL сервисов в исходники расширения, сгенерированные метаданные, примеры, журналы или документацию.
Стратегия тестирования
Используйте самый узкий путь, доказывающий затронутую границу:
- изменения регистрации CMake или ролей:
cmake -P BuildTools/tests/validate_native_extension_interface.cmake; - изменения хуков, метаданных или кодогенерации: перегенерируйте и проверьте справочники нативных расширений и API, затем выполните
BakeResourcesв настоящем подключающем проекте; - переиспользуемый минимальный серверный хук: запустите
python validate.pyизExamples/MinimalProject; - полный путь lifecycle, role-link, script export и focused-теста: запустите
python validate.pyизExamples/NativeExtensionSample; - экспорт в скрипты: добавьте проверку компиляции или запекания скриптов и сфокусированный тест среды выполнения, вызывающий сгенерированный метод на правильной стороне;
- видимое клиенту расширение: соберите и запустите реальный путь отдельного клиента; серверная или headless-проверка не доказывает рендеринг, ввод, динамические библиотеки или поведение пакета;
- внешний SDK или платформенный мост: проверьте включённый, выключенный и упакованный пути среды выполнения на целевой платформе.
Минимальный проект движка является нормативным starter-примером. Examples/NativeExtensionSample показывает полный сфокусированный native-путь: хранит состояние отдельного сервера в ServerEngine.UserData, подключает небольшую библиотеку через текущее integration state FO_SERVER_LIBS, экспортирует один серверный метод и выполняет native unit- и runtime smoke-проверки. Большой игровой проект даёт ценное интеграционное свидетельство, но не определяет переиспользуемый контракт.
Обновление ревизии движка
Считайте изменение gitlink движка событием совместимости расширения:
- сравните старую и новую канонические модели нативного расширения через управление изменениями сгенерированных контрактов;
- проверьте изменения сигнатур, значений по умолчанию и мест вызова хуков, ролей и библиотек, указателей и nullable-типов, сгенерированных метаданных и маркеров совместимости;
- повторно сконфигурируйте проект, чтобы заново проверить роли и разрешить шаблоны исходников;
- пересоберите каждую затронутую нативную роль и перезапеките проектные метаданные и ресурсы;
- обновите документацию и тесты проектного расширения, а при изменении поведения — заметки о миграции и релизе;
- никогда не используйте повторно нативные бинарные файлы или сгенерированные файлы регистрации от предыдущей ревизии движка.
Контрольный список проверки
- Каждый исходник зарегистрирован в самой узкой допустимой роли до
RegisterEngineSources(). - У каждого объявления метаданных правильны цель, макросы пространства имён, словарь указателей и nullable-типов, а для хука — точная сигнатура.
- Состояние на экземпляр движка имеет владельца экземпляра; для общепроцессных глобальных данных явно обоснован общепроцессный жизненный цикл.
- Зависимости, платформенные условия, отключённые заглушки и содержимое пакета соответствуют скомпилированной возможности.
- Сгенерированные справочники API и нативных расширений, а также сводная разница контрактов актуальны.
- Структурные проверки CMake, кодогенерация и запекание, сфокусированные нативные и скриптовые тесты и минимальный реальный путь среды выполнения проходят без предупреждений.
- Проектная документация фиксирует настройки, персистентность, сервисы, безопасность и релизное поведение, которые руководство движка намеренно не охватывает.
См. также
- Подключающий проект — владение репозиториями движка и игры.
- Конвейер BuildTools — стадии и компоновка библиотек.
- Сгенерированный API и метаданные — метаданные и сгенерированный скриптовый API.
- Умные указатели — словарь нативных указателей.
- Nullable-типы — граница nullable-типов между нативным кодом и скриптами.
- Безопасность исключений — контракты исключений жизненного цикла и изменений состояния.
- Проектные зависимости — владение проектными библиотеками и SDK, линковка по ролям, доставка на платформы, упаковка и сопровождение.
- Протокол AiControl — нейтральный к проекту конверт управления ИИ, локальная граница угроз, жизненный цикл команд и разделение владения нативной частью и MCP.
- Сопровождение ThirdParty — политика вендорных зависимостей движка.