State Machine Compiler 用法:使用 SMC 工具自动生成状态机代码
Qidi 2020.12.18 (MarkDown & EnterpriseArchitect & Haroopad)
0. SMC 简介
SMC (State Machine Compiler) 目前还没有正式中文名,这里把它意译成 状态机生成器,其主要作者是 Charles W. Rapp。状态机生成器实际上早在 2000 年左右就由 Robert C. Martin (他还有个网名叫 Uncle Bob) 创造出来了,现在一般把他发明的状态机生成器称作 OSMC (Original State Machine Compiler)。Charles 曾经是 Robert 的同事,在后者离职后,前者接手了 OSMC 的维护工作[1]。
本文介绍的 SMC 使用 Java 写成,在 OSMC 的基础上进行了扩展和优化,可用于生成基于 状态模式(State Pattern) 实现的状态机代码,并且支持多达 14 种编程语言[2]。相关源码可以在 SourceForge.net 上查看。如果对 SMC 的代码不感兴趣,也可以直接下载最新版本的 jar 包使用(点我下载)。
1. 为 SMC 配置运行环境
下载并解压后,得到可执行文件 Smc.jar,但需要先安装 Java 运行环境才能运行它。
安装 JRE 的教程网上很多,读者可以自行搜索,或者参考《JRE的安装及环境变量配置》这篇文章进行操作。
2. 选定目标场景,确定状态迁移关系
比如我们对 干饭人 混吃等死的美好生活很感兴趣,想实现一个表现 干饭人的一天 的状态机。经过一段时间遐想后,可以得出和干饭人直接相关的状态有:
- 饿了
- 渴了
- 吃饭
- 喝水
- 闲逛
- 睡觉
- 满足
其中 满足 可以作为干饭人的起始状态,视天气而定,转换到 闲逛 或者 睡觉 状态,在这两种状态下,随时间流逝又可以转换为 饿了 或者 渴了 状态,继而转换到 吃饭 或者 喝水 状态。吃掉一定量的食物后(喝水同理),干饭人有可能还没吃饱,这时就会回到 饿了 的状态;或者有可能已经吃饱但又口渴了,这时就会转换到 渴了 的状态;如果此时既不饿也不渴,就会转回到 满足 的状态,实现一次循环。
如此,我们就可以手绘出一幅状态迁移图,如下所示:

