[Robotics Studio] DSS with VSE [III] -- Day12

  • 5226
  • 0
  • 2009-02-17

[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