Библиотека сайта 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. Такой же подход, позволяющий разрабатывать код в максимальном соответствии с принципом "однократной разработки и повсеместного запуска" без снижения производительности, используется для работы с самостоятельными вспомогательными функциями.
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