Нативные расширения
Нативные расширения позволяют подключающему проекту добавлять код на 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-семантика, владение и сторона среды выполнения должны совпадать со сгенерированным скриптовым объявлением. После любого изменения нативных метаданных пересоберите проект и перезапеките ресурсы; не переносите сгенерированные файлы регистрации между ревизиями движка.
Удалённые вызовы проекта остаются проектными скриптовыми метаданными и используют запечённый каталог, описанный в разделе Удалённые вызовы. Они не являются символами нативного расширения.
Хуки движка
Нативные подписки на события сущностей — отдельная поверхность расширений, отличная от закрытого каталога entry points ///@ EngineHook. В частности, серверное расширение может подписаться на ServerEngine::OnPlayerPreLogin и OnPlayerPreProcess без декларации генерируемого hook. Эти нативные события не добавляют события скрипта или codegen fallback; порядком, отказами и lifecycle-контрактом владеет серверная среда.
Хуки представляют собой необязательные именованные точки входа C++. Объявите хук с ///@ EngineHook и точной сигнатурой из сгенерированного справочника хуков в файле, зарегистрированном в принадлежащей ему роли. Обнаружив объявление, кодогенератор исключает реализацию этого хука по умолчанию из GenericCode-Common.gen.cpp.
Если проект не объявляет хук, кодогенерация создаёт его документированную реализацию по умолчанию. Наличие большинства хуков участвует в сгенерированном состоянии совместимости; текущее исключение — ApplicationShutdownHook. Набор имён хуков закрыт: неизвестное имя приводит к ошибке кодогенерации.
Реализуйте каждый хук ровно один раз. Несколько объявлений при едином решении о генерации реализации по умолчанию могут привести к дублирующим определениям или неразрешённым символам. Держите объявление рядом с исходником реализации и не помещайте обычные комментарии между тегом ///@ и объявлением.
Тела хуков выполняются на границах жизненного цикла или политики. Они обязаны сохранять инварианты движка и следовать контракту исключений владеющей подсистемы. В частности:
- хуки завершения вызываются через защищённые пути остановки и должны освобождать ресурсы без исключений;
- хуки видимости выполняются в авторитетных серверных путях и не должны вводить несинхронизированное изменяемое глобальное состояние;
- хуки конфигурации выполняются при разборе настроек и должны возвращать документированный признак изменения;
- настройка запекателей должна добавлять только запрошенные проектные запекатели и правильно сохранять владение общим контекстом запекания.
У видимости просмотра карты отдельные хуки существ и предметов, отличные от обычной видимости наблюдателя. Они получают nullable-контекст Entity и центр обзора; проект приводит/интерпретирует контекст и задаёт политику null. Сервер предоставляет полное покрытие и требует синхронных читающих хуков. См. видимость просмотра карты.
Состояние и время жизни
InitApp(args, flags) загружает ресурсный конфиг до ApplicationInitHook:
сначала .fomain-server, при его отсутствии — .fomain-client. Флаги frontend,
такие как StandaloneClient, не выбирают конфигурацию.
Предпочитайте состояние, принадлежащее экземпляру движка или доступному из него проектному менеджеру. Данные клиентского или серверного расширения можно присоединить через пользовательский слот владения движка с владеющим указателем движка и явным удалителем. Это изолирует параллельные тестовые экземпляры и задаёт детерминированного владельца при остановке.
Не используйте изменяемые статические переменные уровня файла для реестров, сессий, кэшей или состояния расширения на экземпляр движка. В одном процессе может работать несколько экземпляров движка. Общепроцессный сервис допустим только тогда, когда его семантика действительно общепроцессная, хуки жизненного цикла владеют инициализацией и остановкой, а тесты могут его изолировать или отключить.
Проектный слушатель 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 — политика вендорных зависимостей движка.