Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Processing.js

Глава 17 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: Processing.js
Автор: Mike Kamermans
Перевод: А.Панин

17.3. Компоненты кода

Библиотека Processing.js распространяется и разрабатывается в виде одного большого файла, но в плане архитектуры она может быть разделена на три отдельных компонента: 1) загрузчик, ответственный за преобразования исходного кода на языке Processing в исходный код на языке JavaScript с синтаксическими конструкциями из Processing.js и его исполнение, 2) статические функции, которые могут быть использованы всеми скетчами и 3) функции скетчей, которые должны ставиться в соответствие их отдельным экземплярам.

Загрузчик

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

Предварительная обработка кода

На этапе предварительной обработки кода директивы библиотеки Processing.js выделяются из кода и обрабатываются. Эти директивы могут быть разделены на два типа: установки и инструкции загрузки. Существует небольшое количество директив, оформленных в соответствии с философией "простой работы" и единственный тип установок, которые могут быть изменены авторами скетчей, относятся к взаимодействию со страницей. По умолчанию скетч будет выполняться в том случае, когда страница не находится в фокусе, но директива pauseOnBlur = true устанавливает параметры скетча таким образом, что он будет прерывать исполнение в моменты, когда страница, на которой располагается скетч, уходит из фокуса и восстанавливать работу в момент, когда страница вновь появляется в фокусе. Также по умолчанию клавиатурный ввод перенаправляется скетчу только в том случае, если он находится в фокусе. Это особенно важно в том случае, когда люди выполняют множество скетчей на одной и той же странице, так как клавиатурный ввод, предназначенный для одного из скетчей, не должен обрабатываться другим. Однако, эта функция может быть отключена с помощью директивы globalKeyEvents = true, в результате чего все события от клавиатуры будут передаваться каждому из скетчей, выполняемых на странице.

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

Преобразование кода

Компонент преобразования кода формирует ветви дерева абстрактного синтаксического анализа на основе таких элементов исходного кода, как выражения, методы, переменные, классы, и.т.д. После этого данное дерево абстрактного синтаксического анализа преобразуется в исходный код на языке JavaScript, из которого в процессе исполнения формируется эквивалентная скетчу программа. Этот преобразованный исходный код максимально использует экземпляр фреймворка Processing.js для установления отношений классов, причем классы из исходного кода на языке Processing преобразуются в прототипы языка JavaScript со специальными функциями для установления родительских классов и объединениями с функциями и переменными родительских классов.

Исполнение скетча

Финальным этапом процесса загрузки является исполнение скетча, которое начинается с установления того, завершена ли его предварительная загрузка и, в случае ее завершения, продолжается путем добавления скетча в список исполняемых приложений и генерации в рамках скетча характерного для языка JavaScript события с именем onLoad, таким образом, все обработчики событий скетча смогут выполнить необходимые действия. После этого начинается исполнение цепочки функций приложения Processing: сначала вызывается функция setup, а затем - draw и в том случае, если скетч исполняется в цикле, устанавливается интервал времени для вызовов функции draw, причем длительность этого интервала выбирается с учетом максимального приближения частоты вывода изображения к желаемой частоте кадров для скетча.

Статическая библиотека

Большая часть работы библиотеки Processing.js выполняется в рамках "статической библиотеки", которая предоставляет описания констант, универсальных функций и универсальных типов данных. Большая часть этих элементов выполняет двойную работу, так как они описаны в виде глобальных свойств, но при этом также на них приведены ссылки из экземпляров классов для ускорения исполнения кода. Такие глобальные константы, как коды ключей и обозначения цветов размещены в самом объекте языка Processing, их значения устанавливаются однократно, после чего используются ссылки из объектов, созданных с использованием конструктора языка Processing. Такой же подход, позволяющий разрабатывать код в максимальном соответствии с принципом "однократной разработки и повсеместного запуска" без снижения производительности, используется для работы с самостоятельными вспомогательными функциями.

