Состояния

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.
*    Это случается, если смена состояния запрашивается во время перехода. Новое состояние встает в очередь и рассматривается
*    после завершения перехода.
*/

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

2 Responses to “Состояния”


  • отличная тема! :)
    если еще сделаешь простенький пример с тем же меню, например, то точно скачаю и попробую использовать :)

  • Очень толково. Спасибо.

Leave a Reply