Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > expl

Рейтинг: 5.00. Голосов: 3.

Конвеер Потапенко

Запись от expl размещена 07.02.2012 в 01:37

Цели и задачи

В данном контексте КОНВЕЕР - это механизм, выполняющий последовательность асинхронных действий в заданном порядке. Он универсален и может быть реализован в виде класса.

"Изобрел" Евгений Потапенко, достаточно доходчиво объяснял зачем нужен конвеер и как им пользоваться. А потом его сайт канул в лету. Но на форуме (да и на практике) всплывают задачи, которые лучше решать именно конвеером. Поэтому данное чудо инженерной мысли не должно исчезнуть вслед за сайтом!

Решает задачи:
- запуск следующей асинхронной или синхронной задачи после завершения предыдущей в порядке очереди;
- тривиальная смена порядка асинхронных задач;
- простое добавление следующих асинхронных задач

Алтернативы:
- класс, реализующий последовательное выполнение асинхронных команд;
- просто запуск следующего задания в слушателе завершения предыдущего (запутанность кода при этом подходе конвеер как раз и снижает).

Ограничения:
- непонятны ни стратения не приимущества использования конвеера в нелинейных динамически меняющихся последовательностях асинхронных операций
(но в то же время можно использовать в качестве асинхронной очереди в каком-нибудь исполнителе запросов к серверу - упростит код при определенных условиях);
- не стоит использовать для анимации - с этим лучше справляются твиновые движки (и они более гибко подходят к вопросу прерывания анимаций), но можно для запуска последовательности анимаций, проигрываемых твиновым движком, если Вам кажется что так удобнее или в специфических случаях

Если же асинхронные операции не выстраиваются в цепочку, а подчиняются весьма витиеватой логике, то надо использовать что-то из этого (все зависит от ситуации):
- тупое жонглирование колбеками с подписками/отписками;
- отказ от подписки/отписки - переход на вызов функций update() через равные промежутки времени;
- выделение состояния плюс кучи свитчей, обрабатывающих события в зависимости от состояния;
- паттерн состояние;
- машину состояний;
- обработку колбеков через декоратор;
- цепочку обязанностей;
...
и еще много решений, которые Вы знаете плюс их комбинации.

Почему не рассматриваются примеры на оригинальном конвеере

Сначала хотел разобрать подход на оригинале, но оригинал сложно найти На форуме кто-то выкладывал версию, но она была перегруженной специфическими функциями и почему-то синглтоном (хотя я помню, что некоторые функции просто нельзя выполнить с одним экземпляром конвеера, да и в реальном приложении совсем не хочется ютится на одном конвеере - нужно несколько независимых - иначе просто невозможно с ним работать в реальном проекте).

Если Вы хотите Singleton, чтобы не тянуть ссылки, то не хотите этого, а присвойте экземпляр Conveyour какому-нибудь публичному статическому полю (или сделайте статичный геттер с ленивой инициализацией в каком-нибудь классе) - и будет вам счастье. Где-то синглтон может и хорошо, а здесь ограничивать численность экземпляров нельзя.

Еще сам оригинал был слшиком вариативен, например:

Код AS3:
public function add(...args):ConveyorReturn
1 параметр - делаем одно 2 - делаем другое, а если первый аргумент строка, то... Зачем оно нам нужно.

Поэтому напишем свой конвеер, с преферансом и поэтессами. Версия будет урезанной, но не менее практичной. Всё равно использование фитч оригинала всеми возможными способами только запутает проект, а нам нужна предсказуемая работа и прозрачность кода.

Простое использование конвеера

Принцип добавления асинхронной задачи прост как все гениальное:
- Добавляем функцию (никаких создаваний классов с перегрузкой методов).
- Внутри функции приостанавливаем конвеер.
- Дожидаемся завершения операции.
- Возобновляем работу конвеера - с этого момента пошла выполняться следующая операция (какая? - внути данной операции мы не знаем)

Допустим, нужно подождать numFrames кадров, а затем напечатать "Готово". Просто делаем асинхронную задачу "подождать numFrames кадров" и синхронную задачу "напечатать Готово", добавляем их в конвеер и запускаем.
Код AS3:
//{ 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
Готово
*/
Можно добавлять одно и то же действие несколько раз:
Код AS3:
_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. Отобразить картинки
Тут фишка в том, что ни одно действие нельзя начать, не дождавшись завершения предыдущего.
Как это выглядит без ковеера:
Код AS3:
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;
	}
}
Вроде-бы если постараться, разобраться можно, куски вроде этого только напрягают:
Код AS3:
_loadingPictureIndex++;
if (_loadingPictureIndex >= _pictures.length)
Плюс еще цепочка "получил сообщение о завершении" - запусти _конкретную_ следующую операцию - сложно менять порядок, а ошибиться просто. Плюс вызова функции placePictures не произойдет, если картинок нет в списке, а мало ли чего вы после него хотели сделать. Придется навесить еще немного условной логики. Не айс, короче.

