带可视化编辑器的通用 FSM
介绍带可视化编辑器的通用 FSM。
引言
bitfsm是一个通用的 FSM(有限状态机)实现。它被编写为 C++ 模板类和一些辅助结构。bitfsm 被封装在 C++/CLI 中,因此它对原生 C++ 程序员和 .NET 程序员都很友好。 还有一个通用的可视化编辑器,开发人员可以使用它通过拖放一些状态块和命令行来编辑 FSM 规则。 在本文中,我将解释基本的设计概念、调用的方法以及对编辑器的介绍。
背景
很久很久以前,当我还是个小男孩的时候,电脑对我来说是那么有趣和神秘! 很有趣的是,它现在对我来说仍然很有趣,但不再那么神秘。 抽象是解决编程问题的工具,因此一个正确抽象的模型可以轻松解决复杂的问题。
FSM 是计算模型的经典抽象。 许多问题,尽管并非所有问题,例如编译器的解析器或游戏的 AI 模拟,都可以等同于这个模型。
一个 FSM 包含一组有限的状态和转换规则。 一个 FSM 可以在某个时间处于一个状态,并且它可能会转移到另一个状态或保持它,这取决于它收到转换命令后的规则。
基本设计概念
该库的核心实现为 C++ 模板类,允许您根据需要自定义它。 一个 bitfsm
可以包含可变数量(大于零的整数)的转换命令,由模板参数 _Nc
指示。 bitfsm 使用一个大小为 _Ns
的 RuleStep
数组来表示状态,一个 Status
结构来存储当前状态,还有一个命令缓冲区来保存它接收到的所有转换命令。 数组中的每个 RuleStep
元素都使用一个 Step
结构向量来表示转换规则。 每个 Step
结构都包含一个 Bitset<_Nc>
来表示一组转换命令和一个索引,该索引指示如果收到该组转换命令,下一个状态是什么。 转换命令在 Bitset<_Nc>
中是可堆叠的; Step
结构中的一个布尔变量指示转换命令是使用逻辑 AND 组合关系还是逻辑 OR。
使用 bitfsm
当您使用 bitfsm
时,没有额外的二进制文件要处理,只需包含头文件并调用它即可。
bitfsm
在大多数内部需求中使用整数。 但是程序员总是需要一个更有效的数据结构来满足特定目的。 bitfsm
允许程序员使用任何数据结构作为状态和转换命令标签。 我在本文中使用 std::string
。 bitfsm
模板类接受两个仿函数来执行用户定义的标签到整数的转换。 我们应该定义一些东西来准备使用 bitfsm
,如下所示
// Conversion functor from std::string to int; for states
struct ObjToStatus {
int operator ()(const std::string &_obj) {
if(_obj == "begin") {
return 0;
} else if(_obj == "1") {
return 1;
} else if(_obj == "2") {
return 2;
} else if(_obj == "3") {
return 3;
} else if(_obj == "end") {
return 4;
}
return -1;
}
};
// Conversion functor from std::string to int; for commands
struct ObjToCommand {
int operator ()(const std::string &_obj) {
if(_obj == "_cmd0") {
return 0;
} else if(_obj == "_cmd1") {
return 1;
} else if(_obj == "_cmd2") {
return 2;
} else if(_obj == "_cmd3") {
return 3;
} else if(_obj == "_cmd4") {
return 4;
} else if(_obj == "_cmd5") {
return 5;
}
return -1;
}
};
// Define a type of bitfsm which accepts std::string
// as customized tag type; and contains 5 states, 6 commands
typedef fsm::FSM<5, 6, std::string, ObjToStatus, ObjToCommand> Fsm;
// Declare an instance
Fsm sbs;
当 bitfsm
接收到命令时,状态转换可以根据定义的规则自动进行; 这些命令可以被认为是驱动 bitfsm
的输入。 我们还需要从 bitfsm
获取一些反馈; bitfsm
使用一个通用的监听器模式来完成这项工作。 您可以定义一个状态转换事件回调处理程序,如下所示
class MyStepHandler : public Fsm::StepHandler {
public:
virtual void handleStep(const std::string &_srcTag, const std::string &_tgtTag) {
printf("Status changed from %s to %s\n", _srcTag.c_str(), _tgtTag.c_str());
}
};
我们必须告诉它在 bitfsm
中有多少个状态和命令,否则它将是一个没有逻辑的空 FSM。 我们可以像下面这样注册状态
// Register five states
sbs.registerRuleStepTag("begin");
sbs.registerRuleStepTag("1");
sbs.registerRuleStepTag("2");
sbs.registerRuleStepTag("3");
sbs.registerRuleStepTag("end");
并添加一些转换规则,如下所示
Fsm::CommandParams params;
/////
params.reset();
params.add("_cmd0");
sbs.addRuleStep("begin", params, "1");
// “_cmd0” makes bitfsm transiting from “begin” state to “1”
/////
params.reset();
params.add("_cmd1");
sbs.addRuleStep("begin", params, "2");
/////
params.reset();
params.add("_cmd3");
sbs.addRuleStep("1", params, "3");
/////
params.reset();
params.add("_cmd4");
sbs.addRuleStep("2", params, "3");
/////
params.reset();
params.add("_cmd5");
sbs.addRuleStep("3", params, "end");
现在一切都准备好了。 其余的只是设置开始状态和结束状态,然后将命令参数传递给 walk
方法来运行 bitfsm
,如下所示
bool done = false;
/////
sbs.setCurrentStep("begin"); // Set the beginning state
sbs.setTerminalStep("end"); // Set the terminal state
/////
// Walk…
sbs.walk("_cmd0");
done = sbs.terminated();
// Detect whether the FSM reached the terminal state
/////
sbs.walk("_cmd3");
done = sbs.terminated();
/////
sbs.walk("_cmd5");
done = sbs.terminated();
而且,我们还可以将一组 bitfsm
的规则保存到一个文件中以供信息重新加载。
sbs.writeRuleSteps("backup.fsm"); // Save logic
sbs.readRuleSteps("backup.fsm"); // Load logic
可视化编辑器对于复杂的规则编辑很有帮助。 bitfsm 使用 C++/CLI 提供 .NET 包装器,并使用 C# 提供可视化 bitfsm 编辑器。 编辑器看起来如下所示
通过拖出状态块和转换线,可以轻松编辑状态和转换规则。 该编辑器还允许我们通过单击命令菜单来模拟 FSM 运行。 我们可以在此编辑器中方便地构建 FSM 并确保规则正确。
结论
我的开发原则是获取正确的抽象来解决特定问题,并尽可能使抽象实现可重用。 希望这能帮助您工作! 欢迎提出任何问题、建议和想法。 您可以在 http://code.google.com/p/bitfsm/ 上获取有关 bitfsm
的最新信息,并通过 mailto:hellotony521@gmail.com 与作者联系。