# libvma状态机代码阅读
# 理论基础
## 状态转换关系表现形式
- 状态装换图
![状态机](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-1.png)
- 状态装换表
![状态装换表](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-2.png)
> * 这两种表现形式是等价的,对人类而言,显然状态转换图更为的直观,但对程序而言,状态装换表却更加的直接<br>
> * 程序的世界中,用“二维数组”来承载一个表结构
## 状态机的三要素
* state 状态
* event 事件
* action 转换
## 状态迁移
状态的迁移细分为下面三个步骤:
* 离开之前的状态
* 在执行action过程(状态装换的途中)
* 进入一个新的状态
在这三个步骤中,可以针对性的做不同的事情。
![状态装换表](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-3.png)
**如图所示**
* 每一个状态,在Enter、Leave这两个时机,可以有其对应的处理逻辑。
* 每一个状态,在状态不同的状态时候,分别有其对应的处理逻辑。
# 代码实现分析
基于上面的基础理论,我们来看一下具体的代码实现。
## 状态装换表的实现
// sparse (big) table event entry
typedef struct {
int next_state; // New state to move to
sm_action_cb_t trans_func; // Do-function
} sm_event_info_t;
@sm_event_info_t 用于描述一个事件event/action<br>
@next_state 下一个状态<br>
@trans_func 该事件下事件转换函数
// sparse (big) table state entry (including all events)
typedef struct sm_state_info{
sm_action_cb_t entry_func; // Entry function
sm_action_cb_t leave_func; // Leave function
sm_event_info_t* event_info; // Event -> Transition function
} sm_state_info_t;
@sm_state_info_t 用于描述一个状态 state<br>
@entry_func leave_func 该状态的Entry、Leave函数<br>
@event_info 该状态的事件列表,表识着处于该状态下,当不同event到来时,做何种action
sm_state_info_t* m_p_sm_table; // pointer to full SM table
m_p_sm_table = (sm_state_info_t*)malloc(m_max_states * sizeof(sm_state_info_t));
for (st=0; st<m_max_states; st++) {
m_p_sm_table[st].event_info = (sm_event_info_t*)malloc(m_max_events * sizeof(sm_event_info_t));
}
以上代码中,创建了一个m_max_states * m_max_events的二维表。这个二维表就是状态装换表,用的是表驱动算法。
## 状态机的构建
用户看到的和理解的是上面显示的状态装换图,那么如何将这种状态装换图输入给程序,让程序解析并存储到转换表中呢?
** 首先 **
我们定义一种数据结构,让用户可以形象的表示出他看到和理解状态转换图。
// Short table line
typedef struct {
int state; // State to handle event
int event; // Event to handle
int next_state; // New state to move to
sm_action_cb_t action_func; // Do-function
} sm_short_table_line_t;
@sm_short_table_line_t 我们定义了一种“线”的结构,让用户可以表示出状态转换图中所看到的一条条转换路线
@state 线的一头,起始状态
@next_state 线的另外一头,终止状态
@event 事件
@action_func 动作
** 其次 **
为了让用户可以表示出Entry、Leave时的action,我们定义了两个特殊的event,一个特殊的状态来表示这个过程
#define SM_NO_ST (-2)
#define SM_STATE_ENTRY (-4)
#define SM_STATE_LEAVE (-5)
@个人觉得这个SM_STATE_ENTRY 应该命名为SM_EVENT_ENTRY
![特殊的状态](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-4.png)
这样用户就可以像上面描述状态路线一样来描述Entry、Leave
** 最终 **
用户是这么来描述一个状态装换图的:
sm_short_table_line_t sm_short_table[] = {
// {curr state, event, next state, action func }
{ SM_ST_A, SM_STATE_ENTRY, SM_NO_ST, sm_st_entry},
{ SM_ST_A, SM_EV_1, SM_ST_A, sm_st_A_ev_1},
{ SM_ST_A, SM_EV_2, SM_ST_B,sm_st_A_ev_2},
{ SM_ST_A, SM_EV_3, SM_ST_C, sm_st_A_ev_3},
{ SM_ST_A, SM_STATE_LEAVE, SM_NO_ST, sm_st_leave},
{ SM_ST_B, SM_STATE_ENTRY, SM_NO_ST, sm_st_entry},
{ SM_ST_B, SM_EV_1, SM_ST_B, sm_st_B_ev_1},
{ SM_ST_B, SM_EV_2, SM_ST_C, sm_st_B_ev_2},
{ SM_ST_B, SM_EV_3, SM_ST_A, sm_st_B_ev_3},
{ SM_ST_B, SM_STATE_LEAVE, SM_NO_ST, sm_st_leave},
{ SM_ST_C, SM_STATE_ENTRY, SM_NO_ST, sm_st_entry},
{ SM_ST_C, SM_EV_1, SM_ST_C, sm_st_C_ev_1},
{ SM_ST_C, SM_EV_2, SM_ST_A, sm_st_C_ev_2},
{ SM_ST_C, SM_EV_3, SM_ST_B, sm_st_C_ev_3},
{ SM_ST_C, SM_STATE_LEAVE, SM_NO_ST, sm_st_leave},
SM_TABLE_END
};
@SM_TABLE_END 是预先定义的一个宏,来标识数组结束
** 接口 **
state_machine(void* app_hndl,
int start_state,
int max_states,
int max_events,
sm_short_table_line_t* short_table,
sm_action_cb_t default_entry_func,
sm_action_cb_t default_leave_func,
sm_action_cb_t default_trans_func,
sm_new_event_notify_cb_t new_event_notify_func
);
@app_hndl 应用句柄
@start_state 初始化起始的状态
@max_states 最大状态个数
@max_events 最大事件个数
@short_table 状态机路线图
@default_entry_func
@default_leave_func
@default_trans_func 默认的处理函数
@new_event_notify_func 通知函数,有新事件过来后被调用
g_sm = new state_machine(NULL,
SM_ST_A,
SM_ST_LAST,
SM_EV_LAST,
sm_short_table,
sm_default_trans_func,
NULL,
NULL,
print_event_info);
## 事件触发
int process_event(int event, void* ev_data);
@event 事件
@ev_data 自定义事件数据
一个新事件的到来,会引起状态发生变换,并产生一系列的动作:
1. new_event_notify_func
2. leave_func
3. trans_func
4. entry_func
按照上面的顺序,会调用这么4个回调函数。
## 竞争
* 当一个事件正在处理,另一个事件到来,如何应对?
* process_event() 接口线程安全吗?
状态机的实现采用先来先服务的原则,理论上不存在同时到达的概念,所以引入了一个FIFO队列:
sm_fifo* m_sm_fifo; // fifo queue for the events
int state_machine::lock_in_process(int event, void* ev_data)
{
if (!m_b_is_in_process) {
m_b_is_in_process = 1;
sm_logfunc("lock_in_process: critical section free. Locking it");
}
else {
m_sm_fifo->push_back(event, ev_data);
sm_logfunc("lock_in_process: critical section is in use");
return -1;
}
return 0;
}
void state_machine::unlock_in_process()
{
m_b_is_in_process = 0;
if (m_sm_fifo->is_empty()) {
sm_logfunc("unlock_in_process: there are no pending events");
}
else {
sm_logfunc("unlock_in_process: there are pending events");
sm_fifo_entry_t ret = m_sm_fifo->pop_front();
process_event(ret.event, ret.ev_data);
}
}
以上代码描述了状态机单事件处理的过程:
1. 当当前状态空闲时,直接处理事件
2. 当当前状态非空闲时,推入FIFO队列,留着后续处理
3. 处理完当前事件后,从FIFO队列取后续事件继续处理
** 问题 **
该接口通过`m_b_is_in_process`变量来标识状态,并非线程安全。
# 理论基础
## 状态转换关系表现形式
- 状态装换图
![状态机](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-1.png)
- 状态装换表
![状态装换表](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-2.png)
> * 这两种表现形式是等价的,对人类而言,显然状态转换图更为的直观,但对程序而言,状态装换表却更加的直接<br>
> * 程序的世界中,用“二维数组”来承载一个表结构
## 状态机的三要素
* state 状态
* event 事件
* action 转换
## 状态迁移
状态的迁移细分为下面三个步骤:
* 离开之前的状态
* 在执行action过程(状态装换的途中)
* 进入一个新的状态
在这三个步骤中,可以针对性的做不同的事情。
![状态装换表](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-3.png)
**如图所示**
* 每一个状态,在Enter、Leave这两个时机,可以有其对应的处理逻辑。
* 每一个状态,在状态不同的状态时候,分别有其对应的处理逻辑。
# 代码实现分析
基于上面的基础理论,我们来看一下具体的代码实现。
## 状态装换表的实现
// sparse (big) table event entry
typedef struct {
int next_state; // New state to move to
sm_action_cb_t trans_func; // Do-function
} sm_event_info_t;
@sm_event_info_t 用于描述一个事件event/action<br>
@next_state 下一个状态<br>
@trans_func 该事件下事件转换函数
// sparse (big) table state entry (including all events)
typedef struct sm_state_info{
sm_action_cb_t entry_func; // Entry function
sm_action_cb_t leave_func; // Leave function
sm_event_info_t* event_info; // Event -> Transition function
} sm_state_info_t;
@sm_state_info_t 用于描述一个状态 state<br>
@entry_func leave_func 该状态的Entry、Leave函数<br>
@event_info 该状态的事件列表,表识着处于该状态下,当不同event到来时,做何种action
sm_state_info_t* m_p_sm_table; // pointer to full SM table
m_p_sm_table = (sm_state_info_t*)malloc(m_max_states * sizeof(sm_state_info_t));
for (st=0; st<m_max_states; st++) {
m_p_sm_table[st].event_info = (sm_event_info_t*)malloc(m_max_events * sizeof(sm_event_info_t));
}
以上代码中,创建了一个m_max_states * m_max_events的二维表。这个二维表就是状态装换表,用的是表驱动算法。
## 状态机的构建
用户看到的和理解的是上面显示的状态装换图,那么如何将这种状态装换图输入给程序,让程序解析并存储到转换表中呢?
** 首先 **
我们定义一种数据结构,让用户可以形象的表示出他看到和理解状态转换图。
// Short table line
typedef struct {
int state; // State to handle event
int event; // Event to handle
int next_state; // New state to move to
sm_action_cb_t action_func; // Do-function
} sm_short_table_line_t;
@sm_short_table_line_t 我们定义了一种“线”的结构,让用户可以表示出状态转换图中所看到的一条条转换路线
@state 线的一头,起始状态
@next_state 线的另外一头,终止状态
@event 事件
@action_func 动作
** 其次 **
为了让用户可以表示出Entry、Leave时的action,我们定义了两个特殊的event,一个特殊的状态来表示这个过程
#define SM_NO_ST (-2)
#define SM_STATE_ENTRY (-4)
#define SM_STATE_LEAVE (-5)
@个人觉得这个SM_STATE_ENTRY 应该命名为SM_EVENT_ENTRY
![特殊的状态](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-4.png)
这样用户就可以像上面描述状态路线一样来描述Entry、Leave
** 最终 **
用户是这么来描述一个状态装换图的:
sm_short_table_line_t sm_short_table[] = {
// {curr state, event, next state, action func }
{ SM_ST_A, SM_STATE_ENTRY, SM_NO_ST, sm_st_entry},
{ SM_ST_A, SM_EV_1, SM_ST_A, sm_st_A_ev_1},
{ SM_ST_A, SM_EV_2, SM_ST_B,sm_st_A_ev_2},
{ SM_ST_A, SM_EV_3, SM_ST_C, sm_st_A_ev_3},
{ SM_ST_A, SM_STATE_LEAVE, SM_NO_ST, sm_st_leave},
{ SM_ST_B, SM_STATE_ENTRY, SM_NO_ST, sm_st_entry},
{ SM_ST_B, SM_EV_1, SM_ST_B, sm_st_B_ev_1},
{ SM_ST_B, SM_EV_2, SM_ST_C, sm_st_B_ev_2},
{ SM_ST_B, SM_EV_3, SM_ST_A, sm_st_B_ev_3},
{ SM_ST_B, SM_STATE_LEAVE, SM_NO_ST, sm_st_leave},
{ SM_ST_C, SM_STATE_ENTRY, SM_NO_ST, sm_st_entry},
{ SM_ST_C, SM_EV_1, SM_ST_C, sm_st_C_ev_1},
{ SM_ST_C, SM_EV_2, SM_ST_A, sm_st_C_ev_2},
{ SM_ST_C, SM_EV_3, SM_ST_B, sm_st_C_ev_3},
{ SM_ST_C, SM_STATE_LEAVE, SM_NO_ST, sm_st_leave},
SM_TABLE_END
};
@SM_TABLE_END 是预先定义的一个宏,来标识数组结束
** 接口 **
state_machine(void* app_hndl,
int start_state,
int max_states,
int max_events,
sm_short_table_line_t* short_table,
sm_action_cb_t default_entry_func,
sm_action_cb_t default_leave_func,
sm_action_cb_t default_trans_func,
sm_new_event_notify_cb_t new_event_notify_func
);
@app_hndl 应用句柄
@start_state 初始化起始的状态
@max_states 最大状态个数
@max_events 最大事件个数
@short_table 状态机路线图
@default_entry_func
@default_leave_func
@default_trans_func 默认的处理函数
@new_event_notify_func 通知函数,有新事件过来后被调用
g_sm = new state_machine(NULL,
SM_ST_A,
SM_ST_LAST,
SM_EV_LAST,
sm_short_table,
sm_default_trans_func,
NULL,
NULL,
print_event_info);
## 事件触发
int process_event(int event, void* ev_data);
@event 事件
@ev_data 自定义事件数据
一个新事件的到来,会引起状态发生变换,并产生一系列的动作:
1. new_event_notify_func
2. leave_func
3. trans_func
4. entry_func
按照上面的顺序,会调用这么4个回调函数。
## 竞争
* 当一个事件正在处理,另一个事件到来,如何应对?
* process_event() 接口线程安全吗?
状态机的实现采用先来先服务的原则,理论上不存在同时到达的概念,所以引入了一个FIFO队列:
sm_fifo* m_sm_fifo; // fifo queue for the events
int state_machine::lock_in_process(int event, void* ev_data)
{
if (!m_b_is_in_process) {
m_b_is_in_process = 1;
sm_logfunc("lock_in_process: critical section free. Locking it");
}
else {
m_sm_fifo->push_back(event, ev_data);
sm_logfunc("lock_in_process: critical section is in use");
return -1;
}
return 0;
}
void state_machine::unlock_in_process()
{
m_b_is_in_process = 0;
if (m_sm_fifo->is_empty()) {
sm_logfunc("unlock_in_process: there are no pending events");
}
else {
sm_logfunc("unlock_in_process: there are pending events");
sm_fifo_entry_t ret = m_sm_fifo->pop_front();
process_event(ret.event, ret.ev_data);
}
}
以上代码描述了状态机单事件处理的过程:
1. 当当前状态空闲时,直接处理事件
2. 当当前状态非空闲时,推入FIFO队列,留着后续处理
3. 处理完当前事件后,从FIFO队列取后续事件继续处理
** 问题 **
该接口通过`m_b_is_in_process`变量来标识状态,并非线程安全。