SMC状态机 讲解2 从模型到SMC
- 1、实例化有限状态机(FSM)
- 2、简单转换 Simple Transition
- 3、外部环回转换 External Loopback Transition
- 4、内部环回转换 Internal Loopback Transition
- 5、转换动作
- 6、转换Guard
- 7、转换参数
- 8、Entry 和 Exit动作
- 9、Push 转换
- 10、Pop转换
- 11、默认转换
1、实例化有限状态机(FSM)
private final AppClassContext _fsm;
public AppClass()
{
// 初始化应用程序类
// 实例化有限状态机
// 注意:传递给FSM是安全的
// 构造函数,因为只有FSM的构造函数
// 将其存储在数据成员中
_fsm = new AppClassContext(this);
}
// 实例化后输入FSM启动状态
// 应用对象
public void startWorking()
{
_fsm.enterStartState();
return;
}
2、简单转换 Simple Transition
// State
Idle
{
// 转换到下一个状态的动作
Run Running {}
}
状态和转换名称的命名规则必须为“[A- za -z_][A- za -z0-9_]*”形式。
3、外部环回转换 External Loopback Transition
// State
Idle
{
// 转换到下一个状态的动作
Timeout Idle {}
}
外部环回确实离开当前状态并返回到当前状态。这意味着执行状态的exit和entry操作。这与内部环回转换相反。
4、内部环回转换 Internal Loopback Transition
// 状态
Idle
{
// 转换到下一个状态的动作
Timeout nil {}
}
使用“nil”作为下一个状态将导致转换保持在当前状态,而不是离开它。这意味着状态的退出和进入操作不会被执行。这与外部环回转换相反。
5、转换动作
// 状态
Idle
{
//转换
Run
// 下一个状态
Running
// 动作
{
StopTimer("Idle");
DoWork();
}
}
- 转换的动作必须包含在“{}”中。
- 动作的形式为“[A-Za-z] [A-Za-z0-9_ -] *()”。参数列表(argument list)必须为空或由逗号分隔的字面值组成。例如:整数(正数或负数、十进制、八进制或十六进制)、浮点数、双引号括起来的字符串、常量和转换参数。
- 操作必须是%class类中的成员函数,并且可以被状态机访问。通常这意味着c++中的公共成员函数或Java中的包。
动作参数包括:
4. 整数(例如1234)。
5. 浮点数(如12.34)。
6. 字符串(例如:“中的”)。
7. 一个转换参数。
8. 常量、#define或全局变量。
9. 独立的子程序或方法调用(例如event.getType())。
6、转换Guard
// State
Idle
{
// Trans
Run
// Guard condition
[ctxt.isProcessorAvailable() == true &&
ctxt.getConnection().isOpen() == true]
// Next State
Running
// Actions
{
StopTimer("Idle");
DoWork();
}
Run nil {RejectRequest();}
}
guard必须包含一个条件,该条件是有效的目标语言源代码——也就是说,它将是一个有效的“if”语句。定义的guard可能包含&&s、||s、比较运算符(==、<等)和嵌套表达式。SMC将您的保护条件逐字复制到生成的输出中。
如果guard条件的计算结果为true,则进行转换。如果gurad条件的计算结果为false,则发生以下情况之一(按优先级排序):
- 如果状态有另一个具有相同名称和参数的受保护转换,则检查该转换的保护。
- 否则,如果状态有另一个具有相同名称和参数列表的未保护转换,则进行该转换。
- 如果以上都不是,则遵循默认的转换逻辑。
一个状态可以有多个具有相同名称和参数列表的转换,只要它们都有唯一的gurad。当一个状态确实有多个具有相同名称的转换时,在排序它们时必须小心。状态机编译器将以与您使用的相同的从上到下的顺序检查转换,除了未保护的版本。只有当所有被保护的版本都失败时,才会采取这种做法。guard排序只有在guard不是互斥的情况下才重要,也就是说,对于同一个事件,多个gurad的值可能为true。
7、转换参数
// State
Idle
{
// Transition
Run(msg: const Message&)
// Guard condition
[ctxt.isProcessorAvailable() == true &&
msg.isValid() == true]
// Next State
Running
// Actions
{
StopTimer("Idle");
DoWork(msg);
}
Run(msg: const Message&)
// Next State Actions
nil {RejectRequest(msg);}
}
Note:当使用转换guard和转换参数时,同一转换的多个实例必须具有相同的参数列表。就像c++和Java方法一样,Run(msg: const Message&)和Run()不是同一个转换。在使用多个gurad定义相同的转换时,如果不能使用相同的参数列表,将导致生成不正确的代码。
Tcl “arguments”:
虽然Tcl是一种无类型语言,但Tcl区分了按值调用和按引用调用。默认情况下,如果转换参数没有指定类型,SMC将生成按值调用的Tcl代码。但可以使用"value"或"reference"这些人为类型。
如果你的Tcl-targeted FSM有一个转换:
DoWork(task: value)
Working
{
workOn(task);
}
则生成的Tcl为:
public method DoWork {task} {
workOn $this $task;
}
如果你的Tcl-targeted FSM有一个转换:
DoWork(task: reference)
Working
{
workOn(task);
}
则生成的Tcl为:
public method DoWork {task} {
workOn $this task;
}
8、Entry 和 Exit动作
当转换离开某个状态时,它会在任何转换操作之前执行该状态的退出操作。当转换进入某个状态时,它执行该状态的进入操作。转换按以下顺序执行操作:
- “From”状态的退出动作。
- 将当前状态设置为空。
- 转换操作的顺序与.sm文件中定义的顺序相同。
- 将当前状态设置为“to”状态。
- “To”状态的进入动作。
// 状态
Idle //闲置
Entry {StartTimer("Idle", 1); CheckQueue();}//进入该状态时,执行该操作
Exit {StopTimer("Idle");} //离开该状态时,执行该操作
{
//转换操作
}
从6.0.0版本开始,SMC生成一个enterStartState方法,该方法执行开始状态的进入动作。现在由应用程序在实例化有限状态机后调用start方法。如果不适合在启动时执行入口操作,则不要调用enterStartState。无需调用此方法来设置有限状态机的启动状态,这在FSM实例化时完成。此方法仅用于执行启动状态的进入操作。
如果要调用此方法,请确保在上下文类的构造函数之外调用。这是因为entry调用类方法。如果在上下文类的构造函数中调用enterStateState,则上下文实例将在完成初始化之前被引用,这是一件不好的事情。
enterStartState不防止被多次调用。它应该最多调用一次,并且在发出任何转换之前调用。不遵循这一要求可能会导致不适当的有限状态机行为。
是否执行状态的Entry和Exit操作取决于所采取的转换类型。下表显示了哪些转换执行“from”状态的Exit动作,哪些转换执行“to”状态的Entry动作。
转换类型 | 执行“From”状态的Exit动作? | 执行“To状态的”Entry动作? |
---|---|---|
Simple Transition | 是 | 是 |
External Loopback Transition | 是 | 是 |
Internal Loopback Transition | 否 | 否 |
Push 转换 | 否 | 是 |
Pop 转换 | 是 | 否 |
9、Push 转换
// SMC v1.3.2版本语法
Running
{
Blocked BlockPop/push(WaitMap::Blocked) {GetResource();}
}
这将导致状态机:
-
转换到 BlockPop 状态。
-
执行 BlockPop entry 动作。
-
Push到 WaitMap::Blocked 状态。
-
执行 WaitMap::Blocked entry 动作。
当WaitMap发出pop转换时,控制权将返回到BlockPop,并且从这里发出pop转换。
当一个状态有两个不同的转换,这两个转换推送到相同的状态,但需要以不同的方式处理弹出转换时,使用这个新语法。例如:
Idle
{
NewTask NewTask/push(DoTask) {}
RestartTask OldTask/push(DoTask) {}
}
NewTask
{
TaskDone Idle {}
// Try running the task one more time.
TaskFailed OldTask/push(DoTask) {}
}
OldTask
{
TaskDone Idle {}
TaskFailed Idle {logFailure();}
}
10、Pop转换
pop转换与push转换的不同之处在于:
- 未指定最终状态。这是因为pop转换将返回到发出相应推送的任何状态。
- pop转换有一个可选参数:转换名称 transition name。
在上面的例子中,如果资源请求被授予,则状态机返回到执行推送的相应状态,然后进行该状态的OK转换。如果请求被拒绝,除了采取FAILED转换外,还会发生相同的事情。对应的push转换代码为:
Running
{
Blocked push(WaitMap::Blocked) {GetResource();}
// Handle the return "transitions" from WaitMap.
OK nil {}
FAILED Idle {Abend(INSUFFICIENT_RESOURCES);}
}
从SMC v. 1.2.0开始,可以在pop转换的transition参数之后添加其他参数。这些附加参数与传递给操作的其他参数一样,将被传递到命名转换中。按照上面的例子,给定pop转换pop(FAILED, errorCode, reason),那么FAILED应该被编码为:
FAILED(errorCode: ErrorCode, reason: string)
Idle
{
Abend(errorCode, reason);
}
11、默认转换
如果一个状态接收到一个在该状态中没有定义的转换,SMC会有两个独立的机制来处理这种情况。
- “Default”状态。每个%map都可以有一个名为“Default”的特殊状态(注意D为大写)。与所有其他状态一样,默认状态包含转换
Default
{
//有效的运行请求,但转换在无效状态下发生。对有效消息发送拒绝回复。
Run(msg: const Message&)
[ctxt.isProcessorAvailable() == true &&
msg.isValid() == true]
nil
{
RejectRequest(msg);
}
// 在无效状态下接收到的无效消息将被忽略。
Run(msg: const Message&)
nil
{}
Shutdown
ShuttingDown
{
StartShutdown();
}
}
默认状态转换可能具有非默认转换的保护和参数特性。这意味着对于同一转换,默认状态可能包含多个受保护和一个不受保护的定义。
- “默认”转换。它被放置在状态中,用于备份所有转换。
Connecting
{
// 现在连接到远端,可以登录了。
Connected
Connected
{
logon();
}
// 此时的任何其他转换都是错误的。
// 请停止连接进程,稍后重试。
Default
RetryConnection
{
stopConnecting();
}
}
因为任何转换都可以通过默认转换,所以默认转换:
-
可能没有参数列表。
-
默认转换可能需要一个guard。
-
将Default转换置于Default状态意味着将处理所有转换。