状态机的一种实现jc::fsm介绍(2):简单应用及图形化工具介绍
一、Jc::fsm的接口
Jc::fsm以模板类的方式来实现(具体见jc_fsm2.h代码),使用时,直接定义出一个实例即可。
a) 添加状态
static int add_status(int status, FACT entry_act, FACT exit_act, const char* name);
其中FACT定义如下:
typedef int (TOwner::*FACT)(const void *msg, int msgLen);
各参数含义:
Int status : 状态值 (为了状态机查找效率,jc::fsm中状态值类型强制为int)
FACT entry_act、FACT exit_act:状态节点的enter()和exit()函数,(具体含义见“jc_fsm介绍(1)状态机模型”一文)
const char* name:状态机名称。在打Log及状态机图形化工具jsm2jpg.py中使用
b) 添加事件驱动规则
static int event_add(int from, int to, int value, FACT handle_act);
int from: 源状态
int to: 目标状态
int value: 事件值 (为了状态机查找效率,jc::fsm中事件值类型也强制为int)
FACT handle_act: 状态转换后,调用的act 函数(具体含义见“jc_fsm介绍(1)状态机模型”一文)
c) 添加返回码驱动规则
static int code_add(int from, int to, int value, FACT handle_act)
int from: 源状态
int to: 目标状态
int value: 返回码值 (为了状态机查找效率,jc::fsm中返回码类型也强制为int)
FACT handle_act: 状态转换后,调用的act 函数
d) 添加状态区间的事件驱动规则
static int range_add_event(int from1, int from2, int to, int value, FACT handle_act)
int from1: 源状态区间起始状态; int from2:源状态区间终止状态(包含)
int to: 目标状态
int value: 事件值
FACT handle_act: 状态转换后,调用的act 函数
e) 添加状态区间的返回码驱动规则
static int range_add_code(int from1, int from2, int to, int value, FACT handle_act)
int from1: 源状态区间起始状态; int from2:源状态区间终止状态(包含)
int to: 目标状态
int value: 返回码值
FACT handle_act: 状态转换后,调用的act 函数
f) 触发外部事件
int fire_event(int event, const void* msg = NULL, int msgLen = 0)
调用该函数,先状态机触发外部时间,驱动状态机开始运转。
int event: 事件值
const void* msg:消息数据buffer
int msgLen: 消息长度
二、一个简单的例子
我们以简化的登录流程为例,流程描述大致如下:玩家发起登录请求req_login, 服务器检查玩家密码check_password,密码验证失败,则玩家转到退出流程logout;密码验证成功,服务器获取玩家数据get_data,获取数据成功,则玩家进入s_playing状态,执行play()玩游戏,否则转到logout流程。玩家在 s_playing状态中发起退出请求req_logout,则进入logout流程,执行clean_up清除操作。
对于这个流程,使用jc::fsm来实现的主要代码如下:
(1) TFsm::add_status(0, NULL, NULL, "s_init");
(2) TFsm::add_status(1, NULL, NULL, "s_logining");
(3) TFsm::add_status(2, NULL, NULL, "s_playing");
(4) TFsm::add_status(3, &CFsmTest::clean_up, NULL, "s_logout");(5) TFsm::event_add(0, 1, req_login, &CFsmTest::check_password);
(6) TFsm::code_add(1, 1, check_ok, &CFsmTest::get_data);
(7) TFsm::code_add(1, 2, get_ok, &CFsmTest::play);
(8) TFsm::code_add(1, 3, fail, NULL);
(9) TFsm::event_add(2, 3, req_logout, NULL);
其中第(1)到第(4)行代码,添加了四个状态,其中第四个状态s_logout通知指明了状态的enter函数CFsmTest::clean_up
第(5)行,添加外部事件驱动,处理玩家的登录请求req_login
第(6)行,添加返回码驱动,表示在状态1也就是s_logining状态下,返回码为check_ok时,执行&CFsmTest::get_data操作
第(7)行,表示在状态s_logining下,返回码为get_ok时(获取数据成功),进入状态2,也就是s_playing状态,然后开始执行&CFsmTest::play
第(8)行,表示在状态s_logining下,如果返回码为fail,那么直接转到s_logout状态。这里在验证密码check_password和获取数据 get_data 时,都可能返回fail。那么如何知道返回码呢?这个得结合这两个函数的代码来看,这里没有给出。
第(9)行,添加外部事件req_logout驱动,表示在状态2,也就是s_playing状态下,收到外部事件req_logout时,转到状态3,也就是s_logout 状态。
三、图形化工具jsm2jpg.py
从上面分析,大家也许也发现了一个问题,以上几行状态机初始化代码,虽然基本能表示出状态机的运行流程,但是没能完全描述出跟返回码相关的逻辑,比如上面的第(8)行代码,需要额外查阅check_password()和get_data()代码,才能画出具体的流程图。
另外,我们也知道在实际业务处理中,状态机的状态节点及转换规则很负责,随着业务需求的变化也会不断变化。而我们手动绘制的流程图往往不会随着代码的更新而更新,久而久之,手绘的流程图就成了鸡肋,而状态流程相关代码就成了下一位接手同事的梦魇。
那有没办法让状态转换图,随着代码的更新而更新呢?
Jsm2jpg.py就应运而生了。
Jsm2jpg.py是根据状态机源代码来自动生成状态流程图的
运行jsm2jpg.py –h 可以得到jsm2jpg.py的帮助,如下:
Usage: fsm2jpg.py [options]
Options:
-s SRC_FILE, --src_file=SRC_FILE
the src file, which inlcude fsm implement
-d JPG_DST_DIR, --jpg_dst_dir=JPG_DST_DIR
the diretory where jpg is placed
-n FSM_NAME, --fsm_name=FSM_NAME
the name of the fsm class
-r, --parse_range if parse range code
-t RANGE_TYPE, --range_type=RANGE_TYPE
style of range line
-v, --vertical direction of the graph
--version show program's version number and exit
-h, --help show this help message and exit
以上一个例子为例,我们把状态机代码放在fsm_test.cpp文件中,从例子中可以看出,状态机的类名是CFsmTest,则运行如下代码生成状态转换图:
python fsm2jpg.py –s fsm_test.cpp –n CFsmTest
生成的状态转换图效果如下:
其中红线表示外部事件驱动,蓝线表示返回码驱动。
大家可以对照下以上例子的状态转换描述,这样是不是清晰多了?今后流程改变的话,只要重新运行jsm2jpg.py,就可以得到新的转换图了。