DynamicStateMachine支持状态机的动态生成,用户可以简单设置json配置文件,通过添加状态和转换条件,轻易的生成一个状态机。
对于用户来说,需要关注以下变量
State 状态机中的状态。 每个状态需要包含几个信息:StateNo(状态编号),StateName(状态的名称),DefaultNs(当无转换条件触发时默认的跳转状态,一般为自身),Transitions(转换条件)。
Transition 状态改变的条件。一个状态可以有多个转换条件。 每个transition需要包含几个信息:TransitionName(转换条件名称),ConditonExpr(条件表达式),NextState(满足条件后跳转的状态)
AliasTable 别名表。在transition中的ConditonExpr中涉及到对变量的比较判断,而这个变量名在这里被称为别名(Alias)。 比如ConditonExpr是"numA >3&&numA <=5",这里的numA就是别名。 单独设计一个别名表是为了提高兼容性。在一些情况下,获取值需要比较复杂的表达,这样复杂表达的变量写在条件语句中是很不简约的。 拿CFET2举个例子。CFET2中资源的访问是通过url的形式。想要获取一个值,只需知道它对应的url。但将一长串url写在条件语句中会显得很繁琐,对用户也不方便。所以设计了一个别名表的配置文件。 别名表实际上是一个字典,key是别名,value是一个别名信息类。包含了GetPath(其对应的url,或者其他获取值的方式),ValueType(类型)和DefaultValue(默认值)
以上变量是用户需要在配置文件里配置好的,完善这些变量后就可以生成一个对应状态机。 配置文件示例:

StateConfig.json
{
"States":[{"StateNo":0, "StateName":"a","DefaultNs":0,"Transitions":[{"TransitionName":"goB","ConditonExpr":"numA >3&&numA <=5","NextState":1},{ "TransitionName":"goC","ConditonExpr":"numA>5","NextState":2}]},
{"StateNo":1, "StateName":"b","DefaultNs":1,"Transitions":[{"TransitionName":"goD","ConditonExpr":"numA>10","NextState":2}]},
{"StateNo":2, "StateName":"c","DefaultNs":2,"Transitions":[]}],
"currentstate":0
}
AliasTableConfig.json
{
"AliasTable":{
"numA":{"GetPath":"http://localhost:8001/fakeai/ChannelCount","ValueType":"System.Int32","DefaultValue":2}
}}
DynamicStateMachine通过Appccelerate.StateMachine实现了状态机的逻辑。
DynamicStateMachine可以简单分为两个部分,初始化部分和运行部分。初始化构造了一个完整的等待状态的状态机。而运行部分则通过外界传递的别名对应的值,判断当前状态符合的条件转换,正确的跳转到下一个状态。

因为状态机所有信息都是动态的,不确定的。所以第一步需要从配置文件把所需要的信息都加载进来,然后调用Appccelerate.StateMachine,新建一个状态机。
因为条件是运行时读进来的,所以判断条件是否满足,就需要使用动态编译,这样才能执行条件语句,获取满足的转换条件。
动态编译的代码很简单,就是一个获取当前满足的转换条件的方法getTransition。getTransition中逻辑主要分为2块。声明别名变量,以及根据每个state的转换条件编写的switch case。
在动态编译代码中不确定的有当前状态和别名对应的真实值。因为当前状态会随着状态机内的状态跳转而改变,而别名对应的值可能是变化的,每次获取的都不一样。所以这两个都应该作为参数传进来。
以下是一个待动态编译代码的例子(为了方便观看,去掉了转义斜杠):
using System;
using System.Collections.Generic;
namespace Transitions
{
public class GetTransitions
{
public string getTransition(params object[] Parameters)
{
Dictionary<string, object> AliasValue = Parameters[0] as Dictionary<string, object>;
int stateNo = (int)Parameters[1];
System.Int32? numA = AliasValue["numA"] as System.Int32?;
System.Int32? numB = AliasValue["numB"] as System.Int32?;
System.Int32? numC = AliasValue["numC"] as System.Int32?;
System.String strD = AliasValue["strD"] as System.String;
switch (stateNo)
{
case 0:
if (numA > 10) { return "goB"; }
if (numA <= 10) { return "goC"; }
else { return "Default"; }
case 1:
if (numB > 10) { return "goD"; }
else { return "Default"; }
case 2:
if (numC > 10) { return "goD"; }
else { return "Default"; }
case 3:
if (strD == "end")
{ return "goA"; }
else { return "Default"; }
default: return null;
}
}
}
}
接下来将代码动态编译之后,初始化部分就结束了。
CFET2StateMachine就是在CFET2框架下利用DynamicStateMachine实现的状态机。 逻辑很简单,实例化DynamicStateMachine后,创建一个线程,每隔10s更新一次别名表的真实值,并调用DynamicStateMachine中的Run方法。 CFET2StateMachine封装了几个属性供外界调用。
/// <summary>
/// 获取状态机当前所处状态号
/// </summary>
/// <returns></returns>
[Cfet2Status]
public int CurrentStateNO()
/// <summary>
/// 获取状态机当前所处状态名称
/// </summary>
[Cfet2Status]
public string CurrentStateName()
/// <summary>
/// 手动触发条件转换事件
/// </summary>
[Cfet2Method]
public void RunSM()
/// <summary>
/// 停止当前状态机
/// </summary>
[Cfet2Method]
public void StopSM()
/// <summary>
/// 继续当前状态机
/// </summary>
[Cfet2Method]
public void StartSM()
/// <summary>
/// 重新加载状态机
/// </summary>
[Cfet2Method]
public void ReloadSM()
本文章使用limfx的vscode插件快速发布