scxml-3
编译源码:
预先安装如下:
sudo apt-get install build-essential libboost-all-dev cmake autorevision xsltproc
autorevision用于更新scxml的版本;
xsltproc用于编译单元测试;
编译:
mkdir build cd build cmake .. make -j4 make install
To help with auto-generating state-machines in cmake projects, an scxml_generator.cmake module is installed. The examples demonstrate how this can be used.
To build unit tests, and examples run cmake with
mkdir build
cd build
cmake -DDEVELOPER=1 ..
make -j4
make test
下面看一下例子:
第一个Hello World:
先看图
是一个由一个无条件转移相连接的两个状态组成;
上面的图是由这里的软件来实现的:https://github.com/fmorbini/scxmlgui/
java -jar **.jar (在windows下执行,**.jar为https://github.com/fmorbini/scxmlgui/blob/master/extra/fsm-editor.jar)
通过scxmlgui绘制之后,保存生成一个.scxml文件,内容如下:
<scxml initial="hello" version="0.9" xmlns="http://www.w3.org/2005/07/scxml"> <state id="hello"> <transition target="world"></transition> </state> <state id="world"></state> </scxml>
然后通过上面编译的scxmlcc来将.scxml生成.h文件
scxmlcc -o hello_world.h hello_world.scxml
自定义的行为可以用c++代码来实现,例如在进入和退出的时候的一些动作:
#include "hello_world.h" #include <iostream> using namespace std; typedef sc_hello_world sc; template<> void sc::state_actions<sc::state_hello>::enter(sc::data_model &m) { cout << "hello" << endl; } template<> void sc::state_actions<sc::state_world>::enter(sc::data_model &m) { cout << "world" << endl; } int main(int argc, char *argv[]) { sc sc0; sc.init(); return 0; }
执行之后,会输出如下:
hello
world
init方法初始化整个状态机并进入到它的初始状态;
第二个例子:
Timer Switch:实现了一个简单的定时器表
其大意是,该表可以通过发送一个button事件到状态机来在on或者off之间切换。在on状态的时候,当定时截止之后,会进入到off状态。
在on状态中,一个timer事件来计数timer的值。unconditional事件从on到off需要一个条件,因此只有当timer>=5之后,才会被执行,最后off状态会把counter置为0;
虚点的timer转移意味着,其是一个无目标的转移。意味着在on状态的enter和exit行为都不会被执行当timer事件发生的时候。行为的实现如下:
template<> void sc::state_actions<sc::state_on>::enter(sc::data_model &m) { cout << "on" << endl; } template<> void sc::state_actions<sc::state_off>::enter(sc::data_model &m) { cout << "off" << endl; m.user->timer = 0; } template<> bool sc::transition_actions<&sc::state::unconditional, sc::state_on, sc::state_off>::condition(sc::data_model &m) { return m.user->timer >= 5; } template<> void sc::transition_actions<&sc::state::event_timer, sc::state_on>::enter(sc::data_model &m) { ++m.user->timer; }
对于本例子,一个数据模型必须定义来包含一个timer变量,生成的状态机类有一个user_model结构体做了前向声明,因此一个自定义的数据模型如下:
struct sc::user_model { int timer; };
自定义的数据模型被构造之后,然后传递到状态机的构造函数中:
sc::user_model m; sc sc(&m); sc.init();
完整代码如下:
timer_switch.cpp

/************************************************************************* ** Copyright (C) 2013 Jan Pedersen <jp@jp-embedded.com> ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see <http://www.gnu.org/licenses/>. *************************************************************************/ #include "timer_switch.h" #include "termio.h" #include <iostream> using namespace std; typedef sc_timer_switch sc; struct sc::user_model { int timer; }; template<> void sc::state_actions<sc::state_on>::enter(sc::data_model &m) { cout << "on" << endl; } template<> void sc::state_actions<sc::state_off>::enter(sc::data_model &m) { cout << "off" << endl; m.user->timer = 0; } template<> bool sc::transition_actions<&sc::state::unconditional, sc::state_on, sc::state_off>::condition(sc::data_model &m) { cout << "unconditional: timer is " <<m.user->timer << endl; return m.user->timer >= 5; } template<> void sc::transition_actions<&sc::state::event_timer, sc::state_on>::enter(sc::data_model &m) { ++m.user->timer; cout << "timer is " <<m.user->timer <<endl; } int main(int argc, char *argv[]) { // set unbuffered input and disable echo termio t; t.echo(false); t.canonical(false); cout << "input: b = button event" << endl; cout << " t = timer event" << endl; cout << " q = quit" << endl; sc::user_model m; sc sc(&m); sc.init(); while(true) { char c; cin >> c; cout << "c is " << c << endl; sc::event e = NULL; switch (c) { case 'b': e = &sc::state::event_button; break; case 't': e = &sc::state::event_timer; break; case 'q': return 0; default: continue;//e = &sc::state::unconditional; } sc.dispatch(e); } return 0; }
timer_switch.h

// This file is automatically generated by scxmlcc (version 0.9M-2) // For more information, see http://scxmlcc.org #ifndef __SC_TIMER_SWITCH #define __SC_TIMER_SWITCH #include <deque> #include <vector> #include <string> class sc_timer_switch { public: struct data_model; struct user_model; class state { public: virtual ~state(){} virtual state* event_button(sc_timer_switch&) { return 0; } virtual state* event_timer(sc_timer_switch&) { return 0; } virtual state* unconditional(sc_timer_switch&) { return 0; } virtual state* initial(sc_timer_switch&) { return 0; } template<class T> void enter(data_model&, ...) {} template<class T> void exit(data_model&, ...) {} virtual bool in(const void*) { return false; } }; typedef state* (state::*event)(sc_timer_switch&); template<class C> class state_actions { protected: void enter(data_model&) {} // default enter action void exit(data_model&) {} // default exit action }; template<class C, class P> class composite : public P, public state_actions<C> { virtual state* initial(sc_timer_switch&) { return 0; } public: typedef P parent_t; // LCA calculation template<class T> void enter(data_model&, composite*) {} template<class T> void enter(data_model &m, ...) { P::template enter<T>(m, static_cast<T*>(nullptr)); state_actions<C>::enter(m); } template<class T> void exit(data_model&, composite*) {} template<class T> void exit(data_model &m, ...) { state_actions<C>::exit(m); P::template exit<T>(m, static_cast<T*>(nullptr)); } static const void* id() { static const struct{} _id; return &_id; } bool in(const void *si) { return (si == id() || P::in(si)); } }; class no_state {}; enum transition_type { external, internal }; template<event E, class S, class D0 = no_state> class transition_actions { protected: void enter(data_model&) {} // default enter action bool condition(data_model&) { return true; } // default condition action }; // external/internal transition template<event E, class S, class D = no_state, transition_type T = external> class transition : public transition_actions<E, S, D> { template<transition_type I> struct id { }; void state_enter(D* d, data_model &m, id<internal>, S*) { d->template enter<composite<S, typename S::parent_t> >(m); } // internal transition, where dst is descendant of src void state_enter(D* d, data_model &m, ...) { d->template enter<typename S::parent_t>(m); } // external transition, or dst is not descendant of src void state_exit(S*, data_model &, id<internal>, S*) {} // internal transition, where dst is descendant of src void state_exit(S* s, data_model &m, ...) { s->template exit<typename D::parent_t>(m); } // external transition, or dst is not descendant of src public: state* operator ()(S *s, sc_timer_switch &sc) { if(!transition_actions<E, S, D>::condition(sc.model)) return 0; D *d = sc.get_state<D>(); state_exit(s, sc.model, id<T>(), static_cast<typename D::parent_t*>(nullptr)); transition_actions<E, S, D>::enter(sc.model); state_enter(d, sc.model, id<T>(), static_cast<typename D::parent_t*>(nullptr)); return d; } }; // transition with no target template<event E, class S> class transition<E, S, no_state> : public transition_actions<E, S, no_state> { public: state* operator ()(S *s, sc_timer_switch &sc) { if(!transition_actions<E, S, no_state>::condition(sc.model)) return 0; transition_actions<E, S, no_state>::enter(sc.model); return s; } }; private: bool dispatch_event(event e) { state *next_state; if ((next_state = (model.cur_state->*e)(*this))) model.cur_state = next_state; return !!next_state; } public: void dispatch(event e = &state::unconditional) { bool cont = dispatch_event(e) || dispatch_event(&state::unconditional); while (cont) { if ((cont = dispatch_event(&state::initial))); else if ((cont = dispatch_event(&state::unconditional))); else if (model.event_queue.size()) cont = dispatch_event(model.event_queue.front()), model.event_queue.pop_front(), cont |= !model.event_queue.empty(); else break; } } struct data_model { std::deque<event> event_queue; state *cur_state; template <class S> bool In() { return cur_state->in(S::id()); } user_model *user; const std::string _sessionid; const std::string _name; data_model(user_model* um) : user(um) , _sessionid(std::to_string(reinterpret_cast<long long unsigned int>(this))) , _name("timer_switch") {} } model; sc_timer_switch(user_model *user = nullptr) : model(user) { model.cur_state = get_state<scxml>(); } void init() { dispatch(&state::initial); } struct scxml : public composite<scxml, state> { state* initial(sc_timer_switch&sc) { return transition<&state::initial, scxml, state_off, internal>()(this, sc); } }; struct state_off : public composite<state_off, scxml> { state* event_button(sc_timer_switch &sc) { return transition<&state::event_button, state_off, state_on>()(this, sc); } }; struct state_on : public composite<state_on, scxml> { state* event_button(sc_timer_switch &sc) { return transition<&state::event_button, state_on, state_off>()(this, sc); } state* event_timer(sc_timer_switch &sc) { return transition<&state::event_timer, state_on>()(this, sc); } state* unconditional(sc_timer_switch &sc) { return transition<&state::unconditional, state_on, state_off>()(this, sc); } }; template<class T> T* get_state() { static T t; return &t; } }; #endif
第三个例子:
售卖机器:
本售卖机器可以分配三种类型的coke,在用于插入货币到机器之后。如下图所示:
状态机负责整个逻辑;
Coin Sensor负责检测货币的插入;
keypad负责检测按键的按下;
Display负责显示文本消息;
Coin refund负责退换硬币给用户;
Dispenser负责分配三种不同类型的coke给用户;
Input simulator负责从你的PC的键盘中读取输入,并触发货币传感器和键盘pad以便仿真。
不同组件之间的通信是通过signaling机制;其目的是为了解耦模块之间的依赖;因此单独的模块不知道彼此;
状态机的实现如下所示:
在初始化之后,状态机会进入到collect_coins状态;然后等待货币的插入;当货币插入之后,一个在用户模型user_model中的变量credit通过事件N和D来递增。当zero,coke或者diet按钮在机器上被按下时,对应的事件zero,coke或者diet被触发。这三个transitions到状态dispense_*状态,有一个条件就是credit必须是大于等于对应的coke的价格。如果条件满足,状态机会转移到状态dispense_*state,对应的coke就会被分配,对应的credit会减少。这三个dispense状态是最后的状态,因此他们会触发一个done,状态机会转移到coin_return状态。当在active状态时,如果cancel按钮被按下,对应的cancel事件被触发,状态机也会转移到状态coin_return.
在coin_return状态中,状态机会进入到return_d状态。这个状态有两个条件转移。当credit的值小于一角硬币时,会进入到状态return_n,当credit的值大于等于dime时,这个转移会触发coin refund组件来退换一个dime。并返回到return_d,return_n状态类似;当credi为0时,return_done状态进入,且是最后的状态,会触发一个done事件,状态机会退回到init状态。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】