我们在用C/C++来实现状态机的时候,免不了要用某种代码生成的技术来产生状态迁移表(当然完全的SWITCH CASE也是可以的),用LUA来实现就简单多了,只需100行左右的代码就可以实现一个较完备的状态机框架。

       状态迁移有如下几个方面来描述:

  • from 源状态
  • event 引起迁移的事件
  • to 迁移的目标状态,如果是自身迁移则from和to相同
  • guard 迁移可以成立的守卫条件
  • entry 新状态的进入动作
  • action 执行迁移的动作
  •  leave 转移到其他状态时的离开动作

 

      在LUA中可以这样表示:

        transitions={} --状态迁移表

     function   add_transition(from,evt,to,guard,entry,action,leave)

          local transition =  transitions[from]

          local t = {to=to,guard=guard,entry=entry,action=action,leave=leave}
          if transition then
               transition[evt]=t
          else
              transition={}
              transition[evt] = t
              transitions[from]=transition
          end

     end 

                

      迁移的代码可以这样写:

 

   

local initstate = ...
       --...
        function  do_transition(evt)
              local  row = assert(transitions[initstate] )    
              local  transition = row[evt]
              if not transition then return end
              assert(transition.to) 
              if transition.guard(initstate,evt,transition.to) thentransition.entry(initstate,evt,transition.to)
               if transition.action(evt) thentransition.leave(initstate,evt,transition.to) 
                     initstate = transition.to
              endend

 

 

       guard、entry、action、leave都是函数,可以被不同的迁移重用,因此状态机框架可以有效减少冗余代码,更有点像一个代码编织机--把各种guard、action、entry等函数按照要求放到适当的“空位”上。

      状态和事件的定义可以很简单用整数(效率高)或字符串(描述性更好)就可以了,定义好了状态迁移,还需要一个驱动接口:

    function process_event(evt)   
      assert(evt)    
      do_transition(evt)

    end

奥,你该说了怎么就是一个转发调用呀,其实这只是一个中间版,因为process_event还要处理在状态迁移过程中产生的事件(也就是内部事件),这是通过post_event接口来完成的:

function  post_event(evt)
     if not events then
         events={}
         front = 0
         back = -1
     end     
     back = back + 1
     events[back] = evt
    end    post_event将事件放入事件队列events中然后再由process_event来处理:
   
    function process_event(evt)    
       do_transition(evt)  
       if  front then 
          while front <= back do
             local event = events[front]
                events[front] = nil        
                front = front + 1
                do_transition(event)
           end
        end 
    end
   把上面的代码用用一个state_machine表封装一下看起来会好些:
state_machine={}
state_machine.new=function(initstate)  
   local fsm = state_machine
   fsm.transitions={}
   fsm.initstate = initstate   
   return fsm
endfunction state_machine:add_transition(from,evt,to,guard,entry,action,leave)
--...
end

function state_machine:do_transition(evt)
 --...
end

function state_machine:post_event(evt)
end
  
function state_machine:process_event(evt) 
end

我们还可以以add_transition为基础写些语法糖衣出来,类似于self_transition、add_simple_transition等等。


    这研究过boost mpl库的朋友一定会觉得眼熟,对!上面的代码就是模仿boost mpl库的状态机的例子写的,因此索性例子也照搬了:)

 

local play_event = 100
local stop_event = 101
local pause_event = 102local play_state="playing"
local stopped_state="stopped"
local paused_state="paused"
local  guard=function(fsm,from,evt,to)
    return true
end
local void_=function(_,_,_,_
urn true
endlocal do_play=function(fsm,evt)
   print("do_play/n")
  fsm:post_event(pause_event)
  return true
endlocal do_stop=function(fsm,evt)
  print("do_stop/n")
  return true
endlocal do_pause=function(fsm,evt)
  print("do_pause/n")
  return true
endlocal do_resume=function(fsm,evt)
   print("do_resume/n")
  
  return true
end

local fsm = state_machine.new(stopped_state)
fsm:add_transition(stopped_state,play_event,play_state,guard,void_,do_play,void_)
fsm:add_transition(play_state,pause_event,paused_state_guard,void_,do_pause,void_)
fsm:add_transition(paused_state,play_event,play_state,guard,void_,do_resume,void_)
fsm:add_transition(paused_state,stop_event,stopped_state,guard,void_,do_stop,void_)
fsm:add_transition(play_state,stop_event,stopped_state,guard,void_,do_stop,void_)
fsm:process_event(play_event)
fsm:process_event(stop_event)
fsm:process_event(pause_event)
fsm:process_event(play_event)
fsm:process_event(pause_event)
fsm:process_event(stop_event)
fsm:process_event(play_event)
fsm:process_event(stop_event)