Files
Faset_Engine/docs/studies/13-build-pipeline-and-stack.md
T

33 KiB
Raw Blame History

Сборка игры, доставка и выбор стека собственного движка

Принято 18.09.2026: C++ core/gameplay, затем Lua отдельным модулем (необязателен для конкретной игры); EnTT; собственный retained C++ editor UI с декларативными layout/styles и тёмной темой, ImGui для debug; Vulkan 1.3 и собственный RenderGraph, Slang/совместимый HLSL; SDL3 за API Faset; CMake+Ninja, Clang Linux и clang-cl Windows. Gameplay — статическая библиотека в Player dev/release, Editor plugins — DLL/SO под точный SDK с restart. Сравнение альтернатив ниже — история исследования, не открытый выбор. Канон — архитектура, этапы — PLAN.md. MCP только в Editor/headless editor services, без runtime inspection/mutation и без MCP в Player/SchemaExporter.

Исходные требования: Linux и Windows desktop, игры 2D и 3D, продвинутая графика, полноценное ручное редактирование и управление через MCP. Текущие рекомендации ниже описывают принятый проект; pipeline и движок ещё не реализованы. Исторические варианты помечены отдельно. Проверка источников: 17 сентября 2026. Локальный Godot: 9c776068d6ed23acd0c78bfe534272d1d2a3a619, версия дерева 4.8-dev; официальные текущие страницы Unity показывают 6.6. Исследованы тела desktop exporter, сбор зависимостей, remap импортированных ресурсов, кэш преобразования, запись PCK и конфигурация editor/player. Unity здесь изучен по документации, без утверждения о просмотре закрытой реализации.

Что именно должна делать кнопка Build

Нужны четыре отдельные операции с понятными входами и результатами:

  1. Компиляция кода: engine/player и статическая C++ gameplay-библиотека превращаются в машинный код; отдельный SchemaExporter после сборки выпускает декларативную схему регистраций для Editor. Будущий Lua-модуль добавляется только проектам с такой зависимостью; Lua может поставляться исходниками или согласованным с VM байткодом. Изменение PNG не должно запускать C++ compiler.
  2. Компиляция шейдеров: исходники, include-файлы, defines и описания вариантов превращаются в промежуточный код и reflection metadata. Принятый Slang pipeline выдаёт SPIR-V для Vulkan 1.3 и согласованные сведения о shader resources; существующий HLSL поддерживается в совместимом подмножестве. Создание GPU pipeline остаётся отдельной операцией драйвера; наличие SPIR-V не означает отсутствие runtime compilation/stutter. Vulkan прямо описывает затраты создания pipeline и сохранение pipeline cache между запусками. Khronos: Pipeline Cache.
  3. Подготовка ассетов, или cook: текстуры, модели, сцены, анимация, звук, шрифты и настройки превращаются в runtime-форматы для выбранного профиля. Сюда относятся mipmaps, GPU texture compression, mesh clusters/LOD, collision data и бинарные сцены.
  4. Упаковка: готовый player, код игры, cooked content, необходимые библиотеки и manifest собираются в самостоятельный каталог/архив. Иконки, подпись, symbols и installer — последующие явные шаги, а не скрытые побочные действия импорта.

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

Что действительно делает Godot и чему учит Unity

Повторно используемый player вместо сборки движка при каждом экспорте. EditorExportPlatformPC::export_project() вызывает подготовку template, модификацию и экспорт данных. prepare_template() выбирает debug/release template и копирует его в output; export_project_data() создаёт отдельный PCK либо встраивает его в executable, затем переносит native shared objects. Это непосредственно видно в цепочке экспорта, выборе template, копировании и упаковке данных. Вывод для Faset: переиспользовать собранные библиотеки core и зависимости, но не обещать готовый универсальный Player без компилятора для C++ gameplay. В принятом варианте код игры статически линкуется в Player; новая игра или изменение её кода требуют компиляции/линковки на worker. Правка только ассетов может переиспользовать готовый Player той же игры. Шаблонный export Godot не переносится на этот контракт буквально.

Development player и editor — разные продукты. В Godot TOOLS_ENABLED появляется только у editor; debug features включаются для editor и template_debug; DEV_ENABLED управляет дополнительным кодом разработчика самого движка. Символы и оптимизация настраиваются отдельно. SConstruct. Следовательно, игровой development build может быть оптимизированным и диагностируемым, при этом не содержать редактор. Для собственного движка нужны editor, development player и shipping player, плюс независимые параметры symbols/validation/profiling.

