FSM状态机的应用场景
FSM(有限状态机)广泛应用于各种系统中,尤其适用于那些具有有限状态且状态之间存在明确转换规则的场景。以下是一些典型的应用场景:
- 游戏开发:处理游戏中的不同状态(如菜单、游戏进行中、暂停、游戏结束等)。
- 用户认证系统:管理用户的登录、注销、认证等状态。
- 网络协议解析:处理协议的不同阶段(如握手、数据传输、断开连接等)。
- 设备驱动程序:管理硬件设备的不同操作模式。
- 工作流管理:处理复杂业务流程中的各个步骤和状态。
状态机实现
先给出状态机的架构图:
下面我们就来用 go 代码实现一个状态机。下面有代码的文字解释。
fsm.go
package api
import (
"time"
)
// Event 是在回调中传递的事件信息。
type Event struct {
FSM *FSM // FSM 是当前状态机的引用。
Event int // Event 是事件的名称。
Src int // Src 是状态转换前的状态。
Dst int // Dst 是状态转换后的状态。
Err error // Err 是可选的错误信息,可以在回调中返回。
Args []interface{} // Args 是传递给回调函数的可选参数列表。
}
// FSM 表示有限状态机。
type FSM struct {
current int // current 表示状态机的当前状态。
transitions map[eKey]int // transitions 映射事件和源状态到目标状态。
callbacks map[cKey]Callback // callbacks 映射事件和目标到回调函数。
StateChangeTime time.Time // StateChangeTime 记录状态改变的时间。
}
// EventDesc 定义了状态机的事件描述。
type EventDesc struct {
Name int // Name 是事件名称,用于触发状态转换。
Src []int // Src 是源状态的切片,定义了哪些状态可以触发该事件。
Dst int // Dst 是事件触发后的目标状态。
Before Callback // Before 是在事件触发前调用的回调函数。
Enter Callback // Enter 是在进入目标状态时调用的回调函数。
After Callback // After 是在事件触发后调用的回调函数。
}
// Callback 是回调函数的类型定义。
type Callback func(*Event)
// cKey 是用于将回调映射到目标的键结构。
type cKey struct {
target int // target 是目标状态。
callbackType int // callbackType 是回调类型(如前置、进入、后置回调)。
}
// eKey 是用于存储转换映射的键结构。
type eKey struct {
event int // event 是事件的名称。
src int // src 是源状态。
}
// NewFSM 创建并初始化一个新的 FSM 实例。
func NewFSM(initial int, events []EventDesc) *FSM {
f := &FSM{
current: initial, // 初始化状态机的当前状态。
transitions: make(map[eKey]int), // 初始化状态转换映射。
callbacks: make(map[cKey]Callback), // 初始化回调函数映射。
StateChangeTime: time.Now(), // 设置初始状态改变时间。
}
// 设置事件描述中的状态转换和回调函数。
for _, e := range events {
for _, src := range e.Src {
f.transitions[eKey{e.Name, src}] = e.Dst
}
if e.Before != nil {
f.callbacks[cKey{e.Name, callbackBeforeEvent}] = e.Before
}
if e.Enter != nil {
f.callbacks[cKey{e.Name, callbackEnterState}] = e.Enter
}
if e.After != nil {
f.callbacks[cKey{e.Name, callbackAfterEvent}] = e.After
}
}
return f
}
// Event 触发一个事件,进行状态转换并调用相关回调函数。
func (f *FSM) Event(event int, args ...interface{}) bool {
if f == nil {
return false // 状态机实例为空时,返回false。
}
dst, ok := f.transitions[eKey{event, f.current}]
if !ok {
fmt.Println("未找到状态转换") // 如果找不到状态转换,打印错误信息。
return false
}
e := &Event{f, event, f.current, dst, nil, args} // 创建事件对象。
f.beforeEventCallbacks(e) // 调用事件触发前的回调函数。
f.current = dst // 更新状态机的当前状态。
f.StateChangeTime = time.Now() // 更新状态改变时间。
f.enterStateCallbacks(e) // 调用进入状态的回调函数。
f.afterEventCallbacks(e) // 调用事件触发后的回调函数。
return true
}
// beforeEventCallbacks 调用事件触发前的回调函数。
func (f *FSM) beforeEventCallbacks(e *Event) {
if fn, ok := f.callbacks[cKey{e.Event, callbackBeforeEvent}]; ok {
fn(e)
}
}
// enterStateCallbacks 调用进入状态的回调函数。
func (f *FSM) enterStateCallbacks(e *Event) {
if fn, ok := f.callbacks[cKey{e.Event, callbackEnterState}]; ok {
fn(e)
}
}
// afterEventCallbacks 调用事件触发后的回调函数。
func (f *FSM) afterEventCallbacks(e *Event) {
if fn, ok := f.callbacks[cKey{e.Event, callbackAfterEvent}]; ok {
fn(e)
}
}
// Current 返回 FSM 当前的状态。
func (f *FSM) Current() int {
return f.current // 返回当前状态。
}
// 回调类型常量
const (
callbackNone int = iota
callbackBeforeEvent
callbackEnterState
callbackAfterEvent
)
代码解释
这段代码实现了一个有限状态机(FSM,Finite State Machine)的框架,支持状态转换以及在转换前后执行回调函数。通过这个框架,你可以定义状态机的状态、事件和相应的回调函数,并在运行时根据事件来驱动状态的转换。以下是对这段代码的简要解释:
主要结构体和类型
- Event:表示在状态转换中传递的事件信息。包含了当前的状态机引用、事件名称、源状态、目标状态、错误信息以及可选的参数列表。
- FSM:表示有限状态机的核心结构体。包含当前状态、状态转换映射、回调函数映射以及状态改变的时间。
- EventDesc:定义状态机的事件描述。包含事件名称、源状态列表、目标状态以及可能的回调函数(触发前、进入状态和触发后)。
- Callback:定义回调函数的类型,它是一个接受
*Event参数的函数。 - cKey 和 eKey:用于映射回调函数和状态转换的键结构,分别用于回调映射和转换映射。
主要函数
- NewFSM(initial int, events []EventDesc) FSM:创建并初始化一个新的状态机实例。通过传入初始状态和事件描述列表,设置状态转换和回调函数。
- Event(event int, args ...interface{}) bool:触发一个事件,并根据定义的状态转换执行状态的改变和相应的回调函数。首先会调用事件触发前的回调函数,然后更新当前状态和状态改变时间,接着调用进入状态的回调函数,最后调用事件触发后的回调函数。
- **beforeEventCallbacks(e *Event)、enterStateCallbacks(e Event)、afterEventCallbacks(e Event) :分别处理事件触发前、进入状态和事件触发后的回调函数。
- Current() int:返回状态机当前的状态。
回调类型常量
callbackNone:无回调。callbackBeforeEvent:事件触发前的回调。callbackEnterState:进入状态的回调。callbackAfterEvent:事件触发后的回调。
使用方式
- 通过
NewFSM创建一个 FSM 实例。 - 通过
Event函数来触发事件,改变状态,并调用对应的回调函数。
实战代码
main.go,注意 fsm.go 在同一个目录下
package main
import (
"fmt"
)
// 状态常量
const (
StateIdle = iota
StatePlaying
StatePaused
StateGameOver
)
// 事件常量
const (
EventStart = iota
EventPause
EventResume
EventEnd
)
// 回调函数示例
func beforeStart(e *Event) {
fmt.Println("Before starting the game.")
}
func onEnterPlaying(e *Event) {
fmt.Println("Entering playing state.")
}
func afterEnd(e *Event) {
fmt.Println("After ending the game.")
}
func main() {
// 定义事件描述
events := []EventDesc{
{Name: EventStart, Src: []int{StateIdle}, Dst: StatePlaying, Before: beforeStart, Enter: onEnterPlaying},
{Name: EventPause, Src: []int{StatePlaying}, Dst: StatePaused},
{Name: EventResume, Src: []int{StatePaused}, Dst: StatePlaying},
{Name: EventEnd, Src: []int{StatePlaying, StatePaused}, Dst: StateGameOver, After: afterEnd},
}
// 创建 FSM 实例
fsm := NewFSM(StateIdle, events)
// 触发事件
fmt.Println("Current state:", fsm.Current()) // 输出: Current state: 0 (StateIdle)
fsm.Event(EventStart) // Before starting the game. Entering playing state.
fmt.Println("Current state:", fsm.Current()) // 输出: Current state: 1 (StatePlaying)
fsm.Event(EventPause)
fmt.Println("Current state:", fsm.Current()) // 输出: Current state: 2 (StatePaused)
fsm.Event(EventResume)
fmt.Println("Current state:", fsm.Current()) // 输出: Current state: 1 (StatePlaying)
fsm.Event(EventEnd) // After ending the game.
fmt.Println("Current state:", fsm.Current()) // 输出: Current state: 3 (StateGameOver)
}
代码结构图:
例子的详细解释
- 定义状态和事件常量:首先,我们定义了状态和事件的常量,以便在代码中使用。
- 回调函数:然后,我们定义了一些回调函数,这些函数将在状态转换前后触发。
- 事件描述:接下来,我们定义了事件描述,每个事件描述包含事件名称、源状态、目标状态和可选的回调函数。
- 创建FSM实例:使用定义的事件描述,我们创建了一个FSM实例,初始状态为
StateIdle。 - 触发事件并检查状态:最后,我们触发了一系列事件,并在每次事件后检查当前状态,输出结果如下:
-
- 开始时状态为
StateIdle。 - 触发
EventStart后状态变为StatePlaying。 - 触发
EventPause后状态变为StatePaused。 - 触发
EventResume后状态变为StatePlaying。 - 触发
EventEnd后状态变为StateGameOver。
- 开始时状态为
通过这个例子,可以清晰地看到FSM如何管理状态转换以及在转换过程中如何调用回调函数。这种设计使得状态管理变得更加明确和易于维护。