系列文章目录
第一章 SMC 与有限状态机简单应用
文章目录
目录
前言
最近这段时间,受到项目上要求集成一份现有的模块代码,在收到的这份代码中,使用两套状态机配合完成既定功能。
源码包中包含两个后缀为sm的文本文件,刚开始根据文件内容和相关代码只能猜出这sm文件是一套状态机描述文件,随着慢慢摸索,了解到这是基于有限状态机与SMC(The State Machine Compiler)实现的。
SMC在sourceforge上有开源代码仓,有兴趣的可以自行去看看。
SMC - The State Machine Compiler download | SourceForge.net
引用官方介绍简单说明SMC是什么
SMC takes a state machine stored in a .sm file and generates a State pattern in 14 programming languages. Includes: default transitions, transition args, transition guards, push/pop transitions and Entry/Exit actions. See User Manual for more info.
SMC解析存储在.sm文件中的状态机,并生成14种编程语言的State模型。包括:默认转换、转换参数、转换保护、推送/弹出转换和进入/退出操作。有关更多信息,请参阅用户手册。
一、概述
既然我们都是天选牛马,那么就以一个简单的牛马状态机为例子,简单说明应用 SMC 状态机的步骤。
二、SMC 状态机应用步骤
1.创建状态机
牛马状态机 sm 文件(WorkerStateMachine.sm)如下:
%class Worker
%header Worker.h
%fsmclass WorkerStateMachine
%fsmfile WorkerStateMachine
%start WorkerStatus::Sleeping
%map WorkerStatus
%%
Eating
Entry {
playAnimation("eating");
}
Exit {
playAnimation("burp");
}
{
timeToWork()
Running {}
timeToSleep()
Sleeping {}
}
Running
Entry {
playAnimation("running");
}
Exit {
playAnimation("sweat");
}
{
reachCompany()
Working {}
reachHome()
Eating {}
}
Working
Entry {
playAnimation("typewriting");
}
Exit {
playAnimation("stretch");
}
{
timeToHome()
Running {}
}
Sleeping
Entry {
playAnimation("snore");
}
Exit {
playAnimation("yawn");
}
{
timeToMeat()
Eating {}
}
%%
2.翻译状态机
SMC支持将sm翻译为C++代码,这里将WorkerStateMachine.sm翻译为C++代码的命令如下:
java -jar ./Smc.jar -c++ -cast static_cast WorkerStateMachine.sm
3.集成状态机
在Worker类中集成WorkerStateMachine的代码如下:
// src/state_machine/Worker.h
#ifndef _WORKER_H_
#define _WORKER_H_
#include <string>
#include <map>
#include "WorkerStateMachine.h"
class Worker {
public:
Worker();
virtual ~Worker();
void startEveryDay();
void playAnimation(std::string animationName);
private:
std::map<std::string, int32_t> m_animationMap;
WorkerStateMachine m_workerFsm;
};
#endif // WORKER_H_
//Worker.cpp
#include "Worker.h"
#include <chrono>
#include <thread>
Worker::Worker()
: m_workerFsm(*this) {
m_animationMap = {
{"eating", 0},
{"sweat", 1},
{"typewriting", 2},
{"stretch", 3},
{"snore", 4},
{"yawn", 5},
{"running", 6},
{"burp", 7},
};
}
Worker::~Worker() {
m_animationMap.clear();
}
void Worker::startEveryDay() {
m_workerFsm.enterStartState();
}
void Worker::playAnimation(std::string animationName) {
int32_t t_animationMode = -1;
try {
t_animationMode = m_animationMap.at(animationName);
} catch (const std::out_of_range& e) {
printf("[ERROR] Animation not found: %s\n", animationName.c_str());
printf("[ERROR] %s\n", e.what());
}
if (t_animationMode != -1) {
printf("Playing animation: %s\n", animationName.c_str());
std::this_thread::sleep_for(std::chrono::seconds(5));
}
switch (t_animationMode) {
case 0: // 吃饭结束
m_workerFsm.timeToWork();
break;
case 1: // 跑步结束
m_workerFsm.reachCompany();
break;
case 2: // 打字结束
m_workerFsm.timeToHome();
break;
case 3: // 伸懒腰结束
printf("1111111\n");
break;
case 4: // 打呼噜结束
m_workerFsm.timeToMeat();
break;
case 5:
printf("2222222\n");
break;
default:
break;
}
}
在应用类中实例化一个 SMC 生成的状态机,并将应用类的引用传递给状态机实例作为状态机的上下文(Context);
上述代码中,在 Worker 类中声明一个 WorkerStateMachine 实例 m_workerFsm 作为数据成员,在 Worker 类构造函数的初始化列表中初始化 m_workerFsm,将 Worker 类的引用传入 m_workerFsm 作为 WorkerStateMachine 上下文;
在应用类的构造函数中实例化状态机是安全的,但在应用类构造函数中进入开始状态(enterStartState())是不安全的,因为,状态机开始状态的 Entry 动作中会调用应用类对象,而此时应用类的构造还没有完成,一些对象未初始化或者初始化函数还没执行;
Worker 类中声明一个成员函数 startEveryDay 作为启动状态机的入口,startEveryDay 函数中,通过 WorkerStateMachine 实例 m_workerFsm 执行 enterStartState()控制状态机进入开始状态。
WorkerStateMachine 中设计进入每个状态时播放状态的开始动画,退出每个状态时播放状态的结束动画。动画播放结束后,根据需要进入下一个状态。
4.运行实例
在 main 函数中创建 Worker 实例并开始牛马的每一天:
// main.cpp
#include "Worker.h"
int main() {
Worker worker;
worker.startEveryDay();
return 0;
}
上述简易的代码运行后,输出内容如下:
ts@ts-OptiPlex-7070:~/work/testcase/ccpTest/build$ ./myapp
Playing animation: snore
Playing animation: yawn
2222222
Playing animation: eating
Playing animation: burp
Playing animation: running
三、SMC 状态机相关
1.自动生成状态转换图
SMC支持根据sm自动化生成状态转换图,根据生成转换图的命令如下:
java -jar ./Smc.jar -graph WorkerStateMachine.sm
dot -Tpng ./WorkerStateMachine.dot -o ./WorkerStateMachine.png