Как это выглядит с конвеером:
Код AS3:
_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;
	}
}
Здесь задачи на загрузку отдельных картинок добавлялись прямо при выполнении задачи addPictures, потому что раньше мы просто не знаем что это за картинки и сколько их. И использовался метод addInclude, чтобы эти задачи выполнились сразу после addPictures, а не после placePictures

Строчек здесь получилось столько же (даже немного больше), но код стал намного понятнее. Стало гораздо проще добавлять новые элементы для загрузки и труднее сломать класс. И если даже картинок окажется 0 - задача, добавленная после placePictures всё равно отработает

Что касается реализации addInclude - это отдельная гениальная идея с использованием "закладок". В нашем конвеере она точно такая же как у Потапенко, только делается переносом части списка за время 0(1), а не выкусыванием массива и его реверсом и склеиванием с другой частью. Но не будем о ней больше - букв и так много

Вобщем конвеер - это хороший инструмент, главное не переусердствовать с его применением и не пихать куда ни поподя
as3_potapenko_conveyor.zip
Всего комментариев 10

Комментарии

Старый 07.02.2012 03:04 elder_Nosferatu вне форума
elder_Nosferatu
 
Аватар для elder_Nosferatu
Огромное спасибо!!!

Недавно поднимал тему на форуме. В ответ получил исходник Потапенковского конвеера. Может идея и его, но понятное объяснение стоит больше чем гора непонятного кода. Уж очень много он на свою идею функционала навешал, чтобы так с ходу и разобраться.
Старый 07.02.2012 03:49 TanaTiX вне форума
TanaTiX
 
Аватар для TanaTiX
Статья интересная. У самого есть похожая идея, правда с немного отличающейся реализацией. Но спасибо за альтернативное решение. Думаю, будет что почерпнуть для себя.
...хотя в код особо не вчитывался.
Старый 07.02.2012 10:45 GBee вне форума
GBee
 
Аватар для GBee
Сорри, случайно поправил ваш комментарий, не знаю как откатить. @expl
Обновил(-а) expl 07.02.2012 в 11:19
Старый 07.02.2012 11:19 expl вне форума
expl
Если добавлять loadPictures и с add и с addInclude - не собьется, просто что добавлено внутри loadPictures, добавленного с add будет влеплено в конец конвеера. Другой вопрос, зачем оно нужно вконце списка.
Старый 07.02.2012 11:24 GBee вне форума
GBee
 
Аватар для GBee
Нет вопрос не о том. Я приводил код :о)

Код AS3:
_conveyor = new Conveyor();
_conveyor.add(loadList, url1); //Грузим первый список 
_conveyor.addInclude(loadPictures); //Инклюд один и тот же
_conveyor.add(placePictures, sprite1); //Лепим в первый спрайт
 
_conveyor.add(loadList, url2); //Грузим второй список 
_conveyor.addInclude(loadPictures); //Инклюд один и тот же
_conveyor.add(placePictures, sprite2);//Лепим во второй спрайт
_conveyor.play();
инклюд одного и того же метода не сломает логику?
Старый 07.02.2012 11:27 expl вне форума
expl
Инклюд одного и того же, как и простое добавление одного и того же метода логику не ломает, просто добавляется 2 раза. Впринципе можете подобавлять трейсов и убедиться.
Ну или так:
Код AS3:
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 GBee вне форума
GBee
 
Аватар для GBee
Тогда вообще блеск. Спасибо.
Старый 07.02.2012 12:47 ~~~ вне форума
~~~
 
Аватар для ~~~
Хорошо, конвеер Потапенко - вполне себе неплохой. Точнее, хороший и даже великолепный для реализаций (описаных выше) простых логических шаблонов.
Но. Господа, в кой то веке обратите внимание на as3-commons-async.
Старый 04.03.2012 15:53 Котяра вне форума
Котяра
 
Аватар для Котяра
в кои-то веки.

но не суть. только хотел написать про аскомонс.
таски там хороши, но как передать результат предыдущей команды в следующую?
я хочу
загрузитьКонфиг-> распарсить -> загрузитьНужныеРесурсы
Обновил(-а) Котяра 05.03.2012 в 00:35
Старый 08.03.2012 06:02 Ohcysp Regit вне форума
Ohcysp Regit
http://www.springactionscript.org/as...ipt/core/task/

А как на счет спринговых тасков
Покрайнемере интерфейс их мне нравится
 

 


Часовой пояс GMT +4, время: 15:48.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.