/potapenko.com/articles/conveyor (part 5)/

Второе решение.
Вложенные списки

А что нам мешает помещать в конвейер действия, при выполнении которых будут создаваться действия конвейера? Практически ничего не мешает. Но созданные действия таким способом будут помещены уже в конец списка действий. Очень сложно выстроить правильную последовательность при таком подходе. Об этой проблеме я уже писал в «истории создания конвейера». Проблема уже не столько касается механизмов работы конвейера, суть этой проблемы помочь нам максимально упростить работу с большими списками команд. И решение здесь – это использование иерархичных списков.

В чем суть иерархических списков? Суть в том, что часть команд мы наделяем дополнительным поведением, при котором все новые действия созданные при выполнении этих команд не помещались в конец списка команд, а выполнялись «на том же месте» где находилась команда, которая их создала. Уже при описании этого решения могут быть проблемы с пониманием сути. Итак, есть некий большой список. Но некоторые команды – это не просто команды, они хранят инструкции по созданию других списков, и при выполнении данной команды в место одной команды будут помещены элементы только что созданного списка.

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

Для этого, кроме метода put нам необходим метод include. Он будет, так же как и метод put создавать элементы списка. Но будет иметь дополнительные «способности» для создания вложенных списков.

Например, какой-то объект создал список, а один из элементов этого списка был создан с помощью include и должен вызывать у другого объекта метод, при выполнении которого создастся еще несколько элементов списка. Один из этих элементов опять же будет создан с помощью include. И когда действие, содержащееся в этом элементе, выполнится, создадутся еще действия.

Как это работает:


public function include() 
{ 
  this.put(this, "bookmark_add", 0); 
  
  this.put.apply(this, arguments); 
  
  this.put(this, "bookmark_remove", 0); 
}

Как видно из этого кода мы помещаем не один элемент списка, а три. Первый создает метку в списке конвейера. Второй - это то действие, которое помещается методом include. Третий элемент - это метод конвейера, который ищет метку, удаляет ее, но и все действия, которые были перемещены в конец списка (до метки), перемещает вверх (вместо действия помещенного методом include).

Своего рода новый уровень работы конвейера. Для реализации внутреннего механизма конвейер использует свой «внешний» сервис.

Спецификация include:

Conv.include

Conv.include( function[, timeout][, argument1, … argumentN] )

Conv.include( function, scope, [, timeout][, argument1, … argumentN] )

Conv.include ( object, methodName[, timeout][, argument1, … argumentN])

Parameters

function – ссылка на функцию, которую необходимо выполнить.

object – путь к объекту. Он может задаваться в виде ссылки и (если объект еще не существует в момент добавления действия) – в виде строки, например, "_root.any_object".

methodName – строка, имя вызываемого метода объекта

scope – ссылка на объект - пространство имен, то что при выполнении функции будет this.

timeout – строка или номер, задержка после выполнения. Строка – задержка во фреймах. Номер – в миллисекундах.

arguments – аргументы

Returns

Ничего

Description

Метод полностью идентичен методу Conv.put . Но все действия, помещаемые в список конвейера действием, созданным этим методом, будут выполнены сразу после выполнения этого действия.

Для создания вложенных списков.

Usage


Conv.include(function() 
{ 
  trace("Первый вызов"); 

  Conv.include(function() 
  { 
    Conv.put(trace, "1", "Второй вызов") 
    Conv.put(trace, "1", "Третий вызов") 
  });
  
  Conv.put(trace, "1" , "Четвертый вызов"); 
  
}); 

Conv.put(trace, "1" , "Пятый вызов"); 

// Первый вызов 
// Второй вызов 
// Третий вызов 
// Четвертый вызов 
// Пятый вызов  

А так будет без использования include:


Conv.put(function() 
{ 
  trace("Первый вызов"); 

  Conv.put (function() 
  { 
    Conv.put(trace, "1", "Второй вызов") 
    Conv.put(trace, "1", "Третий вызов") 
  }); 

  Conv.put(trace, "1" , "Четвертый вызов"); 
}); 

