Разработчик игр
0,0
рейтинг
13 февраля 2014 в 19:56

Разработка → Асинхронные очереди в коде

Доброго времени суток.


image
Картинка для привлечения внимания.

В предыдущем (и первом моём посте) мы говорили про удобоваримость чтения написанного кода. То, зачем и был написан мной тот класс Instruction. В этом посте речь пойдёт уже о самом удобстве использования класса инструкции. А так же, о начале написания библиотеки утилиток.


Кратко - зачем всё это?
Приведенные в статье утилиты позволили лично мне избавиться от нагромождения конструкций, в которых одна функция вызывала другую и так далее, где цепочки могли быть весьма и весьма длинными. Например:
private function foo_1():void {
	// code here
	foo_2(param);
}

private function foo_2(param:Object):void {
	// code here
	foo_3(param, param2);
}

// etc

Способы применения:
1) Последовательные анимации. В моём случае, по инструкции программно анимировались UI элементы в окнах, само появление окон, и прочие незначительные эффекты.
2) Так же, инструкции оказались весьма практичными в использовании в случае общения с API сервисов. Зная, какие данные возвращает сервис, можно легко сконструировать инициализацию данных на старте в виде инструкции.

Выигрышем в использовании является читабельность кода. Взглянув на объявление инструкции сразу понятно — что за чем вызывается и с какими параметрами. Добавление новых цепочек и встраивание в уже существующие — так же, с инструкцией — проходит безболезненно.


Почти за два месяца после первой статьи, я успел столкнуться с разными задачами, связанными с последовательным выполнением скриптов. В процессе, класс инструкции оброс дополнительными функциями. А так же, на свет появилась ещё одна утилитка «Order». Порядок, или список задач — можно интерпретировать как угодно. Принцип работы схож с оригинальной инструкцией. Но, обо всём по порядку.

Инструкция обычная:

Пример обычной инструкции
public function init():void {
	Async.instruction
	.add(commandOne, 'Param for commandOne')
	.add(commandTwo, 100500)
	.add(commandThree, [1, 2, 3, 4, 5])
	.execute(finalCommand, 'instruction executed successfully');
}
		
private function commandOne(completeCb:Function, string:String):void {
	trace(string); // 'Param for commandOne'
	completeCb();
}
		
private function commandTwo(completeCb:Function, number:int):void {
	trace(number); // 100500
	completeCb();
}
		
private function commandThree(completeCb:Function, array:Array):void {
	trace(array); // [1, 2, 3, 4, 5]
	completeCb();
}
		
private function finalCommand(input:String):void {
	trace(input); // 'instruction executed successfully'
}

Все функции выполняются последовательно. Так же, стоит учесть, что класс теперь выкинет ошибку в случае, если в одной из команд не будет присутствовать ни одного аргумента. По логике — один аргумент в вызываемой команде должен быть гарантированно — completeCallback, для передачи очереди следующей функции. Ещё момент — в команды и в финальную функцию параметры можно вообще не передавать в момент объявления инструкции.

Инструкция с кучей:

Пример инструкции с кучей
public function init():void {
	Async.instruction.add(commandOne)
	.add(commandTwo, 100500)
	.add(commandThree, { type:'init data' } )
	.executeCollectingHeap(finalCommand);
}
		
private function commandOne(completeCb:Function):void {
	completeCb('commandOne', '1');
}
		
private function commandTwo(completeCb:Function, number:int):void {
	trace(number); // 100500
	completeCb('commandTwo', 2);
}
		
private function commandThree(completeCb:Function, object:Object):void {
	trace(JSON.stringify(object)); // { type:'init data' }
	completeCb('commandThree', {type:3});
}
		
private function finalCommand(heapResult:Array):void {
	trace(heapResult[0]); // ['commandOne', '1']
	trace(heapResult[1]); // ['commandTwo', 2]
	trace(heapResult[2]); // ['commandThree', {type:3}]
	trace('Instruction with heap executed successfully');
}

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

Order, или список действий

