自从 使用coroutine实现状态机 以来已经有两个星期了,在这两个星期里我把这个想发给实现了。语法做了一点点小修改,但是总的来说没有区别。上一篇文章的计算器的例子作为单元测试的一部分被添加了进去,大家可以在看这两个文件:
CoSmcCalculator.txt:测试用的状态机,支持加法和乘法。
CoSmcCalculator.cpp:生成的C++代码。C++当然没有状态机的功能,所以Workflow脚本的状态机自然被改写成了普通的代码,变成了一个真正的状态机。
Workflow脚本要求使用了$state_machine的类必须继承自system::StateMachine,再加上Workflow不允许多重继承里面出现同一个类多次,所以$state_machine的父类当然不能是$state_machine。这个限制是草稿里面没有的。
整个状态机被编译成了一个使用 coroutine 表达的状态机,而coroutine又被编译成了一个状态机,所以生成的代码里面两个嵌套的状态机被合并到了一起,这么扫一眼根本看不懂(逃
Workflow的状态机支持$goto_state和$push_state两种模式。$goto_state说的是你跳转过去之后就不回来了,如果那个状态结束了之后没有跳转到任何其他状态,那么整个状态机就结束了。$push_state就是还要回来的疑似。$goto_state就像jump,$push_state就像函数调用。函数调用自然是有堆栈的,所以$push_state自然也有堆栈——这个被生成的coroutine的previousCoroutine变量隐式表达成了一个外部访问不了的链表。
这个链表的根节点放在了system::StateMachine类里面,类的 ResumeStateMachine 函数识别了多个coroutine之间切换的意图,把他们有机地结合了起来。因为一个coroutine的暂停和结束,有可能意味着$push_state和"pop_state",自然不能让ResumeStateMachine暂停,而是要继续跑下去,直到状态机真的要开始等待外部的输入为止。
状态机的输入是用$state_input定义的,最后他们就变成了函数。你调用这些函数,就是在输入数据。因此最后在测试用例里面,我们的用例长这样子:
func main():string
{
var c = new SMCalculator^();
s=$"[$(c.Value)]";
var handler = attach(c.ValueChanged, func():void
{
s=$"$(s)[$(c.Value)]";
});
c.Digit(1); // 1
c.Dot(); // 1.
c.Digit(5); // 1.5
c.Add();
c.Digit(2); // 2
c.Digit(1); // 21
c.Dot(); // 21.
c.Digit(2); // 21.2
c.Digit(5); // 21.25
c.Mul(); // 22.75 (1.5 + 21.25)
c.Digit(2); // 2
c.Equal(); // 45.5 (22.75 * 2)
c.Clear(); // 0
detach(c.ValueChanged, handler);
return s;
}
这个代码模拟了用户点击计算器按钮的过程,Value属性代表了液晶屏上显示的数据,每次有改变的时候,这个值就会被append到全局变量s里面。最后输出的结果自然应该是:
[0][1][1.][1.5][2][21][21.][21.2][21.25][22.75][2][45.5][0]
因为你在点计算器的时候,上面的数字的确就是这么变化的。