Конвеер Потапенко
Запись от expl размещена 07.02.2012 в 01:37
Цели и задачи
В данном контексте КОНВЕЕР - это механизм, выполняющий последовательность асинхронных действий в заданном порядке. Он универсален и может быть реализован в виде класса.
"Изобрел" Евгений Потапенко, достаточно доходчиво объяснял зачем нужен конвеер и как им пользоваться. А потом его сайт канул в лету. Но на форуме (да и на практике) всплывают задачи, которые лучше решать именно конвеером. Поэтому данное чудо инженерной мысли не должно исчезнуть вслед за сайтом!
Решает задачи:
- запуск следующей асинхронной или синхронной задачи после завершения предыдущей в порядке очереди;
- тривиальная смена порядка асинхронных задач;
- простое добавление следующих асинхронных задач
Алтернативы:
- класс, реализующий последовательное выполнение асинхронных команд;
- просто запуск следующего задания в слушателе завершения предыдущего (запутанность кода при этом подходе конвеер как раз и снижает).
Ограничения:
- непонятны ни стратения не приимущества использования конвеера в нелинейных динамически меняющихся последовательностях асинхронных операций
(но в то же время можно использовать в качестве асинхронной очереди в каком-нибудь исполнителе запросов к серверу - упростит код при определенных условиях);
- не стоит использовать для анимации - с этим лучше справляются твиновые движки (и они более гибко подходят к вопросу прерывания анимаций), но можно для запуска последовательности анимаций, проигрываемых твиновым движком, если Вам кажется что так удобнее или в специфических случаях
Если же асинхронные операции не выстраиваются в цепочку, а подчиняются весьма витиеватой логике, то надо использовать что-то из этого (все зависит от ситуации):
- тупое жонглирование колбеками с подписками/отписками;
- отказ от подписки/отписки - переход на вызов функций update() через равные промежутки времени;
- выделение состояния плюс кучи свитчей, обрабатывающих события в зависимости от состояния;
- паттерн состояние;
- машину состояний;
- обработку колбеков через декоратор;
- цепочку обязанностей;
...
и еще много решений, которые Вы знаете плюс их комбинации.
Почему не рассматриваются примеры на оригинальном конвеере
Сначала хотел разобрать подход на оригинале, но оригинал сложно найти