Пример списка действий
public function init() {
	Async.order
	.add(commandOne)
	.add(commandTwo)
	.add(commandThree)
	.execute(finalCommand);
}

private function commandOne(completeCb:Function):void {
	completeCb('string from commandOne');
}

private function commandTwo(completeCb:Function, string:String):void {
	trace(string); // string from commandOne
	completeCb(2, 'string from commandTwo');
}

private function commandThree(completeCb:Function, number:int, string:String):void {
	trace(number); // 2
	trace(string); // string from commandTwo
	completeCb();
}

private function finalCommand():void {
	trace('Order completed');
}

Этот вид я выделил отдельно, т.к. функционал инструкции был излишним, а городить кучу условий, впаивая новую логику — не захотелось. Суть здесь в том, что каждая команда по цепочке (по порядку) передаёт в следующую какие-либо данные. Очень удобно использовать в случае общения с api сервисов.

Весь код выложен на гитхаб. Заложено начало для небольшой (а, может быть, и большой в дальнейшем, кто знает?!) библиотеки.
Буду очень рад любой критике и комментариям в адрес библиотек. И крайне признателен за поправки или улучшения. Особый щенячий восторг принесёт факт, если то, что я делаю — будет полезно другим пользователям. Благодарю за внимание.
Показалась ли вам полезной данная статья?

