【Strix】PhotonからStrix Cloudに移行してみた ～RPC & MessageRelay編～

今記事は以下の記事のRPC & MessageRelay編です。

https://qiita.com/HarumaroJP/items/cf9034bcf7f5e73d2b5e

まだメインの記事を読んでない方はそちらから読んでいただけると幸いです。

Strix CloudにおけるRPCとMessageRelayの利用までを解説していきたいと思います。

##独自の型をStrixに登録する

```C#
    struct ShootRequest
    {
        public Vector3 dir;
        public float speed;
    }
```
例としてこのstructを登録したいとします。

```C#
    public class MyNetwork: MonoBehaviour
    {
        public void Awake()
        {
            ObjectFactory.Instance.Register(typeof(ShootRequest));
        }
    }
```
この型を初期化時に登録することで利用することができます。
**型の中身がStrixが対応している型であれば、PUN2のようにbyte列に詰め込む作業が必要ありません。**

これによって独自の方でも送信することができるようになりました。



##RPC
```C#
    [StrixRpc]
    void RPCMethod()
    {

    }
```

`StrixBehaviour`を継承したクラスに`StrixRpc`属性を付けることでRPCとして使えるようになります。

###UIDの取得方法
```C#
    UID uid = strixReplicator.ownerUid;
```
メッセージの送信にはUIDという、サーバー側でオブジェクトやルームメンバーを管理するためのIDが必要です。
`StrixReplicator`のインスタンスから取得することができます。

###RPCの送信方法

 1\. 特定のルームメンバーでRPCを呼び出す

```C#
    void Rpc(UID to, //送信したいプレイヤーのUID
    string rpcName, //関数名
    RpcSuccessEventHandler successHandler, //成功時
    FailureEventHandler failureHandler, //失敗時
    params object[] args) //送信するオブジェクト
```

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409758/b676e0d0-6b5b-956f-6431-36d6a615645e.png">

 2\. このメンバーでRPCを呼び出す

```C#
    void Rpc(string rpcName, 
    RpcSuccessEventHandler 
    successHandler, 
    FailureEventHandler failureHandler, 
    params object[] args)
```

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409758/39d03907-5542-f80b-d494-a88484dd597e.png">

 3\. ルームの現在の所有者に対してのみRPCを呼び出す

```C#
    void RpcToRoomOwner(string rpcName, 
    RpcSuccessEventHandler successHandler, 
    FailureEventHandler failureHandler, 
    params object[] args)
```

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409758/38a64408-07a5-fd8a-e7ba-cf5266d0c247.png">

 4\. 現在のメンバーを除くすべてのメンバーでRPCを呼び出す

```C#
    void RpcToOtherMembers(string rpcName, 
    RpcSuccessEventHandler successHandler, 
    FailureEventHandler failureHandler, 
    params object[] args)
```

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409758/827e15cd-fa03-c375-d36f-ef573661ad4a.png">

 5\. 自分を含むすべてのルームメンバーでRPCを呼び出す

```C#
    void RpcToAll(string rpcName, 
    RpcSuccessEventHandler successHandler, 
    FailureEventHandler failureHandler, 
    params object[] args)
```

<img width="300" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409758/e2afc398-e2f9-215c-856c-20121f929ac2.png">


##MessageRelay
PUN2の`RaiseEvent`と同等の機能になります。
ネットワークオブジェクトでなくとも送信でき、リフレクションを使わず、パフォーマンス的にも優しいので
個人的にはこっちのほうが好きです。

RPCのように、ネットワークオブジェクトにメッセージが送られるわけではないので注意してください。
指定したUIDのルームにメッセージを送るだけの機能になっています。

###リレーメッセージの送信
```C#
    void SendRoomRelay(object messageToRelay, //送りたい情報
    RoomRelayEventHandler handler, //成功時
    FailureEventHandler failureHandler, //失敗時
    RequestConfig config = null)
```
自身以外のルームにメッセージを送信します。

```C#
    void SendRoomDirectRelay(UID to, //送信先のUID
    object messageToRelay,
    RoomDirectRelayEventHandler handler,
    FailureEventHandler failureHandler,
    RequestConfig config = null)
```
指定したメンバーのUIDのルームにメッセージを送信します。



###リレーメッセージの受信

リレーメッセージの通知イベントを登録することで利用できるようになります。

```C#
    StrixNetwork.instance.roomSession.roomClient.RoomRelayNotified += RelayNotified;
    StrixNetwork.instance.roomSession.roomClient.RoomDirectRelayNotified += DirectRelayNotified;
```

###独自にラップしてみる

独自にクラスを作ってラップしているので、サンプルを踏まえて説明します。