Если Вы хотите Singleton, чтобы не тянуть ссылки, то не хотите этого, а присвойте экземпляр Conveyour какому-нибудь публичному статическому полю (или сделайте статичный геттер с ленивой инициализацией в каком-нибудь классе) - и будет вам счастье. Где-то синглтон может и хорошо, а здесь ограничивать численность экземпляров нельзя.
Еще сам оригинал был слшиком вариативен, например:
1 параметр - делаем одно 2 - делаем другое, а если первый аргумент строка, то... Зачем оно нам нужно.
Поэтому напишем свой конвеер, с преферансом и поэтессами. Версия будет урезанной, но не менее практичной. Всё равно использование фитч оригинала всеми возможными способами только запутает проект, а нам нужна предсказуемая работа и прозрачность кода.
Простое использование конвеера
Принцип добавления асинхронной задачи прост как все гениальное:
- Добавляем функцию (никаких создаваний классов с перегрузкой методов).
- Внутри функции приостанавливаем конвеер.
- Дожидаемся завершения операции.
- Возобновляем работу конвеера - с этого момента пошла выполняться следующая операция (какая? - внути данной операции мы не знаем)
Допустим, нужно подождать numFrames кадров, а затем напечатать "Готово". Просто делаем асинхронную задачу "подождать numFrames кадров" и синхронную задачу "напечатать Готово", добавляем их в конвеер и запускаем.
//{ waitFrames - асинхронная задача private var _numFrames:int; private function waitFrames(numFrames:):void { _conveyor.stop();//<-- останавливаем _numFrames = numFrames; addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { trace("Осталось кадров: " + _numFrames); _numFrames--; if (_numFrames <= 0) { removeEventListener(Event.ENTER_FRAME, onEnterFrame); _conveyor.play();//<-- всё, отработали, запускаем } } //} private function printComplete():void { // Эта задача синхронная, поэтому конвеер не тормозим trace("Готово"); } // Инициализация сценария и запуск на выполнение _conveyor = new Conveyor(); _conveyor.add(waitFrames, 2); _conveyor.add(printComplete); _conveyor.run(); /* Вывод: Осталось кадров: 2 Осталось кадров: 1 Готово */
_conveyor = new Conveyor(); _conveyor.add(waitFrames, 1); _conveyor.add(printComplete); _conveyor.add(waitFrames, 2); _conveyor.add(printComplete); _conveyor.run(); /* Вывод: Осталось кадров: 1 Готово Осталось кадров: 2 Осталось кадров: 1 Готово */
Этоу самый сложный момент. Если не поймёте - ничего страшного - это не часто используется. Ещё, хоть это и громоздко, можно вместо вложенных списков создавать новый конвеер внутри асинхроной задачи и по завершению выполнения этого внутреннего конвеера завершать задачу.
Итак: Как быть если нужно добавить подзадачи внутри выполняющейся задачи? Т.е. как организовать задачу, состоящую из более мелких подзадач?
На самом деле тут 2 варианта:
1. Создать еще один конвеер (прямо вначале асинхронной операции), подписать на него колбек, завершающий асинхронную задачу (вызывающий play() главного конвеера) и напихать туда подзадач.
2. Использовать addInclude вместо add для добавления главной задачи.
Если же просто добавлять в конвеер задачи внутри подзадачи - то эффект будет немного другой - они окажутся в конце общего списка.
Разберем добавление с помощью addInclude на примере.
Нужно:
1. Загрузить список картинок
2. Последовательно загрузить картинки
3. Отобразить картинки
Тут фишка в том, что ни одно действие нельзя начать, не дождавшись завершения предыдущего.
Как это выглядит без ковеера:
loadList(); ... private static const LIST:String = "example1_list.xml"; private var _listLoader:URLLoader; private function loadList():void { _listLoader = new URLLoader(); _listLoader.addEventListener(Event.COMPLETE, onListLoadComplete); _listLoader.addEventListener(IOErrorEvent.IO_ERROR, onListLoadError); _listLoader.load(new URLRequest(LIST)); } private var _pictures:Array; private function onListLoadComplete(event:Event):void { _pictures = []; var xml:XML = new XML(_listLoader.data); for each (var picture:* in xml.picture) { _pictures.push(String(picture)); } _listLoader = null; loadPictures(); } private function onListLoadError(event:Event):void { _listLoader = null; trace("List loading failure"); } private var _pictureLoader:Loader; private var _loadingPictureIndex:int; private var _loadedPictures:Array; private function loadPictures():void { _loadingPictureIndex = 0; _loadedPictures = []; loadPicture(); } private function loadPicture():void { var picture:String = _pictures[_loadingPictureIndex]; _pictureLoader = new Loader(); _pictureLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, onPictureLoadComplete); _pictureLoader.addEventListener( IOErrorEvent.IO_ERROR, onPictureLoadError); _pictureLoader.load(new URLRequest(picture)); } private function onPictureLoadComplete(event:Event):void { _loadedPictures.push(_pictureLoader.content); _pictureLoader = null; _loadingPictureIndex++; if (_loadingPictureIndex >= _pictures.length) { placePictures(); } else { loadPicture(); } } private function onPictureLoadError(event:Event):void { _pictureLoader = null; trace("Picture loading error"); } private function placePictures():void { var left:Number = 0; for each (var picture:DisplayObject in _loadedPictures) { picture.x = left; addChild(picture); left += picture.width; } }
Плюс еще цепочка "получил сообщение о завершении" - запусти _конкретную_ следующую операцию - сложно менять порядок, а ошибиться просто. Плюс вызова функции placePictures не произойдет, если картинок нет в списке, а мало ли чего вы после него хотели сделать. Придется навесить еще немного условной логики. Не айс, короче.
Как это выглядит с конвеером:
_conveyor = new Conveyor(); _conveyor.add(loadList); _conveyor.addInclude(loadPictures); _conveyor.add(placePictures); _conveyor.play(); ... private static const LIST:String = "example1_list.xml"; private var _conveyor:Conveyor; private var _pictures:Array; private var _loadedPictures:Array; //{ loadList - асинхронная операция private var _listLoader:URLLoader; private function loadList():void { _conveyor.stop(); _listLoader = new URLLoader(); _listLoader.addEventListener(Event.COMPLETE, onListLoadComplete); _listLoader.addEventListener(IOErrorEvent.IO_ERROR, onListLoadError); _listLoader.load(new URLRequest(LIST)); } private function onListLoadComplete(event:Event):void { _pictures = []; var xml:XML = new XML(_listLoader.data); for each (var picture:* in xml.picture) { _pictures.push(String(picture)); } _listLoader = null; _conveyor.play(); } private function onListLoadError(event:Event):void { _listLoader = null; trace("List loading failure"); } //} private function loadPictures():void { _loadedPictures = []; for each (var picture:String in _pictures) { _conveyor.add(loadPicture, picture); } } //{ loadPicture - асинхронная операция private var _pictureLoader:Loader; private function loadPicture(picture:String):void { _conveyor.stop(); _pictureLoader = new Loader(); _pictureLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, onPictureLoadComplete); _pictureLoader.addEventListener( IOErrorEvent.IO_ERROR, onPictureLoadError); _pictureLoader.load(new URLRequest(picture)); } private function onPictureLoadComplete(event:Event):void { _loadedPictures.push(_pictureLoader.content); _pictureLoader = null; _conveyor.play(); } private function onPictureLoadError(event:Event):void { _pictureLoader = null; trace("Picture loading error"); } //} private function placePictures():void { var left:Number = 0; for each (var picture:DisplayObject in _loadedPictures) { picture.x = left; addChild(picture); left += picture.width; } }
Строчек здесь получилось столько же (даже немного больше), но код стал намного понятнее. Стало гораздо проще добавлять новые элементы для загрузки и труднее сломать класс. И если даже картинок окажется 0 - задача, добавленная после placePictures всё равно отработает
Что касается реализации addInclude - это отдельная гениальная идея с использованием "закладок". В нашем конвеере она точно такая же как у Потапенко, только делается переносом части списка за время 0(1), а не выкусыванием массива и его реверсом и склеиванием с другой частью. Но не будем о ней больше - букв и так много
Вобщем конвеер - это хороший инструмент, главное не переусердствовать с его применением и не пихать куда ни поподя
as3_potapenko_conveyor.zip
Всего комментариев 10
Комментарии
![]() ![]() |
|
Сорри, случайно поправил ваш комментарий, не знаю как откатить. @expl
|
|
Обновил(-а) expl 07.02.2012 в 11:19
|
![]() ![]() |
|
Инклюд одного и того же, как и простое добавление одного и того же метода логику не ломает, просто добавляется 2 раза. Впринципе можете подобавлять трейсов и убедиться.
Ну или так: package { import com.potapenko.remake.Conveyor; import flash.display.DisplayObject; import flash.display.Loader; import flash.display.Sprite; import flash.events.Event; import flash.events.IOErrorEvent; import flash.net.URLLoader; import flash.net.URLRequest; public class Example1 extends Sprite { private static const LIST:String = "example1_list.xml"; private var _conveyor:Conveyor; public function Example1() { _conveyor = new Conveyor(); _conveyor.add(loadList); _conveyor.addInclude(loadPictures); _conveyor.add(placePictures, 0); _conveyor.add(loadList); _conveyor.addInclude(loadPictures); _conveyor.add(placePictures, 100); _conveyor.play(); } private var _pictures:Array; private var _loadedPictures:Array; //{ loadList - асинхронная задача private var _listLoader:URLLoader; private function loadList():void { _conveyor.stop(); _listLoader = new URLLoader(); _listLoader.addEventListener(Event.COMPLETE, onListLoadComplete); _listLoader.addEventListener(IOErrorEvent.IO_ERROR, onListLoadError); _listLoader.load(new URLRequest(LIST)); } private function onListLoadComplete(event:Event):void { _pictures = []; var xml:XML = new XML(_listLoader.data); for each (var picture:* in xml.picture) { _pictures.push(String(picture)); } _listLoader = null; _conveyor.play(); } private function onListLoadError(event:Event):void { _listLoader = null; trace("List loading failure"); } //} private function loadPictures():void { _loadedPictures = []; for each (var picture:String in _pictures) { _conveyor.add(loadPicture, picture); } } //{ loadPicture - асинхронная задача private var _pictureLoader:Loader; private function loadPicture(picture:String):void { _conveyor.stop(); _pictureLoader = new Loader(); _pictureLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, onPictureLoadComplete); _pictureLoader.addEventListener( IOErrorEvent.IO_ERROR, onPictureLoadError); _pictureLoader.load(new URLRequest(picture)); } private function onPictureLoadComplete(event:Event):void { _loadedPictures.push(_pictureLoader.content); _pictureLoader = null; _conveyor.play(); } private function onPictureLoadError(event:Event):void { _pictureLoader = null; trace("Picture loading error"); } //} private function placePictures(y:Number):void { var left:Number = 0; for each (var picture:DisplayObject in _loadedPictures) { picture.x = left; picture.y = y; addChild(picture); left += picture.width; } } } } |
|
Обновил(-а) expl 07.02.2012 в 11:32
|
![]() ![]() |
|
Тогда вообще блеск. Спасибо.
|
![]() ![]() |
|
Хорошо, конвеер Потапенко - вполне себе неплохой. Точнее, хороший и даже великолепный для реализаций (описаных выше) простых логических шаблонов.
Но. Господа, в кой то веке обратите внимание на as3-commons-async. |
![]() ![]() |
|
в кои-то веки.
но не суть. только хотел написать про аскомонс. таски там хороши, но как передать результат предыдущей команды в следующую? я хочу загрузитьКонфиг-> распарсить -> загрузитьНужныеРесурсы |
|
Обновил(-а) Котяра 05.03.2012 в 00:35
|
![]() ![]() |
|
http://www.springactionscript.org/as...ipt/core/task/
А как на счет спринговых тасков Покрайнемере интерфейс их мне нравится |
Последние записи от expl
- Конвеер Потапенко (07.02.2012)
- Менеджер курсоров на базе стека (21.01.2012)
- Unit-тестирование haXe во FlashDevelop (08.03.2011)