- PVSM.RU - https://www.pvsm.ru -
В этой [1] серии статей мы заглянем под капот движка QML и раскроем некоторые из особенностей его внутренней работы.Статьи основаны на Qt5 версия QtQuick, QtQuick 2.0.
Большинство людей знают, что каждый элемент в QML файле опирается на конкретный C++ класс. Когда QML файл загружен, движок QML как-то создает один C++ объект для всех элементов в файле. В этом посте мы рассмотрим, как движок QML переходит от чтения текстового файла, включающего полное дерево C++ объектов. В документации Qt присутствует раздел [2] с обширным описанием взаимодействия 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 [3], QQuickText [4] и QQuickMouseArea [5]. Кроме того, эти классы лишь экспортируются в QML, являются приватными и недоступны пользователям Qt напрямую из C++. Элементы рисуются на экране через OpenGL при помощи QML Scenegraph [6] (Дерево сцены QML). Порядок отрисовки и обработки событий определяет QQuickView [7]. QML-файл один в один соответствует генерируемому дереву объектов C++, которое можно проверить при помощи разработанного командой Qt KDAB инструмента, под названием Gammaray [8].

Как и ожидалось, классы QQuickMouseArea и QQuickText отображаться в дереве объектов. Но что такое QQuickRectangle_QML_0? Такого C++ класса просто не существует в исходниках Qt! Мы вернёмся к этому в более поздних постах, пока же будем просто считать, что был использован объект QQuickRectangle.
Пойдем дальше и запустим приложение в QML Profiler [9]:

Как мы видим, львиная доля времени уходит на фазу создания (Creating). И совсем немножко — на стадию отрисовки (Painting), итог работы которой мы и видит на экране. Но что такое фаза компиляции (Compiling)? Подразумевается создание машинного или байт-кода? Давайте взглянем на алгоритм загрузки файла чуть глубже.
Загрузку QML файла можно разделить на 3 независимых этапа, которые мы рассмотрим в следующих разделах:
Прежде всего, QML файл анализируется, при помощи QQmlScript :: Parser. Большинство внутренних правил парсера генерируются автоматически из файла грамматики. Абстрактное синтаксическое дерево (AST) из нашего примера будет выглядеть так:
[10]
(Картинка была сгенерирована при помощи graphviz и этого патча [11])
Изначально AST работает на достаточно низком уровне и на следующем шаге своей работы, на более высоком уровне он превращается в структуры объектов (Objects) [12], свойств (Properties) [13] и значений (Values) [14]. Этим занимается AST::Visitor [15]. На этом уровне, объекты (Objects) соответствуют QML элементам, а пары свойство/значение (Property/Value), такие как «color» и «lightsteelblue» соответствуют свойствам и значениям данных QML-элементов. Даже обработчики сигналов, вроде onClicked на данном этапе всего лишь пары свойство/значение, в данном случае значение — это тело Javascript функции.
В теории, имея структуру объектов, свойств и значений, мы уже можем создать ассоциированные с данными QML-элементами C++-объекты и присвоить им соответствующие значения. Тем не менее, объекты, свойства и значения все еще довольно сырые, и требуют некоторой пост-обработки, прежде чем смогут быть созданы C++ объекты. Пост-обработка осуществляется объектом QQmlCompiler [16], который объясняет что подразумевается под стадией «Компиляция» в нашем профайлере. Компилятор создает объект QQmlCompiledData для заданного QML файла.
Работа с QQmlCompiledData [17] и создание C++ объектов из него происходит гораздо быстрее, чем работа напрямую с объектами, свойствами и значениями. Как было сказано выше, дерево объектов строится для каждого отдельного QML-файла. При многократном использовании QML-файла, к примеру Button.qml, который повсеместно используется в других QML-файлах, для него лишь единожды будет составлен объект QQmlCompiledData, который будет сохранён в памяти и использован для создания C++ объекта каждый раз при встрече с включением файла Button.qml во время разбора приложения. После этого наступает этап создания, который мы видели в окошке профайлера.
Подведем итог: Разбор каждого QML файла и его компиляция выполняется только один раз, после чего объект QQmlCompiledData используется для быстрого создания C++ объектов.
Я не буду вдаваться в подробности о QQmlCompiledData, но одна вещь могла бы привлечь ваше внимание: переменная-член «QByteArray bytecode». Инструкции по созданию С++ объектов и правильное присвоение значений его свойствам составлена в виде байт-кода, который затем интерпретируется интерпретатором байт-кода (от переводчика: интерпретатор интерпретировал-интерпретировал, да не… =) ). Байт-код содержит набор инструкций, и остальное содержимое в QQmlCompiledData используется только в качестве вспомогательных данных при выполнении инструкции.
На этапе создания, байт-код интерпретируется классом QQmlVME [18]. Функция 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
-------------------------------------------------------------------------------
VME имеет стек объектов, все инструкции типа STORE_* работают с объектом находящемся на верхнем уровне. FETCH помещает конкретный QObject на вершину стека, POP удаляет верхний объект. Все инструкции широко используют числовые индексы, например, инструкция STORE_COLOR записывает в свойство 41, которое является индексом мета-свойства object.dinary, целевого объекта QObject.
Подводя итог: После того, как QML файл будет скомпилирован, создание экземпляра его C++ объекта — это всего лишь вопрос выполнения байт-кода сгенерированного по переданным скомпилированным данным.
В конце данной заметки мы познакомились с тем, как разбираются, обрабатываются и компилируются QML-файлы. А затем как с помощью VME создаются связанные с ними объекты. Я надеюсь, что вы вынесли для себя что-то интересное о движке QML.
Оставайтесь с нами до следующего поста, в котором мы рассмотрим, как работают привязки (Bindings) в QML.
От переводчика: Продолжать мне в том же духе или остановиться? Судя по скромному количеству комментариев к предыдущим [20] переводам [21], тема не очень то интересна аудитории Хабра.
PS: И да, я знаю что переводчик из меня так себе, но я стараюсь как могу =) Ошибки и замечания — прошу в личку.
Автор: vitaly_KF
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/13596
Ссылки в тексте:
[1] этой: http://www.kdab.com/category/blogs/qmlengineseries/
[2] раздел: http://doc-snapshot.qt-project.org/5.0/qtqml-main.html#qt-qml-module-documentation
[3] QQuickRectangle: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/quick/items/qquickrectangle_p.h#line136
[4] QQuickText: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/quick/items/qquicktext_p.h#line55
[5] QQuickMouseArea: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/quick/items/qquickmousearea_p.h#line129
[6] QML Scenegraph: http://labs.qt.nokia.com/2010/05/18/a-qt-scenegraph/
[7] QQuickView: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/quick/items/qquickview.h#line59
[8] Gammaray: http://www.kdab.com/kdab-products/gammaray/
[9] QML Profiler: http://labs.qt.nokia.com/2012/02/07/qml-profiler-update/
[10] Image: http://www.kdab.com/wp-content/uploads/stories/ast-big.png
[11] патча: http://www.kdab.com/~thomas/stuff/ast-graph.diff
[12] объектов (Objects): http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlscript_p.h#line299
[13] свойств (Properties): http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlscript_p.h#line230
[14] значений (Values): http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlscript_p.h#line183
[15] AST::Visitor: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlscript.cpp#line443
[16] QQmlCompiler: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlcompiler_p.h#line283
[17] QQmlCompiledData: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlcompiler_p.h#line79
[18] QQmlVME: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlvme_p.h
[19] QQmlMetaType: http://qt.gitorious.org/qt/qtdeclarative/blobs/master/src/qml/qml/qqmlmetatype_p.h
[20] предыдущим: http://habrahabr.ru/post/149979/
[21] переводам: http://habrahabr.ru/post/149909/
Нажмите здесь для печати.