Conv.put(trace, "1" , "Пятый вызов"); 

// Первый вызов 
// Четвертый вызов 
// Пятый вызов 
// Второй вызов 
// Третий вызов 

В принципе include по использованию ничем не отличается от метода put. Можно вообще использовать только один include. Но, во-первых, put – быстрее. Меньше выполняет работы. А во-вторых, нужно понимать различие действий: создание элемента списка (put) и создание элемента списка для создания вложенных списков (include). При таком подходе код будет выглядеть яснее.

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

Сначала метод SPIN:


private function spin() 
{ 
  this.lock(); 
  
  // вычислить игровой результат 
  this.createGameResult(); 
  
  // начать движение колеса 
  // (время 100 кадров) 
  Conv.put ( this , "startWeel" , "100" ); 
  
  // остановить колесо 
  Conv.put(this, "stopWeel", 0); 
  
  // озвучить игровой результат 
  // время – две секунды 
  Conv.put(this, "gameSounds", 2000); 
  
  // анимационно убрать проигравшие ставки 
  Conv.include ( this , "looses" ); 
  
  // анимационно показать выигрыш всех 
  /// выигравших (если такие имеются) 
  Conv.include ( this , "wins" ); 
  
  // разблокировать интерфейс 
  // игрового приложения 
  Conv.put ( this , "unlock" , 0) 
} 

Это практически первый вариант, кроме того, что в список помещается вызов методов wins и looses через include и без указания времени задержки.

Далее опишем методы wins и looses.


private function looses() 
{
for(
var i=0;
i<this.looses_array.length;
i++)
{
// анимационно убрать
// проигравшие ставки
Conv.include(this.looses_array[i],
"loose");
}
}

private function wins()
{
for (
var i=0;
i<this.wins_array.length;
i++)
{
// анимационно показать
// выигрыш у каждой ставки
Conv.include(this.wins_array[i], "win");

this.balance +=
this.wins_array[i].winsValue;

// покажем на дисплее
// баланса игрока
// увеличение баланса
Conv.put(_root.balanceDisplay_mc,
"setBalance", this.balance);
}
}

Может показаться странным, что мы опять используем вызов include для создания действий ставок (уже во вложенном списке). На самом деле - это суть подхода.

В методе spin мы описываем общую последовательность действий. Во вложенных списках мы описываем уже частные моменты, которые нам нужно показать. И, наконец, спускаемся до самих ставок и это уже анимация. И это самый лучший подход. На каждом этапе мы работаем с небольшим и «обозримым» списком. Во вложенных списках мы работам со более детализированными, но так же «обозримыми» списками. Наша задача – ясный код. А метод, который мы используем для решения этой задачи - это иерархичность.

Распределенные вычисления

Еще небольшое отступление. В данном примере нам не обязательно вкладывать списки друг в друга (код не настолько сложен). Мы могли бы просто вызвать сначала метод looses в головном классе, тот в свою очередь вызвал методы у ставок напрямую, а списки мы создавали бы только в ставках. И список сразу бы содержал все необходимые действия без вложенности. Но представьте, какой длины получился бы список (а ставок в рулетке должно быть около 150, а в некоторых вариантах рулетки даже больше)? И сколько вычислительной работы нам нужно сделать сразу? А вложенными списками мы достигаем распределение вычислений во времени. Это замечательное свойство! Признаюсь, что первоначально include создавался именно для этой цели. В одной игре нельзя было вычислить все действия в один момент - Flash просто «зависал». И пришла идея распределения вычислений во времени. А другие замечательные свойства include стали понятны позже, и возможность распределения вычислений во времени отошла на второй план, но не стала от этого менее ценной.

Предыдущая часть | Следующая часть
[1] [2] [3] [4] [5] [6] [7]