PUN2のコールバックをUniRxで扱うOSS「PUN2Rx」

# PUN2Rx
[PUN2Rx](https://github.com/nekomimi-daimao/PUN2Rx)

PUN2のコールバックをUniRXのOperatorに変換します。
[MIT](https://github.com/nekomimi-daimao/PUN2Rx/blob/master/LICENSE)です。

## 概要
PUN2のイベントはコールバックとして伝えられます。それぞれのイベントは独立ではないので、順序立ててハンドリングする必要があります。UniRxのオペレーターとして扱うことでイベントのハンドリングが簡単になります。
また、PUN2のイベントコールバックを受け取るには`MonoBehaviourPunCallbacks`を継承するか、各interfaceを継承する必要があります。PUN2Rxを使うことで基底クラスを自由に変更できます。

最も重要な点として、通常イベントのオペレータは`OnNext`、エラーのオペレータは`OnError`のみを通知します。これによって、ひとつの`Subscribe`で正常系とエラーのハンドリングが可能です。

## 例
### 成功と失敗を一度にSubscribe

```c#
var successStream = this.OnCreateRoomAsObservable();
var failureStream = this.OnCreateRoomFailedAsObservable();
successStream.Merge(failureStream)
    .Subscribe(
        unit => { Debug.Log("success!"); },
        exception => { Debug.Log("failed..."); }
    );
```

### 特定のルームに接続する。途中のエラーコールバックをハンドリング
```c#
// room name you want to join
var roomName = "roomName";

// OnNext --- （ConnectedToMaster => OnJoinedRoom）
var successStream =
    this.OnConnectedToMasterAsObservable().Take(1).IgnoreElements()
        .DoOnCompleted(() =>
            {
                Debug.Log("connect finish!");
                Debug.Log("join room!");
                var roomOptions = new RoomOptions();
                PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default);
            }
        ).Concat(this.OnJoinedRoomAsObservable());


// OnError --- （OnDisconnected or OnJoinRoomFailed）
var failureStream = this.OnDisconnectedAsObservable()
    .Where(cause => cause != DisconnectCause.None && cause != DisconnectCause.DisconnectByClientLogic)
    .Select(cause =>
    {
        throw PUN2Exception.Create((short) cause, cause.ToString());
        return Unit.Default;
    }).Merge(this.OnCreateRoomFailedAsObservable(), this.OnJoinRoomFailedAsObservable());

Observable.Amb(successStream, failureStream)
    .Subscribe(
        unit => { Debug.Log("join room finish!"); },
        exception => { Debug.Log(exception); })
    .AddTo(this);

Debug.Log("connect!");
PhotonNetwork.ConnectUsingSettings();
```

## Operators
[dotnet-callbacks](https://doc.photonengine.com/en-us/pun/v2/getting-started/dotnet-callbacks)

PUN2のコールバックのうち、PUN2Rxが対応しているコールバックは以下の通りです。
`OnNext`のみを通知するのか`OnError`のみを通知するのか。
OnNextで何が流れてくるのか、も記載されています。

Operator | Call | Type
--- | :---: | ---
`Photon.Realtime.IConnectionCallbacks` | -  | - 
OnConnectedAsObservable | OnNext | Unit
OnConnectedToMasterAsObservable | OnNext | Unit
OnDisconnectedAsObservable | OnNext | DisconnectCause
OnRegionListReceivedAsObservable | OnNext | RegionHandler
OnCustomAuthenticationResponseAsObservable | OnNext | Tuple&lt;string, object&gt;
OnCustomAuthenticationFailedAsObservable | **OnError** | Unit
`Photon.Realtime.IInRoomCallbacks` | - | -
OnPlayerEnteredRoomAsObservable | OnNext | Player
OnPlayerLeftRoomAsObservable | OnNext | Player
OnRoomPropertiesUpdateAsObservable | OnNext | HashTable
OnPlayerPropertiesUpdateAsObservable | OnNext | Tuple&lt;PLayer, Hashtable&gt;
OnMasterClientSwitchedAsObservable | OnNext | Player
`Photon.Realtime.ILobbyCallbacks` | |
OnJoinedLobbyAsObservable | OnNext | Unit
OnLeftLobbyAsObservable | OnNext | Unit
OnRoomListUpdateAsObservable | OnNext | List&lt;RoomInfo&gt;
OnLobbyStatisticsUpdateAsObservable | OnNext | List&lt;TypedLobbyInfo&gt;
`Photon.Realtime.IMatchmakingCallbacks` | |
OnFriendListUpdateAsObservable | OnNext | List&lt;FriendInfo&gt;
OnCreateRoomAsObservable | OnNext | Unit
OnCreateRoomFailedAsObservable | **OnError** | Unit
OnJoinedRoomAsObservable | OnNext | Unit
OnJoinRoomFailedAsObservable | **OnError** | Unit
OnJoinRandomFailedAsObservable | **OnError** | Unit
OnLeftRoomAsObservable | OnNext | Unit
`Photon.Pun.IPunOwnershipCallbacks` | |
OnOwnershipRequestAsObservable | OnNext | Tuple&lt;PhotonView, Player&gt;
OnOwnershipTransferredAsObservable | OnNext | Tuple&lt;PhotonView, Player&gt;


## 注意
### typo
`IPunOwnershipCallbacks.OnOwnershipTransfered`
PUN1からですが[^1]、これ多分Typoです。
「Transfer **r** ed」
PUN2をバージョンアップした際にエラーが出たら適宜修正してください。

### DisconnectCause
`IConnectionCallbacks.OnDisconnected`はこれだけなぜか正常イベントとエラーイベントをひとつのコールバックで返してきやがるので、エラーオペレータとして扱いたい場合は[例](#例)のように`Select`で変換して使ってください。むちゃくちゃしてますが他の手段が思いつかない。

```c#
.Select(cause =>
{
    throw PUN2Exception.Create((short) cause, cause.ToString());
    return Unit.Default;
}).Merge(this.OnCreateRoomFailedAsObservable(), this.OnJoinRoomFailedAsObservable());
```

## まとめ
[試作した](https://qiita.com/nekomimi-daimao/items/382c09ba448f011b7369)あとも探してたんですが、なかったので結局自分で作りました。UniRxとPUN2を併用してるプロジェクトなんていくらでもありそうですが……謎。とりあえず自分が欲しい分は作ったので、自分は満足しております。
Taskには対応してません。いまいちいい実装が思いつかないのと、あんまり自分が必要としてないので。
nullチェックのあたりをif文に書き換えれば`.NET 4.6`は必須じゃなくなるんですが、「PUN2とUniRX導入しといて4.6にしてない奴はおらんやろ……」という独断と偏見によってそのままにしてます。書き直すのめんどくさい。あと`#ifdef`はよくわからないなので定義してないです。
`README.md`は最初あたりGoogle翻訳でがんばってたんですが、めんどくさかったのでもうフィーリングで書いてます。まあプログラマなら英語読まなくてもコード読めばわかるでしょう。
RaiseEventに関しては[RER/RES](https://qiita.com/nekomimi-daimao/items/b83701e6aa04bca3d87f)を使ってください[^2]。
最近仕事でドキュメントとか一切作ってなかったので、説明を書くのが一番疲れました。

おしまい。

[^1]: 誰も使ってないから誰も気づいてないのでは……？　ownership周りの使い所がわからん。
[^2]: 結局みんな使い方がよくわからないらしいので意地になっている。