[Robotics Studio] DSS with VSE [II] -- Day11

  • 7511
  • 0
  • 2009-02-17

[Robotics Studio] DSS with VSE [II] -- Day11

昨天我們寫了一個基本的 DSS Service , 可以操控 Visual Simulation Environment.

現在我們再來為它多加一點, 就來幫它加一個功能, 讓 VPL 可以透過這個元件加入一個立方塊到 VSE 當中.

我們開放這個立方塊的初始位置 (X, Y, Z) , 以及這個物體 (Entity) 的名稱給外部輸入 , 所以在 VSE_ExampleType.cs 當中加入下面的 code:

[DataContract]
public class AddBoxInfo
{
    [DataMember]
    public float X { get; set; }

    [DataMember]
    public float Y { get; set; }

    [DataMember]
    public float Z { get; set; }

    [DataMember]
    public string BoxName { get; set; }

}

 

然後, 我也想讓這個元件把這些立方塊 (Box) 看成是自己的狀態之一, 所以我們修改 VSE_ExampleState class 的定義如下:

/// <summary>
/// VSE_Example state
/// </summary>
[DataContract]
public class VSE_ExampleState
{
    [DataMember]
    public List<string> Boxes { get; set; }


    public VSE_ExampleState()
    {
        Boxes = new List<string>();
    }
}

 

現在我們要幫 VSE_Example 新增一個 DSS Operation (為了不混淆, 以後就跟官方文件講一樣的話, 叫做 DSSP , 表示 DSS 對外操作的 Protocol : Decentralize Software Service Protocol)  , 不同於上次我們使用的 Submit, 這次我們改用 Insert , 連同 VSE_ExampleOperations 也加上必要的修改, 如下:

/// <summary>
/// VSE_Example main operations port
/// </summary>
[ServicePort]
public class VSE_ExampleOperations : PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, Subscribe, AddABox>
{
}

[DisplayName("Add a box")]
[Description("Add a box into VSE")]
public class AddABox : Insert<AddBoxInfo, PortSet<DefaultInsertResponseType, Fault>>
{
}

 

現在我們已經用過 Submit, Insert, 為了日後不再解說, 所以我在這裡先把所有內建的 DSSP 做個說明 (接下來如果你忘了 DSS 的架構圖, 可以去 Day8 回憶一下).

在 Microsoft DSS Framework 當中, 為了我們寫 code 方便, 已經幫我們內建了一些 DSSP, 這樣日後我們要對 DSS Service 加上一些可讓外部操控的功能時, 直接宣告一個 class , 採用繼承 , 在繼承時設定好輸出入的資料形態, 然後就可以把該功能 (該 class) 加到該 DSS Service 的 MainPort 當中. 以剛剛的例子而言, 我們宣告了 AddABox 這樣的 class, 繼承了 DSS 內建的 Insert , 也設定好輸入是 AddBoxInfo , 輸出是 DefaultInsertResponseType 或是 Fault 型態. 然後把 AddABox 加入到 VSE_ExampleOperations 的 PortSet 後面的眾多功能之一 , 這個 VSE_ExampleOperations 如果你去看 VSE_Example.cs (在 VSE_ExampleService 當中), 會看到

/// <summary>
/// Main service port
/// </summary>
[ServicePort("/VSE_Example", AllowMultipleInstances = true)]
VSE_ExampleOperations _mainPort = new VSE_ExampleOperations();

 

所以你就知道 VSE_ExampleService 的 MainPort 就是 VSE_ExampleOperations 這個 class 的實體 (instance).

內建的 DSSP 放在 Microsoft.Dss.ServiceModel.Dssp 這個 Namespace 下面, 所以你想看說明, 可以到 RDS 安裝目錄的 documentation 當中找到 CcrAndDssRuntimeClassRef.chm , 當中有很多 class 的定義. 以下我要針對這些內建的 DSSP 作一些說明.

第一大類, 關乎一個 DSS Service 的生死

Create : 一般而言是 DSS 內建的 Constructor Service (屬於 DSS System Service 之一) 會呼叫的, 也就是用來生成該 DSS 的實體, 所以當你寫 Factory 之類的 DSS 也許可以用它. DSS 當中也內建有 Manifest loader service , 這個也會呼叫使用 Create .

Drop : 相對於 Create , 這是用來停止 (Shut down) DSS Service 的, 你可以發現我們的範例都有 DsspDefaultDrop , 就是繼承自 Drop , 如果你要自己寫 Handler, 記得在 ServiceHandler 的屬性加上 ServiceHandlerBehavior.Teardown

第二類, 系統查詢協定使用

Lookup : 用來對外揭露這個 Service 有哪些 Contracts, 一般而言 DsspDefaultLookup 就夠用了.

第三大類, 用來讓外界 (其他 DSS Service) 查詢該 DSS Service 的狀態 (state)使用

一般而言, Get, Query 都不會有 State 改變的問題, 你可以看成是讀取用, 為了與寫入的 DSSP Service 同步協調, 有必要在 Handler 函式的 ServiceHandler 的屬性上面加上 ServiceHandlerBehavior.Concurrent . 這樣所有該 DSS Service 讀取用的 DSSP Handler 都可以同時執行而不會有問題.

Get : 用來讓外界知道該 DSS Service 目前的狀態 (state) , 一般而言就是直接把該 DSS Service 的 state 回傳回去.

