zl程序教程

您现在的位置是:首页 >  Java

当前栏目

looplab/fsm 源码阅读

2023-02-18 16:32:25 时间

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可以看到下面的效果:

至此源码分析完毕。