我们在用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)