В этой серии статей мы заглянем под капот движка QML и раскроем некоторые из особенностей его внутренней работы.Статьи основаны на Qt5 версия QtQuick, QtQuick 2.0.
Большинство людей знают, что каждый элемент в QML файле опирается на конкретный C++ класс. Когда QML файл загружен, движок QML как-то создает один C++ объект для всех элементов в файле. В этом посте мы рассмотрим, как движок QML переходит от чтения текстового файла, включающего полное дерево C++ объектов. В документации Qt присутствует раздел с обширным описанием взаимодействия QML и C++, прочтение которого стоит потраченного времени. В данной серии статей, я предполагаю что пользователь прочёл и понимает описанное в документации.
Пример
В этой статье мы будем использовать пример, который не делает ничего особенного, но включает в себя некоторые интересные части QML:
import QtQuick 2.0
Rectangle {
id: root
width: 360
height: width + 50
color: "lightsteelblue"
property alias myWidth: root.width
property int counter: 1
function reactToClick() {
root.counter++
}
Text {
id: text
text: qsTr("Hello World: " + counter)
anchors.centerIn: parent
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
reactToClick()
}
}
}
Этот файл состоит из трёх элементов: Rectangle, Text и MouseArea. Они соответствуют С++ классам QQuickRectangle, QQuickText и QQuickMouseArea. Кроме того, эти классы лишь экспортируются в QML, являются приватными и недоступны пользователям Qt напрямую из C++. Элементы рисуются на экране через OpenGL при помощи QML Scenegraph (Дерево сцены QML). Порядок отрисовки и обработки событий определяет QQuickView. QML-файл один в один соответствует генерируемому дереву объектов C++, которое можно проверить при помощи разработанного командой Qt KDAB инструмента, под названием Gammaray.
Как и ожидалось, классы QQuickMouseArea и QQuickText отображаться в дереве объектов. Но что такое QQuickRectangle_QML_0? Такого C++ класса просто не существует в исходниках Qt! Мы вернёмся к этому в более поздних постах, пока же будем просто считать, что был использован объект QQuickRectangle.
Пойдем дальше и запустим приложение в QML Profiler:
Как мы видим, львиная доля времени уходит на фазу создания (Creating). И совсем немножко — на стадию отрисовки (Painting), итог работы которой мы и видит на экране. Но что такое фаза компиляции (Compiling)? Подразумевается создание машинного или байт-кода? Давайте взглянем на алгоритм загрузки файла чуть глубже.
Этапы загрузки QML-файла
Загрузку QML файла можно разделить на 3 независимых этапа, которые мы рассмотрим в следующих разделах:
- Parsing (Разбор)
- Compiling (Компиляция)
- Creating (Создание)
Разбор
Прежде всего, QML файл анализируется, при помощи QQmlScript :: Parser. Большинство внутренних правил парсера генерируются автоматически из файла грамматики. Абстрактное синтаксическое дерево (AST) из нашего примера будет выглядеть так:
(Картинка была сгенерирована при помощи graphviz и этого патча)
Изначально AST работает на достаточно низком уровне и на следующем шаге своей работы, на более высоком уровне он превращается в структуры объектов (Objects), свойств (Properties) и значений (Values). Этим занимается AST::Visitor. На этом уровне, объекты (Objects) соответствуют QML элементам, а пары свойство/значение (Property/Value), такие как «color» и «lightsteelblue» соответствуют свойствам и значениям данных QML-элементов. Даже обработчики сигналов, вроде onClicked на данном этапе всего лишь пары свойство/значение, в данном случае значение — это тело Javascript функции.
Компиляция
В теории, имея структуру объектов, свойств и значений, мы уже можем создать ассоциированные с данными QML-элементами C++-объекты и присвоить им соответствующие значения. Тем не менее, объекты, свойства и значения все еще довольно сырые, и требуют некоторой пост-обработки, прежде чем смогут быть созданы C++ объекты. Пост-обработка осуществляется объектом QQmlCompiler, который объясняет что подразумевается под стадией «Компиляция» в нашем профайлере. Компилятор создает объект QQmlCompiledData для заданного QML файла.
Работа с QQmlCompiledData и создание C++ объектов из него происходит гораздо быстрее, чем работа напрямую с объектами, свойствами и значениями. Как было сказано выше, дерево объектов строится для каждого отдельного QML-файла. При многократном использовании QML-файла, к примеру Button.qml, который повсеместно используется в других QML-файлах, для него лишь единожды будет составлен объект QQmlCompiledData, который будет сохранён в памяти и использован для создания C++ объекта каждый раз при встрече с включением файла Button.qml во время разбора приложения. После этого наступает этап создания, который мы видели в окошке профайлера.
Подведем итог: Разбор каждого QML файла и его компиляция выполняется только один раз, после чего объект QQmlCompiledData используется для быстрого создания C++ объектов.
Создание
Я не буду вдаваться в подробности о QQmlCompiledData, но одна вещь могла бы привлечь ваше внимание: переменная-член «QByteArray bytecode». Инструкции по созданию С++ объектов и правильное присвоение значений его свойствам составлена в виде байт-кода, который затем интерпретируется интерпретатором байт-кода (от переводчика: интерпретатор интерпретировал-интерпретировал, да не… =) ). Байт-код содержит набор инструкций, и остальное содержимое в QQmlCompiledData используется только в качестве вспомогательных данных при выполнении инструкции.
На этапе создания, байт-код интерпретируется классом QQmlVME. Функция QQmlVME::run(), интерпретатора последовательно выполняет инструкции байткода, основываясь на большом switch-операторе. При запуске приложения с флагом QML_COMPILER_DUMP = 1, мы можем видеть отдельные инструкции байт-кода:
Index Operation Data1 Data2 Data3 Comments
-------------------------------------------------------------------------------
0 INIT 4 3 0 0
1 INIT_V8_BINDING 0 17
2 CREATECPP 0
3 STORE_META
4 SETID 0 "root"
5 BEGIN 16
6 STORE_INTEGER 45 1
7 STORE_COLOR 41 "ffb0c4de"
8 STORE_COMPILED_BINDING 10 2 0
9 STORE_DOUBLE 9 360
10 FETCH_QLIST 2
11 CREATE_SIMPLE 32
12 SETID 1 "text"
13 BEGIN 16
14 STORE_V8_BINDING 43 0 0
15 FETCH 19
16 STORE_COMPILED_BINDING 17 1 1
17 POP
18 STORE_OBJECT_QLIST
19 CREATE_SIMPLE 32
20 SETID 2 "mouseArea"
21 BEGIN 16
22 STORE_SIGNAL 42 2
23 FETCH 19
24 STORE_COMPILED_BINDING 16 0 1
25 POP
26 STORE_OBJECT_QLIST
27 POP_QLIST
28 SET_DEFAULT
29 DONE
-------------------------------------------------------------------------------
- CREATE_SIMPLE является наиболее важной инструкцией, он создает C++ объект, используя базу данных о зарегистрированных объектах: QQmlMetaType.
- STORE_INTEGER — инструкция присваивания целочисленного значения свойству.
- STORE_SIGNAL используется для создания связанного обработчика сигнала.
- STORE_ * _BINDING используется для создания привязки свойств. Подробнее о привязках мы поговорим в следующем посте этой серии.
- SETID, очевидно, задает идентификатор объекта, который не является обычным свойством.
VME имеет стек объектов, все инструкции типа STORE_* работают с объектом находящемся на верхнем уровне. FETCH помещает конкретный QObject на вершину стека, POP удаляет верхний объект. Все инструкции широко используют числовые индексы, например, инструкция STORE_COLOR записывает в свойство 41, которое является индексом мета-свойства object.dinary, целевого объекта QObject.
Подводя итог: После того, как QML файл будет скомпилирован, создание экземпляра его C++ объекта — это всего лишь вопрос выполнения байт-кода сгенерированного по переданным скомпилированным данным.
Заключение
В конце данной заметки мы познакомились с тем, как разбираются, обрабатываются и компилируются QML-файлы. А затем как с помощью VME создаются связанные с ними объекты. Я надеюсь, что вы вынесли для себя что-то интересное о движке QML.
Оставайтесь с нами до следующего поста, в котором мы рассмотрим, как работают привязки (Bindings) в QML.
От переводчика: Продолжать мне в том же духе или остановиться? Судя по скромному количеству комментариев к предыдущим переводам, тема не очень то интересна аудитории Хабра.
PS: И да, я знаю что переводчик из меня так себе, но я стараюсь как могу =) Ошибки и замечания — прошу в личку.
Автор: vitaly_KF