Как работает компилятор Blueprint — Kismet Compiler в Unreal Engine 4

Статья является переводом странички с официальной документации UE4, которая на данный момент является удалённой. То есть, пока страничка снова не появится в документации (этого уже давно не происходит), данный контент является эксклюзивным и только на XYZ Media 🙂 .

Blueprint классы, так же, как и C++ классы, нужно скомпилировать, прежде чем использовать в игре. Чтобы запустить процесс компиляции, преобразование графов и свойств в класс, нужно нажать кнопку Compile в Blueprint editor.

У каждой ноды в blueprint есть свой обработчик называемый Node Handler’ом.

FKismetCompilerContext — Класс, выполняющий работу по компиляции. Для каждой компиляции создается новый экземпляр. Хранит ссылку на компилируемый blueprint класс.

FKismetFunctionContext — Содержит информацию для компиляции одной функции, например ссылку на связанный blueprint, свойства и сгенерированную UFunction.

FNodeHandlingFunctor — Вспомогательный класс, который обрабатывает один класс в компиляторе (синглтон). Содержит функции для регистрации пин-соединений и генерирования скомпилированных операторов (statemants).

FKismetCompiledStatement — Компилятор переводит ноды в набор скомпилированных операторов (statemants), которые бэкэнд (backend) переводит в операции с байт-кодом.

FKismetTerm — Термин. В графе — литерал, константа или ссылка на переменную. Каждое пин-соединение связано с одним из них. Вы также можете создавать свои собственные термины NodeHandlingFunctorдля временных переменных, промежуточных результатов и т.д.

Стандартный процесс (полной) компиляции Blueprint описан ниже:

Один и тот же UBlueprintGeneratedClass очищается и повторно используется снова и снова, так что указатели на класс не нужно фиксировать. CleanAndSanitizeClass() перемещает свойства и функции из класса в класс «корзину» во временном пакете, а затем очищает все данные в классе.

Компилятор выполняет иттерации по массиву NewVariables в blueprint, а также по некоторым другим местам (construction scripts и т.д.), Чтобы найти все UProperties, необходимые классу, а затем в функции CreateClassVariablesFromBlueprint() создает UProperties в области UClass.

Компилятор создает список функций для класса, обрабатывая события, обычные функции и предварительно компилирует их, то есть вызывает PrecompileFunction() для каждой обработанной функции и события.

Обработка событий

Обработка событий выполняется функцией CreateAndProcessUberGraph(). Она копирует все события в один большой, временный граф. Затем для каждой ноды события в blueprint создается заглушка функции(function stub), а для каждого события создается FKismetFunctionContext.

Обработка функций

Обработка обычных функций в blueprint выполняется функцией ProcessOneFunctionGraph() , которая дублирует каждую функцию во временный blueprint. FKismetFunctionContext так-же, создается для каждой функции.

Предварительная компиляция функций

Предварительная компиляция функций выполняется функцией PrecompileFunction() каждого FKismetFunctionContext. Эта функция выполняет следующие действия:

Планирует выполнение и рассчитывает зависимости данных.

Удаляет все незапланированные ноды, не зависящие от данных.

Запускает функцию RegisterNets(), которая находится на каждом обработчике ноды(Node Handler), для каждой ноды в функции.

Прошлое действие создает FKismetTerms для значений внутри функции.

Создает UFunction и связанные свойства.

Теперь, когда компилятор знает обо всех UProperties и UFunctions, он может сформировать класс. На этом этапе он, по сути, имеет заголовок класса — без заключительных флагов и метаданных — а также Стандартные свойства объекта класса (CDO).

Этот шаг состоит в создании объектов FKismetCompiledStatment для оставшихся нодов, что выполняется с помощью функций Compile(), а также — AppendStatementForNode() из Node Handler’а. Эта функция может создавать FKismetTerm при компиляции , если они используются только локально.

Чтобы завершить компиляцию класса, компилятор завершает флаги класса и распространяет флаги и метаданные из родительского класса, прежде чем, наконец, выполнить несколько заключительных проверок, чтобы убедиться, что при компиляции всё прошло хорошо.

Бэкенд преобразует набор операторов(statments) из контекста каждой функции в байт-код. Используются два бэкэнда:

FKismetCompilerVMBackend — преобразует FKCS в байт-код виртуальной машины UnrealScript, который затем сериализуется в массив скриптов функции.

FKismetCppBackend — выдаёт C ++ — подобный код, только для целей отладки.

Используя специальную функцию CopyPropertiesForUnrelatedObjects() , компилятор копирует значения из старого CDO класса в новый CDO (об этом, ещё раз, в следующем шаге). Свойства копируются с помощью сериализации с тегами, поэтому, если имена связаны, их следует правильно передавать. Компоненты CDO повторно инициализируются и соответствующим образом настраиваются на этом этапе.

Поскольку класс мог изменить размер, а свойства могли быть добавлены или удалены, компилятору необходимо повторно создать экземпляры всех объектов класса, которые были только что скомпилированы. Этот процесс использует TObjectIterator для поиска всех экземпляров класса, порождает новый, а затем использует функцию CopyPropertiesForUnrelatedObjects() для копирования из старого экземпляра в новый.

Дополнительные сведения можно смотреть в классе FBlueprintCompileReinstancer .

Исходный код компилятора

Комментарии: 0