序言:世间万物皆为状态机,状态机在编程过程中使用的十分广泛。使用一个好的状态机类,可使程序有条理,业务逻辑清晰。在 github上有一个经典的状态机 r-lyeh v1.0.0。该状态机支持C++11,单头文件,轻量级,跨平台,支持函数对象(std :: function),可绑定函数回调,类成员函数,lambda表达式。功能非常强大,使用非常方便,是一个不错的C++类,值得推荐。https://github.com/r-lyeh-archived/fsm
1.简单使用
传统方式:面向过程,很大一个switch结构
switch(state){
case state0:std::cout << "进入状态0" << std::endl;break;
case state1:std::cout << "进入状态1" << std::endl;break;
case state2:std::cout << "进入状态2" << std::endl;break;
case state3:std::cout << "进入状态3" << std::endl;break;
case state4:std::cout << "进入状态4" << std::endl;break;
//......
}
使用状态机类:面向对象,思路清晰明了
#include "fsm.hpp"
fsm::stack fsm;//定义一个状态机变量
//事件绑定
fsm.on('state0', 'init') = [&]( const fsm::args &args ) {
std::cout << "进入状态0" << std::endl;
};
fsm.on('state0', 'quit') = [&]( const fsm::args &args ) {
std::cout << "状态0结束" << std::endl;
};
fsm.on('state1', 'init') = [&]( const fsm::args &args ) {
std::cout << "进入状态1" << std::endl;
};
fsm.on('state1', 'quit') = [&]( const fsm::args &args ) {
std::cout << "状态1结束" << std::endl;
};
fsm.on('state2', 'init') = [&]( const fsm::args &args ) {
std::cout << "进入状态2" << std::endl;
};
fsm.on('state2', 'quit') = [&]( const fsm::args &args ) {
std::cout << "状态2结束" << std::endl;
};
//......
fsm.set('state0');//进入状态state0
2.源码自带示例
先看一下源码自带的示例,了解如何使用该状态机类。
示例使用状态机实现一个小游戏,一只蚂蚁战士在家与丛林之间不断地巡逻,在某个时间蚂蚁受到敌人攻击,进入防御状态,血量持续减少,当血量为0时,恢复巡逻状态,继续巡逻。
╮╭
╭─╮ ╭─────╮
( 0 )═{ ﹝﹝﹝ }
╰─╯ ╰─────╯
╯╯ ╰╰
状态表:
| 状态名 | 事件 | function名
─────────────────
| 行走 | 初始化 | init
| 行走 | 结束 | quit
| 行走 | 暂停 | push
| 行走 | 恢复 | back
| 行走 | 刷新 | tick
| 防御 | 初始化 | init
| 防御 | 刷新 | tick
3.源码自带示例分析:
#include
// custom states (gerunds) and actions (infinitives)
enum {
walking = 'WALK',//走路状态,注:单引号表示不是字符串,实际是一个整形,也可设置为一个整数
defending = 'DEFN',//防御状态
tick = 'tick'//刷新
};
struct ant_t {
fsm::stack fsm;
int health, distance, flow;
ant_t() : health(0), distance(0), flow(1) {
// define fsm transitions: on(state,trigger) -> do lambda
fsm.on(walking, 'init') = [&]( const fsm::args &args ) {//行走状态,绑定init函数(初始化)
std::cout << "initializing" << std::endl;
};
fsm.on(walking, 'quit') = [&]( const fsm::args &args ) {//行走状态,绑定quit函数(状态结束时清理)
std::cout << "exiting" << std::endl;
};
fsm.on(walking, 'push') = [&]( const fsm::args &args ) {//行走状态,绑定push函数,暂停时执行
std::cout << "pushing current task." << std::endl;
};
fsm.on(walking, 'back') = [&]( const fsm::args &args ) {//行走状态,绑定back函数,恢复时执行
std::cout << "back from another task. remaining distance: " << distance << std::endl;
};
fsm.on(walking, tick) = [&]( const fsm::args &args ) {//行走状态,定义一个tick动作,用于输出一些信息
std::cout << "\r" << "\\|/-"[ distance % 4 ] << " walking " << (flow > 0 ? "-->" : "<--") << " ";
distance += flow;
if( 1000 == distance ) {
std::cout << "at food!" << std::endl;
flow = -flow;
}
if( -1000 == distance ) {
std::cout << "at home!" << std::endl;
flow = -flow;
}
};
fsm.on(defending, 'init') = [&]( const fsm::args &args ) {//防御状态,绑定init函数(初始化)
health = 1000;
std::cout << "somebody is attacking me! he has " << health << " health points" << std::endl;
};
fsm.on(defending, tick) = [&]( const fsm::args &args ) {//行走状态,定义一个tick动作,用于输出一些信息
std::cout << "\r" << "\\|/-$"[ health % 4 ] << " health: (" << health << ") ";
--health;
if( health < 0 ) {
std::cout << std::endl;
fsm.pop();//血量小于0,结束本状态,恢复为之前的状态
}
};
// set initial fsm state
fsm.set( walking );//初始化状态为行走状态
}
};
int main() {
ant_t ant;//创建一个蚂蚁战士(状态机)
for(int i = 0; i < 12000; ++i) {//满足条件时进入防御状态,战士受到攻击,攻击完成自动恢复为行走状态
if( 0 == rand() % 10000 ) {
ant.fsm.push(defending);
}
ant.fsm.command(tick);//刷新每一帧,输出一些信息
}
}
源代码还有一个CD播放器的例子,大家自行去研究。
4.状态机类fsm实现原理
关键变量:std::map< bistate, fsm::call > callbacks; 实际为一个表,绑定状态和其对应事件的function
关键函数:bool call( const fsm::state &from, const fsm::state &to ), 从表中查找指定状态的指定动作对应的函数回调并执行
在stack结构中有一个表,保存的是每个状态及其所有动作对应的function(即C++11中的std :: function,可以存储,复制和调用任何可调用的目标 :包括函数,lambda表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针),当状态发生变化时会从表中查找到要执行的函数回调并执行。
- 每个状态机都有'init', 'quit', 'push', 'back'四个标准事件,在适当时机自动调用
- init:在进入某一状态时自动调用 ;quit在某一状态结束时自动调用
- push:将某一状态暂停时自动调用;back:将某一状态恢复时自动调用
- 注意:每个状态的四个标准要自己实现并绑定,如果没有实现,则不调用
- 使用set设置初始状态或改变当前状态
5.源码分析:
fsm.hpp
#pragma once
#define FSM_VERSION "1.0.0" /* (2015/11/29) Code revisited to use fourcc integers (much faster); clean ups suggested by Chang Qian
#define FSM_VERSION "0.0.0" // (2014/02/15) Initial version */
#include
#include
#include
#include
#include