2. -
Часть I
Основные задачи асинхронного программирования
ActionScript: Event, EventDispatcher, ENTER_FRAME
JavaScript: onclick etc., EventDispatcher
jQuery: on, trigger, $.Deferred, $.Callbacks, $.when
Python: tornadoweb, IOLoop
3. -
Основные задачи
Создание интерактива для GUI
Клиент-серверное взаимодействие
Загрузка ассетов
Анимация
Взаимодействие между различными частями приложения при
помощи жестких однонаправленных связей (REQ-REP).
- Взаимодействие между различными частями приложения при
помощи слабых связей (PUB-SUB).
- Фоновые процессы
5. Event, EventDispatcher
- Отлично подходит для реализации интерактива в GUI.
Собственно, для этого и создавался.
- Клиент-серверные приложения, загрузка ассетов - не очень
удобно, нативный Loader мало кто использует. Чаще всего
BulkLoader, или велосипеды.
- Анимация, фоновые процессы - самый распространненный
подход - через ENTER_FRAME. Не очень хорошо подходит ни для
того, ни для другого. Для фоновых процессов можно
использовать LocalConnection, который работает на тех же
ивентах, или Worker. С анимацией все намного печальнее.
- Реализация однонаправленных жестких связей - очень плохо.
Нельзя явно описать в интерфейсе или классе-прототипе.
Оверхед.
- Слабые связи при помощи Event - нарушение инкапсуляции из
коробки.
6. Проблемы реализации.
- В нативном AS3 Event используется везде, хотя проектировался
главным образом для GUI. Всвязи с чем содержит много
избыточного функционала (target, currentTarget, bubbling,
capture).
- Проблемы с клонированием в кастомных ивентах GUI. Бажная
реализация самого процесса клонирования.
- Жесткое требование субкласса для
EventDispatcher.dispatchEvent()
- Использование "кнопочного" программирования флешерами
для любой задачи.
8. JavaScript. Event.
- Работа с ивентами реализованна практически также, как в
ActionScript. Только есть пережитки прошлого.
- Используется программистами в основном для интерактива и
для загрузки контента. Или я просто не знаю, поэтому сплю
спокойно.
- Нет ENTER_FRAME - для анимации и подобных задач чаще всего
используется setInterval. И это более правильно.
- В javascript гораздо более простая и удобная работа с
функциями: binding, нет глюков с анонимками.
elementOne.addEventListener('click', doSomething, false);
elementTwo.addEventListener('click', doSomething, false);
elementDyno.onclick = doSomething;
function doSomething () {
this.style.backgroundColor = '#cc0000';
}
9. jQuery.Event
- Расширение нативного функционала.
- Методы on, one - намного более удобный способ
регистрировать хендлеры, чем addEventListener.
- Метод trigger позволяет передавать в хендлер дополнительные
аргументы.
- Любой инстанс jQuery имеет в своем прототипе этот
функционал. Мне это не нравится.
function greet (event, silent) {
if (!silent) {
alert("Hello " + event.data.name);
}
}
$("button").on("click", {name: "Karl"}, greet);
$("button").on("click", ".addy", {name: "Addy"}, greet);
$("button").trigger("click", true); // nothing happens...
10. Backbone.Events
- Чистая, минималистическая реализация, без использования
нативных JS ивентов.
- Реализован как миксин. То есть может расширять любой объект
или его прототип.
- Метод on позволяет подписаться на "иерархические" ивенты, а
также на несколько ивентов сразу.
- listenTo и stopListening позволяют держать все хендлеры в
текущем объекте, в том числе хендлеры для внешних объектов
и потом быстро их чистить.
- trigger может посылать несколько ивентов сразу. Как и в jQuery
есть возможность передавать аргументы в хендлер.
12. jQuery.Callbacks
Хороший пример реализации однонаправленной жесткой связи
между частями приложения.
var foo = function (value) {
console.log("foo:" + value);
};
var callbacks = $.Callbacks();
callbacks.add(foo);
callbacks.fire("hello");
callbacks.fire("world");
var bar = function( value ){
console.log( "bar:" + value );
};
callbacks.add(bar);
callbacks.fire("hello again");
http://jsfiddle.net/DZNHU/
13. jQuery.Deferred, jQuery.when.
Класс для реализации однонаправленных жестких связей между
приложением и асинхронным процессом. Описывает основные
состояния процесса, и дает возможность приложению
регистрировать колбеки для каждого из состояний. Позволяет
создавать сложные процессы, за счет чейнинга. В jQuery
предлагается использовать для реализации клиент-серверных
взаимодействий - все ajax запросы возвращают Deferred. Можно
использовать для реализации любых "процессов".
15. var checkRequest = function (a1, a2) {
var data = a1[0] + a2[0];
if (/Whip It/.test(data)) {
alert( "We got what we came for!" );
}
}
$.when(ajax("/page1.php"), ajax("/page2.php"))
.done(function (a1, a2) {
// a1 and a2 are arguments resolved for
// the page1 and page2 ajax requests, respectively.
// Each argument is an array
// with the following structure: [data, statusText, jqXHR]
// a1[0] = "Whip", a2[0] = " It"
checkRequest(a1, a2);
}); // Alerts after both pages loaded.
// Nasty hack
$.when(ajax("/page1.php"), [" It"]])
.done(checkRequest); // Alerts when page1 is loaded.
17. tornado.concurrent.Future
Инкапсулирует результат асинхронной операции. Ближайший
аналог - $.Callbacks. Отличие - $.Callbacks не имеет никакой
связи с результатом. Привязка ни к объекту, а к процессу и его
результату. Типичный подход для асинхронного
программирования на сервере.
Описание интерфейса
- result() - возвращает результат операции, или выбрасывает
ексепшен.
- exception() - возвращает исключение, как объект.
- add_done_callback(fn) - регистрирует колбек, который
выполнится когда процесс будет завершен. С экземпляром
Future в качестве аргумента.
- done() - возвращает True, если процес завершен.
18. tornado.ioloop
Асинхронные процессы на сервере как правило связанны с
сокетами. Сокет это объект, похожий на файл, который может
находится в двух состояниях: запись и чтение. I/O Loop
использует функцию ядра OS (epoll в Linux) для того, чтобы
определить когда сокет изменит свое состояние.
19. Описание интерфейса
(не полное)
- IOLoop.add_handler(fd, handler, events) - регистрирует колбек
handler, который будет выполнен, когда сокет с дескриптором fd
"сгенерирует" один из ивентов в списке events.
- IOLoop.add_callback(callback, *args, **kwargs) - запускает
колбек на следующей итерации IOLoop.
- IOLoop.add_future(future, callback) - запускает колбек на
следующей итерации IOLoop, после того, как future будет
выполнено.
- IOLoop.add_timeout(deadline, callback) - запускает кобек после
наступления deadline.
20. -
Часть II
Dzyga
DispatcherProxy - работа с ивентами
Promise, Once - работа с колбеками
Task - абстракция для асинхронных процессов
Loop, LoopTask - менеджер колбеков для ENTER_FRAME, реализация
фоновых процессов, инкапсуляция интерактива для GUI.
22. org.dzyga.event.DispatcherProxy
- Расширение функционала EventDispatcher.
- Облегчение работы с нативными флешовыми EventDispatcher.
- Привязка добавленных к флешовому EventDispatcher
обработчиков событий к конкретному классу с возможностью
быстрого удаления зарегистрированных обработчиков.
- Улучшение производительности, за счет группировки большого
числа обработчиков в очередь. Но в большинстве случаев это
будет работать, конечно, медленнее.
23. Краткое описание:
Дублирует функционал для добавления обработчиков событий а
также проксирует нативные методы. Реализует IEventDispatcher.
Для обработчиков добавляемых через DispatcherProxy создаются
экземпляры класса EventListener, которые затем сохраняются в
локальную для данного экземпляра коллекцию для обеспечения
лучших возможностей манипуляции обработчиком, чем в
нативном IEventDispatcher.
Позволяет добавлять аргументы в обработчики событий, биндить
их к другим объектам, а также регистрировать обработчики,
которые автоматически удаляются после первого вызова.
24. Описание интерфейса
Добавление обработчика к проксируемому диспетчеру:
function listen (
eventType:String, callback:Function, once:Boolean = false,
thisArg:* = null, argArray:Array = null):IDispatcherProxy;
Удаление обработчиков с проксируемого диспетчера:
function stopListening (
eventType:String = '',
callback:Function = null):IDispatcherProxy;
Добавление обработчика к любому диспетчеру:
function listenTo (
target:IEventDispatcher, eventType:String,
callback:Function, once:Boolean = false,
thisArg:* = null, argArray:Array = null):IDispatcherProxy;
25. Удаление обработчиков, зарегистрированных данным прокси с
одного или всех диспетчеров:
function stopListeningTo (
target:IEventDispatcher = null, eventType:String = '',
callback:Function = null):IDispatcherProxy;
Триггер для простых ивентов:
function triggerTo (
target:IEventDispatcher, eventType:String):IDispatcherProxy;
Проверка зарегистрированных обработчиков:
function isListeningTo (
target:IEventDispatcher = null, eventType:String = '',
callback:Function = null):Boolean;
26. Пример кода с синтаксическим
сахаром
dispatcher(d)
.listen(MouseEvent.CLICK, onClick)
.listen(MouseEvent.CLICK, onAnotherClick)
.listen(MouseEvent.ROLL_OVER, onOver)
.listen(MouseEvent.ROLL_OUT, onOut)
.listen(MouseEvent.MOUSE_OUT, onOut)
// Next callback will be removed after the first run.
.listen(
Event.ADDED_TO_STAGE, initView,
true, null, ['target init']);
dispatcher(d)
.trigger(MouseEvent.CLICK)
.trigger(MouseEvent.ROLL_OVER)
.trigger(Event.ADDED_TO_STAGE)
// Nothing happens there...
.trigger(Event.ADDED_TO_STAGE);
28. org.dzyga.callbacks.Promise
- Замена и расширение функционала EventDispatcher.
- Более явная реализация обработки асинхронных процессов promise можно объявлять свойством класса и/или интерфейса.
- Реализация жестких однонаправленных связей между частями
приложения: модуль добавляющий колбек в промис имеет
доступ к модулю обработчику, модуль обработчик в обратную
сторону - нет.
Краткое описание
Для добавляемых колбеков создаются инстансы класса Callback,
которые помещаются в сортированный список. При вызове
метода resolve все находящиеся в списке колбеки выполняются.
Аргументы, заданные при добавлении колбека добавляются в
конец списка аргументов, с которыми была вызван метод resolve.
29. Описание интерфейса
Добавление колбека:
function callbackRegister (
callback:Function, once:Boolean = false, thisArg:* = null,
argsArray:Array = null):IPromise;
Удаление колбека:
function callbackRemove (callback:Function = null):IPromise;
Запуск колбеков (выполнение обещания):
function resolve (... args):IPromise;
30. private function firstCallback (inc:int = 1):void {
_firstCounter += inc;
}
private function secondCallback (inc:int = 1):void {
_secondCounter += inc;
}
[Test]
public function testCallbackRegisterUniq ():void {
var promise:Promise = new Promise();
promise
.callbackRegister(firstCallback, false, null, [2])
.callbackRegister(firstCallback)
.callbackRegister(secondCallback)
.resolve();
// 3, if first callback added twice...
assertEquals(2, _firstCounter);
assertEquals(1, _secondCounter);
promise.clear();
}
31. org.dzyga.callbacks.Once
Простейший субкласс Promise. Если Once был хоть один раз
зарезолвлен, то все колбеки, добавляемые в будующем,
запускаются сразу же после добавления. Как и Promise может быть
зарезолвлен сколько угодно раз. Предусмотрена возможность
повторного использования, при помощи метода reset().
32. org.dzyga.callbacks.Task
- Реализация асинхронных процессов
- Инкапсуляция переменных и методов связанных с процессом.
- Отделение функционала процесса от объектов, учавствующих в
процессе. В теории, может вести к большим возможностям
повторного использования кода.
- Организация жестких однонаправленных связей между
модулями.
33. Краткое описание
Cодержит промисы, которые резолвятся на разных стадиях
процесса, и куда, внешние модули могут добавлять колбеки:
-
started - резолвится при запуске асинхронного процесса.
done - резолвится при успешном завершении процесса.
failed - резолвится при неправильном завершении процесса.
finished - резолвится при любом завершении процесса, сразу
после done или failed.
- progress - резолвится на промежуточных стадиях процесса.
Кроме промисов содержит еще поле state, т.е. Task является
стейт-машиной.
34. Описание интерфейса
Запуск процесса, резолв промиса started с переданными
аргументами:
function start (... args):ITask;
Переключение состояний процесса и резолв соответствующих
промисов:
function notify (... args):ITask;
function resolve (... args):ITask;
function reject (... args):ITask;
Очистка всех промисов и сброс состояния процесса:
function clear ():ITask;
Возвращает одно из значений: TaskState.STARTED,
TaskState.RESOLVED, TaskState.REJECTED или TaskState.IDLE:
function get state ():String;
36. -
org.dzyga.eventloop.Loop
Реализация фоновых процессов.
Анимация.
Обработка интерактива (замена MOUSE_MOVE)
Ограничение количества запускаемых каждый фрейм
обработчиков, с целью поддерживать заданный fps.
Краткое описание:
Позволяет запускать колбеки каждый фрейм, в том же фрейме, но
в другом стеке, если есть возможность, или по таймауту.
Поддерживает приоритеты. Если очередь колбеков выполняется
дольше зависящего от fps времени - выполнение колбеков в этом
фрейме прекращается, что позволяет поддерживать нужный фпс,
жертвуя, например временем обработки фоновых процессов.
37. Описание интерфейса
Добавление колбека, который должен запускаться каждый фрейм:
function frameEnterCall (
callback:Function, priority:uint = 1,
thisArg:* = null, argsArray:Array = null):ILoopCallback;
Добавление колбека, который должен запуститься в ближайшее
возможное время:
function call (
callback:Function, priority:uint = 1,
thisArg:* = null, argsArray:Array = null):ILoopCallback;
Добавление колбека по таймауту:
function delayedCall (
callback:Function, delay:Number = 0, priority:uint = 1,
thisArg:* = null, argsArray:Array = null):ILoopCallback;
38. org.dzyga.eventlooop.LoopTask
Служит для реализации задач, требующих значительного
времени для выполнения: например - препроцессинг анимации.
Метод run экземпляра класса будет выполнятся при помощи
метода Loop.call каждый фрейм, столько раз, сколько позволяет
время. Можно задавать приоритет выполнения колбека. Это
субкласс Task.
org.dzyga.eventlooop.FrameEnterTask
Служит для реализации процессов, которые отвечают за
обновление экрана (анимация) или за отслеживание
происходящего на экране (мышь, положение движущихся
объектов). Также как LoopTask должен содержать метод run,
который при помощи метода Loop.frameEnterCall будет
запускаться каждый фрейм.
39. Исходник LoopTask
(оптимизирован для просмотра)
public function get callback ():Function {
if (hasOwnProperty('run')) {
return this['run'];
} else {
return FunctionUtils.identity;
}
}
protected function loopCallbackRegister ():void {
if (!_loopCallback) {
_loopCallback = _loop.call(
runner, priority);
}
}
protected function runner ():void {
if (state == TaskState.STARTED) {
_result = callback.apply(
thisArg, argsArray);
}
if (state == TaskState.STARTED) {
loopCallbackRegister();
}
}
protected function loopCallbackCancel ():void {
if (_loopCallback) {
_loopCallback.cancel();
_loopCallback = null;
}
}
protected function loopCallbackRemove ():void {
if (_loopCallback) {
_loop.callbackRemove(_loopCallback);
_loopCallback = null;
}
}
override public function start (...args):ITask {
loopCallbackRegister();
return super.start.apply(this, args);
}
override public function resolve (...args):ITask {
loopCallbackCancel();
return super.resolve.apply(this, args);
}
override public function reject (...args):ITask {
loopCallbackCancel();
return super.reject.apply(this, args);
}
override public function clear ():ITask {
loopCallbackRemove();
return super.clear();
}
40. Пример реализации интерактива
function loopCallbackRegister ():void {
if (_trackingActive) {
super.loopCallbackRegister();
}
}
function trackingEnable (e:Event = null):void {
_trackingActive = true;
loopCallbackRegister();
}
function trackingDisable (e:Event = null):void {
_trackingActive = false;
loopCallbackCancel();
}
function get trackingActive ():Boolean {
return _trackingActive;
}
function run ():ICell {
var cell:LevelCell = cellMap.cellGet(
_view.mouseX, _view.mouseY);
if (cell != _cell) {
_cell = cell;
notify(_cell);
}
return _cell;
}
function start (...args):ITask {
_trackingActive = _view.active;
_view
.listen(
LevelView.LEVEL_ACTIVATE_EVENT,
trackingEnable)
.listen(
LevelView.LEVEL_DEACTIVATE_EVENT,
trackingDisable);
return super.start.apply(this, args);
}
function stop ():void {
_view
.stopListening(
LevelView.LEVEL_DEACTIVATE_EVENT,
trackingDisable)
.stopListening(
LevelView.LEVEL_ACTIVATE_EVENT,
trackingEnable);
_trackingActive = false;
}