В Quil 2.1.0 была добавлена поддержка middleware (я не знаю русского аналога этого термина, так что буду писать его по-английски). Middleware позволяет расширять скетч без раздувания собственно самого кода. Идея схожа с идеей middleware в ring: middleware - это обычная кложурная функция, которая принимает мапу с параметрами скетча и должна вернуть обновлённую мапу. Что мы можем делать в middleware? В принципе, всё, что душе угодно: можно изменять функции заданные пользователем, такие как draw
, setup
, mouse-pressed
и другие. В качестве примера мы создадим middleware, которое будет вращать скетч относительно центра.
Вращательное middleware
Работа у нашего middleware довольно проста: нужно заменить определённую пользователем draw
специальной вращательной draw
, которая будет делать 2 вещи: поворачивать скетч на какой-то угол, а потом вызывать оригинальную draw
. Как известно, код стоит больше тысячи слов, так что код (и тысяча слов):
; rotated-draw - вышеупомянутая вращательная функция draw
; она вращает скетч на определённый угол в зависимости от текущей итерации
; и потом вызывает исходную пользовательскую draw
; period - число итераций, за которую скетч повернётся на 360 градусов
(defn rotating-draw [period orig-draw]
(let [; вычислим угол для поворота используя удобную map-range функцию
angle (q/map-range (mod (q/frame-count) period)
0 period
0 q/TWO-PI)
center-x (/ (q/width) 2)
center-h (/ (q/height) 2)]
; нам нужно повернуть скетч относительно центра
; так что сначала нужно перенести начало координат в центр
; и только потом вращать
(q/with-translation [center-x center-y]
; повернуть на заданный угол
(q/with-rotation [angle]
; вернём начало координат обратно в левый верхний угол
; наше middleware дожно быть незаметно для пользователя,
(q/with-translation [(- center-x) (- center-y)]
; вызываем пользовательскую draw
(orig-draw))))))
; функция, которая и определяет middelware
(defn rotate-me [options]
(let [; получаем пользовательскую draw или используем пустую ф-цию,
; если пользователь не указал draw
draw (:draw options (fn []))
period 200]
; заменяем пользовательскую draw
; нашей кастомной вращательной draw
(assoc options
:draw (partial rotating-draw period draw))))
; и используем!
(q/defsketch my-sketch
:draw draw
:size [500 500]
:middleware [rotate-me])
Теперь нужно проверить, работает ли. Вот скучное статичное изображение вложенных квадратов:
И оно начинает вращаться, после того, как мы применили rotate-me
middleware:
Предположим, что теперь нам понадобилось изменить период вращения. Мы конечно можем изменить rotate-me
функцию напрямую, но это не очень красиво, что если пользователь захочет изменить период? Не очень хочется, чтобы ему пришлось лезть внутрь middleware и что-то там изменять. К тому же, middleware могла быть подключена как сторонняя библиотека, и изменение внутренностей библиотек вообще задача не из простых. К счастью, нашу проблему можно решить проще: можно задать период в опциях и middleware его прочитает:
(defn rotate-me [options]
(let [draw (:draw options (fn []))
; берём период как значение :rotate-period
; если такого нет, то используем 200 по умолчанию
period (:rotate-period options 200)]
(assoc options
:draw (partial rotated-draw period draw))))
; и задаём период в скетче
(q/defsketch my-sketch
:draw draw
:size [200 200]
:middleware [rotate-me]
:rotate-period 100)
Я не буду ещё раз показывать вращающиеся квадраты, т.к. всё, что изменилось, - это скорость вращения. Вместо этого давайте повращаем не статическую картинку, а анимацию - шарик, отталкивающийся от стен. До вращения:
После:
Заключение
Middleware сильно упрощают написание расширений для Quil. Достаточно легко и шарить middleware с другими пользователями - всё, что нужно - это предоставить функцию, и другие могут её добавить в :middleware
и она будет работать!
Сам Quil использует middleware в своей реализации: например safe-fn
middleware, которое оборачивает все функции, полученные от пользователя так, что если они кидают исключения, то они не пробрасываются дальше, а просто выводятся в консоль, приостанавливая скетч на секунду. Это позволяет пользователю пофиксить свою функции на лету, без необходимости перезапускать скетч. Ещё один пример - deprecated-options
, которое проверяeт опции на наличие устаревших опций и выводит их в консоль, сообщая, что можно использовать вместо них. Вместе с middleware в Quil 2.1.0 был добавлен функциональный режим, который по сути является middleware.
Немного ссылок:
Если вы хотите поиграться с middleware, но не хватает идей, что бы такого реализовать - посмотрите статью на вики, указанную выше. Там есть несколько идей, можно взять их за основу.