Skip to content
yurii-litvinov edited this page Nov 29, 2011 · 3 revisions

QReal:Ubiq --- технология для генерации клиент-серверных приложений со сложной логикой, клиент которых работает на мобильных устройствах. Программирование ведётся в среде QReal на визуальном языке, описанном ниже, а в качестве целевой платформы для генерации используется Ubiq Mobile. Приложение состоит из клиентской и серверной части, обменивающихся между собой сообщениями. Клиентская часть, впрочем, обычно тоже работает на сервере, на мобильнике работает тонкий клиент, предоставляющий по сути удалённый рабочий стол к серверу.

См. http://www.secr.ru/2011/md/terekhov-presentation.pdf в качестве описания мотивировки, контекста и пробного примера.

См. QReal:Ubiq - мысли по развитию по поводу направлений дальнейшей работы.

Язык

Система изображается на трёх видах диаграмм.

  • Мастер-диаграмма (Ubiq Master Diagram) описывает главный класс серверного приложения. Сейчас эта диаграмма должна содержать ровно один элемент Master Node, в котором описано, откуда сервер получает сообщения и что нужно, чтобы их обработать. Такая диаграмма должна быть одна в проекте. Виды узлов на этой диаграмме:
    • Comment Link --- связь, которой соединяют комментарий с любым другим узлом.
    • Constant --- константа, описанная внутри класса сервера. Имеет имя, тип и значение. Может находиться только внутри Master Node.
    • Field --- поле класса сервера. Имеет имя, тип, значение по умолчанию, может быть статическим. Может находиться только внутри Master Node.
    • Handler --- описывает источник событий для сервера, имеет имя (которое может быть либо OnTcpIpMessage, что означает, что сервер может обрабатывать сообщения, полученные по TCP-IP, либо OnMailBoxMessage, что означает, что сервер может обрабатывать сообщения от другого процесса, более подробно в документации по Ubiq). Ещё содержит свойство MessageInputParameter --- параметр конструктора объекта-слушателя событий. Опять-таки, объяснить, что это значит, тут попыток не делается, см. пример и документацию по Ubiq. Может находиться только внутри Master Node. Для каждого должна быть диаграмма активностей, описывающая его поведение.
    • Master Diagram --- сама диаграмма, на ней всё это и должно рисоваться.
    • Master Node --- описание класса серверной части приложения. Может содержать в себе описания обработчиков событий, констант, полей, препроцессоров (узлы Handler, Constant, Field, Preprocessor), имеет свойства "имя" (имя класса сервера), initCode (произвольный код на C#, генерирующийся в конструктор), onTcpIpCloseHandler (произвольный код на C#, выполняющийся при закрытии TCP-IP-соединения). Последние два по-хорошему надо было сделать честными диаграммами активностей. Может быть провязан (через Connections) с диаграммами активностей с реализацией вспомогательных функций, которые сгенерируются как методы этого класса.
    • masterDiagramComment --- комментарий.
    • Preprocessor --- метод, вызываемый перед передачей сообщения обработчику, который может с этим сообщением что-то предварительно сделать. Имеет только один параметр --- имя, которое пока может быть только PreProcessTcpIpMessage (препроцессор для OnTcpIpMessage), соответственно, если препроцессор добавлен в класс, должна быть диаграмма активностей с таким же именем и его реализацией.
  • Диаграмма структур данных (Ubiq Data Structures), на ней описывается класс Message, предназначенный для обмена сообщениями между клиентом и сервером или сервером и другими процессами, а также другие классы, служащие данными. Такая диаграмма должна быть одна в проекте. Узлы на этой диаграмме:
    • comment --- комментарий.
    • Comment Link --- связь, которой комментарий цепляется к любому другому узлу.
    • Custom Class --- произвольный класс, содержащий данные. Имеет имя (которое генерируется в имя класса), содержит в себе поля (типа Field).
    • Data Structures Diagram --- сама диаграмма.
    • Enum Element --- константа, выглядящая как элемент енума. Имеет имя и значение. Может лежать в Message Codes и Error Codes, будет сгенерирована как константа внутри класса Message.
    • Error Codes --- описание кодов ошибок, используемых в классе Message. Должен быть один на диаграмме. Содержит в себе элементы Enum Element
    • Field --- произвольное поле класса Message или произвольного класса (может лежать в Message Class или Custom Class), генерируется в проперти соответствующего класса. Имеет имя (имя проперти), значение по умолчанию, тип, может быть сериализуемым/несериализуемым, для int-овых полей работает свойство serializeAsShort, если оно выставлено, поле сериализуется так, будто оно short, генерируется преобразование типов.
    • Message Class --- описание класса Message, должен быть один на диаграмме. Содержит в себе только поля (элементы Field).
    • Message Codes --- описание кодов кодов сообщения, используемых в классе Message. Должен быть один на диаграмме. Содержит в себе элементы Enum Element
  • Диаграмма активностей (Ubiq Activity Diagram), на таких диаграммах задаётся поведение обработчика, метода, препроцессора и т.д. Их может быть много (по числу обработчиков). Содержание несколько разнится в зависимости от вида диаграммы --- диаграмма активностей для обработчика, диаграмма активностей для препроцессора, вспомогательной функции. Диаграмма для обработчика обычно состоит из нескольких цепочек операторов, начинающихся с узла HandlerStart, препроцессор начинается с Initial Node, функция --- с Function Signature. Узлы, используемые на всех видах диаграммы активностей:
    • Action --- просто действие, имеет свойство "имя", которое должно содержать код на C#, который будет просто скопирован в результат генерации.
    • Activity Diagram --- сама диаграмма.
    • Activity Final Node --- завершающий узел, означает конец цепочки операторов, ни во что не генерируется. Полезен, например, для рисования пустой ветки else, в остальных случаях необязателен.
    • Actual Parameter --- фактический параметр, передаваемый в функцию при вызове. Имеет имя, которое должно быть C#-кодом, подставляемым вместо параметра при вызове функции, может содержаться только в узле Function Call.
    • Comment --- комментарий
    • Comment Link --- связь, которой комментарий цепляется к любому другому узлу.
    • Control Flow --- направленная связь, означающая передачу управления от оператора к оператору. Имеет свойство guard, используемое при генерации Decision Node-ов.
    • Decision Node --- оператор "if". Должен иметь ровно две исходящие связи, ровно у одной из которых свойство guard не пусто. Обе ветки должны сходиться либо на одном Merge Node, либо на одном Activity Final Node, внутри могут быть свои Decision Node-ы. Свойство guard становится условием if-а.
    • Formal Parameter --- формальный параметр, описываемый в заголовке вспомогательной функции. Может содержаться только в узле Function Signature. Имеет имя и тип.
    • Function Call --- вызов функции. Имеет имя, которое должно совпадать с именем диаграммы активностей, реализующей вызываемую функцию. Содержит набор параметров (узлов Actual Parameter), и 0 или один узел Return Value, показывающий, куда положить результат вызова функции.
    • Function Signature --- описание вспомогательной функции, с него начинается цепочка операторов диаграммы активностей для вспомогательной функции. Имеет имя (должно совпадать с именем диаграммы), и тип возвращаемого значения. Может содержать в себе формальные параметры (типа Formal Parameter).
    • Handler Start --- начало обработчика сообщения. С него начинается цепочка операторов на диаграмме активностей обработчика. Имеет имя, которое должно совпадать с именем константы - типа сообщения, описанного на диаграмме структур данных.
    • Initial Node --- начальный узел, с него начинается цепочка операторов диаграммы активностей препроцессора.
    • Merge Node --- точка слияния двух веток выполнения оператора if.
    • Package --- вспомогательный узел, предназначенный для организации диаграмм активностей в связанные блоки в логической модели. Семантической нагрузки не несёт.
    • Return --- возврат значения из вспомогательной функции, генерируется в оператор return C#. Равносилен узлу Action с return <возвращаемое значение>;
    • Return Value --- куда положить возвращаемое функцией значение, может содержаться только в узле Function Call. Имеет имя (код на C#, обычно имя переменной), и тип. Если тип не пуст, генерируется объявление переменной для хранения результата, если пуст, считается, что переменная уже объявлена.

Использование

Чтобы начать пользоваться технологией, надо собрать проектный файл qrealUbiq.pro, должно собраться два плагина --- ubiqEditor и ubiqGenerator. После этого надо скопировать все файлы из папки plugins\ubiq\ubiqEditor\images в папку bin\images\iconset1\images, и все фалы из папки plugins\ubiq\ubiqGenerator\templates в bin\templates. Потом можно открыть пример из models\ubiqExample\ubiq.qrs и посмотреть, как он устроен. Этот пример генерирует серверную часть для приложения, транслирующего с каким-то интервалом картинки с видеокамер на мобильники. Сервер получает сообщения от видеокамер (например, готова к работе/выключена) и от пользователей (начать трансляцию с такой-то камеры, увеличить/уменьшить временной интервал между картинками и т.д.). Можно нажать на кнопку generate и получить в папке bin/output три сгенерированных файла --- DeviceDispatcher.cs, Message.cs, DevRecord.cs. Если бы у нас был solution с серверной частью приложения Ubiq, их можно было бы подложить в папку и собрать.

Внутреннее устройство

Генератор реализован по схеме шаблонной генерации, механизм работы которой таков --- есть исходник на C#, параметризуемые по диаграммам мета которого помечены местами для вставки, например @@Properties@@. Генератор читает файл целиком, генерирует код, подставляемый в определённое место вставки, потом производит текстуальную замену, например, меняя @@Properties@@ на int Field { get; set; }. В процессе генерации подставляемого кода тоже могут использоваться вспомогательные шаблоны, описывающие мелкие синтаксические конструкции, например,

@@Property@@
        public @@Type@@ @@Name@@ { get; set; }

Места для вставок внутри вспомогательного шаблона заполняются так же, как и в больших шаблонах, каждое такое место захардкожено в генераторе, он что-то генерирует и текстуально вставляет на его место.

За загрузку вспомогательных шаблонов и прочие вспомогательные функции типа создания выходных файлов отвечает класс AbstractGenerator, от которого наследуются генераторы, по одному на каждый тип генерируемых файлов, всего три. MessageGenerator генерирует класс Message, который во многом общий для всех видов приложений под Ubiq, но может иметь какие-то дополнительные поля (под каждое из которых генерируется само объявление поля, код конструкторов, код сериализации и десериализации), разные коды типов сообщений и кодов ошибок, это всё задаётся на диаграмме типов данных. CustomClassGenerator генерирует произвольные классы-данные. DispatcherGenerator генерирует код класса серверной части по мастер-диаграмме и диаграммам активностей. Особого описания заслуживает несколько хакерское решение генерации if-ов --- алгоритм сначала генерирует цепочку операторов ветки then, останавливаясь на Merge Node или конце функции, затем генерируется ветка else по тем же правилам, если они остановились на одном узле, генерация продолжается с него, если нет, то это ошибка. Для слежения за тем, на каком операторе какая ветка остановилась, используется структура CodeBranchGenerationResult, описанная в том же классе DispatcherGenerator.

Кроме генераторов, есть ряд вспомогательных классов:

  • Customizer --- говорит GUI настройки плагина: заголовок главного окна и что надо показывать меню с провязками.
  • Generator --- имеет слот generate, который конфигурирует и запускает все три генератора.
  • NameNormalizer --- штука, преобразующая строки в правильные с точки зрения синтаксиса С# идентификаторы, наглый копипаст из как минимум qrxc.
  • UbiqGeneratorPlugin --- стандартный интерфейс тул-плагина, определяет действие "generate"

Заключение

В целом, генератор работает, и даже в каком-то смысле полезен --- для программирования таких приложений ничего не надо знать про ООП и связанные с ним конструкции языка C#, всё описывается в процедурном стиле, что весьма важно для людей, ООП не владеющих, к тому же генерация классов данных действительно удобна. Описание поведения, тем не менее, гораздо проще писать вручную в Visual Studio и не заморачиваться, поэтому над представлением императивных конструкций надо ещё хорошо подумать.

Генератор и язык сейчас находятся в стадии работающего прототипа. Обработки ошибок практически нет, если диаграммы некорректны, результат генерации неопределён. Есть известные недоделки и в самом языке, но пример models\ubiqExample\ubiq.qrs генерируется и работает, именно он был вживую показан на тренинге в Тампере.

Clone this wiki locally