looplab/fsm 源码阅读
2022-9-19 08:53:45 Author: Go语言中文网(查看原文) 阅读量:15 收藏

        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, falsefalse}    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 *FSMstring{     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可以看到下面的效果:

至此源码分析完毕。


推荐阅读

福利
我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


文章来源: http://mp.weixin.qq.com/s?__biz=MzAxMTA4Njc0OQ==&mid=2651453457&idx=2&sn=43955a7983b139473fba082dfc0cf2fe&chksm=80bb26e3b7ccaff5b87d80a8e9372be17c15f36a98e351dbe11b17641b0ef57a3b2ddd9d072d#rd
如有侵权请联系:admin#unsafe.sh