Экспорт — обход графа ресурсов. Godot начинает с выбранных сцен/ресурсов, рекурсивно добавляет их зависимости, отдельно учитывает autoload и include/exclude filters. Это тела _export_find_dependencies и export_project_files. При обработке импортированных ресурсов экспортируются remapped runtime-файлы для нужных feature tags, а служебные секции deps и params удаляются. Выбор вариантов и очистка. Перенимать стоит явные roots и runtime remap; динамическая загрузка по строке требует отдельной декларации, иначе статический граф может не увидеть ресурс.

Кэш и отмена существуют на уровне конкретной операции. _export_customize() проверяет существование результата, время изменения, затем hash исходника и .import. Импорт отдельно хранит checksum исходных и подготовленных файлов. _export_customize, import checksums. PCK writer опрашивает progress и возвращает ERR_SKIP при отмене. Запись файла. Это подтверждает инкрементальность отдельных стадий, но не доказывает полную воспроизводимость любого Godot export. Даже сортировка каталога PCK сама по себе не гарантирует побайтово одинаковый пакет.

Unity представляет сборку как получение target-specific Player, управляемое Build Profiles или BuildPipeline API. Инкрементальный pipeline охватывает content, code, compression и signing; clean build остаётся отдельным режимом. AssetBundles имеют собственные механизмы кэширования. Unity: Introduction to building. Практический урок: единый план сборки должен учитывать разные виды работ, а отдельный asset pack не следует путать с полной сборкой игры. Для API полезен подход BuildPlayerOptions → BuildReport: результат должен быть структурой со статусом и диагностикой. BuildPipeline.BuildPlayer.

Принятая архитектура сборки

Общий build service под editor, CLI и MCP. Все три клиента отправляют один типизированный BuildRequest: project revision, target ОС/архитектура, configuration, renderer feature profile, startup scene, content roots, output path. Сервис создаёт job ID и неизменяемый snapshot настроек. Редактирование проекта во время cook не должно тихо смешивать две ревизии. Для несохранённых сцен интерфейс явно предлагает сохранить snapshot либо показывает, что будет собрана последняя сохранённая версия.

Основные состояния: planning → compiling/cooking → packaging → validating → succeeded/failed/cancelled. Они нужны для восстановления клиента после перезапуска editor и повторного запроса MCP. В пределах срока хранения журнала повтор с тем же project/client scope, request ID и hash параметров возвращает существующий job; тот же ID с иным payload отклоняется. Согласованная запись результата и публикации предотвращает повторный build после разрыва ответа; это контракт сервиса, который ещё надо реализовать и проверить. В отчёте сохраняются toolchain versions, hashes входов, выполненные/пропущенные шаги, длительности, предупреждения и путь готового артефакта. Это наше проектное решение, не описание поведения Godot.

Компиляторы и импортёры запускаются дочерними процессами. Editor не должен зависать из-за shader compiler или падения конвертера модели. Для каждого процесса задаются argv без shell-конкатенации, рабочий каталог, ограниченный набор environment variables и отдельный staging output. stdout/stderr сохраняются полностью; распознанные ошибки дополнительно превращаются в diagnostics с файлом, строкой, asset ID и шагом. При отмене сигнал получает группа процессов; после периода завершения сервис останавливает оставшихся потомков. Отменённая работа не публикует cache entry и не заменяет последнюю успешную игру.

Публикация артефакта — последняя транзакция. Сначала все файлы собираются в staging, затем проверяются manifest и обязательные зависимости. Только успешная проверка делает каталог новой опубликованной версией, по возможности атомарным rename на том же файловом разделе. Сбой, занятый Windows executable или отмена оставляют предыдущую сборку доступной. Сервис должен показать причину конфликта output, а не объявить успех по exit code только одного compiler.

Player и SchemaExporter не содержат MCP. Runtime содержит scene/resource loading, simulation, renderer, audio/input и Lua лишь при зависимости проекта. Editor UI/plugins, asset importers, build service, SchemaExporter и MCP adapter идут в отдельные targets. Это правило действует для development и release, не только для shipping по умолчанию. Editor MCP запускает/останавливает Play и читает editor/build/import logs, но не получает world/session inspection/mutation через другой транспорт. Состав пакета и граф зависимостей проверяют эту границу.

Инкрементальный cook и проверяемая воспроизводимость

Предлагаемый cache key: hash содержимого входов и транзитивных зависимостей + importer/version + нормализованный recipe + целевой формат/feature profile + версия runtime schema. Для shader key дополнительно нужны include graph, compiler version, entry point, defines и flags. Timestamp годится как быстрый локальный hint; окончательное решение release-сборки не должно полагаться только на него.

Manifest связывает стабильный asset ID с исходной ревизией, cooked hash, runtime path, типом, размером, прямыми зависимостями и вариантом платформы. Build roots включают startup scenes, явно объявленные preload/autoload, shader/material variants и наборы для динамической загрузки. Проверка заранее ловит missing references, коллизии регистра имён между Linux/Windows и неподдерживаемый формат текстуры. Editor может объяснить «этот файл включён, потому что сцена A ссылается на материал B».

