[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 的程式...
所以比較好的寫法是像這樣:
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 玩)
Day12Code.zip