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可以看到下面的效果:
至此源码分析完毕。
推荐阅读