looplab-fsm 源碼閱讀
github.com/looplab/fsm 實現了一個有限狀態機,下面研究下它的源碼,除了測試文件外,它有下面幾個文件:
errors.go //定義了錯誤
fsm.go //定義了狀態機的核心邏輯
event.go //定義了事件結構體
graphviz_visualizer.go //生成graphviz格式的文件
mermaid_visualizer.go // 生成mermaid格式的文件
visualizer.go //
總的來說代碼分爲兩部分:1,定義狀態機;2,實現狀態機的可視化。
第一部分:狀態機的定義
狀態機大體上可以分爲兩部分:狀態和驅動狀態變化的事件。首先我們來定義一個門的狀態機,它有兩個狀態 open,closed。對應的有兩個事件 open 和 close 來驅動狀態機狀態的變化。
fsm := fsm.NewFSM(
"closed",
fsm.Events{
{Name: "open", Src: []string{"closed", "open"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{
//事件之前
"before_open": func(e *fsm.Event) {
fmt.Println("before_open")
e.Async()
},
"before_event": func(e *fsm.Event) {
fmt.Println("before_event")
e.Async()
},
//離開老狀態之前
"leave_closed": func(e *fsm.Event) {
fmt.Println("leave_closed")
e.Async()
},
"leave_state": func(e *fsm.Event) {
fmt.Println("leave_state")
e.Async()
},
//進入新狀態之前
"enter_open": func(e *fsm.Event) {
fmt.Println("enter_open")
e.Async()
},
"enter_state": func(e *fsm.Event) {
fmt.Println("enter_state")
e.Async()
},
//事件執行之後
"after_open": func(e *fsm.Event) {
fmt.Println("after_open")
e.Async()
},
"after_event": func(e *fsm.Event) {
fmt.Println("after_event")
e.Async()
},
},
)
if err := fsm.Event("close"); err != nil {
fmt.Println(err)
}
if err := fsm.Event("open"); err != nil {
fmt.Println(err)
}
fmt.Println(fsm.Current())
err := fsm.Transition()
if err != nil {
fmt.Println(err)
}
fmt.Println(fsm.Current())
graphviz, _ := vfsm.VisualizeWithType(fsm, "graphviz")
ioutil.WriteFile("fsm.graphviz", []byte(graphviz), fs.ModePerm)
diagram, _ := vfsm.VisualizeWithType(fsm, "mermaid-state-diagram")
ioutil.WriteFile("fsm.diagram.md", []byte("```mermaid\n"+diagram+"\n```"), fs.ModePerm)
flowChart, _ := vfsm.VisualizeWithType(fsm, "mermaid-flow-chart")
ioutil.WriteFile("fsm.flowChart.md", []byte("```mermaid\n"+flowChart+"\n```"), fs.ModePerm)
可以看到,定義狀態機的時候有三部分組成:狀態機的初始狀態,狀態機的事
件(包括了多個源事件和一個目的事件),狀態機的事件回調。整體來說,回
調可以分爲四組 8 個回調,按執行順序依次爲:
1,事件開始之前
A,before_xxx,特定的狀態之前
B,before_event 所有狀態之前
2,離開老狀態
A,leave_xxx 離開特定狀態
B,leave_state 離開所有狀態
3,進入新狀態
A,enter_xxx, 進入特定狀態
B,enter_state 進入所有狀態
4,事件執行完畢之後
A,after_xxx 進入特定狀態之後
B,after_event 進入所有狀態
接着就是兩個比較重要的接口:fsm.Event("open") 通過傳入事件驅動狀態的變化,通過傳入的事件,從 transitions 中篩選出對應的 transition,初始化當前目標狀態的 transaction,除了執行 transaction 本身的交易邏輯外還執行上述 8 個 callback 方法,。fsm.Transition() 僅僅執行 transaction 邏輯。下面結合源碼具體看看:
fsm.go
func NewFSM(initial string, events []EventDesc, callbacks map[string]Callback) *FSM {
f.transitions[eKey{e.Name, src}] = e.Dst
f.callbacks[cKey{target, callbackType}] = fn
NewFSM 主要工作是展開我們傳入的參數,變成 transitions 的 map 和 callbacks 的 map,方便後面調用。其中狀態機 FSM 的定義如下:
type FSM struct {
current string //當前狀態
transitions map[eKey]string
// 事件名,事件類型到目標狀態映射
callbacks map[cKey]Callback
//回調目標狀態,回調類型到回調方法映射
transition func()
//調用transition的方法
transitionerObj transitioner
stateMu sync.RWMutex
eventMu sync.Mutex
}
type EventDesc struct {
Name string
Src []string
Dst string
}
type Callback func(*Event)
type Events []EventDesc
type Callbacks map[string]Callback
爲了線程安全,對狀態轉換和事件回調兩個 map 都定義了鎖。他們的 key 分別是:
type cKey struct {
target string
callbackType int
}
type eKey struct {
event string
src string
}
其中 transitioner 是一個接口
type transitioner interface {
transition(*FSM) error
}
默認實現如下,它調用了狀態機的 transition 方法:
func (t transitionerStruct) transition(f *FSM) error {
f.transition()
}
狀態機實現的函數接口有:
func (f *FSM) AvailableTransitions() []string
func (f *FSM) Can(event string) bool
func (f *FSM) Cannot(event string) bool
func (f *FSM) Current() string
func (f *FSM) Event(event string, args ...interface{}) error
func (f *FSM) Is(state string) bool
func (f *FSM) Metadata(key string) (interface{}, bool)
func (f *FSM) SetMetadata(key string, dataValue interface{})
func (f *FSM) SetState(state string)
func (f *FSM) Transition() error
其中
func (f *FSM) Can(event string) bool
_, ok := f.transitions[eKey{event, f.current}]
return ok && (f.transition == nil)
//執行事件扭轉
func (f *FSM) Event(event string, args ...interface{}) error{
e := &Event{f, event, f.current, dst, nil, args, false, false}
err := f.beforeEventCallbacks(e)
f.transition = func() {
f.enterStateCallbacks(e)
f.afterEventCallbacks(e)
}
f.leaveStateCallbacks(e)
err = f.doTransition()
}
func (f *FSM) enterStateCallbacks(e *Event){
if fn, ok := f.callbacks[cKey{f.current, callbackEnterState}]; ok {
fn(e)
}
if fn, ok := f.callbacks[cKey{"", callbackEnterState}]; ok {
fn(e)
}
}
//執行
func (f *FSM) Transition() error{
return f.doTransition()
}
func (f *FSM) doTransition() error {
return f.transitionerObj.transition(f)
}
接着我們看下 event.go 裏面事件的定義:
type Event struct {
FSM *FSM
Event string
Src string
Dst string
Err error
Args []interface{}
canceled bool
async bool
}
有兩個函數
func (e *Event) Cancel(err ...error)
func (e *Event) Async()
第二部分:狀態機的可視化
支持兩種格式,三種方式的可視化 graphviz_visualizer.go 實現了 Graphviz 格式展示狀態機
writeHeaderLine(&buf)
writeTransitions(&buf, fsm.current, sortedEKeys, fsm.transitions)
writeStates(&buf, sortedStateKeys)
writeFooter(&buf)
mermaid_visualizer.go 實現 MermaidDiagramType 格式化,支持兩種格式:
VisualizeForMermaidWithGraphType
case FlowChart:
return visualizeForMermaidAsFlowChart(fsm), nil
case StateDiagram:
return visualizeForMermaidAsStateDiagram(fsm), nil
visualizer.go 定義了基礎接口和公用函數的實現
func Visualize(fsm *FSM) string{
VisualizeWithType
case GRAPHVIZ:
return Visualize(fsm), nil
case MERMAID:
return VisualizeForMermaidWithGraphType(fsm, StateDiagram)
case MermaidStateDiagram:
return VisualizeForMermaidWithGraphType(fsm, StateDiagram)
case MermaidFlowChart:
return VisualizeForMermaidWithGraphType(fsm, FlowChart)
}
在前文的例子中我們生成了上述三種格式對應的文件如何可視化呢,對於 graphviz,可以轉化成圖片格式
dot -T png fsm.graphviz -o fsm.dot.png
對於 Mermaid 格式,可是安裝 vscode 插件 Markdown Preview Mermaid Support,然後通過 markdown 代碼段的方式可視化,打開後點擊 cmd shift v 可以看到下面的效果:
至此源碼分析完畢。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/AB6_KSPR7cvpd2Uj9rnxgw