JavaScript状态机程序逻辑编辑器

制作背景

之前做Win8 Metro动态加载内容框架的时候,由于采用了XAML+JavaScript的方法,程序复杂的执行逻辑是由JavaScript控制的,而页面一多,流程一复杂,制作起来就非常麻烦,还要考虑不同XAML页面返回事件的名称。所以就写了个状态机模型的程序制作工具。

技术支持

说到制作状态机,无疑微软的VS是最强大的,微软本身就有Workflow。使用微软的工作流就可以很方便的制作出一个状态机,然后在用System.Workflow下的类库提取,整理结构,然后生成JavaScript就完事了。

使用VS来制作工作流XAML是因为VS非常直观方便,所以只使用微软工作流里的StateMachine、State、Transition、WriteLine(占位识别),支持嵌套状态机。

技术详解

  • 解析微软工作流XAML

    首先需要引用微软工作流相关的类库System.Activities、System.Workflow.ComponentModel、System.Workflow.Activities、System.Workflow.Runtime、System.WorkflowServices。

    读取活动树,path为VS生成的工作流XAML文件路径:

    Activity act = ActivityXamlServices.Load(path);
    

    然后对活动树进行解析,活动树的类型主要处理类型:StateMachine、State(需要处理状态的Enter、Exit和Transition)、Literal`1(状态转移条件填写为True时)、CSharpValue`1(状态转移条件需要后续编程时)、WriteLine(普通预埋点)

    状态转移条件

    状态转移条件

    这一步的目的是把微软工作流的复杂对象进行整理,方便序列化给JavaScript使用,并对预埋的需要扩展JavaScript编程的地方创建JavaScript函数名(创建方法需要根据活动树生成,要保证修改工作流后原有的未更改部分生成名一致),并记录到函数表上。

    整理后的活动树

    整理后的活动树
  • JavaScript代码填写程序补完

    这一部分没什么太多说的,把之前解析出来的JavaScript函数列出来,填写上即可。当时我还是使用DataSet比较多,所以函数表使用DataSet做的,请各位想象成喜欢的形式:

    void initCodeTable()
        {
            codeTable = new DataTable("Code");
            codeTable.Columns.Add("Guid");
            codeTable.Columns.Add("Event");
            codeTable.Columns.Add("Interface");
            codeTable.Columns.Add("Codes");
            CM.Tables.Add(codeTable);
        }
    

    这里事件的概念主要是配合Win8Metro那头的框架设计的。事件都是XAML+JS Api中的。
    事件列表

    事件列表
  • JavaScript组装

    上一步已经把JavaScript函数全都做完了,接下来把这些函数生成到最终的html文件中就好了。这里预先写一个模版文件,执行主体JavaScript状态机就在这个文件里面。

    JavaScript的功底比较菜,没做封装,各位凑合看看吧:

  1 <!DOCTYPE html>
  2 
  3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
  4 <head>
  5     <meta charset="utf-8" />
  6     <title></title>
  7     <script type="text/javascript" src="jquery-1.8.2.min.js"></script>
  8     <script type="text/javascript" src="core-min.js"></script>
  9     <script type="text/javascript" src="enc-base64-min.js"></script>
 10     <script type="text/javascript" src="json.js"></script>
 11     <script type="text/javascript" src="UppApp.js"></script><!--UppBox通讯类库-->
 12     <script type="text/javascript" src="Network.js"></script>
 13 </head>
 14 <body>
 15     <script type="text/javascript">
 16         //省略UppBox应用层初始化
 17 
 18         var DEBUG = false;
 19         var NETDEBUG = false;
 20         var SMEngine;
 21         var currSM;
 22         var SUSPEND = false;
 23 
 24         //状态机数据注入点
 25         //<!--JSONDATA-->
 26 
 27         //状态用临时变量
 28         var tempVar = (function () {
 29             function tempVar() { }
 30             return tempVar;
 31         })();
 32 
 33         function MessageBus(para);//接收Win8Metro发来的信息,具体内容略function SendMsg(cmd, msg);//向Win8Metro发送信息,具体内容略
 34           function DebugInfo(para);//调试信息传递,可通过控制台/UppBox/body的DOM节点
 35 
 36         function InitApp() {
 37             //网络状态,配置项等的初始化注入点
 38             //<!--INITDATA-->
 39             AppDemo.initFW();//UppBox初始化
 40             SMEngine = eval(json);//json为上方注入的内容,是个Json结构对象
 41             currSM = SMEngine;
 42             if (DEBUG) DebugInfo("Init()" + currSM.DisplayName);
 43             EntryState(currSM.States[0]);
 44             try {
 45                 AppLoaded();
 46             } catch (e) {
 47             }
 48         }
 49 
 50         function runFunction(func, para) {
 51             if (func == null) return false;
 52             switch (func.Type) {
 53                 case "Action":
 54                     var funName = func.Code;
 55                     try {
 56                         if (DEBUG) DebugInfo("runFunction():" + funName + " para:" + para);
 57                         var result = eval(funName + "(para);");
 58                         return result;
 59                     } catch (e) {
 60                         return false;
 61                     }
 62                     break;
 63 
 64                 default:
 65                     break;
 66             }
 67         }
 68 
 69         function TAction(action, entryState, para) {
 70             if (DEBUG) DebugInfo("TAction:" + action.DisplayName + " " + entryState.DisplayName + " " + para);
 71             try {
 72                 if (para != "[SUSPEND]") {
 73                     var result = runFunction(action, para);
 74                     if (result == false) {
 75                         SUSPEND = true;
 76                         setTimeout(function (action, entryState) {
 77                             TAction(action, entryState, "[SUSPEND]");
 78                         }, 500, action, entryState);
 79                     } else {
 80                         EntryState(entryState);
 81                     }
 82                 }
 83                 else {
 84                     if (SUSPEND == false) {
 85                         EntryState(entryState);
 86                     } else {
 87                         setTimeout(function (action, entryState) {
 88                             TAction(action, entryState, "[SUSPEND]");
 89                         }, 500, action, entryState);
 90                     }
 91                 }
 92             } catch (e) {
 93             }
 94         }
 95 
 96         function Trigger(para) {
 97             if (SUSPEND) {
 98                 if (DEBUG) DebugInfo("SUSPEND():" + para);
 99                 otherFun(para);
100                 return;
101             }
102             for (var i = 0; i < currSM.currState.Transitions.length; i++) {
103                 var result = runFunction(currSM.currState.Transitions[i].Trigger, para);
104                 if (result == true) {
105                     var result = runFunction(currSM.currState.Transitions[i].Condition, para);
106                     if (result == true) {
107                         var stateName = currSM.currState.Transitions[i].To;
108                         for (var j = 0; j < currSM.States.length; j++) {
109                             var sns = currSM.States[j].DisplayName.split("_");
110                             var CurrName = sns[sns.length - 1];
111                             if (CurrName == stateName) {
112                                 ExitState();
113                                 TAction(currSM.currState.Transitions[i].Action, currSM.States[j], para);
114                                 return;
115                             }
116                         }
117                         return;
118                     }
119                 }
120             }
121             var result = runFunction(currSM.currState.Main, para);
122             if (result != true) {
123                 if (DEBUG) DebugInfo("otherFun(): para:" + para);
124                 otherFun(para);
125             }
126         }
127         
128         function otherFun(para) {
129             try {
130                 var str = "";
131                 var msg = "";
132                 try {
133                     str = para.split(" ");
134                     if (str.length > 1) msg = str[1];
135                     for (var i = 2; i < str.length; i++) {
136                         msg += " " + str[i];
137                     }
138                     eval(str[0] + "(msg)");
139                 } catch (e) {
140                     str = para;
141                     eval(str + "()");
142                 }
143             } catch (e) {
144             }
145         }
146 
147         function EntryState(state) {
148             if (DEBUG) {
149                 var debug = "EntryState():" + state.DisplayName;
150                 if (currSM.currState != null) debug += " CurrState:" + currSM.currState.DisplayName;
151                 DebugInfo(debug);
152             }
153             switch (state.Entry.Type) {
154                 case "Action":
155                     if (!state.IsFinal) {
156                         currSM.currState = state;
157                     } else {
158                         while (true) {
159                             if (currSM.preSM == null) break;
160                             currSM = currSM.preSM;
161                             if (!currSM.currState.IsFinal) break;
162                         }
163                     }
164                     runFunction(state.Entry, null);
165                     Trigger("");
166                     break;
167 
168                 case "StateMachine":
169                     currSM.currState = state;
170                     state.Entry.preSM = currSM;
171                     currSM = state.Entry;
172                     EntryState(currSM.States[0]);
173                     break;
174 
175                 default:
176                     break;
177             }
178         }
179 
180         function ExitState() {
181             if (DEBUG) DebugInfo("ExitState():" + currSM.currState.DisplayName);
182             if (currSM.currState != null) {
183                 runFunction(currSM.currState.Exit, null);
184             }
185         }
186 
187         //函数注入点,编辑器中写的各种函数在此注入
188         //<!--FUNCDATA-->
189 
190         //举个例子
191         //Entry:SM_23717574ae2bff0c6e0984140518db_欢迎界面初始化_Entry_初始化_Entry:
192         function A_87846c21b09faea4a622f8331aa9a4(para){
193             var str;
194             try {
195                 str = para.split(" ");
196                 switch (str[0]) {
197                     default:
198                     break;
199                 }
200             }catch(e){}
201 
202             SendMsg("Storyboard", "[NULL] Start 霓虹灯");
203             SendMsg("Storyboard", "[NULL] Start 进入");
204             SendMsg("Storyboard", "[NULL] Start 眨眼睛");
205         }
206     </script>
207 </body>
208 </html>
View Code

其他

程序逻辑编辑器界面

程序逻辑编辑器界面

程序逻辑编辑器界面

程序逻辑编辑器界面

程序逻辑编辑器界面
HTML5支持WebSocket可以连接UppBox,自写的JS状态机引擎在调试模式下可以把与C#代码交互过程全部传输出来,还可以传输指令,方便调试。

作者:SuperEVO
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
原文地址:https://www.cnblogs.com/zhang740/p/3716291.html