继之前分享的一篇《Robotics Studio学习教程:第十天——利用 DSS 建立虚拟环境(2)》, 我们将继续分享下一篇《Robotics Studio学习教程:第十一天——利用 DSS 建立虚拟环境(3)》,让我继续开始我们学习Visual Programming Language,以及使用Robotics Studio学习开发机器人应用的道路吧。
[Robotics Studio] DSS with VSE [III] -- Day12
居然一个 DSS with VSE 要写到 Part III ? 有点混的嫌疑...
但其实我是佛心来着, 如果一口气写太多, 大家或许会消化不良哩...
如果大家有问题请在响应写出来, 如果有多一点提醒, 我也可以把写不好的, 或是写错的地方做一些修正啊.
那昨天的程序还可以加上甚么?
你不觉得一次产生一个方块不够过瘾? 我好想要那个程序一次生好多个, 那个立方块就像 $$ 一样一直掉下来不是很好吗 (想太多了吧!)
那我们就再加上一个 AddBoxes 这样的 DSSP 来玩吧.
首先加上数据宣告 (你应该知道要放在 VSE_ExampleTypes.cs 当中吧):
- [DataContract]
- public class AddBoxesInfo
- {
- [DataMember]
- public AddBoxInfo info { get; set; }
- [DataMember]
- public int count { get; set; }
- }
然后是 DSSP 的宣告以及修改, 如下:
- /// <summary>
- /// VSE_Example main operations port
- /// </summary>
- [ServicePort]
- public class VSE_ExampleOperations : PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, Subscribe, AddABox, AddBoxes>
- {
- }
- [DisplayName("Add boxes")]
- [Description("Add boxes into VSE")]
- public class AddBoxes : Insert<AddBoxesInfo, PortSet<DefaultInsertResponseType, Fault>>
- {
- }
最后 ServiceHandler (在 VSE_ExampleService class 当中) 是像这样:
- [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
- public IEnumerator<ITask> AddBoxesHanlder(AddBoxes abx)
- {
- for (int i = 0; i < abx.Body.count; i++)
- AddBox(new Vector3(abx.Body.info.X, abx.Body.info.Y, abx.Body.info.Z), abx.Body.info.BoxName + "_" + i.ToString());
- // 發一個狀態變更通知
- base.SendNotification(_submgrPort, abx);
- // 回報結果
- abx.ResponsePort.Post(DefaultInsertResponseType.Instance);
- yield break;
- }
这样你稍微修改一下 VPL 的程序, 就可以自己一次产生很多个立方块啰...
但是...你有没有发现这些立方块都是瞬间一次产生出来的???
这样子, 在 VSE 的物理学引擎的规范上, 是无法允许同一时间在唯一的空间位置上存在两个以上的实体的 (也就是发生碰撞) , 所以你会发现不但一次产生多个立方块, 接下来这些立方块就向四面八方喷射出去...蛮烂的.
那我们可以不可以在每次 AddBox 之后加上 Thread.Sleep(1000) ?
当然可以, 而且执行以后效果是不错的. 只是这样做就可惜了 CCR 帮我们规画的所谓同时运算的架构了 (还记得 Handler 回传的是 IEnumerator<ITask> ?)
而且你现在只是要暂停 (所以 Thread.Sleep 就可以搞定), 如果之后是要等候某件事完成, 不采用 IEnumerator<ITask> , 你就只能用 for loop 不断去问, 这样很容易写出 busy waiting 的程序...
所以比较好的写法是像这样:
- 1 yield return Arbiter.Receive(false, TimeoutPort(1000), time => { });
这表示我们这一次要回传一个 ITask, 这个 ITask 是 Arbiter.Receive 所产生的, 用来处理当一个 Port 收到讯息时该如何动作.
TimeoutPort 就是产生一个 port, 这个 TimeoutPort 接收一个单位为千分之一秒的数值, 当时间到了的时候就会把一个讯息往该 port 送.
所以上面这行就会让 AddBoxesHandler 先暂时回传该 ITask , 等到该 ITask 被执行了以后才会再回来到AddBoxesHandler 当中.
最后, 这个 AddBoxesHandler 其实没做检查, 也没把数据加入状态 (state) , 所以我们应该把 AddABoxHanlder 的那些 code copy-paste 过来...
等等, copy-paste ? 虽然这是程序人员的秘技, 但是我还是建议把重复的 code 写成一个 function , 这样才好整理, 将 code 抽出来如下:
- /// <summary>
- /// 加入一個立方塊, return ture 表示成功, false 表示失敗
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <typeparam name="TBody"></typeparam>
- /// <param name="dssp_operation"></param>
- /// <param name="boxname"></param>
- /// <param name="pos"></param>
- /// <returns></returns>
- private bool DoAddBox<T, TBody>(T dssp_operation, string boxname, Vector3 pos) where T : Insert<TBody, PortSet<DefaultInsertResponseType, Fault>> where TBody : new()
- {
- // 先檢查是否傳了正確的資料
- if (string.IsNullOrEmpty(boxname))
- {
- dssp_operation.ResponsePort.Post(
- Fault.FromCodeSubcodeReason(
- FaultCodes.Sender,
- DsspFaultCodes.OperationFailed, "No Name!"));
- return false;
- }
- // 再檢查是否允許加入這樣的資料
- if (_state.Boxes.Exists(n => String.Compare(n, boxname, true) == 0))
- {
- dssp_operation.ResponsePort.Post(
- Fault.FromCodeSubcodeReason(
- FaultCodes.Sender,
- DsspFaultCodes.DuplicateEntry, "box with that name is already exists."));
- return false;
- }
- // 執行 state 的變更
- _state.Boxes.Add(boxname);
- // 在 VSE 當中加上立方塊
- AddBox(pos, boxname);
- return true;
- }
然后 AddABoxHandler 以及 AddBoxesHandler 的 code 就可以改成像这样:
- /// <summary>
- /// AddABox 的 ServiceHandler
- /// </summary>
- /// <param name="abx"></param>
- /// <returns></returns>
- [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
- public IEnumerator<ITask> AddABoxHanlder(AddABox abx)
- {
- if (DoAddBox<AddABox, AddBoxInfo>(abx, abx.Body.BoxName, new Vector3(abx.Body.X, abx.Body.Y, abx.Body.Z)) == false)
- yield break;
- // 發一個狀態變更通知
- base.SendNotification(_submgrPort, abx);
- // 回報結果
- abx.ResponsePort.Post(DefaultInsertResponseType.Instance);
- }
- [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
- public IEnumerator<ITask> AddBoxesHanlder(AddBoxes abx)
- {
- for (int i = 0; i < abx.Body.count; i++)
- {
- if (DoAddBox<AddBoxes, AddBoxesInfo>(abx, abx.Body.info.BoxName+"_"+i.ToString(), new Vector3(abx.Body.info.X, abx.Body.info.Y, abx.Body.info.Z)) == false)
- yield break;
- yield return Arbiter.Receive(false, TimeoutPort(1000), time => { });
- }
- // 發一個狀態變更通知
- base.SendNotification(_submgrPort, abx);
- // 回報結果
- abx.ResponsePort.Post(DefaultInsertResponseType.Instance);
- }
这样有没有觉得写 DSS 的时候会被泛型 (Generic Programming) 搞死?? (VPL 在这方面就胜出了)
其他有几个实验大家可以自己玩玩看:
1. 如果我不想要写 DoAddBox 函式, 而改在 AddBoxesHandler 当中的循环对 MainPort Post 一个 AddABox 对象(讯息), 是否也可以做到加入多个 box ?
-- 答案是可以的, 但是你会发现不管你怎么做 (Thread.Sleep or TimeoutPort) , 所有的 box 都是一次出现...
你知道为什么会一次出现吗? (提醒你想一想关于 ServiceHandlerBehavior.Exclusive 的属性)2. 如果我把数据定义当中的 AddBoxesInfo 改为继承 AddBoxInfo , 用扩充 Property 的方式, 而不是像我们原本的做法, 把 AddBoxInfo 作为 AddBoxesInfo 的 Property, 那会发生甚么事?
-- 答案是 AddBoxesHandler 会运作不正常...为什么?? (因为 AddBoxesHandler 被系统加挂的方法是靠侦测传进来的数据形态, 一旦数据型态有继承的关系, 自然就会找到其他可以处理的 Handler)
附上程序内容 (VPL 以及 DSS , 要先做出 DSS Service , 再用 VPL 玩).
让我们继续一下章教程:
《Robotics Studio学习教程:第十三天——让我们一起来玩P3DX》