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