Для детерминированного content pack нужно фиксировать порядок записей, сериализацию floating-point/строк, compression version/settings, seeds генераторов и нормализацию путей; исключить абсолютный путь рабочего каталога и время сборки из content hash. Изменение одной текстуры должно пересобирать её варианты и зависимые данные согласно графу, сохраняя остальные cache hits. Общий pack допускает отдельный этап переупаковки, даже если cook не выполнялся.

Побайтовая воспроизводимость native executable — дополнительная цель: compiler/linker, debug paths, подписи и platform metadata могут вносить различия. Не обещать её автоматически из-за content-addressed cache. Сначала доказать идентичность unsigned cooked data и одинаковость manifest при двух clean сборках одного snapshot; затем вводить reproducible native build отдельным профилем.

Для доставки на старте достаточно каталога и ZIP: Windows executable, требуемые DLL и данные; Linux executable, нужные .so, корректные права и данные. Отдельно сохраняются symbols. Нужно определить поддерживаемую Linux runtime baseline и проверять запуск на ней, а не лишь на машине разработчика. Native dependencies, выбранный CRT и GPU driver остаются частью проверки; C++ gameplay статически линкуется в Player, но это не устраняет все динамические системные зависимости.

История: четыре варианта стека, рассмотренные 17.09.2026

Следующие A–D сохраняют аргументы раннего сравнения. 18.09 выбран C++ с первым gameplay на C++, а Lua добавляется затем; остальные языковые варианты не являются текущими финалистами.

A. C++ core, SDL3, Vulkan, Lua gameplay, CMake/Ninja. Наиболее прямой кандидат для исследований GPU-driven rendering, visibility buffer и streaming: renderer и runtime находятся в одном native окружении, границы памяти и GPU synchronisation видны разработчику. SDL3 даёт окна/input и создание Vulkan surface; он не заменяет renderer. SDL_Vulkan_CreateSurface. Lua встраивается через официальный C API; игровые объекты следует представлять проверяемыми handles, а bulk-работу оставлять core. Lua: C API. Цена — собственные metadata/reflection, Inspector bindings, диагностика и управление временем жизни. Hot reload Lua требует миграции игрового состояния; hot reload произвольного C++ core не обещать. Первая версия может просто перезапускать player, сохраняя editor.

B. C++ core/renderer плюс C# gameplay и .NET host. Подходит, если native graphics нужен вместе с C# API для авторов игры. Но это не «вариант A с бесплатной заменой Lua»: появляются runtime hosting, managed/native lifetime, генерация bindings, загрузка игровых assemblies и отдельная проверка упаковки. Microsoft описывает nethost/hostfxr для C++ host и отдельно оговаривает framework-dependent модель этих hosting API; self-contained managed apps рассматриваются как самостоятельные executables. Microsoft: custom .NET host. Поэтому обещание автономной доставки такого native host нужно подтвердить выбранной схемой размещения runtime на чистых машинах. Это разумный запасной вариант, но для малого проекта его сложнее довести до цельного UX, чем A или C.

C. C#/.NET core и editor, native renderer/physics через C ABI. Финалист, если скорость создания editor, типизированных command API, сериализации и игровой логики важнее прямого контроля всего native core. Начальный player — обычная self-contained .NET публикация под конкретный RID: она доставляет runtime вместе с игрой, не требуя установленного SDK/.NET у игрока. dotnet publish. Native renderer может быть Vulkan; выбор C# не предписывает графический API. Граница должна передавать массивы и команды пакетами, с явным ownership и стабильными handles, избегая тысяч мелких переходов на каждый объект/свойство.

NativeAOT здесь опция последующей shipping-оптимизации, не исходная обязанность. Он запрещает динамическую загрузку managed assemblies и runtime code generation, требует trimming; reflection/генерируемая сериализация нуждаются в проверке совместимости. Это не запрет native P/Invoke. NativeAOT limitations, native interop. JIT editor и AOT player могут существовать отдельно, но игровые типы и serialization metadata должны быть доступны AOT заранее. Начальный JIT player заметно уменьшает число одновременно решаемых задач.

D. Rust core, wgpu, Lua scripting, Cargo. Кандидат при сильном опыте команды в Rust: типы и ownership помогают оформлять runtime границы, Cargo организует зависимости и сборку. wgpu даёт native Vulkan/D3D12 backend и переносимый API. wgpu. Однако editor reflection, undo transactions, scene inheritance и скриптовые bindings всё равно придётся проектировать. Для C++ middleware потребуется FFI; перестройка сложного render graph под правила владения тоже инженерная работа. Rust не делает native зависимости автоматически переносимыми: Cargo отдельно настраивает target linker. Cargo target configuration. Без уже имеющегося опыта Rust это третий кандидат, а не способ бесплатно сократить трудоёмкость.