```C#
public class MyRelay{
    static readonly StrixNetwork Instance = StrixNetwork.instance;
    static Dictionary<RelayType, Action<long, RelayData>> relayActions = new Dictionary<RelayType, Action<long, RelayData>>();

    //有効化
    public void Enable() {
        StrixNetwork.instance.roomSession.roomClient.RoomRelayNotified += RelayNotified;
        StrixNetwork.instance.roomSession.roomClient.RoomDirectRelayNotified += DirectRelayNotified;
    }

    //無効化
    public void Disable() {
        StrixNetwork.instance.roomSession.roomClient.RoomRelayNotified -= RelayNotified;
        StrixNetwork.instance.roomSession.roomClient.RoomDirectRelayNotified -= DirectRelayNotified;
    }

    //イベントの種類、Primaryキーとカスタムデータのコールバックを登録
    public static void RegisterEvent(RelayType type, Action<long, RelayData> action) {
        if (!relayActions.TryGetValue(type, out _)) {
            relayActions.Add(type, action);
            return;
        }

        Debug.LogError($"{type}のActionは既に存在しています！");
    }

    void DirectRelayNotified(NotificationEventArgs<RoomDirectRelayNotification> data) {
        try {
            RelayData customData = (RelayData) data.Data.GetMessageToRelay();
            HandleRelay(data.Data.GetFromUid(), customData);
        }
        catch (Exception e) {
            Debug.LogException(e);
            throw;
        }
    }

    void RelayNotified(NotificationEventArgs<RoomRelayNotification> data) {
        try {
            RelayData customData = (RelayData) data.Data.GetMessageToRelay();
            HandleRelay(data.Data.GetFromUid(), customData);
        }
        catch (Exception e) {
            Debug.LogException(e);
            throw;
        }
    }

    //受信したメッセージを実行
    void HandleRelay(UID fromUid, RelayData customData) {
        if (relayActions.TryGetValue(customData.GetRelayType(), out Action<long, RelayData> action)) {
            action.Invoke(Instance.roomMembers[fromUid].GetPrimaryKey(), customData);
            return;
        }

        Debug.LogWarning($"{customData.GetRelayType()}のActionは存在しません！");
    }

    //全てのルームにメッセージを送信
    public static void SendRelay(RelayType type, object customData) {
        RelayData data = RelayData.Create(type, customData);
        Instance.SendRoomRelay(data, null, args => { Debug.LogWarning($"Relayの送信に失敗しました！\n{args.cause.Message}"); });    
    }

    //指定のルームにメッセージを送信
    public static void SendDirectRelay(UID uid, RelayType type, object customData) {
        RelayData data = RelayData.Create(type, customData);
        Instance.SendRoomDirectRelay(uid, data, null, args => { Debug.LogWarning($"Relayの送信に失敗しました！\n{args.cause.Message}"); });
    }

    //データ送信用の構造体
    public struct RelayData {
        public static RelayData Create(RelayType type, object customData) {
            return new RelayData() {
                dataType = type,
                customData = customData
            };
        }

        RelayType dataType;
        object customData;

        public RelayType GetRelayType() => dataType;

        public T Cast<T>() {
            try {
                return (T) customData;
            }
            catch (Exception e) {
                Debug.LogException(e);
                throw;
            }
        }
    }
}

public enum RelayType : byte {
    Shoot,
    Damage,
}
```

`DirectRelayNotified()`, `RelayNotified()`で受信したデータを受け取っています。
また、引数`NotificationEventArgs`では、

`data.Data.GetFromUid()` : 送信元のUID
`data.Data.GetMessageToRelay()` : メッセージのobject
を取得することができます。

それを、`HandleRelay()`内で辞書内のデリゲートを実行しているといった形になっています。
`StrixNetwork.instance.roomMembers`には、UIDとCustomizableMatchRoomMemberのペアが入っています。
そこでUIDを検索して、`CustomizableMatchRoomMember.GetPrimaryKey()`でPrimaryKeyを取得しているというわけです。

しかし、UIDは普通にinterfaceなので、辞書で毎回検索して引っ張り出して来るのはあんまりパフォーマンス的には良くないです、、笑
もしかしたらカスタムデータにPrimaryキーを仕込んで置いたほうが早いかも？
long型だから8バイトも通信容量食うのはかなり痛いけど、、


###使い方

```C#
        MyRelay.RegisterEvent(RelayType.Shoot, (from, data) => {
            //受信したカスタムデータをキャスト（ここでは架空の構造体）
            ShootRequest r = data.Cast<ShootRequest>();

            if (playerControllers.TryGetValue(from, out PlayerController player)) {
                 player.OnShootRequest(r);
            }
        });
```

`RegisterEvent()`にメッセージのタイプと受信した際のデリゲートを渡すことで、登録できます。
PlayerControllerなどプレイヤーに紐づくクラスを適当に作っておいて、それをPrimaryKeyと紐付けた辞書を作っておきます。

そしてメッセージリレーを受信した際にそれを検索して、実行するという仕組みになっています。

実際にこのまま使うのは難しいので、お好みで改良してください。

ルームサーバーへの接続（マッチメイキング）は次の記事で解説します！
【Strix】 PhotonからStrix Cloudに移行してみた ～コールバック編～　※工事中

##参考リンク
https://www.strixengine.com/doc/unity/guide/ja/unitysdk/synchronization/rpc.html

https://www.strixengine.com/doc/unity/guide/ja/unitysdk/messagerelay/overview.html