Re: вышла библиотека Nemerle.Statechart версия 1.1
От: CodingUnit Россия  
Дата: 02.11.13 02:55
Оценка: 33 (3)
Сегодня я опишу несколько функций и начну с простых примеров, в старых постах я писал на обычных примерах, они давно уехали вниз и я хочу рассказать о новых функциях которых не было раньше.

Начну с простого примера для тех кто не знаком с автоматами и их интерпретацией UML в частности. Опишу простой автомат, он понадобился мне когда надо было распознавать задержку кнопки и выполнять функцию в автомате если кнопка была нажата долгое время, в wpf есть RepeatButton но это совсем не то что мне было нужно и я написал простой автомат чтобы распознавать долгое нажатие кнопки.



Суть его такова, у нас есть два состояния кнопки когда она не нажата и надо определить когда пользователь зажмет кнопку на какой то промежуток времени, с этого времени надо выполнять ускоренное действие, то есть не то которое используется по обычному клику, этот автомат используется в Alarm Clock примере для быстрого ввода времени при настройке, когда надо быстро прокрутить цифры до нужного числа, кликать постоянно сложно, а функцию кнопки можно использовать и для других целей. Итак у нас есть ненажатое состояние NotPressed, когда пользователь нажимает кнопку автомат переходит в следующее состояние проверки Check, если он быстро отжимает кнопку автомат переходит в состояние NotPressed и все возвращается к началу как будто был обычный клик. Если пользователь зажимает кнопку то после 1 секунды срабатывает временное событие и автомат переходит в состояние Задержан (Holded) что означает что клавиша задержана, и можно производить какое то действие если клавиша задержана. Далее если клавиша была отжата, автомат возвращается в начальное состояние и вызывает действие released что клавиша была отжата.
Казалось бы простая задача, но ее решение может быть не столь простым, возиться с таймерами, вводить какие то флаги когда клавиша задержана и производить действия, при том надо будет распознавать когда отжали из состояния когда не была задержана и когда была, все это начало спагетти, а если логика бывает посложней тут и бывает лес флагов, if которые приходится муторно отлаживать и добиваться правильного функционирования, при том никто не гарантирует что ошибка не вскроется потом, а здесь мы имеем тривиальную диаграмму, которая переводится в такой же код на библиотеке и все поведение описывается в пределах диаграммы и терминах автоматов состояний, никакой ошибки быть не должно, поскольку все продумано по наглядной диаграмме изначально.
На диаграмме имеются три состояния (state) в овалах с именами слева направа NotPressed, Check, Holded, состояния бывает простые и композитные, простые не имеют внутренних подсостояний, а композитные имеют, о них расскажу позднее, здесь у нас простые состояния которые достаточны для решения задачи. Начальное состояние выбирается с помощью Начального псевдосостояния (Initial), который обозначен черным кружочком из которого идет переход в состояние, которое будет считаться начальным, с него автомат начинает выполнение. Псевдосостояние это фиктивное ненастоящее состояние, поскольку автомат не может в нем находиться в течении времени и должен сразу перейти из него дальше. Состояния соединены между собой переходами (transition), переходы осуществляются по событию (event), события обозначаются словом рядом с переходом. В стандарт UML помимо простых событий входят временные события (timed events), самое распространенное это after, событие которое отсчитывает время от входа в состояние из которого осуществляется переход, если автомат успевает перейти в другое состояние, временное событие не сработает, в данном случае у нас после 1 секунды сработает событие after и автомат перейдет из Check в Holded. У состояний есть внутренние действия и активности, есть действия которые вызываются при входе в состояние, входные действия (entry), выходные действия (exit), обозначаемые соответствующими ключевыми словами с косой чертой / за которой идет имя действия или выражение на языке, что делает автомат при входе или выходе. В данном случае у нас есть действие holded которое вызывается когда автомат входит в состояние Holded. Входные действия гарантированно вызываются при входе в состояние до всех остальных действий и активностей внутри состояния, поэтому подходят для вызова каких то инициализирующих процедур, перед работой самого состояния, при выходе могут вызываться действия, они вызываются после всех других действий и активностей, и могут использоваться для выполнения очистки ресурсов и других действий при выходе из состояния. В состоянии Holded также есть внутренняя активность (activity), которая обозначается ключевым словом do с косой чертой и именем действия. Активности от действий отличаются тем что могут выполняться долго в течении большого промежутка времени, они выполняются в своем потоке параллельно другим действиям, они начинают работать после входных действий которые выполняются до завершения, то есть синхронно, и автоматически завершаются если состояние перестает быть активным, если активность завершилась то порождается завершающее событие (completion event) и могут выполняться переходы по завершению (completion transition) но об этом позднее. В данном случае у нас просто активность которая выполняется все время пока автомат в состоянии Holded.
Также из состояния Holded идет переход по событию release, когда пользователь отжимает кнопку, к нему также прикреплено действие released, обозначается через косую черту после события прикрепленного к переходу, действия при переходах выполняются после выходных действий состояния из которого выходит переход. Это действие должно вызываться когда у нас произошло отжатие и до этого кнопка была зажата то есть автомат находился в состоянии Holded.
У меня к действиям автомата holded и released прикреплены посылка событий hour_holded и hour_released и такие же для минут, они посылаются в автомат Будильника, где он обрабатывает их дает быстро изменять время.
Из диаграммы переведем автомат в код, этот пример есть в примерах под названием FastKeystroke:


  [statechart(
  <#
  
  state NotPressed
  {
    push => Check;
  }
  
  state Check
  {
    after (1 s) => Holded;
    release => NotPressed;
  }
    
  state Holded
  {
    entry / holded;
    release / released => NotPressed;
    do / doing_while_hold;
  }
    
  #>)]
  public class FastKeystroke
  {
    
    public NeedCancel : bool
    {
      get
      {
        IsInState(StateHolded)
      }
    }
    
  }


Как видите все наглядно, состояния описываются начиная с ключевого слова state Name {} с именем состояния далее в фигурных скобках идет описание тела состояния, переходы, действия и активности. Переход обозначается как событие => состояние; также вписываются действия при переходах событие / действие => состояние;
все остальные элементы описываются аналогично как в UML, entry / holded; входное действие, do / doing_while_hold; активность внутри состояния.
Когда автомат компилируется он при разрешении имен действий и активностей наблюдает за методами, если есть метод с таким именем, автомат использует его и привязывает к соотв. действию, если метода с таким именем не нашлось библиотека генерирует для него событие, которое запускается когда выполняется соотв. действие внутри автомата, к действию можно позднее прикрепиться из внешних классов. В данном случае я оставил действие holded на него идет подписка извне, также и для активности, для них можно создать методы внутри класса при этом активность будет иметь такую сигнатуру:

doing_while_hold(tok : CancellationToken) : void
{
 while (!tok.IsCancellationRequested)
 {
 }
}

в качестве параметра передается CancellationToken он нужен для автоматического завершения активности, если состояние перестанет быть активным и в методе надо обрабатывать флаг IsCancellationRequested, сами же do активности реализованы с помощью Task ов, облегченный вариант потоков, они идеально подходят для активностей. Также я добавил свойство IsCancel с методом IsInState(StateHolded), оно нужно для того чтобы знать что нужно отменить событие click при отжатии, чтобы у кнопки не сработал обычный клик, обрабатывается уже в wpf, IsInState это метод который генерируется для каждого автомата, проверяет что автомат находится в соотв.состоянии, StateHolded это публичное статичное поле, в котором лежит экземпляр класса состояния, к ним можно обращаться извне, IsInState используется также в сторожевых условиях, но об этом позднее.

Я хочу провести некоторый рефакторинг автомата, у автомата наблюдается некоторая повторяемость, которую можно упростить переход по событию release идет как из Check так и из Holded почему бы нам не обобщить их, назовем общее состояние для них обоих как Pressed, и из него один переход release:



Теперь у нас иерархичный автомат, появилось состояние Pressed у которого два подсостояния Check и Holded, при этом Check начальное состояние Pressed в него он переходит сразу после события push. Когда активно внутреннее состояние, активно и родительское состояние, иерархичные автоматы это разновидность подхода придуманного Давидом Харелом и позднее обобщенного в стандарте UML, они позволяют описывать более сложное поведение и структурировать автомат, при этом соблюдается LSP Liskov Substitution Principle, внутреннее состояние обладает чертами родителя и реагирует на события родителя, в данном случае событие release запускает переход как из Check так и из Holded, два перехода мы описали через один, иерархичные состояния позволяют и обобщить повторяющиеся конструкции, что упрощает дизайн автомата. Я убрал действие из перехода по событию release в выходное действие Holded. Описание автомата в библиотеке осталось таким же декларативным:



[statechart(
  <#
  
  state NotPressed
  {
    push => Check;
  }
  
  state Pressed
  {
    release => NotPressed;

    state Check
    {
      after (1 s) => Holded;
    }
    
    state Holded
    {
      entry / holded;
      exit / released;
      do / doing_while_hold;
    }
  }
  #>)]
  public class FastKeystroke
  {
    
    public NeedCancel : bool
    {
      get
      {
        IsInState(StateHolded)
      }
    }
    
  }


появляется состояние Pressed, в которое вкладываются подсостояния Check и Holded. В родительском остается переход release => NotPressed;
Также возможен синтаксис : Parent этот синтаксис применяется когда большая глубина вложенности и позволяет упростить описание до такого:


  state Pressed
  {
    release => NotPressed;
  }

  state Check : Pressed
  {
    after (1 s) => Holded;
  }
    
  state Holded : Pressed
  {
    entry / holded;
    exit / released;
    do / doing_while_hold;
  }


здесь появляется описание родительского состояния Pressed через : после имени состояния, такое описание похоже на описания классов и будут понятны всем кто пишет в стиле ООП.
После компиялции автомата, будет сгенерирован класс содержащий все обьявления необходимые для его работы. Автомат после создания через конструктор необходимо запустить через метод Initiate() это переводит автомат в начальное состояние, далее события можно посылать через специально сгенерированные методы по именам событий например fsm.push(); fsm.release(); их можно прикрепить к кнопке для событий например PreviewMouseDown и PreviewMouseUp в WPF, также привязаться к событиям действий holded, released чтобы выполнять действия при начале когда кнопка задержана и при отпуске. Я использовал для них посылку событий в другой автомат Будильник. Также можно запускать события через метод PostEvent(), в него подается экземпляр Event, базового класса для событий внутри этого автомата, он генерируется внутри и его экземпляры можно создать, бывают события с параметрами о них расскажу позднее. Для временных событий генерируется таймер System.Timer он стартует при входе в состояние и останавливается при выходе, при переполнении запускает метод события. Методы событий складывают его экземпляр в пул событий который выполнен на ConcurrentQueue и сразу же выходят, пул обрабатывает события в отдельном потоке по одному, для обеспечения run-to-completion step, в него можно посылать события из любых потоков, пока автомат не выполнит одного события до завершения включая все действия при переходе в другое, он не может выполнять и реагировать на другие события.
Ну вот и все что я хотел рассказать на сегодня, если что то непонятно пишите. В данном посте рассказал о простых автоматах, состояниях, переходах, действиях при входе, выходе, активностях, об этом я и раньше рассказывал, новыми были временные события, : parent синтаксис и реализация активностей. Дальше расскажу об остальных функциях.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.