Query : 類似 Get, 但是是讓外界取得狀態的子集合, 所以一個 DSS 可以有很多個 Query, 因為狀態可能資料量很多, 如果用 Get 會很耗資源, 所以可以讓外界用 Query 來取得部份的狀態資訊.

Subscribe : 用來讓其他 DSS Service 註冊, 這樣我們這個 DSS Service 就可以在狀態改變時透過 SubscriptionManager (屬於 DSS System Service 之一) 來通知註冊的 DSS, 一般流程如下 (來自官方的圖):

image

第四大類, 用來讓外界更改或寫入 DSS Service 的狀態

你可以看成寫入動作, 所以有必要在這類的 Handler 函式的 ServiceHandler 屬性上面加上 ServiceHandlerBehavior.Exclusive , 這不但會使得所有寫入動作不會一起執行 , 而且讀取也不會跟寫入動作同時執行(這樣會造成資料不一致) . 一般而言,建議你在撰寫這類 DSSP Handler 的時候, 在 DSS 更改或寫入狀態之前要做一些前置檢查動作, 發現有問題就要及早報告失敗, 更改完畢再對 SubcriptionManager 發個通知. (如上圖) 最後才回報更改或寫入結果.

Insert : 就是對 DSS 的 state 執行加入資料的動作.

Update : 就是對 DSS 的 state 執行更改部分資料的動作 (相較於 Replace, 這個只是更改部分資料)

Upsert : Update or Insert 的意思, 就是如果找到要變更的資料, 就變更, 找不到就新增資料. 如果沒這個, 你呼叫 Update , 發現沒有再來 Insert, 在這個過程當中, 很有可能別的 DSS 也來插一腳, 關於同步運算的問題就頭大了. 所以有必要提供 atomic operation (一次運算完畢, 運算過程不會被其他同時運算干擾).

Delete : 相對於 Insert, 就是刪除 DSS state 當中的資料.

Replace : 相較於 Update, 這個是更改 state 的全部資料 (一般做法就是把整個 state 替換掉).

最後一類 : 就是跟 DSS state 無關的 DSSP

一般而言這類 Handler 函式的 ServiceHandler 屬性也是採用 ServiceHandlerBehavior.Concurrent .

Submit : 輸出入的結果與 DSS state 內容沒有關係, 屬於獨立運算之類的 DSSP.

 

好了, 噴完一堆口水以後, 下次我們新增 DSSP 的時候就由上面這幾種當中挑選最適合的來繼承.
(挑錯了怎麼辦, 我也不知道如果你掛羊頭賣狗肉會有甚麼後果啊!)

回到我們剛剛寫的程式, 只差最後一步了, 就是把 AddABox 的 ServiceHandler 寫好.

我們在 VSE_ExampleService class 當中加上如下的 code:

/// <summary>
/// 在虛擬環境 (VSE) 當中加上一個立方體
/// </summary>
/// <param name="position"></param>
/// <param name="bxname"></param>
void AddBox(Vector3 position, string bxname)
{
    Vector3 dimensions =
        new Vector3(0.2f, 0.2f, 0.2f); // meters

    // create simple movable entity, with a single shape
    SingleShapeEntity box = new SingleShapeEntity(
        new BoxShape(
            new BoxShapeProperties(
            100, // mass in kilograms.
            new Pose(), // relative pose
            dimensions)), // dimensions
        position);

    // mass or density can be specified.  No need to specify both
    box.State.MassDensity.Mass = 0;

    // Name the entity. All entities must have unique names
    box.State.Name = bxname;

    // Insert entity in simulation.
    SimulationEngine.GlobalInstancePort.Insert(box);
}

/// <summary>
/// AddABox 的 ServiceHandler
/// </summary>
/// <param name="abx"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator<ITask> AddABoxHanlder(AddABox abx)
{
    // 先檢查是否傳了正確的資料
    if (string.IsNullOrEmpty(abx.Body.BoxName))
    {
        abx.ResponsePort.Post(
            Fault.FromCodeSubcodeReason(
                FaultCodes.Sender,
                DsspFaultCodes.OperationFailed, "No Name!"));
        yield break;
    }

    // 再檢查是否允許加入這樣的資料
    if (_state.Boxes.Exists(n => String.Compare(n, abx.Body.BoxName, true) == 0))
    {
        abx.ResponsePort.Post(
            Fault.FromCodeSubcodeReason(
                FaultCodes.Sender,
                DsspFaultCodes.DuplicateEntry, "box with that name is already exists."));
        yield break;
    }

    // 執行 state 的變更
    _state.Boxes.Add(abx.Body.BoxName);

    // 在 VSE 當中加上立方塊
    AddBox(new Vector3(abx.Body.X, abx.Body.Y, abx.Body.Z), abx.Body.BoxName);

    // 發一個狀態變更通知
    base.SendNotification(_submgrPort, abx);

    // 回報結果
    abx.ResponsePort.Post(DefaultInsertResponseType.Instance);

    yield break;
}

 

大功告成, 最後, 用  VPL 寫個程式來玩玩, 如下:

image

我用了兩個 SimpleDialog (不同名稱 (name), 所以不是分身) , 第一個是 Prompt , 就是讓使用者填一串文字, 然後把該文字塞給 VSE_Example 的 Add abox, 設定在 X,Y,Z 都是 3, BoxName 則是 SimpleDialog 吐出的 TextData , 然後成功的話再重複做, 失敗就跳一個 Error 的 Alert, 再回去重複.

結果就可以一直產生立方塊...

如下圖:

image

玩到後來居然給我這個畫面:

image

微軟你......難怪被人叫 M$