Конвеер Потапенко
Запись от 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
Комментарии
07.02.2012 10:45 | |
Сорри, случайно поправил ваш комментарий, не знаю как откатить. @expl
|
|
Обновил(-а) expl 07.02.2012 в 11:19
|
07.02.2012 11:27 | |
Инклюд одного и того же, как и простое добавление одного и того же метода логику не ломает, просто добавляется 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
|
07.02.2012 12:43 | |
Тогда вообще блеск. Спасибо.
|
07.02.2012 12:47 | |
Хорошо, конвеер Потапенко - вполне себе неплохой. Точнее, хороший и даже великолепный для реализаций (описаных выше) простых логических шаблонов.
Но. Господа, в кой то веке обратите внимание на as3-commons-async. |
04.03.2012 15:53 | |
в кои-то веки.
но не суть. только хотел написать про аскомонс. таски там хороши, но как передать результат предыдущей команды в следующую? я хочу загрузитьКонфиг-> распарсить -> загрузитьНужныеРесурсы |
|
Обновил(-а) Котяра 05.03.2012 в 00:35
|
08.03.2012 06:02 | |
http://www.springactionscript.org/as...ipt/core/task/
А как на счет спринговых тасков Покрайнемере интерфейс их мне нравится |
Последние записи от expl
- Конвеер Потапенко (07.02.2012)
- Менеджер курсоров на базе стека (21.01.2012)
- Unit-тестирование haXe во FlashDevelop (08.03.2011)