Библиотека Processing.js должна поддерживать большое количество сложных типов данных не только для того, чтобы реализовывать поддержку типов из языка программирования Processing, но также и для осуществления внутренних операций. Эти типы данных также описаны в рамках конструктора Processing:
  • Char, внутренний объект, используемый для совместимости с некоторыми особенностями типа данных char языка программирования Java.
  • PShape,представляющий объекты контуров.
  • PShapeSVG, расширение для объектов PShape, созданное для представления контуров в формате SVG XML.
    Для функционирования объекта PShapeSVG мы реализовали код, осуществляющий формирование инструкций преобразования из формата SVG в формат данных элемента <canvas>. Так как язык программирования Processing не реализует полной поддержки формата SVG, мы создали код, не опираясь на функции каких-либо сторонних библиотек для работы с файлами SVG, что подразумевает возможность учета каждой строки кода, относящейся к функции импорта данных в формат SVG. Этот код всего лишь производит анализ необходимых данных и не занимает лишнее место из-за того, что не реализует неподдерживаемые в рамках классического языка Processing функции в четком соответствии со спецификацией.
  • XMLElement, объект документа XML.
    Для функционирования объекта XMLElement мы также реализовали наш собственный код на основе функций браузера, предназначенный для первоначальной загрузки элемента XML в структуру представления ветвей (Node) и последующего обхода этой структуры для формирования упрощенного объекта представления документа. Это также значит, что в библиотеке Processing.js нет неиспользуемого кода, занимающего место и потенциально приводящего к ошибкам после применения исправления, использующего некорректную функцию.
  • PMatrix2D и PMatrix3D, которые выполняют матричные операции в двумерных и трехмерных режимах.
  • PImage, который представляет ресурс изображения.
    На самом деле это просто обертка над объектом Image с некоторыми дополнительными функциями и свойствами для соответствия API объекта API, используемому языком программирования Processing.
  • PFont, представляющий ресурс шрифта.
    Объекта для работы со шрифтом Font, объявленного в рамках языка JavaScript, не существует (во всяком случае, на данный момент), поэтому вместо хранения шрифта в виде объекта, наша реализация PFont загружает шрифт средствами браузера, вычисляет его метрики на основе текста, выводимого браузером при использовании данного шрифта, после чего кэширует результирующий объект PFont. Для повышения скорости работы объект PFont содержит ссылку на канву, которая использовалась для установления свойств шрифта с целью использования в случае, когда должно быть вычислено значение свойства textWidth, но из-за того, что регистрация объектов PFont реализуется на основе пары имя/размер, в случае использования скетчем множества шрифтов разных размеров или вообще множества шрифтов, значительно увеличится потребление памяти. По этой причине каждый из объектов PFont будет очищать свою кэшированную канву и вместо ее использования вызывать стандартную функцию вычисления значения textWidth в том случае, если кэш значительно увеличивается в размере. Вторая стратегия сохранения памяти заключается в том, что в случае продолжения увеличения размера кэша после очистки каждой кэшированной канвы объекта PFont, кэширование шрифтов полностью отключается и изменения шрифтов скетча приводят к созданию новых впоследствии отбрасываемых объектов PFont в случае любого изменения имени шрифта, размера текста или самого текста.
  • DrawingShared, Drawing2D и Drawing3D, которые реализуют все функции для работы с графикой. Объект DrawingShared в наибольшей степени оказывает воздействие на скорость работы библиотеки Processing.js. Он устанавливает, работает ли скетч в двумерном или в трехмерном режиме вывода графики, после чего связывает все функции для работы с графикой либо с функциями объекта Drawing2D, либо с функциями объекта Drawing3D. После этого можно быть уверенным в том, что будет использован кратчайший путь кода для выполнения инструкций вывода графики, а также скетчи, работающие в двумерном режиме, не будут использовать функции для работы с трехмерной графикой и наоборот. Связывая функции обработки графики только с одним из двух наборов функций, мы повышаем скорость работы приложения, так как в этом случае не приходится в рамках каждой функции устанавливать режим вывода графики и выбирать путь исполнения кода, что в свою очередь позволяет нам сократить объем кода, не связывая функции, которые гарантированно не будут использоваться.
  • ArrayList, контейнер, эмулирующий поведение объекта ArrayList из Java.
  • HashMap, контейнер, эмулирующий поведение объекта HashMap из Java.
    ArrayList и HashMap в частности, являются специальными структурами данных при рассмотрении их реализации в рамках языка Java. Эти контейнеры функционируют с использованием концепций языка Java о эквивалентности и хэшировании, при этом все объекты языка Java содержат методы equals и hashCode, позволяющие хранить их в списках и таблицах.
    В случае использования контейнеров без хэширования, поиск объектов осуществляется на основе эквивалентности, а не идентичности. Следовательно, вызов list.remove(myobject) приводит к итерационному обходу списка в поисках элемента, для которого вызов метода element.equals(myobject) вместо равенства element == myobject возвращает истинное логическое значение. Из-за того, что все объекты должны реализовывать метод equals, мы реализовали "виртуальную функцию equals" на уровне языка JavaScript. Эта функция принимает два объекта в качестве аргументов, проверяет, реализует ли каждый из них свою функцию equals и в том случае, если это так, использует эти функции. Если же функции не реализованы, а переданные объекты являются примитивами, выполняется проверка эквивалентности примитивов. В противном случае эти объекты считаются неэквивалентными.
    В случае использования хэширующих контейнеров ситуация становится еще интереснее, так как хэширующие контейнеры работают по принципу деревьев, сформированных из ссылок. На самом деле контейнер состоит из динамически меняющегося количества списков, каждый из которых поставлен в соответствия определенному хэш-коду. Поиск объектов начинается с поиска контейнера, хэш-код которого совпадает с искомым кодом, после чего поиск объекта осуществляется в рамках найденного контейнера путем установления эквивалентности объектов. Так как все объекты языка Java реализуют метод hashCode, мы также разработали "виртуальную функцию вычисления хэш-кода", которая принимает единственный объект в качестве аргумента. Функция проверяет, реализует ли объект свою функцию hashCode и в том случае, если это так, использует эту функцию. В противном случае хэш-код вычисляется с помощью того же алгоритма хэширования, который используется языком Java.