Проголосовало 139 человек. Воздержалось 98 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Алексей Харламов @Frost47rus
карма
14,0
рейтинг 0,0
Разработчик игр
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (25)

  • –2
    Господа, напишите, пожалуйста, за что именно минусуете. Я на ресурсе недавно. И всё не могу понять — тут просто любят анонимно минусить? Или вы считаете, что я что-то делаю не верно, или, может, вообще зря? Тогда, прошу, поделитесь мыслями.
    • +3
      Все просто, у вас некие мысли в вакууме, немного кода немного текста, плохое начало «Доброго времени суток, Хабр и хабражители.» Лучше «Привет %username%» картинка не очень, плохой тег. И совсем не понятно где применять данный велосипед, да и зачем?
      • –2
        Спасибо за комментарий. Некие мысли в вакууме — это почему? В первой статье были описаны мотивы создания «велосипеда», зачем он нужен, и способы его применения. К слову, я не нашёл подходящих реализаций в гугле. Может, плохо искал, или кто-нибудь поделится своими? Или только молчаливо будем ставить минусы?
        Очень странно слышать про «плохое приветствие». Картинка не очень, плохой тег — всё это, как и плохое приветствие — вводит меня в ступор. Может быть, вы мне поможете понять — хабр это место развлечений и «правильных» картинок с тегами? Или всё-таки место, где люди опытом делятся?
        Всем молчаливым «минусаторам»: горите конструктивный комментарий, особенно негативный — намного лучше молчаливого минуса.
        Вот взять, например, результаты голосования. На данный момент — 8 человек отметили, что им статья была полезна. И я бесконечно рад тому, что мой, пусть и небольшой опыт, послужит этим восьми людям на пользу. Этот вариант ответа — не требует комментариев. А вот минусы без комментариев — не ясны. Звезды не так сошлись? Минусующему мой ник не понравился? Информация бесполезна?
        Сам факт минуса — может говорить о том, что кто-то не хочет видеть такие статьи(или статьи от меня конкретно) на хабре. Так не вопрос — скажите об этом. Чего стесняться?
      • –1
        С подачи знакомого, на всякий случай добавил ещё один вариант ответа в опросе.
        • –1
          Как выяснилось, не зря добавил последний вариант. Теперь всё встало на свои места.
  • 0
    А существующий код, на js не подходит типа habrahabr.ru/post/111634/
    • –1
      Простите, совсем не понял ваш комментарий.
      • +1
        Action Script это ECMAScript
      • 0
        В более общем виде — вы исследовали существующие аналоги прежде чем писать свое?
        • –1
          Аналоги на других языках? Когда начинал — нет. Первоначальная инструкция писалась из, так сказать, крайней нужды. Впоследствии, же — да. Например инструкция Order появилась после того, как я увидел конструкцию waterfall в JS библиотеке async. Понравилась идея перебрасывания аргументов от функции к функции. А аналога моей первоначальной инструкции — я так и не нашёл до сих пор, кстати.
          • 0
            Хорошо, а что нельзя сделать на github.com/CodeCatalyst/promise-as3, что можно сделать с вашей инструкцией?

            dataService
                .load( id )
                .log( category )
                .then( parseData )
                .log( category, "Data parsing" )
                .then( populateUI )
                .log( category, "UI population" );
            
            • 0
              Я исходил так же из того, что в код не будут добавляться новые структуры данных.
              Встраивая промисы в код — вы начинаете оперировать везде двумя новыми структурами: Promise и Deffered. Вам придётся достаточно сильно изменить текущую структуру функций, чтобы промисы заработали: изменить возвращаемое значение на Promise, добавить объявление Deffered значения, поставить его возвращение и везде следовать его структуре.
              С инструкцией это происходит менее болезненно. В цепочке функций, которую вы хотите замкнуть в инструкцию — заменится вызов следующей функции на коллбэк. И всё. Оперировать функции будут теми же самыми данными, что и без инструкции.
              Не знаю, как для вас, но для меня это очень весомый аргумент.
              • 0
                Для меня весомый аргумент то, что промисы хорошо масштабируются и их удобно применять практически везде, плюс они есть практически на любой платформе — promisesaplus.com/implementations, то есть вы перейдете с ас на яваскрипт и на питон и там будет знакомый очень подход для решения не простой задачи.
  • 0
    не вижу пользы в этом велосипеде.
    сам много думал на эту тему, но увы — ничего лучшего нативной реализации придумать не получается.
    все равно для каждой итерации в этой цепочке нужно создавать функцию с набором действий — так в чем выигрыш в итоге?
    цепочки, по типу event > handler > event > handler… или, как у вас, callback > callback > callback > ..., без дополнительных команд случаются крайне редко.
    • –1
      Здесь могу возразить. Например, инициализация приложения. Первое что пришло в голову: например, инициализация метаданных. Вы же врядли будете запихивать обработку всего и вся в одну функцию (что-нибудь типа parseMetaData). Вы наверняка разобьёте разбор данных на несколько этапов, в которые обязательно будут входить как минимум:
      1) игровые константы
      2) описание базовых характеристик игровых объектов
      3) описание базовых уровней или локаций
      4) описание порогов уровней персонажа
      5) описание различных игровых достижений и бонусов за них
      6) этот список можно продолжать и продолжать.
      И вместо последовательного вызова из одной функции другой — вы получаете визуально понятную структуру.
      С евентами — вам придётся диспетчить эвенты из каждой функции, причём, чтобы отличать эвенты друг от друга — вам придётся именовать эти эвенты в соответствие с отработанной функцией. И чтобы поменять местами отработку двух функций — вам потребуется влезть внутрь каждой функции, задиспетчить другие эвенты, и менять структуру хэндлеров.
      А в случае с инструкцией — вам нужно просто местами поменять две строчки. Одну ниже, другую — выше.
      В случае с евентами — вам придётся добавлять новый эвент для завершения очередного этапа. В случае с инструкцией — вы просто прописываете нужную функцию в нужное место, не генерируя при этом никаких новых эвентов, которые только запутают. Вы поразбирайте инициализацию в десятка четыре-пять функций в одном классе — тогда поймёте, что евентовая система тут совершенно не подойдёт. Она ещё больше нагромоздит и запутает код.

      Но вопрос коллбек или эвент — почти из разряда холивара. Так что тут на Ваше усмотрение. Я вижу плюс эвентов в других схемах использования. Но не в этом конкретном вопросе.
      • 0
        коллбек или евент — это не холивар, это выбор адоба.
        ну скажите, что мне даст ваша инструкция, если мне надо загрузить данные, а по комплиту вызвать обработку данных, а потом инициализацию сцены? или я запускаю какую-то анимацию на 5 секунд. мне ведь нужно достаться ее окончания, а только потом что-то делать. так что, реализация на евентах более оптимальная, чем просто цепочка функций…
        опять же, зачем тут эта цепочка?
        1) игровые константы
        2) описание базовых характеристик игровых объектов
        3) описание базовых уровней или локаций

        зачем это все вообще в одном классе? константы идут в конфиг какой-то, а инициализация объектов идет внутри соотвествующих классов. соответственно, разносить что-то, типа new Location(), new Level() по разным функциям уже нет смысла.
        • 0
          Предположим класс, загружающий метаданные всей игры и его инициализация:
          private function init(initializeComplete:Function):void {
          	Async.order
          	.add(prepareLoadData)
          	.add(loadData)
          	.add(parseData)
          	.execute(initializeComplete);
          }
          
          private function prepareLoadData(completeCb:Function):void {
          	var url:String = 'http://static.site.ru/meta.json';
          	completeCb(url);
          }
          
          private function loadData(completeCb:Function, url:String):void {
          	var loader:URLLoader = new URLLoader();
          	var request:URLRequest = new URLRequest(url);
          	loader.addEventListener(Event.COMPLETE, completeCb);
          	loader.load(request);
          }
          
          private function parseData(completeCb:Function, e:Event):void {
          	var data:Object = JSON.parse(e.target.data);
          	Async.instruction
          	.add(initDefaultValues, data.defaults)
          	.add(initShopLotsVO, data.shopLots)
          	.add(initLocationsData, data.defaultLocations)
          	.add(initHeroLevels, data.heroLevels)
          	.execute(completeCb);
          }
          // all other functions initDefaultValues, initShopLotsVO etc...
          

          И пожалуйста, если вам понадобилось добавить инициализацию доп. данных после получения — просто вписываете новую строчку .add(initNewData, data.newData) в функции parse и т.д…
          • 0
            с примером намного лучше, признаю.
            я не подумал о том, что можно запихнуть коллбек вместо хендлера в строке:
            loader.addEventListener(Event.COMPLETE, completeCb);
            велосипед остался велосипедом, но вы обосновали его право на существование :)
            • 0
              Спасибо. Этот велосипед мне помог упростить множество вещей, т.е. оказался полезным. Я подумал, что он может быть полезным и другим — судя по минусам, я ошибся. Больше я сюда писать ничего не буду. =)
              • 0
                в данном случае важно само написание велосипеда. вы, по крайней мере, себе упроситили немного жизнь и получили бесценный опыт.
                а по поводу ресурса — это да, я вам дал совет выше, куда стоит писать по теме флеша )
                • 0
                  Эм… или я слепой… куда стоит писать по теме флеша?)
                  • 0
                    мои извинения.
                    я вчера написал еще один коммент про блоги на флешер.ру, но он не отправился — у меня лимит на 1 сообщение в 5 минут :)
                    • 0
                      На флешер.ру мне дали вечный бан через некоторое время после регистрации без указания причины. Обратная связь молчит.
                      Я так и не понял, что я сделал не так, если я ничего не делал.
  • 0
    Пару лет назад открыл для себя реализацию js promises на as3: github.com/CodeCatalyst/promise-as3.
    Использовал уже в двух проектах на Air, сделал на промисах сервисы, работу с базой, загрузку картинок — все работает замечательно.

    Есть еще библиотека as3commons-async от спринга, но там java головного мозга, пользоваться ей неудобно.

    У меня есть пару библииотек, которыми я хотел бы поделится, но не знаю будет ли кому интересно:
    1. Оптимированный в разы FlexXB — xml парсер, для мобилок.
    2. JSON десериализатор с поддержкой мэппинга типов, на аннотациях.
    3. SQL фреймворк для автоматической генерации базы по классам + построитель запросов и транзакций типа www.querydsl.com/
    4. Маленькая библиотечка для рефлекшена через describeTypeJSON.
    • 0
      Спасибо за ссылки. Промисы — видел пару раз, но всё не доходили руки посмотреть что это такое в деле.
      И было бы, конечно же, интересно посмотреть ваши библиотеки, особенно заинтересовал 3-ий пункт.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.