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.Dstf.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 transitionerstateMu sync.RWMutexeventMu sync.Mutex}
type EventDesc struct {Name stringSrc []stringDst string}
type Callback func(*Event)type Events []EventDesctype Callbacks map[string]Callback
为了线程安全,对状态转换和事件回调两个map都定义了锁。他们的key分别是:
type cKey struct {target stringcallbackType int}type eKey struct {event stringsrc string}
其中transitioner是一个接口
type transitioner interface {transition(*FSM) error}
默认实现如下,它调用了状态机的transition方法:
func (t transitionerStruct) transition(f *FSM) error {f.transition()}
状态机实现的函数接口有:
func (f *FSM) AvailableTransitions() []stringfunc (f *FSM) Can(event string) boolfunc (f *FSM) Cannot(event string) boolfunc (f *FSM) Current() stringfunc (f *FSM) Event(event string, args ...interface{}) errorfunc (f *FSM) Is(state string) boolfunc (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 *FSMEvent stringSrc stringDst stringErr errorArgs []interface{}canceled boolasync 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 格式化,支持两种格式:
VisualizeForMermaidWithGraphTypecase FlowChart:return visualizeForMermaidAsFlowChart(fsm), nilcase StateDiagram:return visualizeForMermaidAsStateDiagram(fsm), nil
visualizer.go定义了基础接口和公用函数的实现
func Visualize(fsm *FSM) string{VisualizeWithTypecase GRAPHVIZ:return Visualize(fsm), nilcase 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可以看到下面的效果:
至此源码分析完毕。
推荐阅读