3. 编写 .sm 文件
理清状态迁移关系之后,为了下一步使用 SMC 工具自动生成代码,我们就可以开始编写 .sm 文件了。
3.1 .sm 文件的格式和语法
关于 .sm 文件的格式和语法,详细说明可以参考 官方文档(点我阅读)。本文只对基础用法进行说明,搭配 3.2节 中的 示例文件 进行阅读,效果更佳。
-
格式
- 添加源文件版权声明/免责声明
使用
%{...}%将声明内容括起来进行标识,比如:%{ // put disclaimer or copy right here %}- 添加注释
使用
//进行标识,比如:Thirsty { // to drink processState Drinking {} }- 添加具体实现类的类名
用于实现状态机中的具体行为的类的名字。用
%class关键字来标识,比如:%class EatMealMan- 添加声明具体实现类的头文件名
包含具体实现类的类声明的头文件的名字。用
%header关键字来标识,比如:%header EatMealMan.h- 指定要生成的状态机类的类名
要生成的状态机类的名字。用
%fsmclass关键字来标识,比如:%fsmclass EatMealManFSM- 指定要生成的状态机类所在的源文件名
要生成的包含状态机类实现的源文件名(不含扩展文件名)。用
%fsmfile关键字来标识,比如:%fsmfile EatMealManFSM- 指定状态机起始状态
使用
%start关键字来标识,比如:%start EatMealManMap::Satisfied- 指定状态迁移图名称
使用
%map关键字来标识,比如:%map EatMealManMap- 添加各状态定义及迁移关系
使用
%%...%%将各状态及其迁移关系扩起来进行标识,比如:%% Satisfied { processState [!context.getOwner().isGoodWeather()] Sleeping {} processState Wandering {} } Wandering { processState Satisfied { hangOutAWhile(ctxt.getStateDuration()); } } Sleeping { processState Satisfied { sleepAWhile(ctxt.getStateDuration()); } } %% -
语法
- 语法总览
StateName Entry { // 在这里添加进入 StateName 状态时的行为 } Exit { // 在这里添加退出 StateName 状态时的行为 } { TriggerMethod [triggerConditions] NewStateName { // 在这里添加转换到 NewStateName 状态前的行为 } }其中
StateName是当前的状态名,NewStateName是要切换到的状态名;
Entry和Exit及它们的行为定义都是可选的,如果没有需要可以不写;
TriggerMethod是触发状态转换的方法名,triggerCondition是转换到NewStateName状态需要满足的条件。triggerCondition也是可选的。- 简单状态迁移
只需定义当前状态、目标状态、触发方法。如下:
Thirsty { // to drink processState Drinking {} }- 条件性状态迁移
在简单状态迁移写法的基础上,在
TriggerMethod后,使用 方括号[]添加状态转换的保护条件(Guard Condition)。只有当条件满足时,才会进行状态切换。如下:Sleeping { processState [context.getOwner().isThirsty()] Thirsty {} processState [context.getOwner().isHungry()] Starving {} processState Satisfied {} }在多个相同的
TriggerName后添加不同的保护条件和目标状态,其含义为:在该函数中,满足不同条件时分别切换到不同的状态。- 指定转换到新状态前的行为
在目标状态的花括号中添加的行为,会在转换到目标状态前被执行。如下:
Thirsty { // to drink processState Drinking { setThirstyFlag(true); } }- 带参数的状态迁移
在
TriggerMethod后,使用 圆括号()添加触发函数的参数。如下:Thirsty { maintenanceTime(isBlocked: bool) nil { startCLI(isBlocked); } }其中
maintenanceTime是触发函数的名字,isBlocked是函数参数,bool是参数类型。这个函数的作用是使干饭人进入到维护模式,以便开发人员实时检查运行状态。nil表示这个函数不会触发状态切换,状态机会将当前状态设置为最终状态。- 指定进入状态时的行为
使用
Entry关键字来指定进入状态时的行为。如下:Eating Entry { eatBread(ctxt.getBreadSupply()); } { // still hungry after eating processState [context.getOwner().isHungry()] Starving {} // feel thirsty after eating processState [context.getOwner().isThirsty()] Thirsty { setThirstyFlag(true); } // well fed processState Satisfied { setStarvingFlag(false); } }这个示例表示:进入
Eating状态时,就执行eatBread()函数。- 指定退出状态时的行为
类似的,使用
Exit关键字来指定退出状态时的行为。如下:Satisfied Exit { saySomething(); } { // raining outside processState [!context.getOwner().isGoodWeather()] Sleeping {} // warm outside processState [context.getOwner().isGoodWeather()] Wandering {} maintenanceTime(isBlocked: bool) nil { startCLI(isBlocked); } }- 定义默认方法
某些情况下,我们可能需要为某个状态提供多个函数方法。但由于生成的代码基于 状态模式 实现,每个状态要提供的方法个数和名称都必须相同。这时,我们就可以定义默认方法,作为其它不必支持多个函数方法的状态的默认实现;或者作为当我们对各状态要处理的所有场景考虑不周时的最终保障。
新增
Default状态,并为需要实现的默认方法指定内容。如下:Default { processState nil { doSelfCheck(); dumpRuntimeInfo(); } }
3.2 .sm 文件示例
了解 .sm 文件的格式及语法后,根据前文得出的状态迁移关系,编写文件 EatMealMan.sm。内容如下:
%{
//
// Copyright (c) 2020
// All rights reserved.
//
// Author: Qidi (huang_qi_di@hotmail.com)
//
%}
// This FSM works for the EatMealMan class only and
// only the EatMealMan class may instantiate it.
%class EatMealMan
%header EatMealMan.h
%fsmclass EatMealManFSM
%fsmfile EatMealManFSM
// A %map name cannot be the same as the FSM class name.
%start EatMealManMap::Satisfied
%map EatMealManMap
%%
Satisfied
{
// raining outside
processState [!context.getOwner().isGoodWeather()]
Sleeping
{}
// warm outside
processState [context.getOwner().isGoodWeather()]
Wandering
{}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Wandering
Entry
{
// waterDemand and breadDemand is
// the consumption happened during wandering
consumeWater(ctxt.getWaterDemand());
consumeBread(ctxt.getBreadDemand());
}
{
// happen to be thirsty
processState [context.getOwner().isThirsty()]
Thirsty
{}
// happen to be hungry
processState [context.getOwner().isHungry()]
Starving
{}
// neither thirsty nor hungry
processState
Satisfied
{
hangOutAWhile(ctxt.getStateDuration());
}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Sleeping
Entry
{
consumeWater(ctxt.getWaterDemand());
consumeBread(ctxt.getBreadDemand());
}
{
processState [context.getOwner().isThirsty()]
Thirsty
{}
processState [context.getOwner().isHungry()]
Starving
{}
processState
Satisfied
{
sleepAWhile(ctxt.getStateDuration());
}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Thirsty
{
// to drink
processState
Drinking
{
setThirstyFlag(true);
}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Starving
{
// to eat
processState
Eating
{
setStarvingFlag(true);
}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Drinking
Entry
{
drinkWater(ctxt.getWaterSupply());
}
{
// still thirsty after drinking
processState [context.getOwner().isThirsty()]
Thirsty
{}
// feel hungry after drinking
processState [context.getOwner().isHungry()]
Starving
{
setStarvingFlag(true);
}
// refilled
processState
Satisfied
{
setThirstyFlag(false);
}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Eating
Entry
{
eatBread(ctxt.getBreadSupply());
}
{
// still hungry after eating
processState [context.getOwner().isHungry()]
Starving
{}
// feel thirsty after eating
processState [context.getOwner().isThirsty()]
Thirsty
{
setThirstyFlag(true);
}
// well fed
processState
Satisfied
{
setStarvingFlag(false);
}
maintenanceTime(isBlocked: bool)
nil
{
startCLI(isBlocked);
}
}
Default
{
processState
nil
{
doSelfCheck();
dumpRuntimeInfo();
}
}
%%
4. 生成状态机代码
生成状态机代码的命令格式为:
java -jar <smc.jar文件位置> <目标语言类型> [参数] <sm文件名>
以我本地执行的具体命令为例,生成 C++ 源文件 EatMealManFSM.cpp 和头文件 EatMealManFSM.h,命令如下:
java -jar ..\Smc.jar -c++ -noex -nocatch -nostreams EatMealMan.sm
其中 -noex 表示不生成抛出异常代码,-nocatch 表示不生成捕获异常代码,-nostreams 表示不使用 iostream 库打印日志信息。
更多参数含义可以参考 官方说明(点我查看)。
生成的 EatMealManFSM.cpp 内容如下:
//
// ex: set ro:
// DO NOT EDIT.
// generated by smc (http://smc.sourceforge.net/)
// from file : EatMealMan.sm
//
//
// Copyright (c) 2020
// All rights reserved.
//
// Author: Qidi (huang_qi_di@hotmail.com)
//
#include "EatMealMan.h"
#include "EatMealManFSM.h"
using namespace statemap;
// Static class declarations.
EatMealManMap_Satisfied EatMealManMap::Satisfied("EatMealManMap::Satisfied", 0);
EatMealManMap_Wandering EatMealManMap::Wandering("EatMealManMap::Wandering", 1);
EatMealManMap_Sleeping EatMealManMap::Sleeping("EatMealManMap::Sleeping", 2);
EatMealManMap_Thirsty EatMealManMap::Thirsty("EatMealManMap::Thirsty", 3);
EatMealManMap_Starving EatMealManMap::Starving("EatMealManMap::Starving", 4);
EatMealManMap_Drinking EatMealManMap::Drinking("EatMealManMap::Drinking", 5);
EatMealManMap_Eating EatMealManMap::Eating("EatMealManMap::Eating", 6);
void EatMealManState::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
Default(context);
}
void EatMealManState::processState(EatMealManFSM& context)
{
Default(context);
}
void EatMealManState::Default(EatMealManFSM& context)
{
assert(false);
}
void EatMealManMap_Default::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.doSelfCheck();
ctxt.dumpRuntimeInfo();
context.setState(endState);
}
void EatMealManMap_Satisfied::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Satisfied::processState(EatMealManFSM& context)
{
if (!context.getOwner().isGoodWeather())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Sleeping);
context.getState().Entry(context);
}
else if (context.getOwner().isGoodWeather())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Wandering);
context.getState().Entry(context);
} else
{
EatMealManMap_Default::processState(context);
}
}
void EatMealManMap_Wandering::Entry(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
ctxt.consumeWater(ctxt.getWaterDemand());
ctxt.consumeBread(ctxt.getBreadDemand());
}
void EatMealManMap_Wandering::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Wandering::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
if (context.getOwner().isThirsty())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Thirsty);
context.getState().Entry(context);
}
else if (context.getOwner().isHungry())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Starving);
context.getState().Entry(context);
}
else
{
context.getState().Exit(context);
context.clearState();
ctxt.hangOutAWhile(ctxt.getStateDuration());
context.setState(EatMealManMap::Satisfied);
context.getState().Entry(context);
}
}
void EatMealManMap_Sleeping::Entry(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
ctxt.consumeWater(ctxt.getWaterDemand());
ctxt.consumeBread(ctxt.getBreadDemand());
}
void EatMealManMap_Sleeping::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Sleeping::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
if (context.getOwner().isThirsty())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Thirsty);
context.getState().Entry(context);
}
else if (context.getOwner().isHungry())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Starving);
context.getState().Entry(context);
}
else
{
context.getState().Exit(context);
context.clearState();
ctxt.sleepAWhile(ctxt.getStateDuration());
context.setState(EatMealManMap::Satisfied);
context.getState().Entry(context);
}
}
void EatMealManMap_Thirsty::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Thirsty::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
context.getState().Exit(context);
context.clearState();
ctxt.setThirstyFlag(true);
context.setState(EatMealManMap::Drinking);
context.getState().Entry(context);
}
void EatMealManMap_Starving::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Starving::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
context.getState().Exit(context);
context.clearState();
ctxt.setStarvingFlag(true);
context.setState(EatMealManMap::Eating);
context.getState().Entry(context);
}
void EatMealManMap_Drinking::Entry(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
ctxt.drinkWater(ctxt.getWaterSupply());
}
void EatMealManMap_Drinking::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Drinking::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
if (context.getOwner().isThirsty())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Thirsty);
context.getState().Entry(context);
}
else if (context.getOwner().isHungry())
{
context.getState().Exit(context);
context.clearState();
ctxt.setStarvingFlag(true);
context.setState(EatMealManMap::Starving);
context.getState().Entry(context);
}
else
{
context.getState().Exit(context);
context.clearState();
ctxt.setThirstyFlag(false);
context.setState(EatMealManMap::Satisfied);
context.getState().Entry(context);
}
}
void EatMealManMap_Eating::Entry(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
ctxt.eatBread(ctxt.getBreadSupply());
}
void EatMealManMap_Eating::maintenanceTime(EatMealManFSM& context, bool isBlocked)
{
EatMealMan& ctxt = context.getOwner();
EatMealManState& endState = context.getState();
context.clearState();
ctxt.startCLI(isBlocked);
context.setState(endState);
}
void EatMealManMap_Eating::processState(EatMealManFSM& context)
{
EatMealMan& ctxt = context.getOwner();
if (context.getOwner().isHungry())
{
context.getState().Exit(context);
// No actions.
context.setState(EatMealManMap::Starving);
context.getState().Entry(context);
}
else if (context.getOwner().isThirsty())
{
context.getState().Exit(context);
context.clearState();
ctxt.setThirstyFlag(true);
context.setState(EatMealManMap::Thirsty);
context.getState().Entry(context);
}
else
{
context.getState().Exit(context);
context.clearState();
ctxt.setStarvingFlag(false);
context.setState(EatMealManMap::Satisfied);
context.getState().Entry(context);
}
}
//
// Local variables:
// buffer-read-only: t
// End:
//
生成的 EatMealManFSM.h 内容如下:
//
// ex: set ro:
// DO NOT EDIT.
// generated by smc (http://smc.sourceforge.net/)
// from file : EatMealMan.sm
//
#ifndef EATMEALMANFSM_H
#define EATMEALMANFSM_H
#define SMC_NO_EXCEPTIONS
#include <statemap.h>
// Forward declarations.
class EatMealManMap;
class EatMealManMap_Satisfied;
class EatMealManMap_Wandering;
class EatMealManMap_Sleeping;
class EatMealManMap_Thirsty;
class EatMealManMap_Starving;
class EatMealManMap_Drinking;
class EatMealManMap_Eating;
class EatMealManMap_Default;
class EatMealManState;
class EatMealManFSM;
class EatMealMan;
class EatMealManState :
public statemap::State
{
public:
EatMealManState(const char * const name, const int stateId)
: statemap::State(name, stateId)
{};
virtual void Entry(EatMealManFSM&) {};
virtual void Exit(EatMealManFSM&) {};
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
protected:
virtual void Default(EatMealManFSM& context);
};
class EatMealManMap
{
public:
static EatMealManMap_Satisfied Satisfied;
static EatMealManMap_Wandering Wandering;
static EatMealManMap_Sleeping Sleeping;
static EatMealManMap_Thirsty Thirsty;
static EatMealManMap_Starving Starving;
static EatMealManMap_Drinking Drinking;
static EatMealManMap_Eating Eating;
};
class EatMealManMap_Default :
public EatMealManState
{
public:
EatMealManMap_Default(const char * const name, const int stateId)
: EatMealManState(name, stateId)
{};
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Satisfied :
public EatMealManMap_Default
{
public:
EatMealManMap_Satisfied(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Wandering :
public EatMealManMap_Default
{
public:
EatMealManMap_Wandering(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void Entry(EatMealManFSM&);
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Sleeping :
public EatMealManMap_Default
{
public:
EatMealManMap_Sleeping(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void Entry(EatMealManFSM&);
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Thirsty :
public EatMealManMap_Default
{
public:
EatMealManMap_Thirsty(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Starving :
public EatMealManMap_Default
{
public:
EatMealManMap_Starving(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Drinking :
public EatMealManMap_Default
{
public:
EatMealManMap_Drinking(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void Entry(EatMealManFSM&);
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManMap_Eating :
public EatMealManMap_Default
{
public:
EatMealManMap_Eating(const char * const name, const int stateId)
: EatMealManMap_Default(name, stateId)
{};
virtual void Entry(EatMealManFSM&);
virtual void maintenanceTime(EatMealManFSM& context, bool isBlocked);
virtual void processState(EatMealManFSM& context);
};
class EatMealManFSM :
public statemap::FSMContext
{
public:
explicit EatMealManFSM(EatMealMan& owner)
: FSMContext(EatMealManMap::Satisfied),
_owner(owner)
{};
EatMealManFSM(EatMealMan& owner, const statemap::State& state)
: FSMContext(state),
_owner(owner)
{};
virtual void enterStartState()
{
getState().Entry(*this);
return;
}
inline EatMealMan& getOwner()
{
return (_owner);
};
inline EatMealManState& getState()
{
assert(_state != NULL);
return dynamic_cast<EatMealManState&>(*_state);
};
inline void maintenanceTime(bool isBlocked)
{
getState().maintenanceTime(*this, isBlocked);
};
inline void processState()
{
getState().processState(*this);
};
private:
EatMealMan& _owner;
};
#endif // EATMEALMANFSM_H
//
// Local variables:
// buffer-read-only: t
// End:
//
5. 生成状态迁移图
要想生成状态迁移图,需要先生成 .dot 文件。命令格式为:
java -jar <smc.jar文件位置> -graph [参数] <sm文件名>
以我本地执行的具体命令为例,生成 .dot 文件 EatMealMan.dot,命令如下:
java -jar ..\Smc.jar -graph -glevel 1 EatMealMan.sm
再使用 graphviz 工具集(点我下载安装)中的 dot 命令,生成状态迁移图:
dot -Tpng EatMealMan.dot -o EatMealManMap.png
其中,-Txxx 参数用于指定生成的图片格式,-o 参数用于指定生成的图片文件名。
有关 graphviz 的安装和详细使用方式,网上也有很多。本文不作说明,请读者朋友们自行检索。
自动生成的状态迁移图如下:

和手绘的相比,自动生成的迁移图丑是丑了点,但状态间的关系是一致的。
6. 基于生成的状态机代码实现具体功能
SMC 工具为我们生成的代码只能管理状态转换关系,各状态中被调用的函数的具体行为仍然要我们自己实现 —— 也就是要实现上文中出现的 isGoodWeather()、isHungry()、sleepAWhile()、consumeWater() 等函数的函数体。
在 EatMealMan.sm 中已经指定了具体实现类的类名和头文件名分别为 EatMealMan 和 EatMealMan.h。于是我们创建源文件 EatMealMan.cpp 和头文件 EatMealMan.h。
EatMealMan.cpp 内容如下(用于说明目的,并未完整实现):
#include "EatMealMan.h"
EatMealMan::EatMealMan()
{
_fsm = new EatMealManFSM();
}
EatMealMan::~EatMealMan() {}
bool EatMealMan::isGoodWeather()
{
return mWeatherState;
}
bool EatMealMan::isThirsty()
{
float waterShortage = mMaxWater - mWaterStorage;
if (waterShortage < 0.1)
return true;
return false;
}
bool EatMealMan::isHungry()
{
float breadShortage = mMaxBread - mBreadStorage;
if (breadShortage < 1)
return true;
return false;
}
float EatMealMan::getWaterDemand() {}
float EatMealMan::getBreadDemand() {}
void EatMealMan::consumeWater(float waterVolume) {}
void EatMealMan::consumeBread(float breadNum) {}
void EatMealMan::hangOutAWhile(long durationSeconds) {}
void EatMealMan::sleepAWhile(long durationSeconds) {}
long EatMealMan::getStateDuration() {}
void EatMealMan::setThirstyFlag(bool val)
{
mThirstyState = val;
}
void EatMealMan::setStarvingFlag(bool val)
{
mStarvingState = val;
}
float EatMealMan::getBreadSupply() {}
float EatMealMan::getWaterSupply() {}
void EatMealMan::eatBread(float breadNum) {}
void EatMealMan::drinkWater(float waterVolume) {}
void EatMealMan::doSelfCheck() {}
void EatMealMan::dumpRuntimeInfo() {}
void EatMealMan::startCLI(bool isBlocked) {}
EatMealMan.h 内容如下:
#ifndef __EATMEALMAN_H__
#define __EATMEALMAN_H__
Class EatMealMan {
bool mWeatherState;
bool mStarvingState;
bool mThirstyState;
float mWaterStorage;
float mBreadStorage;
const float mMaxBread = 10.0;
const float mMaxWater = 2.0;
EatMealManFSM _fsm;
public:
bool isGoodWeather();
bool isThirsty();
bool isHungry();
float getWaterDemand();
float getBreadDemand();
void consumeWater(float waterVolume);
void consumeBread(float breadNum);
void hangOutAWhile(long durationSeconds);
void sleepAWhile(long durationSeconds);
long getStateDuration();
void setThirstyFlag(bool val);
void setStarvingFlag(bool val);
float getBreadSupply();
float getWaterSupply();
void eatBread(float breadNum);
void drinkWater(float waterVolume);
void doSelfCheck();
void dumpRuntimeInfo();
void startCLI(bool isBlocked);
};
#endif
这里有三点需要注意。
其一是在 EatMealMan.h 中引用了状态机类的头文件 EatMealManFSM.h,并添加了状态机成员 EatMealManFSM _fsm:
#ifndef __EATMEALMAN_H__
#define __EATMEALMAN_H__
#include "EatMealManFSM.h"
Class EatMealMan {
//... 省略
EatMealManFSM _fsm;
public:
//... 省略
};
#endif
其二是在具体实现类的构造函数中实例化了状态机对象,并使状态机进入到初始状态:
#include "EatMealMan.h"
EatMealMan::EatMealMan()
{
_fsm = new EatMealManFSM();
_fsm.enterStartState(); // 这会调用当前状态的 Entry() 方法
}
EatMealMan::~EatMealMan() {}
//... 省略
其三是需要把头文件 statemap.h 加入到编译环境中。SMC 的开发者在 SourceForge.net 上为不同的编程语言提供了不同版本的头文件,我们需要根据自己要生成的编程语言类型选择合适的版本进行下载(点我下载 C++ 版本头文件)。
至此,我们有了状态机类 EatMealManFSM,也有了具体实现类 EatMealMan,并且将它们关联在了一起。所有准备工作都已完成,接下来我们只需要在要处理具体状态的地方调用 EatMealManFSM::processState() 方法就可以了。比如:
EatMealMan::threadLoop()
{
_fsm.processState();
}

3216

被折叠的 条评论
为什么被折叠?



