這個狀態機http://www.cnblogs.com/flytrace/p/5587033.html的lua版本
-- LUA 有實現枚舉值的好辦法么 local sc_enum = { -- event flow be broke up none = "none", -- event flow pass througth next state forward = "forward", -- deffer a speciafic event --deffer = 4 -- select by default(the first position in the list) shallow = "shallow", -- the state will remain actived state that last time was deep = "deep", -- 不實現。該模式定義不良好。多個嵌套的子並行狀態相對復雜,流程不清晰,數據結構也不直接。 -- 還沒找到比較良好的並行狀態語義。 -- only one substate can run at one time by default singleman = "singleman", -- the state will be enabled always, substate in parallel region can't transit to none-parallel region parallel = "parallel", } return sc_enum
-- Date 2016-7-13 -- yanfei --[[ 一個sc_fsm也是一個sc_state,狀態機本身也可以作為一個狀態,但目前子狀態機只能視為狀態,未能作為獨立的機制運行 sc_fsm { type2states -- save all states accord to their name in a dictionary } --]] sc_helper = require("fsm.sc_helper") require("utils.sc_list") local sc_queue = require("utils.sc_queue") local sc_enum = require("fsm.sc_enum") local sc_state = require("fsm.sc_state") local classprototype = require("utils.middleclass") -- a fsm is a state local fsm = classprototype("sc_fsm", sc_state) -- 建立類型-狀態表 local function buildstates(fsm, astate) fsm.type2states[astate.statename] = astate if astate.substates then for statename_, state_ in ipairs(astate.substates) do assert(fsm.type2states[state_.statename] == nil, string.format("[%s]: has a state named [%s] already!", fsm.statename, state_.statename)) buildstates(fsm, state_) end end end -- 重載initialize -- @statename_ 狀態名字 -- @basestate_ 父狀態 -- @statemode_ 運行模式 -- @operation_ 激活操作 function fsm:initialize(statename_, basestate_, statemode_, operation_) sc_state.initialize(self, statename_, basestate_, statemode_, operation_) end function fsm:init(initstatename) assert(type(self) == 'table', "Make sure that you are using 'fsm:init' instead of 'fsm.init'") self.type2states = {} buildstates(self, self) local initstate = self.type2states[initstatename] assert(initstate and initstate:isInstanceOf(sc_state), string.format("[%s] hasn't state named [%s]", self.statename, initstatename)) -- 建立主激活列表 local astate = initstate while astate do astate.activesubstate = astate.substates and astate.substates[1] or nil self.ineststate = astate.activesubstate and astate.activesubstate or astate astate = astate.activesubstate end astate = initstate while astate ~= self do astate.basestate.activesubstate = astate astate = astate.basestate end astate = self while astate.activesubstate ~= nil do if astate.activesubstate.enter then astate.activesubstate:enter(self) end astate = astate.activesubstate end if not self.eventqueue then self.eventqueue = sc_queue:newqueue() end self.suspend = false self.__lockqueue = false self.__transiting = false return true end -- 跳轉到目標狀態 function fsm:transit( targetname_ ) assert(type(self) == 'table', "Make sure that you are using 'fsm:transit' instead of 'fsm.transit'") if self.__transiting then self.__transiting = false return sc_enum.none end self.__transiting = true; local targetstate = self.type2states[targetname_] assert(targetstate and targetstate:isInstanceOf(sc_state), string.format("[%s] hasn't state named [%s]", self.statename, targetname_)) -- detect the whole active state list local astate = self.ineststate -- local bstate = targetstate local dis = astate.depth - bstate.depth if dis < 0 then bstate, astate = astate, targetstate end -- bstate is topper dis = math.abs(dis) for i = 1, dis do astate = astate.basestate end --now they are both same depth if astate == bstate then -- is family self.__transiting = false return sc_enum.none end -- find nearest ancestor both while astate ~= bstate do astate, bstate = astate.basestate, bstate.basestate end -- first we will call exit chain while self.ineststate ~= astate do if self.ineststate.exit then self.ineststate:exit(self) end self.ineststate = self.ineststate.basestate end -- now we will decide the enter chain -- build active chain down side local astate = targetstate while astate do astate.activesubstate = (astate.substates and astate.statemode == sc_enum.shallow) and astate.substates[1] or astate.activesubstate self.ineststate = astate.activesubstate and astate.activesubstate or astate astate = astate.activesubstate end -- build active chain up side astate = targetstate while astate ~= self do astate.basestate.activesubstate, astate = astate, astate.basestate end -- call enter chain while bstate.activesubstate ~= nil do if bstate.activesubstate.enter then bstate.activesubstate:enter(self) end bstate = bstate.activesubstate end return sc_enum.none end function fsm:process_event( eventname_, ...) assert(type(self) == 'table', "Make sure that you are using 'fsm:process_event' instead of 'fsm.process_event'") if self.suspend then return nil end if self.__lockqueue then return nil end local pevent = {...} pevent.eventname = eventname_ if not self.eventqueue then self.eventqueue = sc_queue:newqueue() end local queue = self.eventqueue queue:push_back(pevent) self.__lockqueue = true --local eventcount = queue:count() while true do --eventcount = eventcount - 1 local astate = self local processflag_ = sc_enum.forward pevent = queue:pop_front() if not pevent then break end while astate.activesubstate ~= nil do processflag_ = astate.activesubstate:_internal_process(self, pevent) if processflag_ == sc_enum.none then return processflag_ end astate = astate.activesubstate end end self.__lockqueue = false return processflag_ end -- function fsm:post_event( eventname_, ... ) local pevent = {...} pevent.eventname = eventname_ self.eventqueue:push_back(pevent) end function fsm:context( statename_ ) local targetstate = self.type2states[targetname_] --assert(targetstate and targetstate:isInstanceOf(sc_state), string.format("[%s] hasn't state named [%s]", self.statename, targetname_)) return targetstate end return fsm
-- Date 2016-7-13 -- yanfei --[[ -- sc_state -- { statename, -- 狀態名 basestate, -- 父狀態 statemode, -- 歷史模式 operation, -- 操作,完成並行,篩選。目前保留字段 substates, -- 子狀態列表 activestate, -- 當前激活狀態 events,, -- event-callback表 -- } -- 一個狀態是一個節點,basestate指向父狀態,substates指向子狀態列表,所有狀態形成一棵樹 --]] require("utils.sc_list") local sc_enum = require("fsm.sc_enum") --require("fsm.sc_event") local classprototype = require("utils.middleclass") local sc_state = classprototype("sc_state") local rawget = rawget local rawset = rawset local string = string -- 重載initialize -- @statename_ 狀態名字 -- @basestate_ 父狀態 -- @statemode_ 運行模式 -- @operation_ 激活操作 function sc_state:initialize(statename_, basestate_, statemode_, operation_) assert(type(statename_) == "string","statename must be a string") self.statename = statename_ self.statemode = statemode_ or sc_enum.shallow --self.operation = operation_ or sc_enum.singleman -- 處理父子關系 assert(basestate_ and basestate_:isInstanceOf(sc_state) or basestate_ == nil, string.format("[%s]: base state must be a sc_state", statename_)) self.basestate = basestate_ if basestate_ then basestate_:addsubstate(self) end -- 狀態深度 top state is 1 self.depth = self.basestate and self.basestate.depth + 1 or 1 end -- 創建該狀態的子狀態 function sc_state:createstate(statename_, statemode_, operation_) return sc_state:new(statename_, self, statemode_, operation_) end -- 添加子狀態 function sc_state:addsubstate(...) -- 填充子狀態列表 self.substates = self.substates or {} for i, state_ in ipairs{...} do assert(state_:isInstanceOf(sc_state), "substate must be a sc_state") assert(self.substates[state_.statename] == nil, string.format("%s has a substate named [%s] already!", self.statename, state_.statename)) table.insert(self.substates, state_) end end -- 綁定事件 -- @eventname_ 事件名 -- @callbackname_ 事件處理函數名 function sc_state:bind_event(eventname_, callbackname_) assert(type(self) == 'table', "Make sure that you are using 'sc_state:process_event' instead of 'sc_state.process_event'") assert(type(eventname_) == 'string', "Make sure that callbakname is a string") assert(type(callbackname_) == 'string', "Make sure that callbakname is a string") --local callback_ = self[callbackname_] local callback_ = rawget(self, callbackname_) assert(callback_, string.format("[%s] bind_event event[%s]: hasn't a function named [%s]", self.statename, eventname_, callbackname_)) assert(type(callback_) == "function", string.format("[%s]:bind_event event[%s]: callback must be a function", self.statename, eventname_)) self.events = self.events or {} self.events[eventname_] = function(fsm, ...) return callback_(self, fsm, ...) end end function sc_state:_internal_process(fsm, pevent) if not self.events then return sc_enum.forward end local callback_ = self.events[pevent.eventname] if not callback_ then return sc_enum.forward end return callback_(fsm, unpack(pevent)) end return sc_state
--region *.lua --Date 2016.7.17 -- yanfei local queue = {} function queue:newqueue() local o = {first = 0, last = -1} setmetatable(o, self) self.__index = self return o end function queue:push_front(v) local first = self.first - 1 self.first = first self[first] = v end function queue:pop_front() local first = self.first if first > self.last then return nil end local v = self[first] self[first] = nil self.first = first + 1 return v end function queue:push_back(v) local last = self.last + 1 self.last = last self[last] = v end function queue:pop_back() local last = self.last if last < self.first then return nil end local v = self[last] self[last] = nil self.last = last - 1 return v end function queue:count() return self.last - self.first + 1 end return queue --endregion
--region *.lua module ("sc_list", package.seeall) --- An iterator over the elements of a list. -- @param l list to iterate over -- @return iterator function which returns successive elements of the list -- @return the list <code>l</code> as above -- @return <code>true</code> function elems (l) local n = 0 return function (l) n = n + 1 if n <= #l then return l[n] end end, l, true end --- An iterator over the elements of a list, in reverse. -- @param l list to iterate over -- @return iterator function which returns precessive elements of the list -- @return the list <code>l</code> as above -- @return <code>true</code> function relems (l) local n = #l + 1 return function (l) n = n - 1 if n > 0 then return l[n] end end, l, true end --- Fold a binary function into an iterator. -- @param f function -- @param d initial first argument -- @param i iterator -- @return result function fold_x (f, d, i, t) local r = d for e in i (t) do r = f (r, e) end return r end function fold (f, d, t) local r = d for e in elems (t) do r = f (r, e) end return r end ----- Fold a binary function through a list right associatively. ---- @param f function ---- @param e element to place in right-most position ---- @param l list ---- @return result --function foldr (f, e, l) -- return _G.fold (function (x, y) return f (y, x) end, -- e, relems, l) --end function find(f, t) for e in elems (t) do if f(e) then return e end end return nil end function findall(f, t) local result = {} for e in elems(t) do if f(e) then result[#result+1] = (e) end end return result end function join(a, b) return {unpack(a), unpack(b)} end function cons(a, t) return {a, unpack(t)} end --endregion
使用
-- 只是一個例子 local sc_enum = require("fsm.sc_enum") local sc_fsm_pt = require("fsm.sc_fsm") local gamemain_fsm = sc_fsm_pt:new("gamemain_fsm") local print = print --[[ -- gamemain_state --]] local gamemain_state = gamemain_fsm:createstate("gamemain_state") function gamemain_state:enter(fsm) print("enter "..self.statename) end function gamemain_state:exit(fsm) print("exit "..self.statename) end function gamemain_state:update(fsm, delta) print("update "..self.statename) print("timedelta: "..tostring(delta)) end gamemain_state:bind_event("game_update", "update") --[[ -- gamemain_init_state --]] local gamemain_init_state = gamemain_state:createstate("gamemain_init_state") function gamemain_init_state:enter(fsm) print("enter "..self.statename) end function gamemain_init_state:exit(fsm) print("exit "..self.statename) end --[[ -- gamemain_init_waiting_state --]] local gamemain_init_waiting_state = gamemain_init_state:createstate("gamemain_init_waiting_state") function gamemain_init_waiting_state:enter(fsm) print("enter "..self.statename) end function gamemain_init_waiting_state:exit(fsm) print("exit "..self.statename) end --[[ -- gamemain_idle_state --]] local gamemain_idle_state = gamemain_state:createstate("gamemain_idle_state") function gamemain_idle_state:enter(fsm) print("enter "..self.statename) end function gamemain_idle_state:exit(fsm) print("exit "..self.statename) end function gamemain_idle_state:update(fsm, delta) print("update "..self.statename) print("timedelta: "..tostring(delta)) --fsm:process_event("game_upadte", 0.0001) end function gamemain_idle_state:tolobby(fsm, msg1, msg2) print(msg1..' '..msg2) print("ready to taransit to lobby") return fsm:transit("gamemain_lobby_state") end gamemain_idle_state:bind_event("game_update", "update") gamemain_idle_state:bind_event("event_tolobby", "tolobby") --[[ -- gamemain_net_state --]] local gamemain_net_state = gamemain_state:createstate("gamemain_net_state") function gamemain_net_state:enter(fsm) print("enter "..self.statename) end function gamemain_net_state:exit(fsm) print("exit "..self.statename) end --[[ -- gamemain_net_send_state --]] local gamemain_net_send_state = gamemain_net_state:createstate("gamemain_net_send_state") function gamemain_net_send_state:enter(fsm) print("enter "..self.statename) end function gamemain_net_send_state:exit(fsm) print("exit "..self.statename) end --[[ -- gamemain_net_receive_state --]] local gamemain_net_receive_state = gamemain_net_state:createstate("gamemain_net_receive_state") function gamemain_net_receive_state:enter(fsm) print("enter "..self.statename) end function gamemain_net_receive_state:exit(fsm) print("exit "..self.statename) end --[[ -- gamemain_lobby_state --]] local gamemain_lobby_state = gamemain_state:createstate("gamemain_lobby_state") function gamemain_lobby_state:enter(fsm) print("enter "..self.statename) end function gamemain_lobby_state:exit(fsm) print("exit "..self.statename) end --[[ -- gamemain_lobby_shop_state --]] local gamemain_lobby_shop_state = gamemain_lobby_state:createstate("gamemain_lobby_shop_state") function gamemain_lobby_shop_state:enter(fsm) print("enter "..self.statename) fsm:post_event("event_opendoor", "opendoor") end function gamemain_lobby_shop_state:exit(fsm) print("exit "..self.statename) end function gamemain_lobby_shop_state:update(fsm, delta) print("update "..self.statename) print("timedelta: "..tostring(delta)) end function gamemain_lobby_shop_state:opendoor(fsm) print("open the door") end gamemain_lobby_shop_state:bind_event("game_update", "update") gamemain_lobby_shop_state:bind_event("event_opendoor", "opendoor") --[[ -- gamemain_lobby_dungeon_state --]] local gamemain_lobby_dungeon_state = gamemain_lobby_state:createstate("gamemain_lobby_dungeon_state") function gamemain_lobby_dungeon_state:enter(fsm) print("enter "..self.statename) end function gamemain_lobby_dungeon_state:exit(fsm) print("exit "..self.statename) end return gamemain_fsm
--region *.lua --Date sc_helper = require("fsm.sc_helper") local sc_event = require("fsm.sc_event") local queue = require("utils.sc_queue") -- 獲取定義好的gamemain狀態機 local gamemain_fsm = require("game.gamemain_fsm") -- 打印狀態樹 sc_helper:printstates(gamemain_fsm) -- 初始化狀態機,初始狀態為gamemain_init_state,進入gamemain_init_state時將自動進入其子狀態gamemain_init_waiting_state -- 進入某個狀態后,如果有子狀態,會嘗試進入其子狀態 gamemain_fsm:init("gamemain_init_state") -- 自頂向下打印當前激活的狀態鏈 sc_helper:printactivestates(gamemain_fsm) -- 狀態遷移到gamemain_idle_state gamemain_fsm:transit("gamemain_idle_state") sc_helper:printactivestates(gamemain_fsm) -- 發送事件game_update, 在gamemain_idle_state中注冊了對該事件的響應 gamemain_fsm:process_event("game_update", 0.0333333) -- 發送事件event_tolobby,在gamemain_idle_state中注冊了對該事件的響應。在響應中將調用gamemain_fsm:transit遷移到gamemain_lobby_state gamemain_fsm:process_event("event_tolobby", "hello", "world") sc_helper:printactivestates(gamemain_fsm) gamemain_fsm:process_event("game_update", 0.0333333) print("over") --endregion