C# core не требует IL2CPP. Unity IL2CPP — конкретный backend Unity: managed IL преобразуется в C++, затем native compiler собирает результат вместе с его runtime; он устанавливается как модуль Unity. Использовать его как общедоступный компонент нашего движка в план не закладываем. Unity: IL2CPP.

Принят Vulkan; историческое сравнение с wgpu и граница платформенной сборки

Vulkan рационален для desktop graphics research: можно напрямую проектировать resource states, descriptors, memory allocation, indirect workloads и нужные extensions. Цена — больше собственного backend-кода, validation, синхронизации и испытаний на разных GPU. wgpu сокращает объём низкоуровневой обвязки и даёт несколько backend, но ставит их возможности в рамки своего API и версии. Эти решения независимы от языка core.

Неверно говорить, что современный wgpu вообще не имеет mesh shaders или ray tracing: текущая документация содержит EXPERIMENTAL_MESH_SHADER, EXPERIMENTAL_RAY_QUERY и другие экспериментальные features. Mesh shader support и пути компиляции различаются по backend; ray query указан как native Vulkan feature. wgpu Features. Для advanced renderer нужен короткий feature spike на конкретных GPU/драйверах: indirect count, descriptor indexing, нужные atomics, subgroup operations, timestamps и выбранный RT путь. Наличие флага в документации не равнозначно готовому переносимому backend. Для Faset выбран один backend — Vulkan 1.3; второй backend не входит в начальный план. Профили развития: Baseline — обычный raster для 2D/3D без обязательных RT/mesh shaders; GPU-driven — после проверки нужных indirect/descriptor/subgroup features; Advanced — после проверки конкретного RT/atomic/mesh пути. Профиль задаёт формат cooked assets и fallback; неподдерживаемая GPU получает объяснение до загрузки несовместимого pipeline. Конкретный минимум GPU/driver ещё надо выбрать и зафиксировать отдельным ADR.

Для надёжной первой поставки приняты два native build worker: Windows и Linux. Кнопка в editor может отправить job соответствующему worker, сохраняя единый UX. CMake поддерживает cross compilation через явно заданные compiler/toolchain/sysroot; это конфигурация, а не автоматическое получение всех SDK. CMake toolchains. NativeAOT официально не поддерживает cross-OS compilation, хотя допускает некоторые cross-architecture пары при наличии tools. Microsoft: AOT cross compilation. Даже готовый player template для другой ОС лишь упрощает упаковку: тест запуска всё равно нужен на целевой системе.

Реализация выбранного стека и проверка

Языковой конкурс завершён: C++ первым, Lua следующим модулем. Собственный retained UI использует общий AuthoringService; выбранные Vulkan/Slang/SDL3 и CMake/Ninja проверяются прототипом, а не считаются уже интегрированными. Подробный порядок — в PLAN.md.

Toolchain и linkage. Linux использует Clang с зафиксированными стандартной библиотекой и runtime baseline; Windows — clang-cl с выбранными Windows SDK/UCRT/VCRuntime и linker. LLVM не содержит автоматически весь Windows SDK. Editor DLL/SO требуют совпадающего SDK/build fingerprint; gameplay статически линкуется в Player. После правки C++: stop → build → restart; hot reload не обещается. Clang: Windows headers/libraries.

Schema export. Отдельный служебный executable связывается с C++ registrations и выдаёт schema manifest для Inspector/MCP authoring API. Он не запускает игровой мир, не содержит MCP и не входит в экспорт. Custom Inspector принадлежит editor module. Подробная граница — в 16.

Проверки выбранной реализации:

  • Создать 2D сцену со спрайтом и 3D сцену с материалом, светом и физикой; одну authoring-правку выполнить руками, вторую через editor MCP, проверить общий Undo и сохранение JSON со стабильными IDs.
  • Упаковать сцены в development/release Player с бинарными cooked assets для Windows/Linux; запустить без Editor, MCP, SDK и исходников. Проверить runtime libraries и отсутствие MCP targets/listeners, включая development Player.
  • Изменить texture, shader include и gameplay C++ по очереди; измерить cook/compile/schema export/package и точную invalidation chain. Сравнить cold, warm и no-op build без заранее обещанных чисел.
  • Отменить compiler/cook, вызвать ошибку shader/schema export, затем повторить сборку. Последняя успешная игра и схема остаются доступными; GUI/MCP получают одну editor-диагностику.
  • Проверить несовместимый editor plugin fingerprint, циклическую module dependency и ошибочную зависимость Player от Editor/MCP — получить понятный отказ.

Все перечисленные испытания ещё предстоят; построение документации не является испытанием движка.