Администрирование

Последней функцией библиотеки статического кода является поддержание в актуальном состоянии списка выполняющихся на данный момент на странице экземпляров скетчей. Список экземпляров скетчей формируется на основе идентификаторов канвы, используемой при загрузке каждого из скетчей, поэтому пользователи могут осуществить вызов Processing.getInstanceById('идентификатор_канвы') и получить ссылку на свой скетч для взаимодействия с ним.

Код экземпляров классов

Код экземпляров классов разрабатывается в форме объявлений p.functor = function(arg, _) для API языка Processing, и в форме p.constant = _ для переменных состояния скетча (где p является установленной ссылкой на скетч). Ни одно из этих объявлений не находится в отдельных блоках кода. Наоборот, код организован на основе функций, поэтому код, реализующий операции с экземплярами классов PShape размещен в непосредственной близости к объявлению объекта PShape и код для осуществления графических операций с участием экземпляров классов размещается недалеко или в самих объявлениях объектов Drawing2D и Drawing3D.

Для повышения быстродействия большая часть кода, который мог бы быть разработан в форме статического кода с оберткой в рамках экземпляра класса, реализуется исключительно в форме кода экземпляра класса. Например, функция lerpColor(c1, c2, ratio), которая устанавливает цвет, относящийся к процессу работы алгоритма линейной интерполяции двух цветов, объявляется в форме функции экземпляра класса. Вместо того, чтобы использовать вызов p.lerpColor(c1, c2, ratio), являющийся оберткой над какой-либо статической функцией Processing.lerpColor(c1, c2, ratio), в том случае, когда фактически никакая из других частей библиотеки Processing.js не использует функцию lerpColor, код будет выполняться быстрее в том случае, если он будет представлен в форме функции экземпляра класса. Хотя такой подход и увеличивает размер описания класса, большая часть функций, в отношении которых у нас могло бы возникнуть желание о преобразовании их в функции экземпляров классов вместо использования обертки для функции статической библиотеки, представлена функциями малого размера. Следовательно, увеличивая потребление памяти, мы создаем действительно быстрые пути исполнения кода. В то время, как для всего кода объекта Processing при запуске одномоментно резервируется участок памяти размером в 5 MB, необходимый для работы отдельных скетчей код занимает около 500 KB памяти.


Далее: Разработка библиотеки Processing.js