# Diarkis ヘルプセンター

Diarkis はオンラインマルチプレイゲームを実現させるためのフレームワーク・エンジンです。Kubernetes を使ってオートスケールするだけでなく、自律して起動・動作するサーバを持ちクライアント・サイドにも対応しています。

Diarkis の語源は、ギリシャ語の Diarkis (διαρκής) で、「永遠のもの、止まらないもの」という意味の言葉です。

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Diarkis の概要</strong></td><td>Diarkis のコンセプトや概要についての説明です。</td><td></td><td><a href="/pages/I464pbKKwwtNTXKfj7sd">/pages/I464pbKKwwtNTXKfj7sd</a></td></tr><tr><td><strong>始めよう</strong></td><td>サンプルを使って Diarkis での開発を始める方法を説明します。</td><td></td><td><a href="/pages/l9dIJ4vXhSv2ygQOQKAE">/pages/l9dIJ4vXhSv2ygQOQKAE</a></td></tr><tr><td></td><td><strong>Diarkis モジュール</strong></td><td>Diarkis を各モジュール毎に解説します。</td><td><a href="/pages/p0RyZZX5QgxgNb9yo90E">/pages/p0RyZZX5QgxgNb9yo90E</a></td></tr><tr><td><strong>Diarkis サーバー</strong></td><td>Diarkis のサーバーの詳細いついて解説します。</td><td></td><td><a href="/pages/zuV4dnt9OY4pWyWcLuUu">/pages/zuV4dnt9OY4pWyWcLuUu</a></td></tr><tr><td><strong>Diarkis クライアント</strong></td><td>Diarkis のクライアントライブラリとサンプルについて解説します。</td><td></td><td><a href="/pages/4WJlyCKaB2UlebsAEoPo">/pages/4WJlyCKaB2UlebsAEoPo</a></td></tr><tr><td><strong>Diarkis ツール</strong></td><td>その他 Diarkis 開発時に利用するツールを紹介します。</td><td></td><td><a href="/pages/odXihqBNVI8tRZIsilX1">/pages/odXihqBNVI8tRZIsilX1</a></td></tr><tr><td><strong>API リファレンス</strong></td><td>サーバーとクライアントの API リファレンス</td><td></td><td><a href="/pages/wndEAjizJgPEc0OvA0tc">/pages/wndEAjizJgPEc0OvA0tc</a></td></tr><tr><td><strong>FAQ</strong></td><td>よくある質問について解説します。</td><td></td><td><a href="/pages/osA4idcHPDIbZnJcvo72">/pages/osA4idcHPDIbZnJcvo72</a></td></tr><tr><td><strong>ライセンスと購入</strong></td><td>Diarkis を使用する際のライセンス購入について</td><td></td><td><a href="/pages/sBNdSQir7lNSKRR5WkT8">/pages/sBNdSQir7lNSKRR5WkT8</a></td></tr></tbody></table>


# Diarkis の概要

## Diarkis とモジュール

Diarkis はオンライン・マルチプレイヤー・ゲームなどのためのサーバーとクライアントのネットワーク・ミドルウェアです。

本ミドルウェアには、サーバー側とクライアント側の両方の SDK が付属しています。（サーバー側：Go、クライアント側：C++ および C#）

クライアント側には Unreal Engine のプラグインと Unity Engine の SDK も用意されています。

### サポートする OS

* **Linux**
* **Windows**
* **MacOS**
* **iOS**
* **Android**

### サポートする Platform:

* **PS4, PS5**
* **Xbox One, Xbox Series X, Xbox Series S**
* **Nintendo Switch**
* **Steam**

## サーバー・アーキテクチャの理念

Diarkis のサーバー設計は、サーバーの**非集中化**と**分散化**に重点を置いています。

Diarkis サーバーは、単一のサーバーのように動作するサーバークラスタを形成するように設計されています。サーバーは100％ [Golang](https://go.dev/) で書かれています。

この設計により、サーバー・クラスターは**耐障害性**（クラスター内の一部のサーバーに障害が発生しても、クラスタ内の残りのサーバーは全く影響を受けず、ゲーム・クライアントは単純に継続するために再接続する必要がある）と**水平方向にスケーラブル**（ユーザーのトラフィック量に応じて、単純にスケールイン/スケールアウトするために追加または削除）であることができます。

以下の図は、Diarkis サーバー・クラスターがどのように構成されているかを示しています。

1. Pod はサーバーです。
2. Diarkis クライアントを持つすべてのユーザー・クライアントは、UDP または TCP Pod と直接通信します。
3. Diarkis は、TCP および UDP ネットワーク・プロトコルの両方をサポートしています。
4. UDP の場合、Diarkis は再送、パケット順序制御、MTU 超過能力を備えた独自の RUDP を実装しています。

<figure><img src="/files/Y64HiygDU7a6JBY4Rqnw" alt=""><figcaption></figcaption></figure>

## 従来のアーキテクチャとの比較

Diarkis を使用せずに、同様のネットワーク・サーバー・システムを実装する方法は多数あります。

ここでは、そのようなシステムの最も一般的な方法の一つを取り上げ、Diarkis と比較します。

<figure><img src="/files/AcP1yGEJvOHjMofC4iMu" alt=""><figcaption></figcaption></figure>

### サーバーの維持管理 - 従来の方法

従来の方法では、リアルタイム・サーバーの追加や削除など、サーバーに対する変更はすべて手動でのメンテナンスと変更が必要です。これにより、人為的なエラーやその他の潜在的な問題が発生する可能性があります。

<figure><img src="/files/j7HiYhQZDsEDf5N9HGDw" alt=""><figcaption></figcaption></figure>

一方、Diarkis は手動メンテナンスを全く必要としません。すべてが自動的に処理されます。

<figure><img src="/files/QRzgjbntzI8ZOl63TbaN" alt=""><figcaption></figcaption></figure>

### システム全体の耐障害性

従来の方法で解決するのが非常に難しい問題が一つあります。それは、システム全体の単一障害点です。

システム全体を管理するために中央制御が必要です。この場合は、データベースになります。

<figure><img src="/files/3CpYkpa8WPDX63l0b0eO" alt=""><figcaption></figcaption></figure>

Diarkis のサーバー・クラスターは完全に分散化されており、中央制御システムが存在しないため、Diarkis サーバーのクラスター全体を管理する中央制御システムがありません。これにより、システム全体の単一障害点の問題が効果的に解消されます。

<figure><img src="/files/avVQUlbiTSVZSOkf9tD2" alt=""><figcaption></figcaption></figure>

## クライアントからサーバーへの通信

Diarkis は、主にリモート・クライアントとのサーバー・リレー同期を使用します。

つまり、Diarkis サーバーはクライアントがデータを交換するためのハブとして機能します。

### クライアントとサーバーの通信

Diarkis は主にリモート・クライアントとのサーバー・リレー同期を使用します。

これは、Diarkis サーバーがクライアント間のデータ交換のハブとして機能することを意味します。

<figure><img src="/files/UqYX678YWAxn7lfRhJ9Q" alt=""><figcaption></figcaption></figure>

Diarkis が提供するもう一つの同期方法は peer-to-peer (P2P) 通信です。

クライアントはデータを peer-to-peer で直接送受信します。サーバー・リレーのようにクライアント間にサーバーは存在しません。Diarkis はクライアントが直接通信するためのディスカバリーポイントとして機能します。

peer-to-peer 通信には 2 つのステップが必要です。まず、クライアントは自分のアドレスを交換し、[ホールパンチング](https://en.wikipedia.org/wiki/Hole_punching_\(networking\))を行います。ホールパンチングが成功すると、クライアントはパケットを直接送受信することができます。

<figure><img src="/files/RUPdy42sHnbwiwQE7tUj" alt=""><figcaption></figcaption></figure>

## Diarkis クライアント SDK

Diarkis クライアントは C++ で書かれています。また、 C# インターフェースもあります。クライアント SDK には Unreal Engine プラグインと Unity Engine プラグインが付属しています。

## Diarkis モジュール

Diarkis にはゲーム開発者が使用できる組み込みモジュールがあります。

* [Room モジュール](/diarkis-modules/room)
* [MatchMaker モジュール](/diarkis-modules/matchmaker)
* [Field モジュール](/diarkis-modules/field)
* [P2P（peer-to-peer）モジュール](/diarkis-modules/p2p)
* [DM（Direct Message）モジュール](/diarkis-modules/dm)
* [Notifier モジュール](/diarkis-modules/notifier)
* [Session モジュール](/diarkis-modules/session)
* [Group モジュール](/diarkis-modules/group)


# 始めよう

Diarkis で開発を始めるには、Diarkis サーバーおよび Diarkis クライアント SDK を組み込んだクライアント・アプリケーションが必要です。

本ドキュメントでは、 Diarkis サーバーテンプレートを使ってサーバを構築する手順および、クライアントからサーバーに接続しサンプルを動かす方法をチュートリアル形式で紹介いたします。


# Diarkis サーバーテンプレート

## はじめに

Diarkis では速やかに開発を始められるように、OSS でサーバー・テンプレートを用意しております。

<https://github.com/Diarkis/diarkis-server-template>

サーバーテンプレートは、開発者が迅速にプロジェクトを開始できるように設計された汎用性の高いテンプレートです。このテンプレートには、デフォルトで最小限の設定が含まれており、多様なプロジェクト要件に対応するための拡張性が備わっています。

## サーバーテンプレートを利用してサーバーを起動する

まずはローカル環境でサーバーを起動してみましょう。

詳細はチュートリアル [1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)を参照してください。

または、 [diakis-sever-template の README](https://github.com/Diarkis/diarkis-server-template) を参照してください。

## サーバーテンプレートのテストクライアントを利用する

ローカルでサーバーを起動したら、テストクライアントで疎通確認をしましょう。

また、テストクライアントは Diarkis が提供する様々なビルトインコマンドを試す、実装したカスタムコマンドの疎通確認に大変便利です。

詳細はチュートリアル [2. テストクライアントで疎通確認する](/getting-started/tutorial/test-client)を参照してください。

### サーバーサンプルを利用する

サーバーテンプレートには他にも様々な examples が用意されています。MatchMaker を使った様々なサンプルなどがありますので参照してください。

<https://github.com/Diarkis/diarkis-server-template/tree/develop/examples>


# Diarkis クライアント SDK

## はじめに

Diarkis クライアント SDK (以下、クライアント SDK) は様々なプラットフォームで動作するアプリケーションから Diarkis サーバーへ接続し Diarkis の機能を使用するための SDK です。

**C++ 版**と **C# 版**の SDK が提供されており、それぞれの言語から利用することが可能です。 クライアント SDK に含まれる **Diarkis クライアントランタイム (以下、ランタイム)** では各プラットフォームの差異を吸収して共通の API を提供しており、この API を使用することにより同一のコードを使用して各プラットフォームで Diarkis の機能を使用することができます。 また、C++ 版では Unreal Engine、C# 版では Unity から Diarkis を使用するためのサンプル実装が提供されています。

クライアント SDK およびサンプルをダウンロードするには Diarkis のライセンスが必要です。詳細については、 [ライセンスと購入](/support/license-and-billing)よりお問い合わせください。

## 対応プラットフォーム

* Windows 10/11
* Linux
* macOS
* PS4
* PS5
* Switch
* Xbox One(GDK)
* Xbox Series S/X
* Android
* iOS

## クライアント SDK のセットアップ

クライアント SDK は各プラットフォームごとに zip アーカイブの形で配布されており、アーカイブを展開するだけで使用することができます。\
開発環境として想定される Windows/macOS/Linux 版のパッケージにはベースとなるファイルが一式含まれており、それ以外のプラットフォームのパッケージはこれらのメイン開発環境用のパッケージと組み合わせて使用する差分のみが含まれています。そのため、先にメイン開発環境用のパッケージを展開し、同じ場所にその他のプラットフォームの開発環境用のパッケージを展開してください。\
構成の都合上、同一のファイルが複数のパッケージに含まれていることがありますが、展開時に上書きしていただいて問題ありません。

## パッケージ構成

### C++ SDK パッケージ

```
. # パッケージルート
|   CHANGELOG.md
|   SAMPLE_README.md
|   
+---diarkis-module # diarkis-module のソースコード
|   +---Client
|   |   +---Private
|   |   \---Public
|                   
+---include # Diarkis ランタイム・ライブラリのヘッダーファイル
|   \---diarkis
|               
+---platforms # 各プラットフォーム固有のヘッダーファイルやライブラリ
|   \---win-vs2019 
|       +---include
|       \---lib
|                           
+---samples # C++ サンプル
|   +---directmessage_simple
|   +---group_sample
|   +---matching_and_turn
|   +---matchmaker_ticket
|   +---p2p_rudp_sample
|   +---room_broadcast
|   \---session_simple
|               
\---third-party # サードパーティのライブラリ等
```

## ランタイム構成

ランタイムは **Diarkis ランタイムライブラリ** と **Diarkis Module** から構成されています。

**Diarkis ランタイムライブラリ** はランタイムのコアとなる機能が含まれており、ビルド済みライブラリとして提供されています。 詳細については [**Diarkis ランタイムライブラリ**](/diarkis-client/runtime-library) を参照してください。 **Diarkis Module** は ランタイムをアプリケーションに簡単に組み込めるように **Diarkis ランタイム・ライブラリ** を使用するために必要な実装や便利な機能を実装したフレームワークで、ソースコードの形で提供されています。詳細については [**Diarkis Module**](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/diarkis-module.md) を参照してください。

## Diarkis ランタイム・ライブラリ

### 概要

**Diarkis ランタイム・ライブラリ** はランタイムのコアとなる低レベルな機能が含まれています。

### 主な機能

* 基盤機能
  * Diarkis TCP/UDP/RUDP 通信
  * スレッド管理
  * メモリ管理とカスタム・アロケーター
  * NAT タイプ判定
* Diarkis の各機能
  * Room モジュール
  * MatchMaker モジュール
  * Field モジュール
  * P2P モジュール
  * DM(Direct Message) モジュールA
  * Session モジュール
  * Group モジュール

Diarkis ランタイム・ライブラリの詳細については [Diarkis ランタイム・ライブラリ](/diarkis-client/runtime-library) を参照してください。

## Diarkis Module

### 概要

**Diarkis ランタイム・ライブラリ**では低レベルな機能が提供されており、実際にアプリケーションとして動かすためにはもう少し追加機能の実装が必要となります。\
**Diarkis Module** は ランタイムをアプリケーションに簡単に組み込めるように **Diarkis ランタイムライブラリ** を使用するために必要な実装や便利な機能を実装したフレームワークです。\
パッケージ内の以下の場所にソースコードが配置されています。

`diarkis-module`

Diarkis Module の詳細については [Diarkis Module](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/diarkis-module.md) を参照してください。


# チュートリアル

ここではローカルでサーバーを起動する方法をサーバー・テンプレートを使って説明します。

Diarkis を動かす際は、まずはチュートリアルを一通り実施することをお勧めいたします。

* サーバー・テンプレートを使ってローカルでサーバーを起動する
* テストクライアントで疎通確認する
* カスタムコマンドを実装する
* クライアントから接続して各種サンプルを実行する


# 1. Diarkis サーバーをローカル環境で起動する

## **はじめに**

本ページでは [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template) を利用して Diarkis サーバーをローカル環境で起動する手順を解説します。Diarkis での開発を始める前にこちらの手順を一通り試すことをおすすめいたします。

## **環境準備**

サーバー・テンプレートを導入するには Go 1.22 以降が必要です。

インストール方法はプラットフォーム毎に異なりますので、公式ドキュメントに従ってインストールします。 <https://go.dev/doc/install>

Diarkis のサーバー開発は、macOS, Linux, Windows で行うことができます。

Windows 環境でサーバーを起動する場合は [Diarkis サーバーを Windows 環境で起動する](/diarkis-server/setup-windows) をご覧ください。

## サーバー・テンプレートからプロジェクトを生成する

1. まずは任意の PATH で `git clone https://github.com/Diarkis/diarkis-server-template.git` として、repository をクローンします。
2. 以下のコマンドを実行して、プロジェクトを生成します。出力先は `output` に指定した絶対パスとなります。

{% code overflow="wrap" %}

```bash
make init project_id={project ID} builder_token={builder token} output={absolute path to install}

# 例
make init project_id=00000000000 builder_token=xxxx-yyyy-zzzz output=../server_bin
```

{% endcode %}

* `project_id`: 弊社が発行したプロジェクトID
* `builder_token`: 弊社が発行したBuilder Token
* `output`: 生成したプロジェクトの出力先。ここでは `../server_bin` として説明します

過去バージョンが必要な場合は、任意のバージョンの tag をチェックアウトするか、[リリース一覧](https://github.com/Diarkis/diarkis-server-template/releases)からダウンロードすることでご利用いただけます。

プロジェクト生成の詳細については、以下も参照してください。

🔗 <https://github.com/Diarkis/diarkis-server-template/blob/develop/README.md>

## セットアップ

生成したプロジェクトにて以下コマンドを実行します。

```bash
cd path/to/project
make init
```

これにより、開発に必要な各種リソース （コード補完を利用するためのリファレンス など）がダウンロードされます。

## サーバー・バイナリの生成

ローカル向けにバイナリを生成する際は、以下の make タスクを実行します。

```bash
make build-local
```

上記を実行すると、 `remote_bin` 配下に、diarkis のバイナリが生成されます。

```bash
% ls remote_bin
health-check http         mars         ms           tcp          testcli      udp
```

## サーバーの起動

MARS, HTTP, UDP の各サーバーを起動します。

```bash
make server target=mars
make server target=http
make server target=udp
# 本チュートリアルでは利用しませんが、TCP サーバーは以下コマンドで起動できます。
# make server target=tcp

# make コマンドを利用せずに以下のコマンドでも同様に起動可能です
./remote_bin/mars ./configs/mars/main.json
./remote_bin/http
./remote_bin/udp
```

## **接続情報の取得**

mars, http, udp が起動していれば、curl で以下のように接続情報を取得することができます。これでサーバーの起動は完了です。

{% code overflow="wrap" %}

```bash
% curl -X POST http://127.0.0.1:7000/endpoint/type/UDP/user/test
{"encryptionMacKey":"xxxxxxxxxx","serverType":"UDP","serverHost":"127.0.0.1","serverPort":7100,"sid":"xxxxxxxxxx","encryptionKey":"xxxxxxxxxx","encryptionIV":"xxxxxxxxxx"}%
```

{% endcode %}

上記の HTTP エンドポイントは UDP サーバーに対して uid `test` で認証したことを意味します。ここから返却された情報を各種クライアント・ライブラリから利用して、パケットのやり取りが可能となります。


# 2. テストクライアントで疎通確認する

{% hint style="info" %}
こちらは 1. のチュートリアルが完了していることが前提となります。
{% endhint %}

## はじめに

[Diarkis サーバーテンプレート](/getting-started/diarkis-server-template) には、コマンドをテストするためのテストクライアントが用意されています。このテストクライアントは Go で書かれています。

これを使って、基本的なビルトインコマンドを発行し、疎通確認することができます。

## テストクライアントのビルド

テストクライアントのバイナリは、Diarkis サーバーテンプレートでビルドすることができます。

前回のチュートリアルで `make build-local` を実行した際に合わせてテストクライアントがビルドされるので、それを利用します。

## Diarkis テストクライアントの起動

以下の make タスクを実行します。

```bash
$ make go-cli host=127.0.0.1:7000 uid=test1
:
[UID: test1][SID(UDP): b674cd4a79594581b90186818c1ef911]
 > Connected UDP
```

* host: Diarkis サーバーのエンドポイント
* uid: 接続するユーザーの ID
* clientKey: クライアントキー（デフォルトでは無効になっているので今回は不要です）

## ms (MARS Stats) ツール

Diarkis の現在の情報を取得できるツールです。

現在の CCU やパケット数、Room 数など分析に役立つ情報を取得できます。

```
# ./remote_bin/ms {MARS address:port} {no-color}
$ watch ./remote_bin/ms 127.0.0.1:6779 no-color
─────────────────────────────────────────────────────────────────────────────────────────── 1.1.0 ───────────────────────────────────────────────────────────────────────────────────────────────────
ADDRESS                    PUBLIC-ADDRESS   STATUS   STARTED                            CCU   MESH-IN   MESH-OUT   UDP-IN   UDP-OUT   TCP-IN   TCP-OUT   MM-SEARCH   ROOMS   P2P-ATTEMPTS   P2P-SUCCESS
HTTP/HTTP/127.0.0.1:8100   127.0.0.1:7000   ONLINE   2024-10-04T17:58:39+09:00[16min]   0     2         2          0        0         0        0         0           0       0              0
UDP/UDP/127.0.0.1:8101     127.0.0.1:7100   ONLINE   2024-10-04T17:58:40+09:00[16min]   1     2         2          0        0         0        0         0           0       0              0
```

***

## Room を作成・参加する

テストクライアントを 2 つ起動して Room を作成・参加してみましょう。

一方のクライアントで  `room create` コマンドを発行して Room を作成したあとに、他方のクライアントで `room join` コマンドを発行して Room に参加してみましょう。

### uid: test1 で Room を作成する

```bash
> room create
Which client to create a room? [tcp/udp] > udp
[UID: test1][SID(UDP): xxxx][RoomID: yyyy]
> New member joined room - Hello from test2
```

### uid: test2 で Room に参加する

```textile
> room join
Which client to join a room? [tcp/udp] udp
Type room ID > yyyy
handleOnJoinRoom
UDP Room yyyy joined - success true and it was created at 1728033877
New member joined room - Hello from test2
[UID: test2][SID(UDP): zzzz][RoomID: yyyy]
```

***

## ms で CCU と Room 数を確認する

テストクライアントを2つ起動してRoom に参加したことで、CCU と Room 数が増えていることが確認できます。

```
$ watch ./remote_bin/ms 127.0.0.1:6779 no-color
────────────────────────────── 1.1.0 ─────────────────────────────────
ADDRESS                    PUBLIC-ADDRESS   STATUS   CCU ... ROOMS ...
HTTP/HTTP/127.0.0.1:8100   127.0.0.1:7000   ONLINE   0   ...   0   ...
UDP/UDP/127.0.0.1:8101     127.0.0.1:7100   ONLINE   2   ...   1   ...
```

***

## Room メンバー全員にメッセージを送信する

`room broadcast` コマンドを発行し、任意のメッセージを送ることができます。

`room +` というショートハンドでも同様のことが可能です。

### uid: test1 で broadcast する

```
> room broadcast
Which client to broadcast to a room? [tcp/udp] udp
Type room ID: yyyy
Type message: Hello, Diarkis!!
Room broadcast - Hello, Diarkis!!
[UID: test1][SID(UDP): xxxx][RoomID: yyyy]
```

### uid: test2 でメッセージを受信

```
[UID: test2][SID(UDP): zzzz][RoomID: yyyy]
 > Room broadcast - Hello, Diarkis!!
```

***

## Room の任意のメンバーにメッセージを送信する

`room message` コマンドを発行し、任意のメッセージを送ることができます。

### uid: test1 から test2 に送信する

```
> room message
Which client to message to a room? [tcp/udp] udp
Type user ID: test2
Type message: nice!
[UID: test1][SID(UDP): xxxx][RoomID: yyyy]
```

### uid: test2 でメッセージを受信

```
[UID: test2][SID(UDP): zzzz][RoomID: yyyy]
 > Room message - nice!
```

***

## Room から退出する

`room leave` コマンドを発行し、Room を退出することができます。

```
[UID: test1][SID(UDP): xxxx][RoomID: yyyy]
> room leave
Which client to leave a room? [tcp/udp]
udp
Type room ID:
yyyy
Room left. success:true
[UID: test1][SID(UDP): xxxx]
```

***

## テストクライアントのコマンド確認

`help` コマンドで、利用できるコマンドの一覧が確認できます。

`help room` のように引数にモジュール名をいれることで、特定のモジュールに絞って確認もできます。

```
> help
================ Command List ================
help                    - Display the list of valid commands
help {module name}      - Display the list of valid commands for the module
reconnect               - Reconnects to another server
disconnect              - Disconnects
ph                      - Sends a TCP Hey
h                       - Sends a UDP Hello
rh                      - Sends an RUDP Hello
die                     - Ungraceful disconnect from the server
:
=============================================
 > help room
================ Command List ================
room create             - Creates a room
room join               - Joins a room
room join random        - Joins a random room or creates a new room
room leave              - Leaves a room
room get owner          - Get a room owner id
room get members        - Get a room member ids
room get num            - Get a number of room members
room migrate            - Migrates a room to another server
room message            - Sends a message to one selected member of the room
room broadcast          - Reliable broadcast to a room
room +                  - Alias for `room broadcast`
room ubroadcast         - Unreliable broadcast to a room
room -                  - Alias for `room ubroadcast`
room p2p                - Starts P2P with room members
room relay              - Sends a relay message to other room members
room relay to           - Sends a relay message to selected room members
room relay profile      - Sends a relay profile
room relay to profile   - Sends a relay profile to selected room members
room props update       - Update room property
room props get          - Get room property
room props incr         - Increment room property
room props sync         - Sync room property
room reserve            - Reserves a room
room cancel reservation - Cancel a room reservation
room register           - Registers a room with a type
room find               - Finds rooms by type
room obj incr           - Increment room objects
room obj delete         - Delete room objects
room obj update         - Update room objects
room chat               - Chat in a room
room chat log           - Get chat log in a room
=============================================
```


# 3. カスタムコマンドを実装する

{% hint style="info" %}
こちらは 1. 2. のチュートリアルが完了していることが前提の内容となっております。
{% endhint %}

## はじめに

続いて Diarkis のカスタマイズ性を体験するために、カスタムコマンドを実装してみましょう。

本チュートリアルでは以下のようなコマンドを実装します。

* Room の敵に攻撃するコマンド
* コマンドバージョン: 2 （0, 1 はビルトインコマンドで利用されています）
* コマンド ID: 300
* リクエスト: 攻撃タイプ (type) を指定してコマンドを送信 (1: 近接、2:遠距離)
* レスポンス: 誰がダメージを与えたか、ダメージ値と合計ダメージ値を返却
* ルームメンバー全員に push 通知

実装の流れは以下を想定しています。

* puffer (Diarkis が提供するデータシリアライザ) を使ってペイロードを生成するためのコードを出
* サーバーにカスタムコマンドを実装
* テストクライアントにコマンドを実装

## Puffer モジュール（Diarkis が提供する データシリアライザ）

Puffer は JSON からデータをシリアライズ、デシリアライズするためのコードを生成するツールです。

出力先は C++、C#、Go に対応しているため、Unreal や Unity で開発するクライアントと、Go で開発するサーバーとのパケットデータに関する実装を簡略化できます。

Puffer の詳細については API リファレンスをご確認ください。

<https://docs.diarkis.io/docs/server/v1.0.0/diarkis/puffer/index.html>

### カスタムコマンドのデータ定義用 JSON ファイルの作成

💾 ./puffer/json\_definitions/custom/attack.json を追加します。

**attack** はカスタムコマンドのリクエストに利用するデータです。

* type: 攻撃タイプを uint8 型で格納できます

**attackResult** はレスポンス、ルームメンバーへの push 通知に使うデータです。

* type: 攻撃タイプを uint8 型で格納できます
* uid: 誰がダメージを与えたか。 string 型
* damage: ダメージ値。 uint16 型
* totalDamage: 合計ダメージ値。 uint16 型

```json
{
  "attack": {
    "ver": 2,
    "cmd": 300,
    "package": "custom",
    "properties": {
      "type": "u8"
    }
  },
  "attackResult": {
    "ver": 2,
    "cmd": 300,
    "package": "custom",
    "properties": {
      "type": "u8",
      "uid": "string",
      "damage": "u16",
      "totalDamage": "u16"
    }
  }
}
```

### コード生成

以下のコマンドを実行して、コードを生成します。puffer 以下の各言語のディレクトリにコードが出力されます。

```bash
$ make gen
# puffer 以下の各言語のディレクトリにコードが出力されます
puffer
├── cpp/custom/Attack.h
├── cpp/custom/AttackResult.h
├── cs/custom/Attack.cs
├── cs/custom/AttackResult.cs
├── go/custom/attack.go
└── go/custom/attackresult.go
```

## サーバーに attack コマンドハンドラを実装する

サーバーに attack コマンドを実装しましょう。

💾 ./cmds/custom/attack.go を追加します。

コード内のコメントにて、どんなことを実装しているか記載していますので、合わせてお読みください。

{% code overflow="wrap" fullWidth="true" %}

```go
package customcmds

import (
	"errors"
	// pattack は先程生成した puffer のパッケージです。
	pattack "handson/puffer/go/custom"

	"github.com/Diarkis/diarkis/derror"
	"github.com/Diarkis/diarkis/room"
	"github.com/Diarkis/diarkis/server"
	"github.com/Diarkis/diarkis/user"
	"github.com/Diarkis/diarkis/util"
)

// attack は Room 内の敵に対して攻撃します。
// 
// Diarkis の全てのコマンドは決まった引数を取ります。
// - ver: コマンドのバージョン
// - cmd: コマンド ID
// - payload: コマンドにわたすデータ
// - userData: ユーザーデータ
func attack(ver uint8, cmd uint16, payload []byte, userData *user.User, next func(error)) {

	// attack コマンドは Room の敵に攻撃するコマンドなので、
	// Room に参加していない場合はエラーとなります。
	// Room に参加しているかどうかは roomID を取得して確認します。
	roomID := room.GetRoomID(userData)
	// room に参加していない場合は、 roomID は空で返却されるので、エラーハンドリングを行います
	if roomID == "" {
		// ユーザーに userData.ServerRespond() でエラーのレスポンスを返します。
		err := errors.New("not in the room")
		userData.ServerRespond(derror.ErrData(err.Error(), derror.NotAllowed(0)), ver, cmd, server.Bad, true)
		// レスポンスを返したら next(err) して return します。
		next(err)
		return
	}

	// pattack.NewAttack() したあとに、req.Unpack(payload) することで
	// ペイロードをデシリアライズできます。
	req := pattack.NewAttack()
	req.Unpack(payload)

	// Type を元にダメージを計算します。
	damage := 0
	switch req.Type {
	case 1: // 近接攻撃 (D20)
		damage = util.RandomInt(1, 20)
	case 2: // 遠距離攻撃 (D12 + 3)
		damage = util.RandomInt(1, 12) + 3
	default:
		err := errors.New("invalid attack type")
		userData.ServerRespond(derror.ErrData(err.Error(), derror.InvalidParameter(0)), ver, cmd, server.Bad, true)
		next(err)
		return
	}

	// 算出したダメージを敵の合計ダメージに加算します。
	// Room に Property として情報を保存することができます。
	// ここでは "DAMAGE" というキーに対してダメージを加算しています。
	// room.IncrProperty を使うと加算後の数値を取得することができます。
	// その他にも Property を扱うための関数が用意されています。詳細は以下をご覧ください。
	// https://docs.diarkis.io/docs/server/v1.0.0/diarkis/room/index.html
	updatedDamage, updated := room.IncrProperty(roomID, "DAMAGE", int64(damage))
	if !updated {
		err := errors.New("incr property failed")
		userData.ServerRespond(derror.ErrData(err.Error(), derror.Internal(0)), ver, cmd, server.Err, true)
		next(err)
		return
	}
	logger.Info("Room %s has been attacked by %s using %s attack by %d damage. Total damage: %d", roomID, userData.ID, req.Type, damage, updatedDamage)

	// 誰がダメージを与えたか、ダメージ値と合計ダメージ値を返却し、ルームメンバーに通知します。
	// res := pattack.NewAttackResult() したあとに返却するデータをセットします。
	res := pattack.NewAttackResult()
	res.Type = req.Type
	res.Uid = userData.ID
	res.Damage = uint16(damage)
	res.TotalDamage = uint16(updatedDamage)

	// コマンドを実行したユーザーには userData.ServerRespond()
	// 他のルームメンバーには room.Relay() を使って結果を通知します。
	userData.ServerRespond(res.Pack(), ver, cmd, server.Ok, true)
	room.Relay(roomID, userData, ver, cmd, res.Pack(), true)
	// レスポンスを返したら next(nil) して return します。
	next(nil)
}
```

{% endcode %}

### attack コマンドを公開する

コマンドを実装したら、外部に公開する必要があります。

💾 ./cmds/custom/main.go を編集し、以下を追加します。

`Expose()` に対して、 `diarkisexec.SetServerCommandHandler()` を追加して、attack コマンドを公開します。

```diff
diff --git a/cmds/custom/main.go b/cmds/custom/main.go
index f2374ed..e98f5ef 100644
--- a/cmds/custom/main.go
+++ b/cmds/custom/main.go
@@ -37,6 +37,7 @@ func Expose() {
        diarkisexec.SetServerCommandHandler(custom.GetFieldInfoVer, custom.GetFieldInfoCmd, getFieldInfo)
        diarkisexec.SetServerCommandHandler(CustomVer, getUserStatusListCmdID, getUserStatusList)
        diarkisexec.SetServerCommandHandler(CustomVer, resonanceCmdID, resonanceCmd)
+       diarkisexec.SetServerCommandHandler(custom.AttackVer, custom.AttackCmd, attack)
 }
```

## テストクライアントに attack コマンドを追加する

サーバーの attack コマンドを実行するためにテストクライアントに attack コマンドを追加します。

💾 ./testcli/handson/attack.go を追加します。

クライアントを実行するための setup の他、サーバーにコマンドを送信する Attack() 、レスポンスをハンドリングする onResponse()、push通知をハンドリングする onPush() を実装しています。

```go
package handson

import (
	"fmt"

	pattack "handson/puffer/go/custom"

	"github.com/Diarkis/diarkis/client/go/tcp"
	"github.com/Diarkis/diarkis/client/go/udp"
)

type Handson struct {
	tcp *tcp.Client
	udp *udp.Client
}

func SetupHandsonAsTCP(c *tcp.Client) *Handson {
	h := &Handson{tcp: c}
	h.setup()
	return h
}

func SetupHandsonAsUDP(c *udp.Client) *Handson {
	h := &Handson{udp: c}
	h.setup()
	return h
}

func (h *Handson) setup() {
	if h.tcp != nil {
		h.tcp.OnResponse(h.onResponse)
		h.tcp.OnPush(h.onPush)
		return
	}
	if h.udp != nil {
		h.udp.OnResponse(h.onResponse)
		h.udp.OnPush(h.onPush)
	}
}

func (h *Handson) onResponse(ver uint8, cmd uint16, status uint8, payload []byte) {
	if ver != pattack.AttackVer || cmd != pattack.AttackCmd {
		return
	}
	if status != uint8(1) {
		fmt.Printf("Attack failed: %v\n", string(payload))
		return
	}
	res := pattack.NewAttackResult()
	err := res.Unpack(payload)
	if err != nil {
		fmt.Printf("Failed to unpack attack response: %v\n", err)
		return
	}
	fmt.Printf("You dealt %d damage. Total damage: %d\n", res.Damage, res.TotalDamage)
}

func (h *Handson) onPush(ver uint8, cmd uint16, payload []byte) {
	// no push
	if ver != pattack.AttackVer || cmd != pattack.AttackCmd {
		return
	}
	res := pattack.NewAttackResult()
	err := res.Unpack(payload)
	if err != nil {
		fmt.Printf("Failed to unpack attack response: %v\n", err)
	}
	fmt.Printf("%s dealt %d damage. Total damage: %d\n", res.Uid, res.Damage, res.TotalDamage)
}

func (h *Handson) Attack(attackType uint8) {
	req := pattack.NewAttack()
	req.Type = attackType
	if h.tcp != nil {
		h.tcp.Send(pattack.AttackVer, pattack.AttackCmd, req.Pack())
		return
	}
	if h.udp != nil {
		h.udp.Send(pattack.AttackVer, pattack.AttackCmd, req.Pack())
	}
}
```

テストクライアントに `handson attack` コマンドを登録します。

💾 ./testcli/main.go を編集します。

cli.RegisterCommands(string, \[]cli.Command) でテストクライアントに新しいコマンドを登録できます。

```diff
diff --git a/testcli/main.go b/testcli/main.go
index bdca4ab..2dbdd2c 100644
--- a/testcli/main.go
+++ b/testcli/main.go
@@ -3,8 +3,11 @@ package main
 import (
        "bufio"
        "fmt"
+       "handson/testcli/handson"
        "handson/testcli/resonance"
        "os"
+       "strconv"
+       "strings"

        "github.com/Diarkis/diarkis/client/go/test/cli"
 )
@@ -12,17 +15,24 @@ import (
 var (
        tcpResonance *resonance.Resonance
        udpResonance *resonance.Resonance
+       tcpHandson   *handson.Handson
+       udpHandson   *handson.Handson
 )

 func main() {
        cli.SetupBuiltInCommands()
        cli.RegisterCommands("test", []cli.Command{{CmdName: "resonate", Desc: "Resonate your message", CmdFunc: resonate}})
+       cli.RegisterCommands("handson", []cli.Command{
+               {CmdName: "attack", Desc: "Attack for hands-on", CmdFunc: attack},
+       })
        cli.Connect()
        if cli.TCPClient != nil {
                tcpResonance = resonance.SetupAsTCP(cli.TCPClient)
+               tcpHandson = handson.SetupHandsonAsTCP(cli.TCPClient)
        }
        if cli.UDPClient != nil {
                udpResonance = resonance.SetupAsUDP(cli.UDPClient)
+               udpHandson = handson.SetupHandsonAsUDP(cli.UDPClient)
        }
        cli.Run()
 }
@@ -45,3 +55,30 @@ func resonate() {
                udpResonance.Resonate(message)
        }
 }
+
+func attack() {
+       reader := bufio.NewReader(os.Stdin)
+       fmt.Println("Which client to join a room? [tcp/udp]")
+       client, _ := reader.ReadString('\n')
+       fmt.Println("Enter the attack type. [1: melee, 2: range]")
+       attackTypeStr, _ := reader.ReadString('\n')
+       attackTypeStr = strings.Trim(attackTypeStr, "\n")
+       attackType, err := strconv.Atoi(attackTypeStr)
+       if err != nil || attackType != 1 && attackType != 2 {
+               fmt.Println("Invalid attack type.")
+               return
+       }
+
+       switch client {
+       case "tcp\n":
+               if tcpHandson == nil {
+                       return
+               }
+               tcpHandson.Attack(uint8(attackType))
+       case "udp\n":
+               if udpHandson == nil {
+                       return
+               }
+               udpHandson.Attack(uint8(attackType))
+       }
+}
```

バイナリを生成し、udp サーバーを再起動する

再度 `make build-local` を実行してバイナリを生成します。

サーバーとテストクライアントが更新されます。

エラーが無いことが確認できたら、一度 udp サーバーを停止して、再起動してください。

## attack コマンドの動作確認

`room create` `room join` した後に `handson attack` コマンドを発行し、ダメージが加算されることを確認してください。

### uid: test1 で handson attack を実行

```
> handson attack
Which client to join a room? [tcp/udp]
udp
Enter the attack type. [1: melee, 2: range]
1
You dealt 13 damage. Total damage: 13
```

### uid: test2 で attack 結果の push 通知を受信

```
[UID: test2][SID(UDP): zzzz][RoomID: yyyy]
 > test1 dealt 13 damage. Total damage: 13
```


# Diarkis クライアントからサーバに接続する

## はじめに

本ページでは C++ の `room_broadcast` サンプルを使用して「Diarkis サーバーをローカル環境で起動する」で起動した Diarkis サーバーに接続して通信を行う方法を説明します。\
`room_broadcast` サンプルでは Room モジュールを使用して Diarkis サーバー上に仮想の部屋を作成し、2人のユーザーが接続してお互いにデータの送受信を行います。 サンプルの詳しい説明は [room\_broadcast](broken://pages/UQBFCMlPKjhZLYEDxmC3) を参照してください。

## Windows 環境

1. `samples/room_broadcast/win-x64/room_broadcast.sln` を Visual Studio で開きます。
2. プロジェクト > プロパティ > デバック > コマンド引数 を指定します。

   ```
    $(endPoint) $(uid) $(clientKey) 例 127.0.0.1:7000 1111 AAAA
   ```
3. F5 でビルドしプログラムを起動します。サンプル・プログラムの起動後、Diarkis サーバーに接続すると以下のようなログが表示されます。

   ```
   Start
   FileLoggerBackend created files ./logs/1111/diarkis-log.log
   ============================
   Loop=1
   Endpoint: 127.0.0.1:7100
   UID     : 1111

   Connecting to the UDP server...
   Connected
   Joining a room...
   Joined
   Room members: 1111
   ```

   `room_broadcast` サンプルでは複数人が Room に接続するのを待ってサンプルの処理を進める流れとなっているため１人が接続しただけだと他のユーザーの接続を待つ状態となります。
4. Windows ターミナル(コマンド・プロンプト)などから、別プロセスで複数クライアントを実行します。\
   `samples/room_broadcast/win-x64/x64/Debug/bin/room_broadcast.exe` に実行ファイルが出力されています。

   ```
   > ./x64/Debug/bin/room_broadcast.exe $(endPoint) $(uid) $(clientKey)
   例 : room_broadcast.exe 127.0.0.1:7000 2222 BBBB
   ```

   2人のユーザーが Diarkis サーバに接続すると以下のログが出力され、Diarkis サーバーの Room モジュールを介したデータの送受信が行われていることが確認できます。

   ```
   Connecting to the UDP server...
   Connected
   Joining a room...
   Joined
   Room members: 1111, 2222
   Room Broadcast
   Stats Room Broadcast num 100
   Stats Room Broadcast num 200
   Stats Room Broadcast num 300
   ```

## macOS 環境

Coming Soon

## Linux 環境

Coming Soon


# サンプル

Diarkis を使ったモジュールやユースケース別のサンプルを紹介します。

Diarkis クライアント SDK では様々なサンプルを用意しています。

## C++ サンプル

C++ 版クライアント・ランタイムの使用方法を紹介するためのサンプル・プログラムです。 C++ 版 Diarkis クライアント SDK パッケージの `samples` 以下に配置されています。\
詳細については C++ 版 Diarkis クライアント SDK パッケージに含まれる `SAMPLE_README.md` を参照してください。

* [directmessage\_simple](/diarkis-client/samples/unity)
  * DM モジュールの基本的な使用方法を紹介するサンプル・プログラムです。
* [group\_sample](/diarkis-client/samples/cpp/matching-and-turn)
  * Group モジュールの使用方法を紹介するサンプル・プログラムです。
* [matching\_and\_turn](/diarkis-client/samples/cpp/matchmaker-ticket)
  * Diarkis サーバに接続し MatchMaker モジュールでマッチメイキングを行った後、別の Diarkis サーバーに接続しなおしてマッチしたユーザーと同じ Room に入って通信を行うサンプルです。 ゲーム等で良く行われるフローを Diarkis で実装した場合のサンプルとなります。
* [matchmaker\_ticket](/diarkis-client/samples/cpp/p2p-rudp-sample)
  * MatchMaker のチケット機能の使用方法を紹介するサンプル・プログラムです。
* [p2p\_rudp\_sample](/diarkis-client/samples/cpp/session-simple)
  * P2P モジュールの RUDP 機能の使用方法を紹介するサンプル・プログラムです。
* [room\_broadcast](broken://pages/UQBFCMlPKjhZLYEDxmC3)
  * Room モジュールと P2P モジュールを使用したサンプル・プログラムです。Room 経由のリレー通信と P2P 通信の使用方法を紹介します。
* [session\_simple](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/getting-started/broken-reference/README.md)
  * Session モジュールの基本的な使用方法を紹介するサンプルです。

## C++ Unreal Engine Plugin サンプル

* [FieldWalker](/diarkis-client/samples/unreal-engine/diarkis-plugin-sample)
  * C++ 版クライアント SDK を Unreal Engine のプラグインとして組み込んだサンプルです。\
    このサンプルでは **Diarkis** の複数のモジュールを使用して位置の同期、メッセージの送受信、マッチメイキング等を行う統合的なサンプルとなっています。

## C# Unity Plugin サンプル

* [FieldWalker](/diarkis-client/samples/unity/field-walker)
  * C# 版クライアント SDK を Unity のプラグインとして組み込んだサンプルです。\
    このサンプルでは **Diarkis** の複数のモジュールを使用して位置の同期、メッセージの送受信、マッチメイキング等を行う統合的なサンプルとなっています。

## サーバーサンプル

[Diarkis Server Template ](https://github.com/Diarkis/diarkis-server-template/tree/develop/examples)のリポジトリに様々なサンプルを用意しております。


# Diarkis のモジュール

Diarkis は開発者が利用できる複数のビルトイン・モジュールを提供しています。

要件に合わせて、一つまたは複数を組み合わせて利用することで、低コストで開発をすることができます。

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><span data-gb-custom-inline data-tag="emoji" data-code="1f3e0">🏠</span> <strong>Room</strong></td><td>複数ユーザーでのメッセージのやりとりや状態共有</td><td></td><td><a href="/pages/ObIjhfjjn6CYL4RkeAsc">/pages/ObIjhfjjn6CYL4RkeAsc</a></td></tr><tr><td><span data-gb-custom-inline data-tag="emoji" data-code="1f19a">🆚</span> <strong>MatchMaker</strong></td><td>高速で堅牢かつスケーラブルなマッチメイキング</td><td></td><td><a href="/pages/FQd8uMGEIMikWpi9ME7X">/pages/FQd8uMGEIMikWpi9ME7X</a></td></tr><tr><td><span data-gb-custom-inline data-tag="emoji" data-code="26f3">⛳</span> <strong>Field</strong></td><td>大人数で同じ空間と時間を共有できる仮想空間</td><td></td><td><a href="/pages/fZ3OfrERXaFbTfShtFgN">/pages/fZ3OfrERXaFbTfShtFgN</a></td></tr><tr><td><span data-gb-custom-inline data-tag="emoji" data-code="1f517">🔗</span> <strong>P2P</strong></td><td>Room を介さずにクライアント同士で直接通信する</td><td></td><td><a href="/pages/3HumIsVd1PCns7YL82il">/pages/3HumIsVd1PCns7YL82il</a></td></tr><tr><td><span data-gb-custom-inline data-tag="emoji" data-code="1f4ec">📬</span> <strong>DM</strong></td><td>接続中のユーザーと 1:1 でメッセージをやり取りをする</td><td></td><td><a href="/pages/gE0B7r0AdTAnMrMOi0IG">/pages/gE0B7r0AdTAnMrMOi0IG</a></td></tr><tr><td><span data-gb-custom-inline data-tag="emoji" data-code="1f508">🔈</span> <strong>Notifier</strong></td><td>接続中の全ユーザーにメッセージ配信</td><td></td><td><a href="/pages/0iJk5pJFCtPQuYIdBPMa">/pages/0iJk5pJFCtPQuYIdBPMa</a></td></tr><tr><td><strong>👫 Session</strong></td><td>複数のセッションにてメッセージのやり取りや状態共有</td><td></td><td><a href="/pages/4tsqasueaDTrew5W9nrK">/pages/4tsqasueaDTrew5W9nrK</a></td></tr><tr><td><strong>🏘️ Group</strong></td><td>大人数でメッセージのやり取りをする</td><td></td><td><a href="/pages/ZJCYCRv2IorUBd2xXWEo">/pages/ZJCYCRv2IorUBd2xXWEo</a></td></tr><tr><td><strong>🦅 CSAR</strong></td><td>DGS 開発ができる</td><td></td><td><a href="/pages/gWNjh8wyYbeuTmd3vflP">/pages/gWNjh8wyYbeuTmd3vflP</a></td></tr></tbody></table>


# Room モジュール

## 概要

Diarkis Room モジュールは、遠隔地にいる複数のユーザーがパケットを送受信できるデジタル空間を作ることができます。これは、サーバ・リレー・システムであり、テンポの速いゲームが可能です。

一つの Diarkis サーバーに、Diarkis Room で作成された複数のルームを持つことができます。各ルームは何人のユーザーを参加させるかを決めることができます。

技術的な詳細については、サーバー [APIドキュメント](https://docs.diarkis.io/docs/server/current/diarkis/room/index.html) をお読みください。

Diarkis Room は、Diarkis P2P (peer-to-peer) フォールバック用の [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) としても使用されます。

## Diarkis Room とは

Diarkis Room は、リモート・ユーザーがサーバーを介してパケットを交換できるようにする中継サーバーです。サーバーはカスタマイズ可能で、受信パケットを検証したり、サーバー上のパケットを好きなように操作することができます。

## Diarkis Room で出来ないこと

Diarkis Room は専用のゲーム・サーバー（Dedicated Game Server）ではありません。サーバー上でゲームを実行することはありません。サーバー上でゲームのロジックを実装することは可能ですが、物理演算やコリジョン判定などの実装には基本的には不向きです。

## Diarkis Room の特徴

メンバーが自由にメッセージを送受信（ブロードキャスト）することができます。また Property や State を共有することができます。それらは、メンバーが自由に追加・変更・削除できるルームに付随する値です。

Property は任意のタイミングでルームの状態を保持、同期します。ルームのメンバーと自動的に同期したい場合は State を使うことで一定間隔でルームの状態を同期することが可能です。

## Diarkis Room のパケット交換図（ブロードキャストとメッセージ）

下図は、Diarkis Room のブロードキャストとメッセージの仕組みを説明したものです。

Diarkis Room には、ルームのメンバー間でパケットを交換する2つの方法があります。

ブロードキャストは全ルーム・メンバーにメッセージを送信し、メッセージはルームの選択されたメンバーにメッセージを送信します。

<figure><img src="/files/zeBOmSJ6eHgF8RSXDvNQ" alt=""><figcaption></figcaption></figure>

## Diarkis Room Property（状態の保存）

Diarkis Room は、Property でルームの状態を保持することができます。同期は任意のタイミングで行うことができ、Room に関連する低、中頻度の情報のやりとりに適しています。

## Diarkis Room State（状態の同期）

また Diarkis Room には、State を使ってメンバーと自動的に状態を同期する方法があります。Room に配置しているオブジェクトなど、これから入室するメンバーを含めてすべてのメンバーに自動的に同期したい場合に最適です。

<figure><img src="/files/VYGOQWTkJYbwuJD4dxKG" alt=""><figcaption></figcaption></figure>


# Room モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

Room モジュールは、TCP、UDP サーバー上でセットアップをすることが可能です。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用してセットアップできます。

以下の様にサーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := ""

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Room:       &diarkisexec.Options{ExposeCommands: true},
	})
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}
```

サーバー・テンプレートで簡単にサーバーを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# Room サンプル


# Room モジュールをクライアントから利用する

## はじめに

本ページでは C++ クライアント・ランタイムから **Diarkis Module** を使用して Room モジュールを使用する際の流れについて説明します。\
Room モジュールを使用する前に Diarkis サーバーとの接続が完了している必要がありますので、（link to Diarkis モジュール利用の全体的な流れ）を参照して Diarkis サーバーと接続してください。 Room モジュールは TCP/UDP どちらでも使用可能です。

## 基本的な使い方

### Diarkis Module の Room をセットアップする

まず初めに `DiarkisInterfaceBase::SetupRoom()` を呼び出して **Diarkis Module** の **DiarkisRoomBase** をセットアップします。\
`SetupRoom` では **DiarkisRoomBase** を初期化しロギングやイベント・コールバックの設定が行われます。 セットアップ完了後、各種 Room の機能ができるようになります。

### Room を作成する

`DiarkisRoomBase::SendCreateRoom()` を使用して Room をサーバー上に作成することができます。\
Room 作成時は参加可能人数、空の Room を残すかどうか、作成と同時に部屋へ参加するか、Room のサーバー上での生存時間、データ送信の間隔等を指定可能です。\
詳細については `DiarkisRoomBase::SendCreateRoom()` のドキュメントを参照してください。

また、サーバー上で Room の作成が完了すると `DiarkisRoomBase::OnRoomCreation()` がイベントとして通知されます。\
ユーザーはこのイベントの引数で作成された Room の ID を取得することができます。 **Diarkis Module** を使用している場合、内部でこの ID を保存しており、`DiarkisRoomBase::GetRoomID()` 等のメソッドで Room ID を取得することができます。

### Room に参加する

`DiarkisRoomBase::SendJoinRoom()` で Room ID を指定して Room に参加することができます。Room ID は DM 等、何らかの通信を使用して Room を作成したユーザーから通知してもらう必要があります。\
また、`DiarkisRoomBase::SendRandomJoinRoom()` を使用して参加可能な Room にランダムで参加する機能もあります。 この機能では参加可能な Room が無かった場合、新たに Room を作成します。

サーバー上で Room へユーザーが参加した場合、参加したユーザーには `DiarkisRoomBase::OnRoomJoin()`が、すでに Room に入っているユーザーには `DiarkisRoomBase::OnRoomMemberJoin()` がイベントとして通知されます。

### Room に参加しているユーザーにメッセージを送る

Room に参加している状態で以下のメソッドを使用することで任意のデータを他のユーザーに送信することができます。送信したデータはサーバー上で処理され、サーバーから他のユーザーへ送信されます。

* `DiarkisRoomBase::SendBroadcastToRoom()`
  * 部屋に参加しているユーザー全員にデータを送信します。
* `DiarkisRoomBase::SendMessageToRoom()`
  * 引数で指定したユーザーだけにデータを送信します。
* `DiarkisRoomBase::SendRelay()`
  * 部屋に参加している自分以外のユーザーにデータを送信しますが BroadcastToRoomと異なりサーバーから可能な限り早くデータを送信します。
* `DiarkisRoomBase::SendRelayTo()`
  * 引数で指定したユーザーだけにデータを送信しますが MessageTo と異なりサーバーから可能な限り早くデータを送信します。

これらのメソッドで送信されてたメッセージを受信した場合、以下のイベントが発生し受信したデータを受け取ることができます。

* `DiarkisRoomBase::OnRoomMemberBroadcast()`
  * `DiarkisRoomBase::SendBroadcastToRoom()` で送信したデータの受信イベント
* `DiarkisRoomBase::OnRoomMemberMessage()`
  * `DiarkisRoomBase::SendMessageToRoom()` で送信したデータの受信イベント
* `DiarkisRoomBase::OnRoomRelay()`
  * `DiarkisRoomBase::SendRelay()` で送信したデータの受信イベント
* `DiarkisRoomBase::OnRoomRelayTo()`
  * `DiarkisRoomBase::SendRelayTo()` で送信したデータの受信イベント

### Room から離脱する

`DiarkisRoomBase::SendLeave()` を実行することで現在参加している Room から離脱することができます。 離脱処理がサーバー上で実行されると `DiarkisRoomBase::OnRoomLeave()` イベントが発火して処理結果を通知します。また、離脱したユーザー以外のユーザーでは `DiarkisRoomBase::OnRoomMemberLeave()` が発火してユーザーが部屋から離脱したことが通知されます。離脱した結果、Room のオーナーが変わった場合は `DiarkisRoomBase::OnRoomOwnerChange()` が発火します。

## Room の情報を取得する

Room に関する情報をサーバーに問い合わせて取得することが可能です。

### Room のオーナーを取得する

`DiarkisRoomBase::SendGetOwnerID()` を実行することで現在の Room のオーナー ID をサーバーに問い合わせることができます。\
問い合わせた結果は `DiarkisRoomBase::OnRoomGetOwnerID()` が発火することで通知され結果が `DiarkisRoomBase` 内に保存されます。\
保存されている ID は `DiarkisRoomBase::GetOwnerUID()` で取得することができます。

### Room の参加メンバーを取得する

`DiarkisRoomBase::SendGetMemberIDs()` を実行することで現在 Room に参加しているユーザーの ID のリストをサーバーに問い合わせることができます。\
問い合わせた結果は `DiarkisRoomBase::OnRoomMemberIDs()` が発火することで通知され結果が `DiarkisRoomBase` 内に保存されます。\
保存されているユーザー ID のリストは `DiarkisRoomBase::GetRoomMembers()` で取得することができます。

### Room の参加メンバー数を取得する

`DiarkisRoomBase::SendGetNumberOfMembers()` を実行することで現在 Room に参加しているユーザーの数をサーバーに問い合わせることができます。\
問い合わせた結果は `DiarkisRoomBase::OnRoomNumberOfMembers()` が発火することで通知されます。

## Room で情報を共有する

サーバー上の Room を使用して Room に参加しているユーザー間でデータを共有することができます。

### プロパティを使用してデータを共有する

Coming Soon

### オブジェクトを使用してデータを共有する

Coming Soon

### Room 内でメッセージをやり取りする

Coming Soon

## 特殊な機能

### Room の参加者で P2P 接続をする

Room に参加しているユーザー同士で P2P 接続を行うことができます。\
Room に参加しているユーザーが `DiarkisRoomBase::SendStartP2PSync()` を呼び出すことで P2P 接続プロセスが開始されます。このリクエストを受信したサーバーは Room に参加しているユーザーに対して接続先のアドレスのリストを送信します。クライアント側では `DiarkisRoomBase::OnStartP2PSync()` が発火してこの通知を受け取り、送信されてきた接続先のアドレスのリストに対してホールパンチ・プロセスを開始します。ホールパンチングが完了すると、接続したピア同士で直接通信することが可能となります。

### Room の予約

Coming Soon

### Room を検索する

Coming Soon

### Room のマイグレーション

Room を使用中にマイグレーションが発生した場合、Room 専用のマイグレーション処理を実行する必要があります。

Room モジュールの特性上同じ Room に参加しているメンバーは同じサーバーに接続している状態となります。Room が作成されている Diarkis サーバーがスケールインする場合、Room に参加している全メンバーのサーバーの再接続と、同じ Room への移動が必要となります。Room のオーナーが マイグレートを呼び出すだけで、自動でマイグレーション処理が実行されます。

マイグレーションの詳細については [マイグレーション](/diarkis-client/diarkis-module/migration) を参照してください。

Room に参加しているときにマイグレーション発生する場合、`DiarkisTcpBase/DiarkisUdpBase` の `OnOffline()` ではなく、`DiarkisRoomBase::OnOffline()` が呼び出され、接続中のサーバーのスケールインと Room のマイグレーション処理が必要なことが通知されます。

SDK 付属のサンプルコードでは以下のように実装されています。

```
void DiarkisRoom::OnOffline()
{
    DiarkisRoomBase::OnOffline();

    // サーバのスケールインにより、入室している Room があるサーバが offline になります。
    // アプリ側の実装に合わせて、適したタイミングで SendMigrateRoom を呼び出して Room（サーバ）の移動を行ってください。
    if (GetOwnUID() == GetOwnerUID())
    {
        // Room のオーナーが Migrate を呼ぶ
        this->SendMigrateRoom();
    }

}
```

ただ、Room の場合はサンプルのコメントにもあるように Room のオーナだけが `DiarkisRoomBase::SendMigrateRoom()` を呼び出す必要があります。\
また、`DiarkisTcpBase/DiarkisUdpBase` 同様、呼び出し後、Room から一時的に切断されるため `DiarkisRoomBase::SendMigrateRoom()` を呼び出すタイミングはアプリの都合に合わせて調整してください。

サーバー側も含めた詳細なフローについては、以下の API ドキュメントを参照してください。

<https://docs.diarkis.io/docs/server/v1.0.0/diarkis/room/index.html#MigrateRoom>


# Room のその他の機能

WIP


# MatchMaker モジュール

## 概要

Diarkis MatchMaker は、オンラインマルチプレイヤーゲームのユーザーのマッチメイキングを幅広くカバーします。Diarkis MatchMaker のデザインは非常にユニークです。中央集権的なデータストレージを持つのではなく、Diarkis サーバー・クラスター内の複数のサーバを共有ストレージとして使用します。

Diarkis MatchMaker は**高速**で、**堅牢**かつ**スケーラブル**です。

Diarkis MatchMaker の分散型デザインにより、**フォールトトレラント**（障害耐性）と**スケーラブル**（拡張可能）な特性を持っています。

Diarkis MatchMaker の詳細およびドキュメントについては、[こちらのサーバー API ドキュメント](https://docs.diarkis.io/docs/server/current/diarkis/matching/index.html#hdr-Matchmaking_Rule_Examples)をご覧ください。

## 従来のマッチメイキング方法

ここでは、従来の一般的なマッチメイキングの実装方法と、Diarkis MatchMaker によるマッチメイキングの実装方法を比較します。

### データベース方式

<figure><img src="/files/DmIm7SiCgXDkQgnSnq8M" alt=""><figcaption></figcaption></figure>

データベース方式では、マッチメイキング・データを保存し、データベースのクエリを使用してマッチを作成します。

#### データベース方式の特徴

* スケールの限界はデータベースのパフォーマンスの限界です。
* マッチメイキング・プロセスの速度は、固定間隔で実行されるバックグラウンド・プロセスの間隔に大きく依存します。
* 異なるデータベースにいるユーザーは**マッチングされません**。

### インメモリ方式

<figure><img src="/files/94dllThMZrAyVtgHg5rb" alt=""><figcaption></figcaption></figure>

インメモリ方式では、フロント・サーバー（ユーザーと直接通信するサーバー）を使用してマッチメイキング・データを保存し、マッチメイキング操作を実行します。

#### インメモリ方式の特徴

* スケールの限界は、マッチメイキングのストレージと実行者としても機能するフロント・サーバーの限界です。
* 異なるフロント・サーバーにいるユーザーは**マッチングされません**。

## Diarkis のマッチメイキング方式

<figure><img src="/files/YoWbnhAR7Q6GldNb1ik1" alt=""><figcaption></figcaption></figure>

従来のマッチメイキング方法と比較して、Diarkis MatchMaker は**無制限にスケーリング**が可能です。

データはクラスター内のすべてのサーバーに共有されるメモリに保存されているため（冗長性を含む）、マッチメイキング操作は非常に高速に行われ、クラスター内の各サーバーは複数の操作を並行して実行することができます。これにより、**非常に高速**な**マッチメイキング**と**堅牢な耐障害性**（単一障害点がないため）が実現します。

### Diarkis MatchMaker の特徴

* スケーリングに制限がありません。サーバー・ノードを追加または削除するだけで、マッチメイキングのスケールイン/スケールアウトが可能です。
* すべてのユーザーは、異なるサーバーにいてもマッチングが可能です。これは、Diarkis クラスタ内のすべてのサーバーがマッチメイキング・データを共有しているためです。
* Diarkis MatchMaker は、要求に応じて複数のマッチメイキング操作を並行して実行できるため（バックグラウンド操作はありません）、マッチメイキング結果が非常に速く（秒単位、さらにはミリ秒単位で）得られます。
* Diarkis MatchMaker には単一障害点がないため、サーバー障害がマッチメイキング全体に影響を与えることはありません。

## マッチメイキング中の通信

Diarkis MatchMaker は、マッチングされたユーザーが自由に通信できるユニークな機能を備えています。これは、ゲームにロビー機能を組み込むのに最適です。

例えば、ユーザーが他のユーザーとマッチングを待っている間に装備やコスチュームを変更するシナリオを考えてみましょう。Diarkis MatchMaker は、ユーザーがそのような変更をリアルタイムで同期し、メッセージをやり取りすることを可能にします。

## バックフィルマッチメイキング

Diarkis MatchMaker はバックフィルをサポートしています。マルチプレイヤー・ゲーム・セッションを開始する際に、他のユーザーがプレイしている間に追加のユーザーをゲーム・セッションに参加させる必要があるシナリオを考えてみましょう。ここでバックフィルが役立ちます。バックフィルは、すでにマッチングされたユーザーがプレイしている間に、他のユーザーがマッチングしてゲーム・セッションに参加できるように「扉を開けておく」ことを可能にします。

## チームマッチメイキング

Diarkis MatchMaker は、複数のマッチメイキングを並行して実行することができます。これにより、Diarkis MatchMaker を通じてチームを編成し、対戦相手のチームを見つけることができます。

<figure><img src="/files/QaE3TmVfgzs72RArs2uX" alt=""><figcaption></figcaption></figure>


# MatchMaker モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

MatchMaker モジュールは、マッチメイキングの定義を HTTP サーバーで必ず行う必要があります。HTTP サーバーのメモリ上でデータが管理されるためです。

ビルトイン・コマンドの公開は UDP、TCP サーバーでセットアップが可能です。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用してセットアップできます。

以下の様にサーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := ""

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		MatchMaker: &diarkisexec.Options{ConfigPath: "/configs/shared/matching.json", ExposeCommands: true},
	})
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}
```

サーバー・テンプレートで簡単にサーバーを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# MatchMaker のフロー

## 概要

Diarkis MatchMaker はマッチメイキングで実現したいことに合わせて、2つの方式を用意しております。

* Host/Search 型: ホストがマッチングを開始し、ゲストがサーチして条件の合うホストを検索、参加する方式。クライアントから条件を指定してマッチングすることを想定しており、ルームマッチングのようなクライアントが好きな条件で検索、入室したいような機能に適しています。また、ホストはパスワードによるロックも可能です。
* Ticket 型: サーチとホストの両方を行う Ticket を発行して自動でマッチングを行う方式。サーバー側でTicket Type ごとにフィルタリング条件を設定することで、クライアントは任意の Ticket Type を指定するだけでマッチングを開始できます。\
  また、クライアントの更新なしにサーバー更新だけで変更することも可能です。\
  ランクマッチなどユーザーのプロパティによって自動マッチングするような機能に適しています。

どちらの方式も、マッチメイキングのルール定義である profile に対して、検索条件に指定する property を定義します。

{% hint style="warning" %}
サーバーをカスタマイズする場合は Enterprise プランの契約が必要になります。
{% endhint %}

## 処理フロー

### Host/Search 型

この方式はクライアントから送信した profileID、 property を元にルームを作成、検索するシンプルな方式です。サーバー側はマッチメイキングの profile に利用する property を定義するだけで、マッチメイキングをすぐに開始することができます。

ホストは任意の条件を指定して募集し、ゲストは条件に合うホストのリストを取得して任意のルームに入室できるため、リストから参加したいルームを検索したい場合に適しています。

基本的な流れは以下の通りです。

1. ホストが profile、 property を指定してルームを作成しマッチングを開始
2. ゲストが profile、 property を指定してルームを検索
3. ゲストが検索結果から任意のルームを指定してルーム参加
4. ルーム入室中は各種操作を実施可能
   1. メッセージ同期
   2. ルーム退出
   3. （ホストのみ）強制退出
   4. （ホストのみ）ルーム削除（解散）

{% @mermaid/diagram content="sequenceDiagram
actor h as Host
actor s as Search
participant du as DiarkisUDP
participant dh as DiarkisHTTP(Storage)

Note over du: 初期化フェーズ
alt マッチングルールを定義
du->>du: matching.Define(profileID, props)
Note left of du: profileID ごとにマッチングに利用する properties を定義する
end

alt ルーム作成（ホストが実施）
h->>du: hostMatchmaking(ver=1,cmd=100)
du->>du: ルーム作成処理
du->>dh: matchmaking データを登録(matching/Add)
du-->>h: ルーム作成完了(roomID)
end

alt ルーム検索（ゲストが実施）
s->>du: listMatchmaking(ver1,cmd=207)
du->>dh: matchmaking データを検索
dh-->>du: return results
du-->>s: ルーム検索完了(検索結果)
end

alt ルーム参加（ゲストが実施）
s->>du: claimMatchmaking(ver=1,cmd=205)
du->>du: ルーム作成処理
alt ルームが満室になったら
du->>dh: matchmaking データを削除
end
du-->>s: ルーム参加完了(roomID,ownerID,memberIDs)
end

alt メッセージ同期
s->>du: syncMatchmakingMembers(ver=1,cmd=204)
du-->>s: メッセージプッシュ(ver=1,cmd=204,message)
du-->>h: メッセージプッシュ(ver=1,cmd=204,message)
du-->>s: メッセージ同期完了("OK")
end

alt ルーム退出
s->>du: leaveMatchmaking(ver=1,cmd=203)
du->>du: ルーム退出処理
du-->>s: ルーム退出プッシュ(ver=1,cmd=203,message)
du-->>h: ルーム退出プッシュ(ver=1,cmd=203,message)
du-->>s: ルーム退出完了("OK")
end

alt 強制退出（ホストのみ）
s->>du: kickFromMatchmaking(ver=1,cmd=217)
du->>du: 強制退出処理
du-->>s: 強制退出プッシュ(ver=1,cmd=217,message)
du-->>h: 強制退出プッシュ(ver=1,cmd=217,message)
du-->>s: 強制退出完了("OK")
end

alt ルーム削除（解散）（ホストのみ）
s->>du: disbandMatchmakingHost(ver=1,cmd=202)
du->>du: ルーム削除処理
du->>dh: matchmaking データを削除
du-->>s: ルーム削除プッシュ(ver=1,cmd=202,message)
du-->>h: ルーム削除プッシュ(ver=1,cmd=202,message)
du-->>s: ルーム削除完了("OK")
end" fullWidth="true" %}

### Ticket 型

マッチングを自動的に行うために Ticket と呼ばれるものを発行する方式です。

Ticket にはフェーズがあり、発行時は検索を繰り返し、一定の回数を検索して一致するものがなければ、 Add フェーズに入り、他人から検索される状態になります。メンバーが最大に達すると完了フェーズに入りマッチング完了状態になります。

また、Ticket Type ごとに発行可能なため、様々な条件のマッチメイキングに対応できます。

さらに、各種コールバックを利用することで、 Ticket Type を指定するだけで、 Diarkis サーバー側で property を API サーバーと連携して取得したり、位置情報と組み合わせて近い人同士をマッチングさせる、最大人数に達成しなくてもマッチングを完了させるなど、複雑なマッチング条件を構築することも可能です。

ランクマッチなど複雑でセキュアなマッチングを実現したい場合に有効な方式となります。

Ticket 型の代表的なコマンドは以下の通りです。

* チケット発行: チケットを発行してマッチングを開始する
* メッセージ同期: マッチングしたメンバーとメッセージのやりとりをする
* チケットキャンセル (オーナーのみ): チケットを削除してマッチングをキャンセルする。ルームに入室したメンバー全員が退出となります
* ルーム退出: マッチングして入室したルームから退出する

処理フローはサーバー側は少々複雑となりますが、クライアントが利用するコマンドなどはシンプルになるので、サーバー更新だけでマッチメイキングの処理を変更することが可能となります。

{% hint style="success" %}
Diarkis サーバーテンプレート Ticket 型の実装 example があります。こちらも参考にしてください。

<https://github.com/Diarkis/diarkis-server-template/tree/develop/examples/matching>
{% endhint %}

{% @mermaid/diagram content="sequenceDiagram
actor Client1 as クライアント1 (Player A)
actor Client2 as クライアント2 (Player B)
participant Server as Diarkis UDP
participant Storage as Diarkis HTTP<br/>(Storage)
participant Callbacks as アプリケーション<br/>コールバック<br/>(Diarkis UDP に登録)

```
Note over Server: 初期化フェーズ
alt マッチングルールを定義
  Server->>Server: matching.Define(profileID, props)
  Note right of Server: profileID ごとにマッチングに利用する properties を定義する
end
loop ticketType ごとに各種コールバック登録
  Note over Server, Callbacks: 必要に応じて ticketType ごとに様々なタイミングでフック処理を追加できる
  Server->>Callbacks: SetOnIssueTicket(ticketType, callback): チケット発行時のコールバック
  Server->>Callbacks: SetOnTicketAllowMatchIf(ticketType, callback): マッチ判定時のコールバック
  Server->>Callbacks: SetOnTicketMatch(ticketType, callback): マッチング成功時のコールバック
  Server->>Callbacks: SetOnTicketMemberJoined(ticketType, callback) メンバーがルームに入ったときのコールバック
  Server->>Callbacks: SetOnTicketMemberJoinedAnnounce(ticketType, callback) メンバーがルームに入ったときのアナウンス
  Server->>Callbacks: SetOnTicketMemberLeave(ticketType, callback) メンバーがルームを退出したときのコールバック
  Server->>Callbacks: SetOnTicketMemberLeaveAnnounce(ticketType, callback) メンバーがルームを退出したときのアナウンス
  Server->>Callbacks: SetOnTicketCanceled(ticketType, callback) マッチングキャンセル時のコールバック
  Server->>Callbacks: SetOnMatchedTicketCanceled(ticketType, callback) マッチしたチケットがキャンセルされたときのメンバーのコールバック
  Server->>Callbacks: SetOnTicketComplete(ticketType, callback) マッチング完了時のコールバック
  Server->>Callbacks: SetOnTicketTimeout(ticketType, callback) タイムアウト時のコールバック
end

Note over Client1, Storage: Player A がチケットを開始
Client1->>Server: issueTicket(ver=1,cmd=218,ticketType)
Server->>Server: StartTicket(ticketType, userData)
Server->>Callbacks: OnIssueTicket callback 実行
Callbacks-->>Server: TicketParams 返却
Server->>Server: Ticket 作成。検索フェーズに移行
Server->>Client1: チケット作成完了("OK")
activate Server

Note over Server: 検索フェーズ (Player A)
loop Search Interval
    Server->>Storage: SearchWithRangeWithTags() Ticket 検索
    Storage-->>Server: 検索結果 (空)
    Server->>Server: searchInterval ミリ秒の間 sleep
    Note right of Server: searchTries 回または emptySearches 回空検索になるまで繰り返す
end

Server->>Server: Add フェーズに移行
Server->>Server: マッチング用ルーム作成
Server->>Storage: 自分の Ticket を matchmaking データに追加
Note right of Server: 他人が Ticket を検索できる状態になる
deactivate Server

Note over Client2, Storage: Player B がチケットを開始
Client2->>Server: issueTicket(ver=1,cmd=218,ticketType)
Server->>Server: StartTicket(ticketType, userData)
Server->>Callbacks: OnIssueTicket callback 実行
Callbacks-->>Server: TicketParams 返却
Server->>Server: Ticket作成。検索フェーズに移行
Server->>Client2: チケット作成完了("OK")
activate Server

Note over Server: 検索フェーズ (Player B)
loop Search Interval
    Server->>Storage: SearchWithRangeWithTags() Ticket 検索
    Storage-->>Server: 検索結果 (Player A を発見!)
    Server->>Server: マッチング用ルームに入室を試行
    
    Note over Server: マッチ判定
    Server->>Callbacks: OnAllowMatchIf callback 実行
    Callbacks-->>Server: true (マッチ許可)
    
    Server->>Server: Player A のマッチング用ルームに参加
    Server->>Server: 自身の Ticket を削除
end
deactivate Server

Note over Server: マッチ成功処理
Server->>Server: ルーム入室後の処理を実行
Server->>Callbacks: OnMatchedMemberJoined callback 実行
Server->>Callbacks: OnMatchedMemberJoinedAnnounce callback 実行

alt OnMatch が true を返す場合は maxMembers に達しなくてもマッチングを完了状態にできる
    Server->>Callbacks: OnMatch callback 実行
    Callbacks-->>Server: true
    Server->>Server: markAsComplete(): 完了フェーズに移行

else maxMembers に達した場合
    Server->>Server: メンバー数チェック
    Server->>Server: markAsComplete(): 完了フェーズに移行
    Server->>Callbacks: OnTicketComplete callback 実行
    Callbacks-->>Server: 完了メッセージ
    Server->>Client1: マッチング完了通知
    Server->>Client2: マッチング完了通知
else まだメンバーが不足している場合はタイムアウト時間を延長
    Server->>Storage: addToMatchmaking(): matchmaking データのタイムアウト延長
    Note over Server: 追加のプレイヤーを待機
end


Note over Client1,Storage: Ticket 参加中に任意で呼び出すコマンド
alt メッセージ同期
    Note over Client2: Ticket の他のメンバーとメッセージ同期
    Client1->>Server: TicketBroadcast(ver=1,cmd=224)
    Server->>Client1: メッセージ通知
    Server->>Client2: メッセージ通知
    Server->>Client1: メッセージ同期完了("OK")
else キャンセル処理 (オーナーのみ)
    Note over Client2: キャンセル処理
    Client1->>Server: CancelTicket(ver=1,Cmd=222)
    Server->>Server: Stop() - Ticket 削除
    Server->>Callbacks: OnTicketCanceled callback 実行
    Server->>Client2: キャンセル通知
    Server->>Callbacks: OnMatchedTicketCanceled callback 実行
    Server->>Client1: キャンセル完了("OK")
else ルーム退出 (オーナーが退出した場合はキャンセル)
    Note over Client2: ルーム退出処理
    Client2->>Server: LeaveFromTicketMatchmaking(ver=1,Cmd=225)
    Server->>Server: ルーム退出
    Server->>Callbacks: OnMatchedMemberLeaveAnnounce callback 実行
    Server->>Client2: ルーム退出通知
    Server->>Callbacks: OnMatchedMemberLeave callback 実行
    Server->>Client1: ルーム退出完了("OK")
end

Note over Server: タイムアウト処理 (並行動作)
par
    Server->>Server: Ticket タイムアウト監視
    alt タイムアウト発生
        activate Server
        Server->>Server: timeoutTicket(): タイムアウト処理
        Server->>Storage: removeFromMatchmaking(): matchmaking データを削除
        Server->>Server: Ticket 削除
        Server->>Server: Room 削除
        Server->>Callbacks: OnTicketTimeout callback 実行
        Server->>Callbacks: OnMatchedTicketTimeout callback 実行
        Server->>Client1: タイムアウト通知
        Server->>Client2: タイムアウト通知
        deactivate Server 
    end
end

Note over Server, Storage: 完了フェーズ
alt 
  Server->>Storage: removeFromMatchmaking():matchmaking データを削除
  Server->>Server: Ticket 削除
end" fullWidth="true" %}
```

## 参考

Diarkis MatchMaking のサーバー API の詳細については、以下を参照してください。

* <https://docs.diarkis.io/docs/server/v1.1.0/diarkis/matching/index.html>


# Field モジュール

## 概要

Diarkis Field は、Diarkis サーバー・クラスターに接続されているすべてのユーザーが共有するデジタル・スペースを作り出します。

このフィールドでは、ユーザー同士が「見える」状態になり、互いにインターアクションを行うことができます。Diarkis サーバ・クラスタは、クライアントから送信された座標に基づいて、誰が見えているか、誰が見えていないかを計算します。

通常、リモート・ユーザーがパケットを共有するためには、同じサーバーに接続されている必要がありますが、Diarkis Field では、サーバー間の境界がありません。すべてのユーザーが同じデジタル・スペースを共有し、パケットを交換することができます（「見える」状態）。

詳細な技術文書については、[こちらの API ドキュメント](https://docs.diarkis.io/docs/server/current/diarkis/field/index.html#hdr-How_Field_Works)をご覧ください。

## 拡張性

Diarkis Field は Diarkis サーバー・クラスター内の複数のサーバーで構成されているため、

スケーリングはクラスター内のサーバーを追加または削除するだけで対応できます。

## Diarkis Field の仕組み

<figure><img src="/files/bHvacBiOA7NcZEO1dlk2" alt=""><figcaption></figcaption></figure>

## サーバーの追加/削除

クラスタにサーバーを追加するほど、各サーバーが処理するユーザー数が少なくなります。クラスター内で稼働するサーバーの数を変更すると、すべてのユーザーが自動的に適切なサーバーに割り当てられ、同期が中断されることはありません。


# Field モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

Field モジュールは、HTTP 、UDP、TCP サーバーでセットアップする必要があります。

HTTP サーバーで Field のメモリデータを管理し、UDP、TCP サーバーでビルドイン・コマンドを公開することで利用可能となります。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

HTTP サーバーの main 関数に以下の様に追加します。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := "/configs/shared/mesh.json"

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Field: &diarkisexec.Options{ConfigPath: "/configs/shared/field.json", ExposeCommands: true},
	})

	diarkisexec.SetupDiarkisHTTPServer("/configs/http/main.json")
	diarkisexec.StartDiarkis()
}
```

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用してセットアップできます。

HTTP サーバーと同様に UDP、TCP サーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := ""

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Field: &diarkisexec.Options{ConfigPath: "/configs/shared/field.json", ExposeCommands: true},
	})
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}
```

サーバー・テンプレートで簡単にサーバーを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# P2P モジュール

## 概要

Diarkis P2P は、ユーザー・デバイスがサーバーを介さずに直接通信することを可能にします。

Diarkis サーバー・クラスターは、NAT トラバーサル技術を使用してクライアントのディスカバリーポイントとして機能します。

peer-to-peer 通信では、各パケット交換のためにサーバーが介在しないため、クライアント・デバイスはネットワーク遅延を最小限に抑えることができます。

Diarkis P2P は UDP ネットワーク・プロトコルのみをサポートし、RUDP（信頼性のある UDP）の独自実装を持ち、パケットの配送と順序を保証します。

接続されたデバイス間で交換されるパケットはすべて暗号化されており、暗号鍵は接続されたデバイス間の接続に固有のもので、安全な通信を確保します。

### 仕組み

peer-to-peer 通信には二つのステップが必要です。まず、クライアントは自分のアドレスを交換し、[ホールパンチング](https://en.wikipedia.org/wiki/Hole_punching_\(networking\))を行います。ホールパンチングが成功すると、クライアントはパケットを直接送受信することができます。

<figure><img src="/files/7rAr4vd1ZJh3DDbOYb4G" alt=""><figcaption></figcaption></figure>

### NAT Types

Diarkis P2P では、クライアント SDK にホールパンチング、いわゆる NAT Traversal の機能が含まれています。NAT Traversal は、ゲーム機のシステムやその他多くのネットワーク・アプリケーションで一般的に使用されている技術ですが、Diarkis P2P の大きな特徴は、通常のホールパンチング手法では接続が難しい **Symmetric Cone 型のルーター** に接続された端末とも、必ずではないものの **通信の確立が可能となる点** にあります。これにより、**成功率の高い通信**が期待できます。

<table><thead><tr><th width="187.25">NAT Type</th><th width="142.19921875">Other Solutions</th><th width="138.33984375">Diarkis</th></tr></thead><tbody><tr><td>Full</td><td>○</td><td>○</td></tr><tr><td>Restricted</td><td>○</td><td>○</td></tr><tr><td>Port Restricted</td><td>○</td><td>○</td></tr><tr><td>Symmetric</td><td>✖︎</td><td>△</td></tr></tbody></table>

### サーバー・リレーへのフォールバック

Diarkis P2P のユニークなアーキテクチャにより、クライアント・デバイスは Diarkis サーバー・クラスターとの接続を維持することができます。これにより、ピアツーピア接続が確立できなかった場合でも、通信を peer-to-peer からサーバー・リレーにフォールバックすることができ、すべてのユーザーがネットワーク設定に関係なく通信を続けることができます。


# P2P モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

P2P モジュールは、UDP サーバー上でセットアップをすることが可能です。

> ⚠ Diarkis P2P は現在のところ UDP/RUDP のみに対応しています。

## セットアップ

Diarkis P2P を有効にするには、 UDP の設定ファイルに `"enableP2P": true` を追加することで有効となります。

```json
  "enableP2P": true
```

UDP サーバーの設定ファイルは、UDP サーバーを起動する main 関数内の `diarkisexec.SetupDiarkisUDPServer(path)` に渡している引数がパスになります。

```go
	// 以下の例では configs/udp/main.json が設定ファイルとなります。
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
```

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

サーバー・テンプレートで簡単にサーバを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# P2P サンプル

## 概要

P2P の機能を試すには Room サンプル上で確認することが出来ます。

[Room サンプル](/diarkis-modules/room/sample)


# DM (Direct Message) モジュール

## 概要

Diarkis DM（Direct Message）は、二人のユーザーが自由にパケットを交換できる機能を提供します。ユーザーがどのタイプのルームにも参加する必要はなく、受信者のユーザー ID を指定してメッセージを送信するだけです。つまり、他のユーザーのユーザー ID を知っていれば、直接通信を行うために他のユーザーを検索する必要はありません。

このモジュールは一対一の通信を目的としており、複数のユーザーにメッセージを送信することもできますが、通常は推奨されません。複数のユーザーにメッセージを送受信する必要がある場合は、Diarkis Room や他の類似モジュールを使用することを検討してください。

Diarkis P2P とは異なり、Diarkis DM はサーバーをユーザー間のハブとして使用します。これはサーバー・リレーであり、ユーザー間のメッセージを配信するために複数のサーバ（最大二つのサーバー）を介してメッセージを送信することができます。このため、非常に低遅延で高頻度のメッセージ交換が必要な場合には、Diarkis DM の使用は理想的ではありません。

詳細な技術情報については、[こちらの API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/dm/index.html)をご覧ください。


# DM モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

DM モジュールは、利用するすべての Diarkis サーバーでセットアップする必要があります（HTTP, UDP, TCP）

セットアップは、クライアントにビルトイン・コマンドを公開するだけで簡単に完了します。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用して簡単にセットアップできます。

以下の様にサーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := "/configs/shared/mesh.json"

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		DM:         &diarkisexec.Options{ConfigPath: "/configs/shared/dm.json", ExposeCommands: true},
	})
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}
```

サーバー・テンプレートで簡単にサーバーを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# Notifier モジュール

## 概要

Diarkis Notifier は、サーバーからすべての接続された Diarkis クライアント・デバイスにメッセージを送信します。Diarkis Notifier のユニークなデザインにより、Diarkis サーバー・クラスターはすべての接続されたデバイスに遅延なくメッセージを送信し、サーバー負荷の増加も全くありません。

このモジュールは、接続されたユーザー・クライアントに一斉に通知する必要がある場合に便利です。

詳細な技術情報については、[こちらの API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/server/index.html#NotificationService)をご覧ください。

従来、このような機能は、数百万の接続デバイスに一斉にメッセージを配信するために大規模なサーバー・リソースを必要としました。さらに、この種の機能は通常、メッセージ配信の遅延を引き起こし、その問題を軽減することは技術的に困難です。

Diarkis Notifier は、上記の問題をすべて解決します。このモジュールは、サーバーからクライアントへのメッセージを送信するため、一方向のメッセージング・システムとなります。したがって、クライアント間の通信や同期には適していません。


# Notifier モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

Notifier モジュールは、TCP、UDP サーバー上でセットアップをすることが可能です。

Notifier モジュールを利用する際は HTTP 以外すべてのサーバーでセットアップする必要があります。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用してセットアップできます。

以下の様にサーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

また、`diarkisexec.SetupNotificationService(name, interval, callback)` にてメッセージを一斉送信する間隔や内容を設定する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := ""

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Notifier:   &diarkisexec.Options{},
	})
	diarkisexec.SetupNotificationService("Notification", 60, handleNotification)
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}

func handleNotification() (*diarkisexec.Notification, error) {
	// Retrieve notification data from a database by the current time
	notificationData := someDatabase.GetNotificationDataByCurrentTime(year, month, date)

	if notificationData == nil {
		// No notification data to send out
		return nil, nil
	}

	n := &diarkisexec.Notification{}
	n.ID = notificationData.ID
	n.Name = notificationData.Name

	// Ver is used by the client to identify the message when received.
	n.Ver = notificationData.Ver

	// Cmd is used by the client to identify the message when received.
	n.Cmd = notificationData.Cmd

	n.Message = []byte("Notification message says 'Hello from the server'")

	// TTL is in seconds to indicate the availability of the notification data.
	// The notification will be available for the not-connected-clients for the duration of TTL and
	// will be sent to the clients when they connect before TTL expires.
	n.TTL = int64(60 * 60) // one hour

	return n, nil
}
```


# Session モジュール

## 概要

Session は Room、Group、Field、DM と互換性があります。

Session では、ユーザーがメンバーになることができ、すべてのメンバーがメッセージを送受信することができます。

Session には許可される最大メンバー数があり、招待したいだけメンバーを招待できますが、招待を受けた時点で最大数に達している場合、メンバーは Session に参加することができません。

Session には参加できるユーザー数の制限があります。

新しいメンバーが参加したとき、メンバーが退会したとき、Session が削除されたときにイベントが発生します。

Session では、すべてのセッション・メンバーにブロードキャスト・メッセージを送受信することができます。

Session がアクティブである限り、共有プロパティを保存することができます。

技術的な詳細については、サーバー [API ドキュメント](https://docs.diarkis.io/docs/server/current/diarkis/session/index.html) をお読みください。

### セッション招待

Session のオーナー（作成者）として、ユーザー ID を指定して任意のユーザーを Session に招待することができます。

招待されたユーザーは招待メッセージを受け取り、Join 機能を使用して招待を受け入れることができます。招待には TTL（有効期限、秒単位）があり、TTL が切れると、招待を受け入れても Session に参加できる保証はありません。

### 招待の受け入れ

セッション招待機能で招待されたユーザーは、招待を受け入れて Join を呼び出すことで Session に参加することができます。


# Session モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

Session モジュールは、TCP、UDP サーバー上でセットアップをすることが可能です。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用してセットアップできます。

以下の様にサーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := ""

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Session:    &diarkisexec.Options{ConfigPath: "/configs/shared/session.json", ExposeCommands: true},
	})
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}
```

サーバー・テンプレートで簡単にサーバーを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# Group モジュール

## 概要

Group は多数のユーザーが多方向に通信するグループを構築するのに適しています。異なるネットワーク・プロトコルを持つユーザーを接続でき、接続できるユーザー数に制限はありません。

大人数が参加するリアルタイムなメッセージの送受信に適しています。

ただし、参加人数に制限がないため、高頻度に多くのユーザーがデータを送信するような実装をした場合、意図せずサーバーとクライアントの負荷が高くなる可能性があります。

また、Group はサーバー間通信を利用して大人数とコミュニケーションを取るという性質上、同じサーバー内でコミュニケーションを取る Room と比べて RTT は高くなる傾向にあります。


# Group モジュールをサーバーでセットアップする

## 概要 <a href="#diarkis-room-wosettoappusuru" id="diarkis-room-wosettoappusuru"></a>

Group モジュールは、TCP、UDP サーバー上でセットアップをすることが可能です。

## セットアップ <a href="#birutoinkomandowokuraiantonisuru" id="birutoinkomandowokuraiantonisuru"></a>

クライアントにビルトイン・コマンドを公開するには、　diarkisexec パッケージを利用してセットアップできます。

以下の様にサーバーの main 関数に追加します。以下は UDP サーバーでセットアップするサンプルです。diarkisexec の setup 関数は `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import "github.com/Diarkis/diarkis/diarkisexec"

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := ""

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Group:      &diarkisexec.Options{ConfigPath: "/configs/shared/group.json", ExposeCommands: true},
	})
	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")
	diarkisexec.StartDiarkis()
}
```

サーバー・テンプレートで簡単にサーバを立ち上げることができるので、まずはこちらを利用することをお勧めいたします。 [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)


# CSAR (Clustered Server Authoritative Ruler) モジュール

## 概要

**CSAR** (/zɑːr/ ツァーㇽ) は Clustered Server Authoritative Ruler の略で以下を意味しております。

* Clustered: Diarkis サーバーとコミュニケーションを取ることができる
* Server Authoritative: サーバーが権威を持つ＝サーバーでロジックを実行できる＝チートに強い
* Ruler: 支配者＝Authoritative と同義

CSAR を使うことで以下のようなことが実現できます。

* Client-Hosted タイプ と DGS タイプ の ゲームの開発することができます。
  * Diarkis CSAR を利用することで、クライアントの中の１つが ホスト役（オーソリティ）も担う Client-Hosted タイプ と、 Dedicated Game Server (専用ゲームサーバー、以降DGS) が ホスト（オーソリティ）を担う DGS タイプのゲームを開発できるようになります。
* Client-Hosted タイプでは、クライアントの一つがホスト役とクライアント役を担い、他のクライアントはクライアント役のみを担います。
  * ホスト役が切断したりゲームから抜けたときは、ホストがマイグレーションする機能にも対応しています。
  * ゲームで、ホスト役 と クライアント役 の役割を分けてゲームをロジックを開発し易くなります。
* DGS タイプでは、Diarkis クライアントを Diarkis サーバークラスターの一部として動作させることができます。
  * これにより、クラスター内の他の Diarkis サーバーと直接通信し、データを共有することが可能になります。
  * Kubernetes 上で Agones のように ホスティングとスケーリング管理できます。
  * Diarkis CSAR SDK はC++ と C# をサポートしており、Unreal Engine や Unity などの一般的なゲームエンジンや独自のゲームエンジンでも利用できます。
  * これによりチート耐性の強いゲームを開発できるようになります。
* P2P と サーバーリレー の通信経路を意識せずに、堅牢な通信環境を提供いたします。

  * P2P (Peer to Peer)  で通信できるクライアント間とは P2P で通信を行い、P2P で通信できないクライアント間  (ホールパンチに失敗した時など) では Room モジュール を使用した サーバー経由の リレー通信 を組み合わせて最適な通信経路が自動的に使用されます。
  * P2P 接続後も、通信中に P2P の接続が切れたとしてもフォールバック機能でリレー通信に切り替えたり、P2P で送信していた RUDP パケットを リレーサーバー経由で再送する機能に対応しており、堅牢な通信が実現することができます。

Diarkis クライアント で提供されている CSAR の機能について

[CSAR (Clustered Server Authoritative Ruler)](/diarkis-client/csar-clustered-server-authoritative-ruler)

***


# Dedicated Game Server (専用ゲームサーバー) とは？

Dedicated Game Server  (専用ゲームサーバー、以降DGS) とは、実際のゲームループをサーバー上で実行し、そのゲームセッションにおける最終的な権威となるサーバーのことです。接続されたプレイヤーデバイスは入力付きの再生デバイスとなります。この構成により、全てのゲームロジックや物理計算がサーバー上で処理・実行され、クライアントデバイスではなくサーバーから各プレイヤーへ結果が送信されるため、不正行為（チート）を防止できます。\
Diarkis CSAR を利用することで、DGS を Diarkis サーバークラスターの一部として動作させることができます。これにより、クラスター内の他の Diarkis サーバーと直接通信し、データを共有することが可能になります。Diarkis CSAR SDK はC++ と C# をサポートしており、Unreal Engine や Unity などの一般的なゲームエンジンや独自のゲームエンジンでも利用できます。<br>

Diarkis サーバークラスターはメッシュベースの機能だけでなく、Dedicated Game Server のオーケストレーションも担当します。また、Diarkis CSAR は Kubernetes 上で Agones のように ホスティングとスケーリングを管理することもできます。

Diarkis CSAR が他の類似機能を提供するソリューションと異なる点は、Diarkis のクラスターアーキテクチャを活かし、ゲーム状態やプレイヤー状態を維持したまま、プレイヤーデバイスがサーバー間を移動できることです。これはサーバーがクラッシュした場合でも機能します。エンドユーザー（プレイヤー）は中断した場所からプレイを再開できます。Diarkis CSAR を専用ゲームサーバーに組み込むのは非常に簡単です。CSAR のライブラリとヘッダファイルをプロジェクトに含めてビルドするだけです。

## CSAR DGS のローカル開発

Diarkis サーバークラスターは、Windows や Mac OS などのローカルマシン上でも動作させることができます。ローカルマシン上でゲームサーバーを実行するために必要なのは Diarkis だけで、他のツールは不要です。そのためセットアップも非常にシンプルです。

ゲーム開発中に専用ゲームサーバーを動かすためだけにクラウドプロジェクトを設定する必要はありません。これにより、開発者はゲームをビルドしながら簡単かつ迅速にイテレーションを回すことができます。

<figure><img src="/files/SORtWnSdtNzCkoUDyQOB" alt=""><figcaption></figcaption></figure>

Kubernetes 環境がなくても各プロセスを起動することで、Diarkis クラスターとして、各種プロセスが管理されます。DGS プロセスを開始、終了するだけで増減が可能です。それにより、開発者が任意のタイミングで DGS をビルドして動作確認することができます。

{% @mermaid/diagram content="graph LR
startServerProcesses\["mars,HTTP,UDP<br>サーバープロセスを起動"]
implement\["クライアント、<br>DGS の実装"]
DGSBuild\["DGS ビルド"]
startDGSProcess\["DGS プロセスの起動<br>（再起動）"]
check\["動作確認"]
startServerProcesses --> startDGSProcess
implement --> DGSBuild
DGSBuild --> startDGSProcess
startDGSProcess --> check
check --> implement" %}

## CSAR DGS のインフラ構成

Diarkis CSAR を使うと、Diarkis クラスター内で容易に DGS を管理することが可能です。Diarkis クラスターの耐障害性は CSAR においても有効で、DGS のホスティングとスケーリングを管理でき、必要に応じて柔軟にスケールすることが可能です。更に、MatchMaker や Room モジュールと連携することで、Diarkis クラスター内だけでマッチングから DGS 上でのゲームセッション開始までシームレスに行うことが可能です。

以下は Diarkis CSAR を使ったゲームプレイ開始から終了までのフローの一例です。

<figure><img src="/files/xjTBXrrPHSt5X3iB1GIl" alt=""><figcaption></figcaption></figure>

1. クライアントはゲームの API サーバーを通じて、Diarkis への認証処理を行います。
2. Diarkis UDP サーバーに接続します。
3. Room を作成および参加します。
4. UDP サーバーにコマンドを発行して、DGS を allocate します。
   1. UDP サーバーから DGS を確保します。
   2. 必要に応じて Autoscaler によって DGS のスケーリングを行います。
   3. UDP サーバーはクライアントに対して、エンドポイントと credential 情報を返します。
5. allocate した DGS に接続します。
6. ゲームセッションを行います。この間の通信方法などのロジック処理は DGS の実装に依存します。
7. ゲームセッションを終了する際は、終了処理を呼び出して、DGS をシャットダウンします。

## ホストマイグレーション

Diarkis CSAR は、ゲームセッションをサーバー間で自在に移行させつつ、ゲーム状態やプレイヤー状態を維持することができます。これにより、Google の Preemptible VM や AWS の Spot Instance などの一時的なサーバーリソースを利用してコストを削減することが可能です。Diarkis CSAR のホストマイグレーションはコスト削減だけでなく、全てのゲームセッションの復元も実現します。DGS がクラッシュや障害で停止した場合、そこで動作していたゲームセッションは失われ、参加していた全てのプレイヤーは進捗を失います。Diarkis CSAR はサーバークラッシュ時に別サーバーへマイグレーションし、全てのゲーム状態とプレイヤー状態を維持したままプレイヤーが中断地点からプレイを続行できるようにします。


# DGS のローカル開発手順 (Windows)

## はじめに

CSAR を使うとローカル環境上で簡単に DGS の開発を行うことができます。

Diarkis サーバークラスターは Windows 上で動かすこともできるので、別途サーバー環境を用意せずにローカル環境のみで DGS を動かす事が可能です。

本ページでは Windows 上のローカル環境で開発フローを回すために以下について解説します。

* CSAR の開発フローの確認
* Diarkis サーバーのビルド、実行方法
* Unity での DGS サーバーのビルド方法

## 動作環境

2025-07-07 現在のバージョン v1.1.0 について、以下の環境で動作を確認しております。

* Windows 11
* Go 1.24

## CSAR DGS のローカル上での動作イメージ

ローカル環境においては、開発効率を重視するために、Diarkis クラスターで管理されている DGS プロセスを allocate するように構築します。それにより、開発者が任意のタイミングで DGS をビルドして動作確認することができます。

<figure><img src="/files/q0rsqYeCZNpdOQxkqy0K" alt=""><figcaption></figcaption></figure>

以下はローカル環境での開発フローの例です。

Diarkis はローカル環境では各プロセスを実行するだけで、Diarkis クラスターとして管理されます。CSAR は DGS の実装・修正をしてビルドおよび実行（Editor においては Play）するだけでクラスターに参加でき、動作確認ができます。

{% @mermaid/diagram content="graph LR
startServerProcesses\["mars,HTTP,UDP<br>サーバープロセスを起動"]
implement\["クライアント、<br>DGSの実装、修正"]
DGSBuild\["DGSビルド"]
startDGSProcess\["DGSプロセスの起動<br>（再起動）"]
check\["動作確認"]
unityEditor\["Play on Unity Editor"]
checkOnUnityEditor\["動作確認"]
implementOnUnityEditor\["クライアント、<br>DGSの実装、修正"]

startServerProcesses --> startDGSProcess
implement --> DGSBuild
DGSBuild --> startDGSProcess
startDGSProcess --> check
check --> implement

startServerProcesses --> unityEditor
unityEditor --> checkOnUnityEditor
checkOnUnityEditor --> implementOnUnityEditor
implementOnUnityEditor --> unityEditor
" %}

## mars, HTTP, UDP サーバーのビルド、起動手順

### Diarkis  Server Template のインストール

以下リポジトリより、Diarkis Server Template のソースコードを取得します。

<https://github.com/Diarkis/diarkis-server-template>

`git clone` して利用中のバージョンをチェックアウト、または releases&#x20;

### プロジェクトの生成

PowerShell から以下のコマンドを実行して、プロジェクトを生成します。

{% code overflow="wrap" %}

```powershell
# parameters: {project_id} {builder_token} {output\}
> .\run-mage.bat examples:install 12345678901 11111111-1111-1111-1111-111111111111 ../server_bin
```

{% endcode %}

* `project_id`: 弊社が発行したプロジェクトID
* `builder_token`: 弊社が発行したBuilder Token
* `output`: 生成したプロジェクトの出力先。ここでは `../server_bin` として説明します

上記のコマンドを実行することで、 `../server_bin` に Diarkis のサンプルプロジェクトが出力されます。

### ビルド

出力されたサンプルプロジェクトの `../server_bin/csar/dgs` が DGS のサンプルプロジェクトとなります。ディレクトリを移動し、以下のコマンドを実行して、Diarkis のサーバーバイナリをビルドします。

```powershell
> cd ..\server_bin\csar\dgs
> .\run-mage.bat build:local
```

ビルドが終了すると、 `remote_bin` ディレクトリにバイナリが出力されます。

### 実行

mars, http, udp をそれぞれ起動します。それぞれ別の PowerShell のウィンドウで起動します。

```powershell
> .\run-mage.bat server mars
> .\run-mage.bat server http
> .\run-mage.bat server udp
```

必要に応じて、Go のテストクライアントで動作確認を実施してください。

👉 [2. テストクライアントで疎通確認する](/getting-started/tutorial/test-client)

## DGS サーバーのビルド、起動手順

### Unity で DGS サーバーを実行する

1. Diarkis Plugin Sample/Sample/Scenes から DiarkisSampleScene を選択してください。
2. DiarkisSampleScene の DiarkisNetworkManager から、`Pre Stored Http Host` に Diarkis サーバーの `アドレス:ポート` を指定してください。
3. DiarkisSampleScene の SceneManager から、`Editor DGS Clound Env` に DGS サーバーを起動する PC の `アドレス` (ポート番号は不要) を指定してください。
   1. Diarkis サーバーと DGS サーバー を同じ PC で起動する場合は、同じ アドレスを指定してください。
   2. DGS サーバーを複数起動される場合は、`Editor DGS Port`  を `7400` 以外をご利用ください。
4. `BuildSettings` の `Scenes In Build` で、DGSSSampleScene と DiarkisSample\_HostClientGameDemo\_2\_InGame を選択します。

<mark style="color:red;">※以下の参考画像では、 Diarkis サーバーを ローカルホスト (127.0.0.1)で起動している場合の設定になります。</mark>

<figure><img src="/files/QiQZRmwGjtZXmBKnOtWz" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/JGxPaQK1Aedqlkials0O" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/4VaOfclIUfEeMLbmCfUW" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/1BIzLzpLB4ugH5euWKrg" alt="" width="375"><figcaption></figcaption></figure>

### DSGサーバー プロセス実行

1. Unity Editor で DGSSampleScene  を起動して、`Play` ボタンで実行します。
2. 実行すると、DGS サーバーのプロセス （relay.exe) が起動され、コンソールウィンドウが起動されます。
3. relay.exe は、UnityEditor で DGSサーバーを起動した時のみ、起動されるプロセスになります。

<figure><img src="/files/RIuGaAadjq0138AyhhY5" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/bfxj2oqrRnSiFaowreyA" alt="" width="375"><figcaption></figcaption></figure>

### DGS サーバープロセスを起動する

DGS サーバーのプロセスのビルド手順

1. Diarkis Plugin Sample/Sample/Scenes から DiarkisSampleScene を選択してください。
2. DiarkisSampleScene の DiarkisNetworkManager から、`Pre Stored Http Host` に  Diarkis Http サーバーの `アドレス:ポート` を指定してください。
3. DiarkisSampleScene の SceneManager から、Editor `DGS Clound Env` に DGS サーバーを起動する PC の `アドレス` (ポート番号は不要) を指定してください。Diarkis サーバーと DGS サーバー を同じ PC で起動する場合は、同じ アドレスを指定してください。
4. `BuildSettings` の `Scenes In Build` で、DGSSSampleScene と DiarkisSample\_HostClientGameDemo\_2\_InGame を選択します。
5. Platform リストから Dedicated Server を 選択して、Switch Platform ボタンを押下します。
6. `Build` ボタンを押下して、Dedicated Server の ヘッドレスの Standalone バイナリーをビルドします。

<mark style="color:red;">※以下の参考画像では、 Diarkis サーバーを ローカルホスト (127.0.0.1)で起動している場合の設定になります。</mark>

#### DGS  サーバープロセスの実行手順

&#x20;以下のように、引数を指定して起動してください。

```powershell
{app} {logFile} {meshFile} {DGS Endpoint} {DGS Port} {DGS Cloud Env}

例
> .\UnityDiarkis_Sample.exe .\log.json .\mesh.json 0.0.0.0 7400 127.0.0.1
```

* logFile : log の config ファイルの log.json パスを指定します。
* meshFile : mesh サーバーの config ファイルの mesh.json パスを指定します。
* DGS Endpoint : DGS サーバーの IP を指定します。
* DGS port : DGS サーバーの Port を指定します。 DGS サーバーを複数起動する場合は、7401, 7402 など 7400 番台以降を指定します。複数起動する際に、同じ Port 番号を指定すると正しく動作しないため予めご留意ください。
* DGS Cloud Env :  起動する DGS サーバーの ローカルアドレスを指定してください。

mesh.json の例

<pre class="language-json"><code class="lang-json"><strong>{
</strong><strong> "marsAddress": "127.0.0.1",
</strong><strong> "marsPort": "6779"
</strong><strong>}
</strong></code></pre>

* address, marsAddress には、Diarkis サーバーの ローカルIP を指定します。

log.json の例

```json
{

  "level": "sys",
   "levels": {
    "ROOM": "verbose",
    "_FIELD": "verbose",
    "_UDP": "network"
  },
  "timeZone": "local",
  "color": true,
  "flat": false,
  "filePath": "./diarkis.test.log",
  "unsafeLogging": true
}
```

* DSGサーバー プロセス実行時の Diarkis サーバー側のログを設定するためのファイルになります。

実行すると以下のようなコンソールが起動します。DGS サーバープロセスのログをご確認頂けます。

<figure><img src="/files/YmVDW1vvlcg5boFF3hC7" alt=""><figcaption></figcaption></figure>

DGS サーバープロセスを終了する時は、コンソールで Ctrl + c で終了し、コンソールウィンドウを閉じてください。

### DGS クライアントプロセスを起動する

DGS クライアントプロセスのビルド手順

1. Diarkis Plugin Sample/Sample/Scenes から DiarkisSample\_HostClientGameDemo\_1\_Menu を選択してください。
2. DiarkisSampleScene の DiarkisNetworkManager から、`Pre Stored Http Host` に  Diarkis Http サーバーの `アドレス:ポート` を指定してください
3. `BuildSettings` の `Scenes In Build` で、DiarkisSample\_HostClientGameDemo\_1\_Menu と DiarkisSample\_HostClientGameDemo\_2\_InGame を選択します。
4. Platform リストから Windows を 選択して、Windows が有効になっていなかったら`Switch Platform` ボタンを押下します。
5. `Build` ボタンを押下して、Windows の Standalone バイナリーをビルドします。

<mark style="color:red;">※以下の参考画像では、 Diarkis サーバーを ローカルホスト (127.0.0.1)で起動している場合の設定になります。</mark>

<figure><img src="/files/oTcsjR2o6triGZmfbeW4" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/k5OhVaf6HOxijM7WvZxm" alt="" width="563"><figcaption></figcaption></figure>

DGS クライアントプロセスの実行手順

1. ビルドした DGS クライアントプロセスを 2つ 起動します。
   1. MinMembers : `2` &#x20;
   2. MaxMembers : `4`
   3. Connection Mode: `SingleAuthority`
   4. Network Type: `DGS`&#x20;
2. パラメータの意味
   1. MinMembers は、設定した人数が集まったら DGS サーバーに接続
   2. MaxMembers は、Room に入れる最大数

<div align="center" data-full-width="true"><figure><img src="/files/4F8OZ4WAho5EHLTWdext" alt="" width="563"><figcaption></figcaption></figure></div>

1. Create / Join Game Instance ボタンを押下して、DGSサーバーと接続が成功すると、以下のような画面になります。

<figure><img src="/files/LHedpKnc6YsTls6fto9u" alt="" width="563"><figcaption></figcaption></figure>

HostClientGameApp.cs の StartGameInstanceProcessCoroutine()

DiarkisSample\_HostClientGameDemo のサンプルを DGS モードで動かした場合、

1. クライアントA が、 MaxMembers=4 を指定して StartGameInstance を呼び出します。
2. MaxMembers 分 他のクライアントB,C,D が、StartGameInstance が呼び出した後に、
3. クライアントA,B,C,D の IsGameInstanceStarted() = true になります。

現状 HostClientGameApp.cs のコードで、各クライアント が StartGameInstance を呼び出してから 20秒間に MaxMembers 分 他のクライアントが StartGameInstance を呼ばないとタイムアウトになってしまいますのでご注意ください。タイムアウトになったクライアントは DGS サーバーとの接続に失敗します。必要に応じて こちらのタイムアウト時間を変更してください。

```csharp

  // ConnectionManager を作成しゲームインスタンスの開始リクエストを送信します。
  // Authoritative Network の機能は ConnectionManager を使用してアクセスするため、ここ以降 DiarkisInterface の機能は基本的には使用しません。
  var connection = DiarkisNetworkManager.GetConnectionManager(gameModeName_);
  if (!connection.IsGameInstanceStarted())
  {
     uid_ = DiarkisNetworkManager.GetDiarkisInterface(gameModeName_).UID;
     connection.StartGameInstance(gameConfig_);
     // クライアント間の直接通信(メッシュ接続)を許可します
     // この設定を有効にすることで Host-Client 型の API だけでなく SendUnicast/Multicast/Broadcast を使用する事が出来るようになります。
     DiarkisAsyncResult res = new DiarkisAsyncResult();
​
     // タイムアウトを20秒で設定。DGS モードの時は 20秒以内に 他のクライアントも GameInstanceStart する必要があるので注意してください。
     // 必要に応じて調整する必要があります。
     yield return DiarkisAsync.WaitFor(() =>
     {
        return connection.IsGameInstanceStarted();
     }, res, 20000);
     gameInstanceStarted_ = connection.IsGameInstanceStarted();
     if (res.completed == false)
     {
        Debug.Log("Game instance start sequence: Time Out");
        yield break;
     }
  }
  Debug.Log("Game instance start sequence: Success");
  connection.AllowSendDataBetweenClients(true);
```


# DGS のデプロイ・スケール手順

## 概要 <a href="#hajimeni" id="hajimeni"></a>

Diarkis CSAR は Kubernetes 上にデプロイすることで、簡単にスケーリング管理することができます。

詳細は、以下サンプルの README を参照してください。

<https://github.com/Diarkis/diarkis-server-template/tree/develop/examples/csar/dgs>


# Diarkis サーバー

## 概要

Diarkis サーバー SDK は Go 言語（Golang）で開発されています。Go の強力な並列処理とネットワークスタックを最大限に活用しており、プロダクション環境において高いスケーラビリティと低レイテンシな通信を実現します。

Diarkis クラスターを構築・稼働させるためには、クラウドサービスの選定に加え、アプリケーションの要件（同時接続数、通信頻度など）に合わせたカスタマイズが必要です。

#### クラスターを構成するサーバータイプ

Diarkis クラスターは、主に以下の 4 種類の役割を持つサーバーで構成されます。

<table><thead><tr><th width="163">サーバー</th><th>説明</th></tr></thead><tbody><tr><td><strong>MARS</strong><a href="#references"><sup>[1]</sup></a></td><td>クラスター内の全ノードの状態管理</td></tr><tr><td><strong>HTTP</strong></td><td>認証連携や接続先エンドポイント（UDP/TCP）</td></tr><tr><td><strong>UDP/RUDP</strong></td><td>UDP または Reliable UDP を使用したリアルタイム通信</td></tr><tr><td><strong>TCP</strong></td><td>TCP を使用したリアルタイム通信を処理</td></tr></tbody></table>

また、以下の図は、Diarkis の基本的なアーキテクチャ構成のシンプルな例を示しています。

### Diarkis アーキテクチャ構成図の例

{% @mermaid/diagram content="architecture-beta
group Clients(internet)\[Clients]
service tcp\_cli(internet)\[Client Devices] in Clients
service udp\_cli(internet)\[Client Devices] in Clients

```
group Kubernetes(cloud)[Kubernetes]
    service lb(server)[Load Balancer] in Kubernetes

    group Diarkis(cloud)[Diarkis] in Kubernetes
        service udp(server)[UDP Servers] in Diarkis
        service http(server)[HTTP Servers] in Diarkis
        service tcp(server)[TCP Servers] in Diarkis
        service mars(server)[MARS] in Diarkis

group External(cloud)[external]
    service api(database)[API Servers] in External

lb:R -- L:http
lb:B -- T:api

tcp_cli:L -- R:tcp
udp_cli:L -- R:udp

%% tcp_cli:B -- R:api
%% udp_cli:B -- R:api

%% Internal cluster routing
udp:B -- T:http
http:B -- T:tcp" %}
```

これらのサーバー構成は [Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)を利用することで簡単に始めることができます。

### ユーザーの接続フロー

Diarkis と通信するための接続を確立するには、HTTP API を介して接続エンドポイントとその他の必要なデータをクラスタから取得する必要があります。

以下の図は、ユーザーが Diarkis サーバークラスターとの接続を開始する際のフローを示しています。

{% @mermaid/diagram content="
sequenceDiagram
participant Client as Client Device
participant App as Application Server
box Cloud/Infrastructure
participant LB as Load Balancer
end
box "Diarkis Cluster"
participant D\_HTTP as Diarkis HTTP
participant D\_Net as Diarkis UDP/TCP
end

```
Note over Client, App: User Authentication
Client->>App: Authenticate
App->>LB: Endpoint Request
LB->>D_HTTP: Route Request
D_HTTP->>D_HTTP: User Creation
D_HTTP-->>LB: Return UDP/TCP Endpoint
LB-->>App: Return UDP/TCP Endpoint
App-->>Client: Return UDP/TCP Endpoint

Note over Client, D_Net: Direct Connection
Client->>D_Net: UDP/TCP Direct Connection" %}
```

***

#### References

* \[1]: プロダクション環境では、**MARS** がダウンした場合に備え、クラスター内の他のノードがその役割を継承する自動選出（Election）アルゴリズムが組み込まれています。


# Diarkis サーバをクラウド環境で起動する

Diarkis サーバーテンプレートに Kubernetes で Diarkis クラスターを構築するためのマニフェストが用意されています。

ベアメタルサーバーや VM でサーバーバイナリを起動して管理することも可能ですが、冗長化や高可用性を高めるために Kubernetes を利用することをお勧めしております。

* GCP（GKE）
* [AWS（EKS）](/diarkis-server/setup-cloud/aws)
* Azure（AKS）
* Alibaba Cloud（ACK）
* Linode（LKE）

diarkis-server-template リポジトリにて、以下の各種クラウドで動作させるためのサンプルを用意しております。詳細は以下の README を参照してください。

<https://github.com/Diarkis/diarkis-server-template/tree/main/src/cloud>


# AWS

このドキュメントでは、AWS（EKS）上でDiarkisを構築、デプロイ、オーケストレーションするプロセスについて説明します。

## 概要

提供されているk8s設定を使用すれば、Diarkisの使用は簡単です。ただし、これらの設定は初期段階のものであるため、必要に応じて自由に修正してください。

***

### 必要条件

1. **Docker** のいずれかの設定：
   1. **MacOS** - Docker for MacOSをインストール。インストールガイドは[こちら](https://docs.docker.com/desktop/setup/install/mac-install/)。
   2. **Linux** - お使いのLinuxディストリビューションに合わせてDockerをインストール。インストールガイドは[こちら](https://docs.docker.com/desktop/setup/install/linux/)。**注意**: Dockerは`x86_64/amd64`アーキテクチャの主要なLinuxディストリビューション用に`.deb`と`.rpm`パッケージを提供しており、Archベースのディストリビューションの[実験的サポート](https://docs.docker.com/desktop/setup/install/linux/#supported-platforms)も提供しています。
   3. **Windows** (WSL2またはHyper-Vバックエンド) - インストールガイドは[こちら](https://docs.docker.com/desktop/setup/install/windows-install/)。初めてDockerをインストールする場合は、バックエンドの選択前にユースケースを考慮してください。
2. **AWSアカウント** と請求が有効になっていること。AWSアカウントやプロジェクトをお持ちでない場合は、[こちら](https://aws.amazon.com/getting-started/)を参照して始めてください。
3. **AWS CLI** (`aws`コマンド) と適切な認証。インストールガイドは[こちら](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) (**注意**: AWS CLIはすべての主要なオペレーティングシステムをサポートしています)。CLI認証のヘルプは[こちら](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-authentication.html)を参照してください。
4. **Kubernetes CLI** (`kubectl`コマンド) は[こちら](https://kubernetes.io/docs/tasks/tools/#kubectl)からダウンロード可能です。
5. **EKS CLI** (`eksctl`コマンド) はAWS Workshopの[こちら](https://eksctl.io/installation/)からダウンロード可能です。

***

## セットアップガイド

以下の手順で、テンプレートDiarkisサーバークラスタの構築、デプロイ、オーケストレーションのプロセスをご案内します。これらの手順で、開始するのに十分な情報が得られるはずです。

### Diarkisイメージ用のECRを作成

Diarkisコンポーネントイメージをデプロイ用にプッシュする前に、まずリモートECRレジストリを準備する必要があります。ベースイメージとしてデフォルトで`alpine`を使用しており、[Docker Hub](https://hub.docker.com/_/alpine/)から取得できます。

```bash
aws sts get-caller-identity  # 正しいターゲットを確認
aws ecr create-repository --repository-name http
aws ecr create-repository --repository-name udp
aws ecr create-repository --repository-name tcp
aws ecr create-repository --repository-name mars
```

***

### Diarkis用のEKSを作成して接続

```bash
eksctl create cluster -f cloud/aws/cluster_config.yaml  # 約10分かかります
```

**注意**: 選択したAZでNATゲートウェイの互換性に関するエラーが発生した場合は、別のAZを選択してください

```bash
aws eks --region ap-northeast-1 update-kubeconfig --name diarkis  # k8s認証情報を取得
```

***

### EKSファイアウォールを開放

EKSノードへの`0.0.0.0/0`からのポート`7000-8000`のTCPおよびUDPトラフィックを許可します。\
セキュリティグループ名`eks-cluster-sg-diarkis-*`で設定することをお勧めします。

***

### サーバーイメージにタグを付けてプッシュ

`server-template`で生成されたプロジェクトルートから、以下のコマンドを実行します：

```bash
make build-local
```

`./remote_bin`にサーバー実行ファイル（`udp`、`tcp`、`http`、`mars`）を生成した後、コンテナイメージをビルドします：

```bash
make setup-aws
make build-container-aws
make push-container-aws
```

***

### マニフェストを適用

```bash
kustomize build k8s/aws/overlays/dev0 | kubectl apply -f -
```

以下の4つのコンポーネントが実行中かどうかを確認します：

```bash
$ kubectl get po -n dev0
NAME                    READY   STATUS    RESTARTS   AGE
http-5c7dbbb6d7-lhjlm   1/1     Running   0          3d14h
mars-0                  1/1     Running   0          3d14h
tcp-88dc5f97d-7sqk9     1/1     Running   0          3d14h
udp-fdc6bbccc-dwc5w     1/1     Running   0          3d14h
```

***

### Diarkisクラスタの確認

まず、パブリックエンドポイントを取得します：

```bash
EXTERNAL_IP=$(kubectl get svc http -o json -n dev0 | jq -r '.status.loadBalancer.ingress[].hostname')
kubectl get svc -n dev0 -o wide  # または、このコマンドを使用
```

取得した`EXTERNAL_IP`にHTTP GETリクエストを送信します：

```bash
curl ${EXTERNAL_IP}/auth/1
```

以下のようなレスポンスが返ってきた場合、正常に動作しています：

```json
{
  "TCP": "ec2-xx-xx-xx-xx.ap-northeast-1.compute.amazonaws.com:7201",
  "UDP": "ec2-yy-yy-yy-yy.ap-northeast-1.compute.amazonaws.com:7101",
  "sid": "xxxxxxxxxx",
  "encryptionKey": "xxxxxxxxxx",
  "encryptionIV": "xxxxxxxxxx",
  "encryptionMacKey": "xxxxxxxxxx"
}
```

項目が不足している場合、デプロイされたコンポーネントのいずれかに問題がある可能性があります。この時点で、Diarkisサポートに連絡することをお勧めします。

***

### クラスタオートスケーラーのセットアップ

```bash
kubectl apply -f cluster-autoscaler-autodiscover.yaml
```

このファイルは`diarkis`というクラスタ名用に事前設定されています。異なるクラスタ名を使用する場合は、マニフェスト内の`diarkis`への参照を修正してください。

***

### ログコレクターのセットアップ

コンテナからのログはCloudWatch Logsを使用して集約できます。\
`fluent-bit`は既に`amazon-cloudwatch`名前空間にデプロイされていますが、権限が設定されていません。

`diarkis-public`と`diarkis-private`ノードに`CloudWatchAgentServerPolicy`を割り当ててログを集約します。ログは`/aws/containerinsights/Cluster_Name/application`の下に表示され、フィルタリングが可能です。


# Diarkis サーバーを Windows 環境で起動する

## はじめに

Diarkis サーバーを Windows バイナリとしてビルドして動かすこともできます。

これにより、Windows マシン上で開発するクライアントエンジニアが WSL なしで Diarkis サーバーを動作させることが可能となり、開発作業の効率化に役立てる事ができます。

ただし、本番環境での利用は想定していないので、ローカル環境や開発環境での利用にとどめてください。

## 動作環境

2025-07-17 現在、以下の環境での動作を想定しております。

* Windows 10/11
* Go 1.24 以上
* （git でリポジトリを close する場合は git）

## 手順

### 必要なツールのインストール

Go 1.22 以上をインストールします。<https://go.dev/doc/install>

scoop などのパッケージマネージャーを使ってインストールすることも可能です。

```powershell
> scoop install go
```

### Diarkis Server Template のインストール

#### リポジトリをクローンする場合

<https://github.com/Diarkis/diarkis-server-template> をクローンします。

<pre class="language-powershell"><code class="lang-powershell"><strong>> git clone git@github.com:Diarkis/diarkis-server-template.git
</strong><strong>
</strong><strong>> cd .\diarkis-server-template\
</strong># 必要に応じて利用するバージョンのタグをチェックアウトします
> git checkout v1.1.0
</code></pre>

#### アセットをダウンロードする場合

<https://github.com/Diarkis/diarkis-server-template/releases> を開き、最新バージョンの Assets をダウンロードし、解凍して利用します。

### プロジェクトの生成

PowerShell から以下のコマンドを実行して、プロジェクトを生成します。

{% code overflow="wrap" %}

```powershell
# parameters: {project_id} {builder_token} {output}
> .\run-mage.bat init 12345678901 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ../server_bin
```

{% endcode %}

* `project_id`: 弊社が発行したプロジェクトID
* `builder_token`: 弊社が発行したBuilder Token
* `output`: 生成したプロジェクトの出力先。ここでは `../server_bin` として説明します

### プロジェクトの初期化

プロジェクトを出力したディレクトリに移動し、以下のコマンドを実行します。

```powershell
> cd ..\server_bin
> .\run-mage.bat init
```

### ビルド

以下のコマンドを実行して、Diarkis のサーバーバイナリをビルドします。

```powershell
> .\run-mage.bat build:local
```

ビルドが終了すると、 remote\_bin ディレクトリにバイナリが出力されます。

### 実行

mars, http, udp をそれぞれ起動します。それぞれ別の PowerShell のウィンドウで起動します。

```powershell
> .\run-mage.bat server mars
> .\run-mage.bat server http
> .\run-mage.bat server udp
```

### 動作確認

Go のテストクライアントを使って疎通確認ができます。

```powershell
# parameters: <HTTP address> <client user ID> <client key> <puffer enabled: true/false>
> .\run-mage.bat goCli 127.0.0.1:7000 user-1 key false
```

* HTTP address: Diarkis HTTP サーバーのアドレス
* client user ID: 認証するユーザーの ID。
* client Key: クライアントキー。開発環境では、`key` を指定します
* puffer enabled: Diarkis Puffer モジュールの利用可否。ここでは false を指定します

正常に認証できると、以下のような表示になり、コマンド待ち受け状態となります。

```
[UID: user-1][SID(UDP): 23942c034d8d4094b09ab7219d28cebc]
 > Connected UDP
```

接続後、 Room の作成をする場合は以下のコマンドを実行して作成することができます。

```
 > Connected UDP
room create
Enter for which protocol to a create a Room (TCP/UDP): (Default: UDP)
Invalid input. Set to default value: UDP
Enter max members [1 - 255] (uint16): (Default: 10)
Invalid input. Set to default value: 10
Enter if allow empty (y/n): (Default: no)
Invalid input. Set to default value: false
Enter if join on creation (y/n): (Default: yes)
Invalid input. Set to default value: true
Enter TTL (seconds) [10 - 65535] (uint16): (Default: 30)
Invalid input. Set to default value: 30
Enter broadcast interval (milliseconds) (uint16): (Default: 100)
Invalid input. Set to default value: 100
# UDP RoomID がプロンプトに表示されて Room に参加している状態となる
[UID: 1111][SID(UDP): c51ea8010bc44ea5b6cd6ef2bb788ad8][UDP RoomID: 580e3cef8588bd287f0000011fa5000000000000000000000000]
```

## 補足

### サンプルプロジェクト

`./run-mage.bat init` で生成されるプロジェクトは Diarkis の基本的な機能を一通り確認できる基本セットです。その他にも様々なサンプルプロジェクトがあります。詳しくは Diarkis Server Template リポジトリの examples ページを参照してください。

🔗 <https://github.com/Diarkis/diarkis-server-template/tree/develop/examples>

### テストクライアント

テストクライアントでは `room create` コマンド以外にも様々なビルトインコマンドを実行して確認することができます。詳細はヘルプセンターの以下ページをご確認ください。

{% embed url="<https://help.diarkis.io/getting-started/tutorial/test-client>" %}

### `magefile`

`.¥run-mage.bat` は内部で [magefile](https://magefile.org/) という Go のツールを利用しています。

make/rake などのようなビルドツールで、Go でビルドフローなどを記載することで、プラットフォームに依存しない管理ができるようになります。

他のターゲットを確認する場合は、以下のように引数なしで実行することで確認できます。

```
> .\run-mage.bat
Targets:
  build:linux              Build server binary for linux or container environment
  build:local              Build server binary for local use
  build:mac                Build server binary for mac use
  diarkis:changeVersion    Change diarkis version.
  diarkis:version          Print the version of diarkis currently used.
  goCli                    Starts Go test client.
  init                     Initialize project
  puffer:clean             Delete all generated protocol code files.
  puffer:gen               Generate go, cpp, and cs code files using puffer (Diarkis packet gen module) from packet definition written in json.
  server                   Start a server locally: Required 1 following argument: mars http udp tcp
```

#### **fakesignal**

diarkis は、SIGUSR1 や SIGUSR2 などの UNIX シグナルを用いてログレベルの切り替えやデバッグ機能の ON/OFF といった機能を提供していますが、Windows には SIGUSR1 などのシグナルが存在しません。

そこで、Windows 上でもテストを行えるよう、fakesignal というツールを diarkis-server-template に同梱させていただいております。


# MARS サーバー

## 概要

MARS (Mesh network Announcement Relay Storage) サーバーは Diarkis が持つ特有のサーバーで Diarkis クラスタに必ず1つ必要なサーバです。

MARS サーバーはスケールや冗長化を必要とせずに、単一障害点にならないという特徴を持っており、短時間のダウンが発生しても Diarkis クラスタ全体には影響を及ぼさない様になっております。問題発生時も再起動することで Diarkis クラスタの健全性は担保されます。

## MARS サーバのセットアップ

```go
package main

import (
	"github.com/Diarkis/diarkis"
	"github.com/Diarkis/diarkis/mars"
)

func main() {
	mars.Setup()
	diarkis.Start()
}
```

## MARS サーバの設定

設定は JSON で記述します。

```json
{
  "address": "127.0.0.1",
  "port": "6779",
  "fullSyncRoles": ["HTTP"],
  "enableMetricsLogging": false
}
```

<table><thead><tr><th width="230">キー</th><th width="119">デフォルト</th><th></th></tr></thead><tbody><tr><td>address</td><td>"127.0.0.1"</td><td>バインドする UDP サーバーのアドレス</td></tr><tr><td>port</td><td>"6779"</td><td>MARS サーバーがバインドするためのポート。UDP サーバーは、指定されたポートから始まる利用可能なポートを自動的に探します。</td></tr><tr><td>fullSyncRoles</td><td>["HTTP"]</td><td>すべてのメッシュ・データを同期するサーバ・ロールの配列</td></tr><tr><td>enableMetricsLogging</td><td>false</td><td>この値を <code>true</code> に設定すると、MARS サーバーは 1 秒ごとにメトリクスの JSON データを標準出力に書き出します。</td></tr></tbody></table>

### バージョンの異なる Diarkis を起動した場合

MARS サーバーは Diarkis サーバーのバージョンをもとにデータを分離して管理します。これによりバージョンが異なるアプリケーション同士でユーザーが混ざることを防ぎます。


# UDP サーバー

## 概要

UDP サーバーは、Diarkis の3つのリアルタイム・コミュニケーション・サーバーのうちの1つです。

クライアントにあらかじめ用意されたモジュールのビルトイン・コマンドの公開や、カスタム・コマンドを実装して公開することができます。

コマンドとは、クライアントから送信され、サーバーで処理されるフォーマットされたパケットのことです。コマンドを利用し、Diarkis のサーバー・クラスターがクライアントと対話します。

## UDP サーバーのセットアップ

クライアントにビルトイン・コマンドを公開するには、diarkisexec パッケージを利用してセットアップできます。

* `diarkisexec.SetupDiarkis()` でモジュールを指定してビルトイン・コマンドを公開できます。各モジュールの `ConfigPath` を指定することで、設定をカスタマイズできます。
* `diarkisexec.SetServerCommandHandler()` でカスタム・コマンドを公開できます。
* `diarkisexec.SetupDiarkisUDPServer()` で UDP サーバーのセットアップができます。引数の JSON ファイルでサーバーの設定をカスタマイズできます。

> :warning: 上記の関数は、 `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import (
	"github.com/Diarkis/diarkis/diarkisexec"
	"github.com/Diarkis/diarkis/server"
	"github.com/Diarkis/diarkis/user"
)

var ver uint8 = 10
var cmd uint16 = 100

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := "/configs/shared/mesh.json"

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Room:       &diarkisexec.Options{ExposeCommands: true},
		P2P:        &diarkisexec.Options{ExposeCommands: true},
		Group:      &diarkisexec.Options{ConfigPath: "/configs/shared/group.json", ExposeCommands: true},
		Dive:       &diarkisexec.Options{ConfigPath: "/configs/shared/dive.json", ExposeCommands: true},
		Field:      &diarkisexec.Options{ConfigPath: "/configs/shared/field.json", ExposeCommands: true},
		DM:         &diarkisexec.Options{ConfigPath: "/configs/shared/dm.json", ExposeCommands: true},
		MatchMaker: &diarkisexec.Options{ConfigPath: "/configs/shared/matching.json", ExposeCommands: true},
		Session:    &diarkisexec.Options{ConfigPath: "/configs/shared/session.json", ExposeCommands: true},
	})

	diarkisexec.SetupDiarkisUDPServer("/configs/udp/main.json")

	diarkisexec.SetServerCommandHandler(ver, cmd, helloWorld)

	diarkisexec.StartDiarkis()
}

func helloWorld(ver uint8, cmd uint16, payload []byte, userData *user.User, next func(error)) {
	userData.ServerRespond([]byte("Hello World"), ver, cmd, server.Ok, true)
	next(nil)
}
```

## Mesh 設定

meshConfigPath には JSON ファイルのパスを指定します。詳細は [#mesh-she-ding](#mesh-she-ding "mention") を参照してください。

## UDP サーバーの設定

設定は JSON で記述します。

```json
{
  "enableP2P": true,
  "address": "127.0.0.1",
  "nic": "eth0",
  "port": "7000",
  "connectionTTL": 10,
  "sendUDPInterval": 0,
  "handleRUDPInterval": 100,
  "rcvWorkers": 1,
  "retryInterval": 1000,
  "maxRetry": 10,
  "enableEncryption": true
}
```

<table><thead><tr><th width="201">キー</th><th width="119">デフォルト</th><th></th></tr></thead><tbody><tr><td>enableP2P</td><td>true</td><td>true に設定すると、クライアントはP2P用に自分のパブリック・アドレスを取得することができます。</td></tr><tr><td>address</td><td>"127.0.0.1"</td><td>バインドする UDP サーバーのアドレス</td></tr><tr><td>nic</td><td>"eth0"</td><td>アドレスを取得するインターフェース名。アドレスが未指定の場合に利用します。</td></tr><tr><td>port</td><td>"7100"</td><td>UDP サーバーがバインドするためのポート。UDP サーバーは、指定されたポートから始まる利用可能なポートを自動的に探します。</td></tr><tr><td>connectionTTL</td><td>10</td><td>接続の TTL。この時間を超えるとクライアントはサーバーから切断されます。</td></tr><tr><td>sendUDPInterval</td><td>0</td><td>UDP パケットの送信間隔（ミリ秒）。10より小さく設定すると、送信パケットはバッファリングされません。</td></tr><tr><td>handleRUDPInterval</td><td>100<br>min: 10</td><td>RUDP パケットの送受信間隔（ミリ秒）。 値が小さいほど、サーバーはより敏感に（高速に）応答するようになりますが、その代償として CPU 負荷が増加します。</td></tr><tr><td>retryInterval</td><td>1000</td><td>RUDP パケットの再試行間隔（ミリ秒）</td></tr><tr><td>maxRetry</td><td>10</td><td>RUDP パケットの最大再試行回数。この値を超えた場合、RUDP 接続はタイムアウトとみなされ破棄されます。</td></tr><tr><td>rcvWorkers</td><td>CPUコア数</td><td>UDP パケットを受信する goroutine の数</td></tr><tr><td>enableEncryption</td><td>true</td><td>falseに設定すると、パケットの暗号化と復号化が無効になります。HTTP サーバーも同様の設定をする必要があります。</td></tr></tbody></table>

> :warning: クラウド環境では、address はプライベート IP アドレスとし、環境変数 `DIARKIS_CLOUD_ENV` を併用してください。

詳細は [server の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/server/index.html)を参照して下さい。


# TCP サーバー

## 概要

TCP サーバーは、Diarkis の3つのリアルタイム通信サーバのうちの1つで、アプリケーションのためのカスタム・コマンドを実装することができます。

コマンドとは、クライアントから送信され、サーバで処理されるフォーマットされたパケットのことです。これは、Diarkis のサーバ・クラスターがクライアントと対話する方法です。

## TCP サーバーのセットアップ

クライアントにビルトイン・コマンドを公開するには、diarkisexec パッケージを利用してセットアップできます。

* `diarkisexec.SetupDiarkis()` でモジュールを指定してビルトイン・コマンドを公開できます。各モジュールの `ConfigPath` を指定することで、設定をカスタマイズできます。
* `diarkisexec.SetServerCommandHandler()` でカスタム・コマンドを公開できます。
* `diarkisexec.SetupDiarkisUDPServer()` で UDP サーバーのセットアップができます。引数の JSON ファイルでサーバーの設定をカスタマイズできます。

> :warning:上記の関数は、 `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import (
	"github.com/Diarkis/diarkis/diarkisexec"
	"github.com/Diarkis/diarkis/server"
	"github.com/Diarkis/diarkis/user"
)

var ver uint8 = 10
var cmd uint16 = 100

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := "/configs/shared/mesh.json"

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Room:       &diarkisexec.Options{ExposeCommands: true},
		Group:      &diarkisexec.Options{ConfigPath: "/configs/shared/group.json", ExposeCommands: true},
		Dive:       &diarkisexec.Options{ConfigPath: "/configs/shared/dive.json", ExposeCommands: true},
		Field:      &diarkisexec.Options{ConfigPath: "/configs/shared/field.json", ExposeCommands: true},
		DM:         &diarkisexec.Options{ConfigPath: "/configs/shared/dm.json", ExposeCommands: true},
		MatchMaker: &diarkisexec.Options{ConfigPath: "/configs/shared/matching.json", ExposeCommands: true},
		Session:    &diarkisexec.Options{ConfigPath: "/configs/shared/session.json", ExposeCommands: true},
	})

	diarkisexec.SetupDiarkisTCPServer("/configs/tcp/main.json")

	diarkisexec.SetServerCommandHandler(ver, cmd, helloWorld)

	diarkisexec.StartDiarkis()
}

func helloWorld(ver uint8, cmd uint16, payload []byte, userData *user.User, next func(error)) {
	userData.ServerRespond([]byte("Hello World"), ver, cmd, server.Ok, true)
	next(nil)
}
```

## Mesh 設定

meshConfigPath には JSON ファイルのパスを指定します。詳細は [#mesh-she-ding](#mesh-she-ding "mention") を参照してください。

## TCP サーバの設定

設定は JSON で記述します。

```json
{
  "connectionTTL": 10,
  "address": "127.0.0.1",
  "nic": "eth0",
  "port": "7200",
  "maxRcvSize": 8000,
  "noDelay": false,
  "enableEncryption": true
}
```

<table><thead><tr><th width="230">キー</th><th width="119">デフォルト</th><th></th></tr></thead><tbody><tr><td>connectionTTL</td><td>10</td><td>接続の TTL。この時間を超えるとクライアントはサーバーから切断されます。</td></tr><tr><td>address</td><td>"127.0.0.1"</td><td>バインドする UDP サーバーのアドレス</td></tr><tr><td>nic</td><td>"eth0"</td><td>アドレスを取得するインターフェース名。アドレスが未指定の場合に利用します。</td></tr><tr><td>port</td><td>"7100"</td><td>UDP サーバーがバインドするためのポート。UDP サーバーは、指定されたポートから始まる利用可能なポートを自動的に探します。</td></tr><tr><td>maxRcvSize</td><td>8000</td><td>各リクエスト・パケットの最大 TCP パケット・サイズ（バイト）</td></tr><tr><td>noDelay</td><td>false</td><td>true に設定すると、Nagle アルゴリズムを無効にします(書き込み前のバッファリングなし)</td></tr><tr><td>enableEncryption</td><td>true</td><td>false に設定すると、パケットの暗号化と復号化が無効になります。HTTP サーバも同様の設定をする必要があります。</td></tr></tbody></table>

> :warning: クラウド環境では、address はプライベート IP アドレスとし、環境変数 `DIARKIS_CLOUD_ENV` を併用してください。

詳細は [server の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/server/index.html)を参照して下さい。


# HTTP サーバー

## 概要

HTTP サーバーは Diarkis のエントリーポイントとなります。アプリケーションサーバーは、HTTP サーバーに接続して、リアルタイム接続のエンドポイントと暗号化キーを取得します。

また、カスタム・エンドポイントを記述することも可能です。

## HTTP サーバーのセットアップ

クライアントにビルトイン・コマンドを公開するには、diarkisexec パッケージを利用してセットアップできます。

* `diarkisexec.SetupDiarkis()` でモジュールを指定してビルトインコマンドを公開できます。各モジュールの `ConfigPath` を指定することで、設定をカスタマイズできます。
* `diarkisexec.SetServerCommandHandler()` でカスタムコマンドを公開できます。
* `diarkisexec.SetupDiarkisHTTPServer()` で UDP サーバーのセットアップができます。引数の JSON ファイルでサーバーの設定をカスタマイズできます。

> :warning: 上記の関数は、 `diarkisexec.StartDiarkis()` を呼ぶ前に実行する必要があります。

詳細は [diarkisexec の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/diarkisexec/index.html)を参照して下さい。

```go
package main

import (
	"github.com/Diarkis/diarkis/diarkisexec"

	"github.com/Diarkis/diarkis-server-template/cmds"
)

func main() {
	logConfigPath := "/configs/shared/log.json"
	meshConfigPath := "/configs/shared/mesh.json"

	diarkisexec.SetupDiarkis(logConfigPath, meshConfigPath, &diarkisexec.Modules{
		Dive:       &diarkisexec.Options{ConfigPath: "/configs/shared/dive.json", ExposeCommands: true},
		Field:      &diarkisexec.Options{ConfigPath: "/configs/shared/field.json", ExposeCommands: true},
		DM:         &diarkisexec.Options{ConfigPath: "/configs/shared/dm.json", ExposeCommands: true},
		MatchMaker: &diarkisexec.Options{ConfigPath: "/configs/shared/matching.json", ExposeCommands: true},
	})

	httpcmds.SetupHTTP()

	diarkisexec.SetupDiarkisHTTPServer("/configs/http/main.json")

	diarkisexec.StartDiarkis()
}
```

## Mesh 設定

meshConfigPath には JSON ファイルのパスを指定します。詳細は [#mesh-she-ding](#mesh-she-ding "mention") を参照してください。

## HTTP サーバーの設定

設定は JSON で記述します。　

<table><thead><tr><th width="230">キー</th><th width="119">デフォルト</th><th></th></tr></thead><tbody><tr><td>address</td><td>"127.0.0.1"</td><td>バインドする UDP サーバーのアドレス</td></tr><tr><td>port</td><td>"7000"</td><td>UDP サーバーがバインドするためのポート。UDP サーバーは、指定されたポートから始まる利用可能なポートを自動的に探します。</td></tr><tr><td>useFixedPort</td><td>false</td><td>trueの場合、HTTP サーバーは指定されたポートでのみバインドされます</td></tr><tr><td>timeout</td><td>5</td><td>HTTP レスポンス・タイムアウト（秒）</td></tr><tr><td>enableEncryption</td><td>true</td><td>falseに設定すると、パケットの暗号化と復号化が無効になります。他のサーバーも同様の設定をする必要があります。</td></tr></tbody></table>

詳細は [http の API リファレンス](https://docs.diarkis.io/docs/server/current/diarkis/server/http/index.html) を参照して下さい。

## **カスタム HTTP エンドポイントの作成方法**

DiarkisのHTTP サーバーでは、カスタムエンドポイントを書くことができます。

```go
package httpcmds

import (
      "github.com/Diarkis/diarkis/server/http"
)

func Expose(rootpath string) {
      // :message is treated as a parameter and the value can be accessed from *http.Params

      http.Get("/hello/:message", handleHello)
}

func handleHello(res *http.Response, req *http.Request, params *http.Params, next func(error)) {
      message := params.GetAsString("message")
      res.Respond(message, http.Ok)
      // move on to other handlers
      next(nil)
}

```

### JSON を使った HTTP エンドポイント

Diarkis の HTTP サーバーは、リクエストの ContentType が `application/json` の時に、リクエストボディを自動的に `req.JSONBody` にデコードします。 JSON Body に格納するためには、オブジェクトを記述する必要があることに注意してください。

```go
package httpcmds

import (
	"github.com/Diarkis/diarkis/server/http"
)

func Expose(rootpath string) {
	http.Post("/echo", handleEcho)
}

func handleEcho(res *http.Response, req *http.Request, params *http.Params, next func(error)) {
	if req.JSONBody == nil {
		err := errors.New("expect JSON body")
		res.Respond(err.Error(), http.Bad)
		next(err)
		return
	}

	message, ok := req.JSONBody["message"].(string)
	if !ok {
		err := errors.New("missing parameter message")
		res.Respond(err.Error(), http.Bad)
		next(err)
		return
	}

	enc, err := json.Marshal(map[string]any{"echo": message})
	if err != nil {
		res.Respond(err.Error(), http.Bad)
		next(err)
		return
	}

	res.SetHeader("Content-Type", "application/json")
	res.SendBytes(enc, http.Ok)
	// move on to other handlers
	next(nil)
}
```


# Metrics API

## Metrics API

Diarkis では、 metrics を取得するためのエンドポイントをデフォルトで用意してあります。

Prometheus に値を入れ、Grafana で可視化したり、JSON 形式で取得して、手元で軽く現在の指標を確認する用途を想定しています。

2秒に1度更新されます。

デフォルトで定義してあるメトリクスの他に、カスタムのメトリクスを追加定義して出力することも可能です。

## Endpoint

* $HTTP\_ENDPOINT/metrics/prometheus/v/3: Prometheus(<https://prometheus.io/>) の scraping endpoint を提供しています。
  * `curl $HTTP_ENDPOINT/metrics/prometheus/v/3`
* $HTTP\_ENDPOINT/metrics/json: 同一内容で json 形式のものを返します。
  * `curl $HTTP_ENDPOINT/metrics/json`

## Diarkis でデフォルトで定義されているメトリクス

| 指標名                                                | 説明                                                |
| -------------------------------------------------- | ------------------------------------------------- |
| Users\_UDP\_node                                   | UDP サーバーに接続しているユーザー数                              |
| Users\_TCP\_node                                   | TCP サーバーに接続しているユーザー数                              |
| UDP\_Packets\_In\_UDP\_node                        | UDP サーバーの受信した UDP パケット数                           |
| TCP\_Packets\_In\_TCP\_node                        | TCP サーバーの受信した TCP パケット数                           |
| TCP\_Packets\_Out\_TCP\_node                       | TCP サーバーの送信した TCP パケット数                           |
| UDP\_Packets\_Out\_UDP\_node                       | UDP サーバーの送信した UDP パケット数                           |
| UDP\_Packets\_In\_UDP\_node                        | UDP サーバーの受信した UDP パケット数                           |
| Commands\_In\_UDP\_node                            | UDP サーバーの受信したコマンドの数（一つのパケットに複数コマンドが含まれる可能性があります。) |
| Commands\_In\_TCP\_node                            | TCP サーバーの受信したコマンドの数（一つのパケットに複数コマンドが含まれる可能性があります。) |
| Commands\_Out\_UDP\_node                           | UDP サーバーがクライアントに向けて、送ったコマンドの数                     |
| (一つのパケットに複数コマンドが含まれる可能性があります。)                     |                                                   |
| Commands\_Out\_TCP\_node                           | TCP サーバー がクライアントに向けて、送ったコマンドの数                    |
| (一つのパケットに複数コマンドが含まれる可能性があります。)                     |                                                   |
| RUDP\_Retries\_UDP\_node                           | UDP サーバーでの RUDP リトライの数                            |
| RUDP\_Split\_In\_UDP\_node                         | UDP サーバーで MTU を超えたパケットの受信数                        |
| RUDP\_Split\_Out\_UDP\_node                        | UDP サーバーで MTU を超えたパケットの送信数                        |
| Mesh\_Packets\_In\_HTTP\_node                      | HTTP サーバーの内部ネットワークでのパケット受信数                       |
| Mesh\_Packets\_In\_UDP\_node                       | UDP サーバーの内部ネットワークでのパケット受信数                        |
| Mesh\_Packets\_In\_TCP\_node                       | TCP サーバーの内部ネットワークでのパケット受信数                        |
| Mesh\_Packets\_Out\_HTTP\_node                     | HTTP サーバーが内部ネットワークで送ったパケット数                       |
| Mesh\_Packets\_Out\_UDP\_node                      | UDP サーバーが内部ネットワークで送ったパケット数                        |
| Mesh\_Packets\_Out\_TCP\_node                      | TCP サーバーが内部ネットワークで送ったパケット数                        |
| Mesh\_Retry\_UDP\_node                             | UDP サーバーが内部ネットワークでリトライを行った数                       |
| Mesh\_Retry\_TCP\_node                             | TCP サーバーが内部ネットワークでリトライを行った数                       |
| Rooms\_UDP\_node                                   | UDP サーバーにある room の数                               |
| Rooms\_TCP\_node                                   | TCP サーバーにある room の数                               |
| Groups\_UDP\_node                                  | UDP サーバーにある group の数                              |
| Groups\_TCP\_node                                  | TCP サーバーにある group の数                              |
| MatchMaker\_Search\_HTTP\_node                     | HTTP サーバーで行った MatchMaker search の数                |
| MatchMaker\_Empty\_HTTP\_node                      | HTTP サーバーで行った空 MatchMaker search の数               |
| MatchMaker\_Ticket\_UDP\_node                      | UDP サーバーにある MatchMaker ticket の数                  |
| MatchMaker\_Ticket\_TCP\_node                      | TCP サーバーにある MatchMaker ticket の数                  |
| MatchMaker\_Ticket\_Search\_UDP\_node              | UDP サーバーで発行した MatchMaker search の数                |
| MatchMaker\_Ticket\_Search\_TCP\_node              | TCP サーバーで発行したMatchMaker search の数                 |
| MatchMaker\_Ticket\_Add\_UDP\_node                 | UDP サーバーで発行した MatchMaker ticket 由来の search の数     |
| MatchMaker\_Ticket\_Add\_TCP\_node                 | TCP サーバーで発行した MatchMaker ticket 由来の search の数     |
| MatchMaker\_Complete\_UDP\_node                    | UDP サーバーで完了した MatchMaker ticket の数                |
| MatchMaker\_Complete\_TCP\_node                    | UDP サーバーで完了した MatchMaker ticket の数                |
| MatchMaker\_Ticket\_Complete\_Time\_Avg\_UDP\_node | UDP サーバーで MatchMaker ticket 完了までかかった平均時間          |
| MatchMaker\_Ticket\_Complete\_Time\_Avg\_TCP\_node | TCP サーバーで MatchMaker 完了までかかった平均時間                 |
| MatchMaker\_Ticket\_Complete\_Time\_Min\_UDP\_node | UDP サーバーで MatchMaker ticket 完了までかかった最小時間          |
| MatchMaker\_Ticket\_Complete\_Time\_Min\_TCP\_node | TCP サーバーで MatchMaker ticket 完了までかかった最小時間          |
| MatchMaker\_Ticket\_Complete\_Time\_Max\_UDP\_node | UDP サーバーで MatchMaker ticket 完了までかかった最大時間          |
| MatchMaker\_Ticket\_Complete\_Time\_Max\_TCP\_node | TCP サーバーで MatchMaker ticket 完了までかかった最大時間          |
| P2P\_Success\_UDP\_node                            | P2P が成功した回数                                       |
| P2P\_Attempt\_UDP\_node                            | P2P を試みた回数                                        |
| Field\_Grids\_UDP\_node                            | UDP サーバーで持っているField grid 数                        |
| Field\_Grids\_TCP\_node                            | TCP サーバーで持っている Field grid 数                       |

## Prometheus 設定方法

\#TODO

## Custom Metrics 設定方法


# サーバー間通信 - Mesh

Diarkis のサーバーは、互いに通信することで他のサーバーから情報を取得や同期を実施したり、特定の操作を実行することができます。

## Mesh 設定

設定は各 HTTP/TCP/UDP サーバーにて実施します。

各サーバーで [diarkisexec.SetupDiarkis()](https://docs.diarkis.io/docs/server/v1.0.0-rc1/diarkis/diarkisexec/index.html) を実行する際に JSON ファイルのパスを指定し、以下のように MARS サーバーのアドレスおよび設定を記述します。パスや JSON ファイルのキーが空の場合はデフォルトの設定となります。

```json
{
  "nic": "eth0",
  "marsAddress": "mars.base.svc.cluster.local",
  "marsPort": "6779",
  "marsAddressCacheTTL": 60,
  "retryInterval": 1000,
  "reliableRetryTimeout": 3000
}
```

<table><thead><tr><th width="234">キー</th><th width="119">デフォルト</th><th></th></tr></thead><tbody><tr><td>nic</td><td>"eth0"</td><td>アドレスを取得するインターフェース名。アドレスが未指定の場合に利用します。</td></tr><tr><td>marsAddress</td><td>"127.0.0.1"</td><td>バインドする UDP サーバーのアドレス</td></tr><tr><td>marsPort</td><td>"6779"</td><td>UDP サーバーがバインドするためのポート。UDP サーバーは、指定されたポートから始まる利用可能なポートを自動的に探します。</td></tr><tr><td>marsAddressCacheTTL</td><td>60</td><td>MARS アドレス・キャッシュの TTL</td></tr><tr><td>retryInterval</td><td>1000</td><td>Mesh パケットの再試行間隔（ミリ秒）</td></tr><tr><td>reliableRetryTimeout</td><td>3000</td><td>Mesh パケットのタイムアウト（ミリ秒）</td></tr></tbody></table>


# Diarkis クライアント


# ランタイム・ライブラリ

## 概要

**Diarkis ランタイム・ライブラリ** はランタイムのコアとなる低レベルな機能が含まれています。

## 主な機能

* 基盤機能
  * Diarkis TCP/UDP/RUDP 通信
  * スレッド管理
  * メモリ管理とカスタム ・アロケーター
  * NAT タイプ判定
  * [Packet Manipulator](/diarkis-client/runtime-library/packet-manipulator)
* 各モジュールの機能

### Diarkis TCP/UDP/RUDP 通信

**Diarkis ランタイム・ライブラリ** では TCP/UDP/RUDP による通信をサポートしています。

### スレッド管理

**Diarkis ランタイム・ライブラリ** ではスレッドの操作をクロス・プラットフォームで実行する `Diarkis::DiarkisThread` を提供しています。\
`Diarkis::DiarkisThread` を使用することにより対応プラットフォームすべてで同じインターフェイスを使用してスレッドを操作することが可能となります。

### メモリ管理とカスタム・アロケーター

**Diarkis ランタイム・ライブラリ**ではカスタム・アロケーターを設定することにより、**Diarkis ランタイム・ライブラリ**内部のメモリの確保/開放処理をユーザーが置き換えることができます。\
`Diarkis::ICustomAllocator` を継承してユーザー独自のアロケーターを実装し、`Diarkis::SetCustomAllocator` を使用してランタイムに設定してください。\
`samples/room_broadcast/main.cpp` に実装サンプル・コードがあります。

### NAT タイプ判定

**Diarkis ランタイム・ライブラリ** では Diarkis サーバーと連携した NAT タイプ判定の機能を提供しています。

### 各モジュールの機能

各モジュールの機能については [Diarkis Module](/diarkis-client/diarkis-module) を使用することをおすすめします。\
ランタイム・ライブラリから直接各モジュールの機能を使用する場合は [C++ API ドキュメント](https://docs.diarkis.io/docs/cpp/current/annotated.html) を参照してください。

## Diarkis ランタイム・ライブラリが使用するリソース

Diarkis ランタイム・ライブラリでは以下のリソースを内部で確保して使用します。

* ソケット
  * ソケットは、UDP サーバ接続時 / TCP サーバ接続時に１つずつ作成されます。
  * UDP
    * DiarkisUdp::Connect(Async) / DiarkisUdp::ConnectDualMode(Async) を呼び出した際にランタイム・ライブラリで作成されます。
    * P2P は、UDP で作成した ソケット を使用されます。
  * TCP
    * DiarkisTcp::Connect / DiarkisTcp::ConnectDualMode を呼び出した際にランタイム・ライブラリで作成されます。
  * 接続する Diarkis サーバが複数ある場合は、DiarkisInterfaceBase のインスタンスを複数作成する必要がありますので、インスタンス分だけ Socket 数が増えます。
* スレッド
  * TCP 接続時は 1 スレッド、UDP 接続時は 2 スレッド作成します。
  * Diarkis クライアントを使用する際には、Diarkis Module 側でもスレッドを作成しています。詳しくは [Diarkis のスレッド](/diarkis-client/diarkis-module/diarkis-nosureddo) をご確認ください。

## 注意点

* **Diarkis Module** の API はスレッド・セーフでは**ありません**。複数スレッドで **Diarkis Module** を利用する場合はアプリケーション側で排他制御を行ってください。


# Diarkis RUDP

## 概要

Reliable User Datagram Protocol (RUDP) はデータの到達が保証されるように実装された UDP 通信です。Diarkis クライアントはサーバーとの通信や P2P 通信において RUDP を用いてデータを送信することができ、ゲームの進行に必要不可欠な情報など重要なデータを確実に相手に届けることができます。このページでは Diarkis の RUDP の仕様と設定について説明します。

## Diarkis RUDP の仕様

Diarkis RUDP が確立される組み合わせはサーバー - クライアント間とクライアント - クライアント間 (P2P 通信) があり得ます。一部の機能は P2P 通信でのみ使用が可能です。

Diarkis RUDP ではデータを受信した場合に Acknowledgement (ACK) を返すことでデータが到達したことを相手に知らせます。もし指定した時間以内に ACK の受信が確認できなければ Diarkis クライアントは同じデータを再送します。本ページではこの時間を **RetryInterval** と呼びます。また、同じデータを指定した回数だけ再送してもなお ACK の受信が確認できなければ、タイムアウトによって Diarkis クライアントは相手との通信を切断します。本ページではこの回数を **RetryMaxCount** と呼びます。

P2P 通信における RUDP に限りデータの到達保証に加えてデータの**順番保証の切り替え機能**を提供しています。順番保証機能を有効にすると、アプリケーションが受信するデータの順番が相手ユーザーが送信した順番と一致することが保証されます。Diarkis ライブラリはシーケンシャル番号通りにパケットが受信できるまでアプリケーションにデータを渡しません。順番保証機能を無効にした場合、Diarkis ライブラリはパケットを受信した順番にアプリケーションにデータを渡し、その順番はネットワークの状況や再送パケットの有無によって変わります。

サーバー - クライアント間の RUDP 通信では常に順番保証が有効になります。

## Diarkis RUDP の設定

### RetryInterval の設定

以下の関数を使用することで RetryInterval を調整することができます。

{% code title="udp.h" %}

```cpp
void SetSendRetryInterval(uint32_t minMs, uint32_t maxMs)
```

{% endcode %}

RetryInterval は `minMs` で指定した時間 (ミリ秒) から始まり、クライアントが再送するたびに値が **2 倍**になり、`maxMs` で指定した時間まで上昇します。 例えば minMs=300ms、maxMs=1000ms、RetryMaxCount が 5 回の場合、RetryInterval は 300ms, 600ms, 900ms, 1000ms, 1000ms と変化します。5 回目の再送がタイムアウトした場合クライアントは通信を切断します。

RetryInterval を Exponential に変化させることでクライアントのネットワーク処理負荷を抑えることができます。もし RetryInterval を固定値にしたい場合は `minMS` と `maxMS` に同じ値を指定することでRetryInterval を再送回数にかかわらず常に一定にすることができます。

### RetryMaxCount の設定

以下の関数を使用することで RetryMaxCount を調整することができます。

{% code title="udp.h" %}

```cpp
void SetSendRetryMaxCount(uint32_t count)
```

{% endcode %}

RetryMaxCount は `count` で指定した値になります。

### 順番保証の設定

a. [Diarkis Module](/diarkis-client/diarkis-module) を利用する場合は `DiarkisP2PBase.h` に定義されている `SendBroadcast` などの関数が持つ `reliability` 引数によって RUDP の動作を制御することができます。`reliability` 引数に列挙型`Reliability` の値を指定してください。

{% code title="DiarkisP2PBase.h" %}

```cpp
System::Result SendBroadcast(const uint8_t* payload, const size_t payloadSize, const Diarkis::Reliability reliability)
```

{% endcode %}

{% code title="common.h" %}

```cpp
enum class Reliability: uint8_t
{
    UNRELIABLE_UNORDERED = 0, // 到達保証なし、順番保証なし (UDP)
    RELIABLE_UNORDERED = 1,   // 到達保証あり、順番保証なし
    RELIABLE_ORDERED = 2      // 到達保証あり、順番保証あり
};
```

{% endcode %}

b. ライブラリを直接使用する場合は以下にある P2P 通信のデータ送信関数において `bFixedOrder` フラグを `true` にして実行します。

{% code title="p2p.h" %}

```cpp
Diarkis::System::Result RSend(const uint8_t* message, size_t messageSize, bool bFixedOrder)
```

{% endcode %}

## 設定における注意事項

* `SetSendRetryInterval` と `SetSendRetryMaxCount` はサーバーとの RUDP 通信に対して設定を行います。P2P 通信における設定は `SetSendRetryIntervalP2P` と `SetSendRetryMaxCountP2P` を使用してください。
* RetryInterval が非常に小さい場合や RetryMaxCount が大きい場合はクライアントは大量のパケットを送信しなければならず、ネットワークの処理負荷が大きくなるのでご注意ください。


# Packet Manipulator

## 概要

Packet Manipulator は Diarkis が扱う通信パケットに対して特定の操作を適用する仕組みです。\
Diarkis Runtime 内に設定された Apply Point と呼ばれる処理の適用タイミングに対して Packet Filter を設定することでパケットに対する操作を適用します。 パケットに対する操作は C++ のコードとして実装することができ、Apply Point 毎に異なる通信パケットに関する情報を参照し、パケットを無視したり、通信量を計算したり、パケットを保存したり、特定のパケットに対してカスタムな処理をさせたりすることが可能です。 また、一般的に利用されることが想定されるパケットに遅延やパケロスを発生させる Packet Filter はプリセットとして提供されており、Packet Filter 自体のコードを書かなくても利用できるようになっています。\
なお、Packet Manipulator はデバッグ用の機能となりますので Release ビルドでは使用することはできません。

**注意点**

* **Packet Manipulator はデバッグ用の機能となります。**\
  **リリースビルドなど、DIARKIS\_DEBUG\_FEATURES が定義されていない環境では事故防止のためにインターフェイスが見えなくなりますのでご注意ください。**
* **本機能は** **experimental feature となります。**\
  **将来仕様や動作が変更される可能性がある点をご了承ください。**&#x20;

## Namespace

Packet Manipulator の全機能は `Diarkis::System::PacketManipulator` namespace 内に存在します。 以降、本ドキュメントで使用するクラス名等では namespace は省略して記載しますのでご留意ください。

## Packet Manipulator

Packet Manipulator の機能にアクセスするには `IPacketManipulator` のインスタンスを使用します。 `IPacketManipulator` のインスタンスは `DiarkisGetPacketManipulator` を呼び出して取得することができます。

### Packet Manipulator の更新処理

`IPacketManipulator` には Packet Filter の状態を含む内部状態を更新する `IPacketManipulator::Update` があります。 このメソッドの呼び出し頻度が Packet Filter 毎の更新頻度につながるため Packet Filter の処理が必要としている頻度で実行する必要があります。\
例えばプリセットのパケット遅延フィルタでは実際にパケットを受け取ったタイミングから、遅延してパケットを処理するため時間を計測しています。 この計測処理が Packet Filter の更新処理内で実行されているため、`IPacketManipulator::Update` が 100 ms 間隔で実行されていると遅延させる時間の計測も 100 ms 間隔となり想定している遅延時間よりも大きくずれる可能性があります。 Packet Manipulator の機能に必要な更新頻度を考慮して `IPacketManipulator::Update` を実行するようにしてください。

## Apply Point

Packet Manipulator では Packet Filter を適用するタイミングを **Apply Point** と呼びます。Apply Point へ Packet Filter を設定することでパケットに対して特定の処理が反映されるようになります。\
Apply Point には以下の種類が存在します。

| 名称                         | 適用タイミング                      | 説明                                           |
| -------------------------- | ---------------------------- | -------------------------------------------- |
| RawUdpReceive              | UDP ソケットでパケット受信時             | P2P 含めて Diarkis が使用するすべての UDP パケットに対して適用されます |
| RawUdpSend                 | UDP ソケットでパケット送信時             | P2P 含めて Diarkis が使用するすべての UDP パケットに対して適用されます |
| RoomPushAndResponseReceive | Room の Push/Response パケット受信時 | Room の Push/Response として受信したパケットにのみ適用されます    |
| P2PMessageReceive          | P2P パケット受信時                  | P2P メッセージとして受信したパケットにのみ適用されます                |

## Packet Filter Set

Packet Manipulator ではひとつの Apply Point に対して複数の Packet Filter を適用する仕組みとして Packet Filter Set が用意されています。 Packet Filter Set は Apply Point に関連付けられていて、`IPacketManipulator::GetOrAllocFilterSet(FilterApplyPoint applyPoint)` を使用することで Apply Point に対する `IPacketFilterSet` を取得することができます。 パケットに対して何か処理を適用するにはこの `IPacketFilterSet` に対して Packet Filter を追加していくことになります。

## Packet Filter の追加

Packet Filter を追加するには前述の `IPacketFilterSet` を使用します。`IPacketFilterSet` にはプリセットの Packet Filter を追加する `IPacketFilterSet::AddPacketDelayFilter` のようなメソッドと任意の型の Packet Filter を追加する `IPacketFilterSet::AddFilter` メソッドが存在します。

### Packet Filter と Apply Point の互換性

Packet Filter には Apply Point との互換性が存在します。これはある Packet Filter の機能が特定の Apply Point で機能するかどうかという設定で Packet Filter 側の実装状況や Apply Point での対応状況によります。 互換性がない Apply Point に Packet Filter を設定した場合、`IPacketFilterSet::Add...Filter` 系のメソッドが nullptr を返しますので返り値は必ずチェックするようにしてください。 Apply Point と Packet Filter の互換性には `IPacketFilter::CheckIfAvailableApplyPoint`が使用されており、Custom Filter を実装する際はこのメソッドで判定を行うことで互換性の設定を行うことができます。

## Preset Packet Filters

Packet Manipulator にはプリセットでいくつかの Packet Filter が用意されています。\
[Packet Manipulator Simple サンプル ](/diarkis-client/samples/cpp/packet_manipulator_simple)に使用した実装例がありますので参照してください。

### Packet Delay Filter

`IPacketFilterSet::AddPacketDelayFilter` で追加することができます。

| 引数          | 説明          |
| ----------- | ----------- |
| probability | パケット遅延の発生確率 |
| minDelay    | 最低遅延時間(ms)  |
| maxDelay    | 最高遅延時間(ms)  |
| seed        | 疑似乱数のシード    |

作成時に指定された確率である一定範囲のパケット遅延が発生するようになります。\
確率判定には疑似乱数が使用されているためフィルタが適用されるパケットや遅延時間はある程度同じになるようになっていますが、最終的には実行時にパケットが到達した順番に依存して変わる可能性があります。

### Packet Loss Filter

`IPacketFilterSet::AddPacketLossFilter` で追加することができます。

| 引数          | 説明          |
| ----------- | ----------- |
| probability | パケットロスの発生確率 |
| seed        | 疑似乱数のシード    |

作成時に指定された確率である一定範囲のパケット遅延が発生するようになります。\
確率判定には疑似乱数が使用されているためフィルタが適用されるパケットはある程度同じになるようになっていますが、最終的には実行時にパケットが到達した順番に依存して変わる可能性があります。

## User Custom Filter

ユーザーが独自の処理をパケットに適用可能な Custom Filter を実装することができます。\
[Packet Manipulator Custom Filter サンプル](/diarkis-client/samples/cpp/packet_manipulator_custom_filter) に使用した実装例がありますので参照してください。

Custom Filter を実装するには `IPacketFilter` とそのサブクラスを継承します。`IPacketFilter` が一番低レベルな Packet Filter インターフェイスを持っているクラスとなり、Packet Manipulator から渡されるフィルタ適用メソッドの引数の型は `IPacketFilterArgument` となります。 しかし、`IPacketFilterArgument` では低レベルなパケットの情報にしかアクセスできないため、実際は各 Apply Point に応じた Filter Base を継承してカスタムフィルタを実装することをおすすめします。 例えば `P2PMessageReceive` Apply Point で利用可能な `IP2PMessageReceiveFilterBase` を継承してカスタムフィルタを実装すると、`IP2PMessageReceiveFilterArgument` 型でパケットに関する情報を取得することができます。`IP2PMessageReceiveFilterArgument` では送信元の UID や IP アドレス、ポートなど `IPacketFilterArgument` ではアクセスできない P2P 固有の情報にアクセスすることが出来るようになります。

### フィルタ適用メソッド

`IPacketFilter` とそのサブクラスには `Apply` メソッドが存在し、Packet Filter 適用のタイミングでパケット情報を渡して実行されます。 ユーザーはこのメソッド内でフィルタ適用時に任意の処理を実装することができます。\
また、`Apply` メソッドの返り値でその後のパケットの扱いをコントロールすることができます。`FilterPostProcess::Skip` を返すことでその後、Diarkis Runtime 内ではそのパケットが無かったものとして扱われます。`FilterPostProcess::ApplyNextFilter` を返すと通常通りの処理を継続します。

### 更新メソッド

`IPacketFilter` とそのサブクラスには `Update` メソッド存在し、Packet Filter に対して定期的に実行したい処理を実装することができます。 `Update` メソッドには引数として、設定されている Apply Point で Diarkis Runtime がパケットを処理するための関数が渡されます。この関数に必要な情報を渡すことで、パケット受信時以外のタイミングでパケットを処理することができます。 このメソッドは `IPacketManipulator::Update` から呼び出されます。

### Apply Point 互換性判定メソッド

`IPacketFilter` とそのサブクラスには `CheckIfAvailableApplyPoint` メソッドが存在し、Packet Manipulator はこのメソッドを使用して Apply Point と Packet Filter に互換性があるかどうか判断します。

### Custom Filter 実装時の注意点

Custom Filter の `Apply` メソッドは Apply Point によってさまざまなスレッドから実行される可能性があります。Custom Filter 内で扱うデータの排他制御には十分ご注意ください。


# Diarkis Module

## 概要

**Diarkis ランタイム・ライブラリ**では低レベルな機能が提供されており、実際にアプリケーションとして動かすためにはもう少し追加機能の実装が必要となります。\
**Diarkis Module** は ランタイムをアプリケーションに簡単に組み込めるように **Diarkis ランタイム・ライブラリ** を使用するために必要な実装や便利な機能を実装したフレームワークです。\
パッケージ内の以下の場所にソース・コードが配置されています。

`diarkis-module`

## 基本構造とファイル構成

**Diarkis Module** は大きく以下の４つの機能に分かれています。

* Diarkis Interface
  * Diarkis サーバーへの接続や接続毎に各モジュールを使用するための状態などを管理するクラスで、Diarkis サーバーへの接続ごとにインスタンスを作成します。
  * `diarkis-module/Client/Private/DiarkisInterfaceBase.cpp` に実装があります。
* 各モジュールごとに実装
  * Diarkis"**ModuleName**"Base という名前で **ModuleName** に該当するモジュールの機能を実装しているクラスです。
  * ユーザーはこのクラスを継承してカスタマイズすることによりアプリケーション独自の挙動を実装することができます。
  * `diarkis-module/Client/Private` 配下に実装があります。
* ロギング関連機能
  * **Diarkis ランタイム・ライブラリ** や **Diarkis Module** のログ出力をサポートする機能です。
  * `diarkis-module/Client/Private/logging` に関連ソースコードが配置されています。
* 補助機能
  * 通常の HTTP アクセスやファイル操作など、Diarkis の機能に直接かかわらない補助機能です。
  * `diarkis-module/Client/Private/utils` に関連ソース・コードが配置されています。

## 各モジュールの使用方法について

**Diarkis Module** から Diarkis の各モジュールの機能を使用する場合は以下の各モジュールのページを参照してください。

* Room モジュール
* MatchMaker モジュール
* Field モジュール
* P2P モジュール
* DM (Direct Message) モジュール
* Session モジュール
* Group モジュール

## Diarkis Module の使い方

Diarkis Module の基本的な使い方、アプリケーションごとのカスタマイズ方法については以下のページを参照してください。

* [Diarkis Module の初期化と終了](/diarkis-client/diarkis-module/how-to-init-deinit)
* [Diarkis Module のカスタマイズ](/diarkis-client/diarkis-module/how-to-customize-module)
* [Diarkis Module のロギング・システム](/diarkis-client/diarkis-module/log-output)
* [マイグレーション](/diarkis-client/diarkis-module/migration)

## Diarkis Module が使用するリソース

Diarkis Module は内部で以下のリソースを使用します。

* [Diarkis のスレッド](/diarkis-client/diarkis-module/diarkis-nosureddo)

## 注意点

* **Diarkis Module** はソース・コードで提供されているため、ユーザーが直接コードを変更してカスタマイズすることも可能です。しかし弊社都合により実装が大きく変更されることがあり、バージョンアップの際にマージが困難になる可能性があります。改変して利用する際はあらかじめご了承ください。
* **Diarkis Module** の API はスレッド・セーフでは**ありません**。複数スレッドで **Diarkis Module** を利用する場合はアプリケーション側で排他制御を行ってください。


# Diarkis Module の初期化と終了

本ページでは `directmessage_simple` サンプルからコードの一部を紹介し **Diarkis Module** を利用する際の全体的な流れを説明します。\
実際のサンプルのソースコードは `samples\directmessage_simple\directmessage_simple.cpp` に配置されています。

## Diarkis ランタイム・ライブラリおよび Diarkis Module の初期化

初めに **Diarkis ランタイム・ライブラリ** および **Diarkis Module** を初期化するために `DiarkisInterfaceBase::DiarkisInit()` を呼び出します。\
この処理はアプリケーション全体で最初に一度だけ実行する必要があります。

```cpp
    // Diarkis ランタイムの初期化処理
    // Diarkis の機能を使用する前にアプリケーション全体で一度だけ呼び出す必要があります。
    // 本サンプルではファイルにログを出力する設定でランタイムを初期化します。
    DiarkisInterface::DiarkisInit(uid, LogOutType::FILE_OUT, true, nullptr);
```

## DiarkisInterfaceBase インスタンスの作成

次に Diarkis サーバーに接続して Diarkis の各種機能を使用するために `DiarkisInterfaceBase` を継承したクラスのインスタンスを作成します。\
この時 Diarkis サーバーへ接続する際に使用する `UID(User ID)` を渡します。

```cpp
    // Diarkis のすべての機能にアクセスするために DiarkisInterface を継承したクラスを作成します。
    diarkis = Diarkis::DiarkisAllocShared<DiarkisInterfaceDirectMessageSimple>(uid);
```

## 低レベル通信レイヤーの初期化

`DiarkisInterfaceBase` のインスタンス作成後、Diarkis サーバーとの通信に使用する TCP/UDP 通信をセットアップします。

```cpp
    diarkis->SetupUdp();
```

## Diarkis サーバーへの接続情報取得

次に Diarkis サーバーへ接続するための情報を取得します。\
付属サンプルではサンプル実装として Diarkis クラスタ内の HTTP サーバーから接続情報を取得するパターンと API サーバー（外部サーバー）経由で接続情報を取得する2パターンが実装されています。

**Diarkis クラスター内の HTTP サーバーから接続情報を取得するパターン**

`DiarkisInterfaceBase::GetEndpoint()` を使用して接続情報を取得します。\
取得した接続情報は `DiarkisInterfaceBase` 内に自動的に保存され接続する時に使用されます。

```cpp
#if API_AUTH == 0
    // Diarkis クラスタ内の HTTP サーバーへアクセスして、DirectMessage 通信で使用するサーバーのエンドポイントを取得します。
    // 通信に使用する方法に応じて適切なサーバー・タイプを指定してサーバーからエンドポイントを取得する必要があります。
    if (diarkis->GetEndpoint(host.c_str(), clientKey.c_str(), serverType.c_str(), endpoint, 256) == false)
    {
        DiarkisUtils::Print("Failed to get %s endpoint: host=%s uid=%s clientKey=%s", serverType.c_str(), host.c_str(), uid.c_str(), clientKey.c_str());
        DiarkisInterface::DiarkisDestroy();
        return 1;
    }

    DiarkisUtils::Print("Endpoint: %s", endpoint);
    DiarkisUtils::Print("UID     : %s\n", uid.c_str());
#endif // API_AUTH
```

また、サンプルでは使用していませんが

* `DiarkisInterfaceBase::RequestEndpointAsync()`
* `DiarkisInterfaceBase::GetEndpointAsyncStatus()`
* `DiarkisInterfaceBase::GetAsyncEndpointResult()`

を使用して、非同期処理でエンドポイント情報を取得することも可能です。

**API サーバー（外部サーバー）経由で接続情報を取得するパターン**

API サーバー（外部サーバー）など、何らかの方法で取得した接続情報を `AuthInfo` へ保存し、実際の接続時にこの情報を渡して接続処理を行います。

```cpp
#if API_AUTH
    struct AuthInfo auth;
    //diarkis->GetAuthInfo(&auth);

    // 以下は API サーバー（外部サーバー）経由で取得した UDP 接続情報を使用して UDP サーバーに接続するためのサンプル・コードです。
    // GetEndpoint() を呼ばない場合は、APIサーバー経由 で取得した接続情報をセットする必要があります。
    // 以下はあくまでサンプルの接続情報になりますので、このままのコードでは UDP サーバーに接続することはできません。
    std::string endpointString = "192.168.10.5:7100";
    std::string sidString = "a19c5dbb52de43e4a1fad47e5df166e0";
    std::string keyString = "495c0ca446704cb3ab37a8bd986c57a1";
    std::string ivString = "5b0ca9eb9de34df0b333eccd878d524f";
    std::string mackeyString = "88253f5c994a45bc9d9a6eb9ed502075";

    // 以下が、endpoint と auth 構造体の sid, cred.key, cred.iv, cred.mac をセットする際のサンプル・コードになります。
    strcpy(endpoint, endpointString.c_str());
    HexadecimalStringToByteArray(sidString.c_str(), auth.sid, DIARKIS_AUTHKEY_LEN);
    HexadecimalStringToByteArray(keyString.c_str(), auth.cred.key, DIARKIS_AUTHKEY_LEN);
    HexadecimalStringToByteArray(ivString.c_str(), auth.cred.iv, DIARKIS_AUTHKEY_LEN);
    HexadecimalStringToByteArray(mackeyString.c_str(), auth.cred.mac, DIARKIS_AUTHKEY_LEN);
#endif // API_AUTH
```

## Diarkis サーバーへ接続

外部から接続情報を取得した場合はこのタイミングで取得した情報を渡します。\
また、接続処理を実行後、実際に接続が完了するまで時間がかかることがあるため接続状態を定期的にチェックして接続が完了したかどうかを確認します。

```cpp
    // HTTP アクセスで取得したエンドポイントへ接続して Diarkis サーバーとの通信を確立します。
    // 本サンプルでは TCP/UDP ソケットを使用して Diarkis サーバーへ接続します。
    bool connectResult = false;
#if API_AUTH == 0
    connectResult = diarkis->ConnectUdp(endpoint);
#else
    connectResult = diarkis->ConnectUdp(endpoint, serverType.c_str(), &auth);
#endif

    // DiarkisInterface::ConnectUdp/Tcp() の呼び出しで接続処理は開始していますが、実際に接続が完了するまでは時間が少し時間がかかることがあります。
    // 確実に接続出来たことを確認するため、TCP/UDP 接続の状態をチェックする。
    while (!diarkis->GetUdpBase()->IsConnected())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
```

## モジュール毎の初期化

Diarkis サーバーへの接続が完了した後は使用したい各モジュールのセットアップを行い、アプリケーションが必要な通信処理を行います。

```cpp
    // DiarkisInterface 内で管理されている DirectMessage モジュールを初期化します。
    // DirectMessage モジュールのインスタンスを確保して、通信に使用する TCP/UDP モジュールと関連付けます。
    // 以降、DirectMessage モジュールのポインターを DiarkisInterface から取得して DirectMessage の機能にアクセスします。
    diarkis->SetupDirectMessage();
```

## Diarkis サーバーから切断

`DiarkisInterfaceBase::Disconnect()` を呼び出すことで Diarkis サーバーからの切断処理が開始されます。\
接続時と同様に実際に切断が完了するまでは時間がかかることがあるため、切断処理を実行後に接続状態をチェックして切断が完了したかどうかを確認します。

```cpp
    // Diarkis サーバーとの接続を切断
    diarkis->Disconnect();

    // DiarkisInterface::Disconnect() の呼び出しで切断処理は開始しているが、実際に切断が完了するまでは時間が少し時間がかかることがある。
    // 確実に切断してから終了処理に移行するため、TCP/UDP 接続の状態をチェックする。
    while (diarkis->GetUdpBase()->IsConnected() == true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
```

## 使用済みインスタンスの開放

Diarkis サーバーからの切断が完了し `DiarkisInterfaceBase` が必要なくなったためインスタンスを開放します。

```cpp
    diarkis = nullptr;
```

## 終了処理

アプリケーションの終了時に `DiarkisInterfaceBase::DiarkisDestroy()` を呼び出して Diarkis 全体の終了処理を行います。 この処理は `DiarkisInterfaceBase::DiarkisInit()` と対になっていて、`DiarkisInit` 同様にアプリケーションのライフサイクル全体で一度だけ呼び出してください。

```cpp
    // Diarkis ランタイムの終了処理
    // Diarkis を使用し終わったらアプリケーション終了時などに一度だけ DiarkisInterface::DiarkisDestroy() を呼び出します。
    DiarkisInterface::DiarkisDestroy();
```


# Diarkis Module のカスタイマイズ

本ページでは `directmessage_simple` サンプルからコードの一部を紹介し **Diarkis Module** をカスタマイズして利用する方法を説明します。\
実際のサンプルのソース・コードは `samples\directmessage_simple\directmessage_simple.cpp` に配置されています。

## Diarkis Module の各機能のカスタマイズ

利用したいモジュールにアプリケーション専用の処理を組み込みます。例えば Room に参加したタイミングや Room からメンバーがいなくなったことをアプリケーション側で認識して UI を更新する、といったことが可能です。 `directmessage_simple` では他のユーザーから 1 度でも何かメッセージを受信したかどうかを判断するために **Diarkis Module** の DM 機能をカスタマイズしています。\
**Diarkis Module** の機能をカスタマイズするには各機能のベース・クラス(Diarkis**ModuleName**Base)を継承します。DM 機能は `DiarkisDirectMessageBase` に実装されており、サンプルではこのクラスを継承して必要な処理を実装します。 以下のコードが実際の実装の一部となります。

```cpp
/*
 * DirectMessage 関連の処理をアプリ側でカスタマイズするための実装。
 * DirectMessage 関連のデータを受信した際に On... 系のコールバックが発火するため、アプリが処理したい内容を実装します。
 * 本サンプルでは取得したメッセージをコンソールへ表示しています。
 */
class DirectMessageSimple : public DiarkisDirectMessageBase
{
public:
    DirectMessageSimple() : DiarkisDirectMessageBase() {}
    virtual ~DirectMessageSimple() {}

    bool IsAnyMessageReceived() const
    {
        return anyMessageReceived_;
    }
private:
    /**
     * @~japanese
     * @brief DirectMessage の通知を切断する際に呼ばれるコールバック・イベントを取得する。
     * @details DirectMessage Disconnect の通知 ( サーバーからの Push ) が送られた際に呼ばれる。
     * @~
     */
    virtual void OnDisconnect(const DiarkisDirectMessageEventArgs& e) override
    {
        ...
    }

    /**
     * @~japanese
     * @brief 他のリモート・ユーザーから　DirectMessage が送られてきた際に呼ばれるコールバック・イベントを取得する。
     * @details DirectMessage Message の通知 ( サーバーからの Push ) が送られた際に呼ばれる。
     * @~
     */
    virtual void OnMessage(const DiarkisDirectMessageEventArgs& e) override
    {
        ...
        anyMessageReceived_ = true;
    }
private:
    bool anyMessageReceived_ = false;
};
```

`DiarkisDirectMessageBase` には主に DM 機能を使用するためのインターフェイスとイベントを受け取るためのインターフェイスが定義されています。DM 機能を使用するためのインターフェイスでは DM の機能を呼び出す前に特定の処理を実行するような用途に使用することができます。一方のイベントを受け取るためのインターフェイスでは DM 機能でメッセージを受信したときなど、何かイベントが発生したときの通知に対する処理を実装することができます。\
サンプル実装では DM でメッセージを受信したときに発火する `DiarkisDirectMessageBase::OnMessage` をオーバーライドし、メッセージを受け取ったかどうかを保存しています。

## DiarkisInterfaceBase のカスタマイズ

**Diarkis Module** を使用した場合、各機能を実装しているクラスのインスタンスはすべて `DiarkisInterfaceBase` クラスが管理しており、インスタンスの生成も `DiarkisInterfaceBase` の内部で行われます。ベース・クラスをカスタマイズした場合、`DiarkisInterfaceBase` 内で生成されるインスタンスのタイプを変更する必要があるため、`DiarkisInterfaceBase` を継承しカスタマイズされた `DiarkisInterfaceBase` クラスを実装します。以下がサンプル実装の一部となります。

```cpp
/*
 * DirectInterface が内部で管理する各機能のモジュールをアプリ固有のものに置き換えるために DiarkisInterfaceBase を継承したクラスを実装します。
 */
class DiarkisInterfaceDirectMessageSimple : public DiarkisInterfaceBase
{
public:
    ...

    void SetupDirectMessage(void)
    {
        if (tcpBase_ != nullptr && tcpBase_->Get() != nullptr)
        {
            // アプリ側でカスタマイズしたクラスを作成します。
            if (dmBase_ == nullptr)
                dmBase_ = Diarkis::DiarkisAllocShared<DirectMessageSimple>();

            // DirectMessage クラスを初期化してコールバック・イベントを登録
            dmBase_->SetupTcp(tcpBase_->Get(), this->GetLoggerFactory());
        }

        if (udpBase_ != nullptr && udpBase_->Get() != nullptr)
        {
            // アプリ側でカスタマイズしたクラスを作成します。
            if (dmBase_ == nullptr)
                dmBase_ = Diarkis::DiarkisAllocShared<DirectMessageSimple>();
            // DirectMessage クラスを初期化してコールバック・イベントを登録
            dmBase_->SetupUdp(udpBase_->Get(), this->GetLoggerFactory());
        }
    }

    std::shared_ptr<DirectMessageSimple> GetDirectMessage()
    {
        return std::static_pointer_cast<DirectMessageSimple>(GetDirectMessageBase());
    }
};
```

各機能のインスタンスの生成は `DiarkisInterfaceBase::Setup...` で行われます。DM 機能のインスタンスは `DiarkisInterfaceBase::SetupDirectMessage()` で行われるためこのメソッドをオーバーライドしてユーザーがカスタマイズした DM 機能のクラスを生成します。また、あわせてカスタマイズした型で DM 機能のインスタンスを取得できるメソッドも追加しています。

これらのカスタマイズしたクラスを使用することで、**Diarkis Module** へアプリケーション固有の処理を組み込みカスタマイズすることができます。

```cpp
std::shared_ptr<DiarkisInterfaceDirectMessageSimple> diarkis;

...

// Diarkis のすべての機能にアクセスするために DiarkisInterface を継承したクラスを作成します。
diarkis = Diarkis::DiarkisAllocShared<DiarkisInterfaceDirectMessageSimple>(uid);

...

std::shared_ptr<DirectMessageSimple> dm = diarkis->GetDirectMessage();

...

// 簡易的なタイミング合わせの実装です。
// "2222" から初回のメッセージが到達したタイミングで "1111" からのデータの送信も開始します。
while (!dm->IsAnyMessageReceived())
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
```

## 注意点

**Diarkis Module** がソース・コードで提供されているという性質上、ベース・クラスを継承せず直接変更することでも同じようにカスタマイズすることは可能です。しかし、**Diarkis Module** のソース・コードはバージョンアップ時に大きく変更される可能性があり、マージが困難になることが想定されます。\
こういった状況を避けるため、本ページで紹介した方法でカスタマイズされることをおすすめします。


# Diarkis Module のロギング・システム

・

## 概要

本ページでは Diarkis Module のロギング・システムについて説明します。

Diarkis Module では Diarkis ランタイム・ライブラリおよび Diarkis Module の動作状況をログに出力しています。出力されたログは標準出力やファイル出力など様々な方法で確認することができます。想定したように動作しない場合や Diarkis ランタイムの動作状況を確認したい場合はまず初めにログ・ファイルを内容を確認することをお勧めします。

## ロギング・システムの設定

Diarkis Module のロギング・システムの設定は `DiarkisInterfaceBase::DiarkisInit()` に渡す引数でコントロールします。ログ・ディレクトリ名、出力方法、ログを出力するかどうか等を設定することができます。

## Diarkis Module のログ出力方法

Diarkis Module では以下の出力方法をサポートしています。

| enum                      | 説明                                                                            |
| ------------------------- | ----------------------------------------------------------------------------- |
| DEBUG\_OUT                | デバッガーへログが出力可能であればデバッガーへ、出力できなければ標準出力にログを出力します                                 |
| FILE\_OUT                 | ファイルへログを出力します。`GetLogDirectoryPath()` で取得したパスに `diarkis-log.log` という名前で出力されます |
| FILE\_OUT\_SPECIFIC\_PATH | 指定したパスへログ・ファイルを出力します                                                          |
| CONSOLE\_OUT              | 標準出力にログを出力します                                                                 |
| DEBUG\_AND\_FILE\_OUT     | DEBUG\_OUT と FILE\_OUT を合わせた挙動になります                                           |
| CUSTOM                    | ユーザーが定義したカスタム・ロガーへログを出力します                                                    |

## ログ出力レベル

Diarkis Module のロギング・システムではログに出力する内容の詳細度を調整するログ出力レベルを設定することができます。`diarkis-module\Client\Private\logging\LoggerFactory.cpp` で実装している `LoggerFactory` のコンストラクターにデフォルトのログ出力レベルが記載されているため、これを変更することで変えることができます。また、`LoggerFactory::SetSeverity()` を使用してアプリ実行中に動的に変更することも可能です。

ログの出力レベルには以下の設定があります。下の設定ほどログの詳細度がまします。また、詳細度が高いログレベルは下位のログ・レベルを常に内包します。 Verbose や Trace は Diarkis ランタイムの動作速度に影響したり、ログ・ファイルが巨大になる可能性がありますので使用する際はご注意ください。

| enum    | 説明                                     |
| ------- | -------------------------------------- |
| None    | ログを出力しません                              |
| Fatal   | 致命的なエラーのログを出力します                       |
| Error   | 一般的なエラーも含めてログを出力します                    |
| Warning | 警告を含めたログを出力します                         |
| Info    | 付加情報を含めたログを出力します                       |
| Debug   | デバッグ情報を含めたログを出力します                     |
| Verbose | 詳細なデバッグ情報を含めたログを出力します                  |
| Trace   | ランタイムの動作や送受信したペイロードなどを可能な限り詳細にログに出力します |

## カテゴリ毎に確認できるログの内容

クライアント・ライブラリのロギング・システムでは、カテゴリ毎にログ・レベルを変更できる仕組みが用意されております。デバック時に確認されたい内容に合わせて、必要なカテゴリのログ・レベルを変更してご確認ください。

<table><thead><tr><th width="233">代表的なカテゴリ</th><th>デバック時に確認できる内容</th></tr></thead><tbody><tr><td>UDP</td><td>UDP サーバーとの Connect, Disconnect の確認。意図しない切断時。パケットロスが発生している時の ack, eack を確認したい時</td></tr><tr><td>RUDP</td><td>パケットロスが発生している時に、RUDP パケット、シーケンス番号の処理状況を確認したい時</td></tr><tr><td>Socket</td><td>意図せずに切断した際など、Socket 回りの処理を確認したい時</td></tr><tr><td>P2P</td><td>P2P 通信時に、ホールパンチやパケット通信周りで意図しない問題があり確認したい時</td></tr><tr><td>Runtime</td><td>アプリケーションを実装する際に、パケットの送受信やサーバーからの通知や応答が来ない時に確認したい時</td></tr><tr><td>Room など各モジュールのカテゴリ</td><td>各モジュールを実装される際に、パケットの送受信やサーバーからの通知や応答が来ない時に確認したい時</td></tr></tbody></table>

## カスタム・ロガーの実装

カスタム・ロガーを使用すると、ロガーの動作をユーザーが自由にカスタマイズすることができます。\
`ILoggerBackend` を継承して `ILoggerBackend::Log()` をオーバーライドすることにより Diarkis ランタイムおよび Diarkis Module のログ出力処理をハンドリングすることができます。\
以下がサンプル実装です。

```
// アプリケーション・カスタムのログ出力サンプル実装
class AppCustomLoggerBackend : public ILoggerBackend
{
    public:
        AppCustomLoggerBackend() {}
        virtual ~AppCustomLoggerBackend() {}

        virtual Result Log(const Diarkis::StdString& message, bool includeNewLine) override
        {
            // ログ・テキストが message として渡されるため、アプリ固有のログ出力を行う
            // 排他制御は行われているため複数スレッドから呼び出しても問題ない
            DiarkisUtils::Print("%s", message.c_str());
            return Diarkis::Results::SUCCESS;
        }
};

...
// カスタム・ロガーを初期化時に指定する疑似コード
AppCustomLoggerBackend customLogger;

DiarkisInterface::DiarkisInit(uid, LogOutType::CUSTOM, bOutLog, &customLogger);


```


# マイグレーション

## 概要

本ページでは Diarkis サーバーにおけるマイグレーションの挙動について説明します。

## マイグレーションとは

Diarkis サーバーはクラスタ構成されており、ユーザーはその中のどれか1つのサーバーに接続しています。 接続している Diarkis サーバーがスケールインしてユーザーが接続しているサーバーが停止するケースを考えます。 サーバーが停止するため一般的なサーバーであればその時接続しているユーザーは切断されることとなります。 しかし、Diarkis サーバーはクラスタ内の他のサーバーへ移動し、サーバーから切断されることなくサービスを継続することができます。\
この、接続中のサーバーから別のサーバーへ接続しなおしてサービスを維持する仕組みを**マイグレーション**と呼びます。

わかりやすい例としてスケールインを用いて説明しましたが、スケールイン時以外にも様々な要因でマイグレーションが発生する可能性があります。 マイグレーション発生時はサーバーとクライアント・アプリケーションが協調してマイグレーションのプロセスを進める必要があります。次項からはマイグレーション発生時の具体的な処理について説明します。

Room に参加している場合は、下記の機能は使用せず [Room 専用のマイグレーション](/diarkis-modules/room/setup-client#room-nomaigurshon) をご確認ください。

## マイグレーション・プロセス全体の流れ

マイグレーションはサーバーからの通知によって始まります。\
様々な要因によりサーバーがマイグレーションが必要と判断すると、サーバーはクライアントへマイグレーションが必要なことを通知し、クライアントでは `DiarkisTcpBase::OnOffline`/`DiarkisUdpBase::OnOffline()` が発火します。このイベントによってマイグレーションが必要なことを認識したクライアントは `DiarkisTcpBase::SendMigrate()`/`DiarkisUdpBase::SendMigrate()` を実行することでサーバーへマイグレートの開始を要求します。 サーバーはマイグレート要求を受け取ると要求したクライアントを切断し、サーバー上のユーザーのデータを新たな接続先のサーバーへ移動し、その後クライアントは新たなサーバーへ再接続します。\
これらの一連のプロセスがマイグレーション時に行われます。

## マイグレーション時のクライアントの対応

前項で説明したように、クライアント側では `DiarkisTcpBase::OnOffline`/`DiarkisUdpBase::OnOffline()` を受け取った後、`DiarkisTcpBase::SendMigrate()`/`DiarkisUdpBase::SendMigrate()` を実行してマイグレーション・プロセスを進める必要があります。

SDK 付属のサンプルでは以下のように `OnOfflien()` 発火後、すぐに `SendMigrate()` を呼ぶように実装されています。

```
void DiarkisTcp::OnOffline()
{
    DiarkisTcpBase::OnOffline();

    // サーバのスケールインにより、接続しているサーバが offline になります。
    // アプリ側の実装に合わせて、適したタイミングで SendMigrate を呼び出して サーバの移動を行ってください。
    this->SendMigrate();
}
```

```
void DiarkisUdp::OnOffline()
{
    DiarkisUdpBase::OnOffline();

    // サーバのスケールインにより、接続しているサーバが offline になります。
    // アプリ側の実装に合わせて、適したタイミングで SendMigrate を呼び出して サーバの移動を行ってください。
    this->SendMigrate();
}
```

なお、`DiarkisTcpBase::SendMigrate()`/`DiarkisUdpBase::SendMigrate()` を実行して再接続処理が開始するとサーバーとの接続が一度切断されるため一時的に通信できなくなります。 切断が発生するタイミングは `DiarkisTcpBase::SendMigrate()`/`DiarkisUdpBase::SendMigrate()` を実行するタイミングでコントロールすることができますので、アプリケーションの都合に合わせて調整してください。


# Diarkis のスレッド

Diarkis クライアントで使用するスレッドについて説明します。

本ページでは Diarkis で使用するスレッドについて説明します。 Diarkis Module のスレッドは、`DiarkisInterfaceBase::DiarkisInit()` の初期化の中で 作成 されます。`IDiarkisTransport::Connect()` を呼び出した際に UDP/TCP/P2P のスレッドが 作成 されます。接続する Diaskis サーバに合わせて ランタイムライブラリのスレッドが増えます。

* ランタイム・ライブラリ
  * UDP サーバー接続時は、以下の２つが作成されます。
    * Send Thread : Send Pedding Buffer に溜まったパケットを送信するためのスレッドです。
    * Receive Thread : Socket で受信したパケットから Event Scheduler に Event を push するためのスレッドです。
  * TCP サーバー接続時は、以下の１つが作成されます。
    * Network Thread : パケットの Send と Receive するための スレッドです。
  * P2P 接続時は、接続相手毎に以下が作成されます。
    * Holepunch Thread : ホールパンチする際にするために一時的に作成されるスレッドです。
* Diarkis Module
  * Runtime Thread : Diarkis サーバからの 応答 / 通知イベントを呼び出すためのスレッドです。
  * Logger Backend Thread : Diarkis のログ・バッファリングして処理するためのスレッドです。

### UDP 接続時の スレッドのシーケンス図

{% @mermaid/diagram content="sequenceDiagram
box Application

```
participant App Thread
participant Runtime Thread
```

end
box Library
participant Pending Buffer
participant Event Scheduler
participant Send Thread
participant Receive Thread
end
box Diarkis Server
participant Server
end

```
Note over Receive Thread: Socket を Max 100 ms<br/>開いて待機
Note over Send Thread: Max 100ms 待機<br/>Pending Buffer に<br/>Push された際に<br/>Thread が起こされる
Note over Runtime Thread: Max 100ms 待機 or<br/>Event Scheduler に<br/>Push された際に<br/>Thread が起こされる
```

　
Activate App Thread
App Thread->>Pending Buffer: Send()/RSend() Pending Buffer<br/> にメッセージ Push
Activate Pending Buffer
loop Send Loop
Pending Buffer->>Send Thread: Thread を Wait から起こす
Activate Send Thread
Send Thread->>Pending Buffer: メッセージチェック・取得して処理継続
Deactivate Pending Buffer
Send Thread->>Server: Send
Activate Server
Deactivate Send Thread
end

loop Receive Loop
Server->>Receive Thread: Recv
Deactivate Server
Activate Receive Thread
Receive Thread->>Event Scheduler: Event Scheduler にイベント Push
Activate Event Scheduler
Deactivate Receive Thread
Event Scheduler->>Runtime Thread: Thread を Wait から起こす
Activate Runtime Thread
end
loop Runtime Loop
Runtime Thread->>Event Scheduler: Event チェック・取得して処理継続
Deactivate Event Scheduler
Runtime Thread->>App Thread:コールバック
Deactivate Runtime Thread
end

　　　　Send Thread->>Pending Buffer: メッセージチェック<br/>PendingBuffer が空なら何もせず Wait。
Activate Send Thread
　　　　Note over Send Thread: Max 100ms間隔
　　　Deactivate Send Thread
Activate Receive Thread
　　　　Receive Thread->>Receive Thread: Socket にメッセージを受信しない場合でも、<br/>異常や終了処理チェックのため<br/>Max100ms に1回ループを回す
　　　Deactivate Receive Thread

　　　Runtime Thread->>Event Scheduler: Eventチェック<br/>Event が無せず Wait
Activate Runtime Thread
　　　　Note over Runtime Thread: Max 100ms間隔
　　　Deactivate Runtime Thread
Deactivate App Thread

" %}

#### 必要に応じて Diakis サーバーの複数用意する場合

例えば、マッチング用と TURN 用の `UDP` サーバを用意する場合は、`DiarkisInterfaceBase` のインスタンスをサーバー分の2つ用意する必要がありますので、ランタイム・ライブラリのスレッドは （`Send Thread` と `Receive Thread` ）ｘ 2 の 計 4 つのスレッドが作成されることになります。

上記の場合でも Diarkis Module の スレッド （`Runtime Thread` と `Logger Backend`）は1つずつに作成されるだけなので、計 マッチング用 x 2 TURN 用 x2 Diarkis Module x2 の 6つになり、 P2P 接続する際は接続相手毎に一時的にスレッドが作成されます。


# CSAR (Clustered Server Authoritative Ruler)


# v1.1.2

本ドキュメントは CSAR モジュール に関するドキュメントです。\
CSAR モジュール は [Diarkis ランタイム・ライブラリ](https://help.diarkis.io/diarkis-client/runtime-library) と [Diarkis-Module](https://help.diarkis.io/diarkis-client/diarkis-module) の上位レイヤーにあたり、Diarkis の低レイヤーの機能を組み合わせて一般的なゲームやアプリの実装モデルを想定した機能を提供し、より使いやすくしたライブラリです。

## 1. Authority

CSAR におけるオーソリティ（authority）とは、ゲームの正当な状態（state）を決定し、他の参加者に対してその状態を配信する権限を持つエンティティを指します。authority の位置づけは Dedicated Game Server (DGS)、ホストクライアント型（listen server／authoritative peer）、メッシュ型などのネットワークトポロジーやゲームの要件によって変わります。

ゲームロジック（authority が行うべき検証や意思決定）はアプリケーション側で実装する必要があります。つまり、どの入力を受け入れるか、どのように競合を解決するか、チート検知のアルゴリズムや最終的な状態更新はアプリケーションの責任です。

## 2. ゲームセットアップ

CSAR では以下に示すゲームのセットアップを指定することができます。authority の有無や他のユーザーとの接続方法に違いがあります。

<table><thead><tr><th width="185.3333740234375">GameInstanceSetup</th><th>説明</th></tr></thead><tbody><tr><td>StartAsDgsServer</td><td>Server-Authoritative (サーバー権威型) のネットワークを構築します。DGS として起動し、このプロセスが authority を持ちます。全状態の最終決定者であり、クライアントはサーバーの通知に従います。</td></tr><tr><td>JoinAsDgsClient</td><td>DGS に接続するためのクライアントとして起動します。DGS のゲームを構築するには StartAsDgsServer と JoinAsDgsClient が必要です。</td></tr><tr><td>HostClient</td><td>Client-Authoritative (クライアント権威型) のネットワークを構築します。いわゆる Host-Client モデルのゲームのためのセットアップです。DGS を用いずにリレーサーバ (Room) や P2P を用いて他のユーザーたちと接続します。クライアントの内1人が authority を持ちゲームロジックを担います。authority を持つクライアントをホストと呼びます。ホストがゲームから切断された場合はマイグレーションによりゲームを継続することができます。</td></tr><tr><td>Mesh</td><td>DGS を用いずにリレーサーバ (Room) や P2P を用いて他のユーザーたちと接続します。単一の authority を置かず、全ユーザーが対等なクライアントとして振る舞います。</td></tr><tr><td>DetachedAuthority</td><td>Client-Authoritative (クライアント権威型) のネットワークを構築します。DGS を用いずにリレーサーバ (Room) や P2P を用いて他のユーザーたちと接続します。ある1人のユーザーが authority を担いますが、そのユーザーは authority を持つのみでクライアントとしては振舞いません。専用サーバーを使うことなく StartAsDgsServer と JoinAsDgsClient の関係に似たネットワークを構築することができます。DGS の開発段階で使用することを想定しています。</td></tr><tr><td>Offline</td><td>Diarkis サーバーとの接続や他のユーザーとの接続をすることなく、HostClient のホストとして動作します。主にシングルプレイヤーやテスト向けです。</td></tr></tbody></table>

### 2.1. NetworkType

`HostClient` もしくは `Mesh` セットアップの場合に限り、Room を使用したリレー通信と P2P 通信の使い方を指定することができます。

<table><thead><tr><th width="145.333251953125">NetworkType</th><th>説明</th></tr></thead><tbody><tr><td>Room &#x26; P2P</td><td>内部的に Room に入室して、 Room 経由の Relay 通信 と P2P を利用して通信します。P2P で通信できるユーザーとは P2P で通信し、P2P で通信できないときは Room 経由で通信します。どの経路で通信しているか意識しないで使用することができます。</td></tr><tr><td>Room</td><td>内部的に Room に入室して、 Room 経由の Relay 通信のみを利用して通信します。</td></tr></tbody></table>

## 3. ゲームインスタンス

DGS を含めユーザー間でデータの送受信を行うグループを表す概念です。 CSAR を使用して通信を行うユーザーは同じゲームインスタンスに参加する必要があります。&#x20;

### 3.1. ゲームインスタンス ID

ゲームインスタンス ID は同時に動作しているすべてのゲームインスタンスの中で一意でなければなりません。異なるセットアップで同じ ID を使用すると問題が発生します。

* 同じ ID を同時に使わないでください。たとえば `HostClient` と `Mesh` セットアップを同じ ID で同時に開始しないでください。
* ID を再利用する場合は、前のインスタンスが完全に終了してからにしてください。
* セッションごとに UUID（GUID）や十分にランダムな文字列を生成して使うのが安全です。
* テスト目的で短い固定文字列（例: "test"）を使うのは、ローカルや開発環境だけにしてください。

## 4.  CSAR に接続する流れ

CSAR に接続し、他のユーザーと通信を行うための手順について説明します。

#### 初期化処理

`ConnectionManager` のインスタンスを作成します。`ConnectionManager` は CSAR の機能にアクセスするためのインターフェイスです。内部に `DiarkisInterface` をもっており、この機能を使用して CSAR の機能を提供します。`DiarkisInterface` は `ConnectionManager` の初期化時に外部から渡すインターフェイスもあり、ユーザーが作成したものを使用することも可能です。

#### Diarkis サーバへ接続

`ConnectionManager::ConnectDiarkisServer` で Diarkis サーバへ接続します。本 API の初期化時にコンフィグとして接続先のサーバのアドレスなどを設定します。接続処理の結果は `DiarkisConnectionEvent` で受け取ることができ `ConnectionManager::AddServerConnectCallback` で呼び出されるコールバックを設定することができます。

#### ゲームインスタンスの開始

Diarkis サーバへ接続完了後、`ConnectionManager::StartGameInstance` でゲームインスタンスを開始することができます。それぞれのユーザーが参加したいゲームセットアップを設定し、同じゲームインスタンス ID を指定して `ConnectionManager::StartGameInstance` を実行することにより同じゲームインスタンスに参加することができます。

## 5. インゲームセッション中のユーザー管理とデータ送受信

`HostClient` セットアップの場合を例にゲームの開始から終了までの流れを説明します。

#### ゲームインスタンスの開始処理

ゲームインスタンス開始処理の結果は `GameInstStartEvent` で受け取ることができ `ConnectionManager::AddGameInstStartCallback` で呼び出されるコールバックを設定することができます。また、ゲームインスタンスに参加した結果、自分がゲームのホスト役となった場合、ゲームサーバの処理の開始を要求する `LaunchHostProcess` イベントが発生します。 このイベントはゲームサーバの処理を新たに開始する必要がある、ゲームインスタンスの開始時と後述するホストマイグレーション時に発生します。 ユーザはこのイベントのコールバックをトリガーとしてゲームサーバの開始処理を行うことができます。 `LaunchHostProcess` イベントは `ConnectionManager::AddLaunchHostProcessCallback` で呼び出されるコールバックを設定することができます。

#### 参加ユーザーの情報取得

ゲームインスタンスに自分以外のユーザーが接続すると `UserConnect` イベントが発生します。`UserConnect` イベントは `ConnectionManager::AddUserConnectCallback` で呼び出されるコールバックを設定することができます。 ゲームインスタンスから自分以外のユーザーが離脱すると `UserDisconnect` イベントが発生します。`UserDisconnect` イベントは `ConnectionManager::AddUserDisconnectCallback` で呼び出されるコールバックを設定することができます。 ゲームインスタンスに参加しているユーザーは `ConnectionManager::GetConnectedClients` もしくは `ConnectionManager::GetConnectedUsers` で取得することが可能です。詳細は API ドキュメントをご参照ください。

#### データの送受信

`SendToHost/SendToClient` を使用します。この API で送信したデータは `DataToHost/DataToClient` イベントで受け取ることができ、`ConnectionManager::AddDataToHost/AddDataToClient` で呼び出されるコールバックを設定することができます。

#### 終了処理

ゲームインスタンスから離脱するには `ConnectionManager::ExitFromGameInstance` を使用します。  ゲームインスタンスから離脱すると `ConnectionManager::ExitFromGameInstance` を実行したユーザーは `GameInstExit` イベントで離脱したことを受け取ることができ、`ConnectionManager::AddGameInstExitCallback` で呼び出されるコールバックを設定することができます。

ゲームインスタンスから離脱後、`ConnectionManager::DisconnectDiarkisServer` を使用して Diarkis サーバから切断します。

#### ホスト役かどうかの判断

`ConnectionManager::IsHost` で自身がホスト役になっているかどうかを確認することができます。

## 6. HostClient セットアップにおけるホストマイグレーションのフロー

`HostClient` セットアップで動作している場合、CSAR は既存のホスト役が不在となった場合でも新たなホスト役を選任してホスト役を切り替える事 (ホストマイグレーション) でゲームを継続することができます。`Mesh` や DGS を用いたゲームでは機能しません。ホストマイグレーションは、主に次のような状況で発生します：

* ホストがゲームインスタンスから退出した場合や、アプリのクラッシュなどによりネットワークから切断された場合
* `ConnectionManager::TransferHost` の実行により、ホストの役割が他のクライアントに委譲された場合

&#x20;ホストマイグレーションは以下のフローで実行されます。

#### ホストの状態の保存

ホストマイグレーションに備えて、ホスト役だけが持っているマスターデータの情報を Diarkis サーバー 上に保存することが可能です。 `ConnectionManager::StoreGameState` を呼び出すことで任意のバイト列を 10 KiB まで保存することができます。

#### クライアントの場合

ホスト役が認識できなくなった場合、`HostOffline` イベントが発生します。 `HostOffline` 通知後、CSAR は 自動的に新たなホスト役に接続する処理を開始します。その後、新たなホスト役が決定し、通信の準備が完了すると `HostMigrationComplete` イベントが発生します。

`HostOffline` イベントは `ConnectionManager::AddHostOfflineCallback` で、`HostMigrationComplete` イベントは `ConnectionManager::AddHostMigrationCompleteCallback` でそれぞれ呼び出されるコールバックを設定することができます。

#### 新しいホストの場合

新しくホスト役として選出された場合、クライアントの側のイベントに加えて `LaunchHostProcess` イベントが発生します。 このイベントでは新たにホスト役として選出されたことの通知とともに前任のホスト役が `ConnectionManager::StoreGameState` で保存したデータがあればそのデータを取得することが可能です。このデータをもとにアプリ側でホスト役の状態を復元することで、ホストが他のコンピュータに移動しても保存時の状態を復帰することできます。`ConnectionManager::GetConnectedClients` で取得できるホスト役から見たクライアントのリストも、この時点で接続しているクライアントが返るようになります。

## 7. ゲームインスタンスのライブマイグレーション

`HostClient` もしくは `Mesh` セットアップのみ使用可能です。Diarkis サーバーがスケールインなどによってシャットダウンされる際に稼働中のゲームインスタンスがある場合、ゲームインスタンスを別な Diarkis サーバーにマイグレートすることでゲームを継続することが可能です。

#### GameInstanceOffline イベント

Diarkis サーバーのプロセスがシャットダウンされる際にサーバーはクライアントに offline になる旨を通知します。クライアント側では `GameInstanceOffline` イベントがトリガーされます。`GameInstanceOffline` イベントはゲームインスタンスを構成するクライアントのうち1人だけにトリガーされます。ホストクライアント型のゲームであればホスト役で、メッシュ型のゲームであれば誰か1人が自動で選ばれそのクライアントでトリガーされます。

`GameInstanceOffline` イベントを受信したクライアントは `ConnectionManager::MigrateGameInstance` を実行することでマイグレーションを開始することができます。マイグレーションが開始されるとゲームインスタンス内の全てのクライアントが自動で新しい Diarkis サーバーに再接続します。

以下は `GameInstanceOffline` イベントのコールバック実装の例です。

```
void OnGameInstanceOffline(const GameInstanceOfflineEventArgs& args)
{
    // Call MigrateGameInstance at the appropriate timing according to the app-side implementation to migrate the GameInstance.
    // アプリ側の実装に合わせて、適したタイミングで MigrateGameInstance を呼び出して GameInstance の移動を行ってください。
    DiarkisUtils::Print("Start migrate game instance...");
    gManager->MigrateGameInstance();
}

```

なお、`ConnectionManager::MigrateGameInstance` を実行して再接続処理が開始すると現在接続しているサーバーと切断されるため一時的に通信できなくなります。 切断が発生するタイミングは `ConnectionManager::MigrateGameInstance` を実行するタイミングでコントロールすることができますので、アプリケーションの都合に合わせて調整してください。

#### GameInstanceMigrationStart イベント

ゲームインスタンスのマイグレーションが開始されると `GameInstanceMigrationStart` イベントが全てのクライアントでトリガーされます。`GameInstanceMigrationComplete` イベントがトリガーされるまで他のクライアントと通信することはできません。

#### GameInstanceMigrationComplete イベント

新しいサーバーに接続しゲームインスタンスのマイグレーションが完了すると `GameInstanceMigrationComplete` イベントが全てのクライアントでトリガーされます。このイベントが成功した場合、他のクライアントとの通信が可能になります。

## 8. その他の機能

#### Authority とメッシュ型の共存

ゲームインスタンスを `HostClient` で初期化後に `ConnectionManager::AllowSendDataBetweenClients` を使用することで同時にメッシュ型の通信を使用することができるようになります。 この機能はデフォルトでは無効になっており、明示的に有効化する必要があります。ゲームインスタンスが開始された後に`ConnectionManager::AllowSendDataBetweenClients` を `true` で呼び出すと `SendBroadcast`, `SendMulticast`, `SendUnicast` を実行してホスト役を介さずクライアント間でデータをやり取りすることができるようになります。これらのデータは`DataToUserCallback` で受信することができます。また、`ConnectionManager::GetConnectedUsers` で他のクライアントの UID を取得することが可能になります。


# v1.1.1

本ドキュメントは CSAR モジュール に関するドキュメントです。\
CSAR モジュール は [Diarkis ランタイム・ライブラリ](https://help.diarkis.io/diarkis-client/runtime-library) と [Diarkis-Module](https://help.diarkis.io/diarkis-client/diarkis-module) の上位レイヤーにあたり、Diarkis の低レイヤーの機能を組み合わせて一般的なゲームやアプリの実装モデルを想定した機能を提供し、より使いやすくしたライブラリです。

## 通信モデル

CSAR では通信モデルとして Single Authority （スター型）と No Authority（メッシュ型）をサポートしており、このどちらかもしくは混在させて使用することができます。

#### Single Authority (スター型)

通信トポロジーのスター型のように、オーソリティとなるホスト役が存在しそのホスト役に対して各クライアントが接続するタイプになります。クライアントはホスト役のみと送受信する事が可能で、ホスト役は全クライアントから受け取ったデータをもとにゲームサーバの処理を行い結果をクライアントに返すようなモデルを想定しています。Single Authority には、Client-Hosted タイプと DGS タイプがあります。\
DGS タイプは、ホスト役はクライアントではなく Dedicated Game Server (専用ゲームサーバー) がオーソリティを担います。クライアントプログラムは CSAR を通して Diarkis クラスタ内に配置した DGS サーバーに接続することができます。\
Client-Hosted タイプは、クライアントの中の１つがホスト役も担います。ネットワークに参加したコンピュータの中から CSAR が自動的にオーソリティとなるホスト役のユーザーを指定します。

#### No Authority (メッシュ型)

オーソリティとなるホスト役が存在せず、各クライアントが対等な関係で接続するタイプです。ネットワークに参加している各コンピュータがそれぞれ全員とデータの送受信をすることが可能です。

## 通信経路の抽象化

[Diarkis ランタイム・ライブラリ](https://help.diarkis.io/diarkis-client/runtime-library) には Room を使用したリレー通信、P2P 通信など様々な経路の通信方法が存在します。 クライアントライブラリを直接使用する場合、目的に応じてユーザーが API を使い分ける必要がありますが、CSAR では初期化時に使用したい通信経路を指定するとその経路の中で利用可能なものを使用してデータの送受信を行います。 たとえば初期化時に Room と P2P を有効にすると P2P を優先して使用しつつ、ホールパンチングに失敗したユーザーに対しては Room でデータを送信するといった動作をします。

## CSAR に接続する流れ

CSAR に接続し、他のユーザーと通信を行うための手順について説明します。

#### 初期化処理

`ConnectionManager` のインスタンスを作成します。`ConnectionManager` は CSAR の機能にアクセスするためのインターフェイスです。内部に `DiarkisInterface` をもっており、この機能を使用して CSAR の機能を提供します。`DiarkisInterface` は `ConnectionManager` の初期化時に外部から渡すインターフェイスもあり、ユーザーが作成したものを使用することも可能です。

#### Diarkis サーバへ接続

`ConnectionManager::ConnectDiarkisServer` で Diarkis サーバへ接続します。本 API の初期化時にコンフィグとして接続先のサーバのアドレスなどを設定します。接続処理の結果は `DiarkisConnectionEvent` で受け取ることができ `ConnectionManager::AddServerConnectCallback` で呼び出されるコールバックを設定することができます。

#### ゲームインスタンスの開始

Diarkis サーバへ接続完了後、`ConnectionManager::StartGameInstance` でゲームインスタンスを開始することができます。 ゲームインスタンスはデータの送受信を行うグループを表す概念で CSAR を使用して通信を行うユーザーは同じゲームインスタンスに参加する必要があります。 ゲームインスタンスはゲームインスタンス ID によって識別され、それぞれのユーザーが同じ ID に対して `ConnectionManager::StartGameInstance` を実行することにより同じゲームインスタンスに参加することができます。

#### **ConnectionMode**

ゲームのタイプによって Authority となるホスト役が必要になるかで指定することができます。

<table><thead><tr><th width="185">Mode</th><th></th></tr></thead><tbody><tr><td>Single Authority</td><td>Authority となるホスト役が存在し、そのホスト役で当たり判定などを行ってゲームが進行するタイプになります。Host-Cliented タイプや DGS タイプのゲームで利用頂けます。</td></tr><tr><td>No Authority</td><td>Authority となるホスト役が存在せず、各クライアントが対等な関係で接続するタイプです。格闘ゲームなどのゲームで利用を想定したモードになります。</td></tr><tr><td>Shared Authority</td><td>全クライアントで Authority を共有して、ホスト役とクライアント役を担います。 ※1</td></tr></tbody></table>

※1 :  Shared Authority モードは、今後削除を予定しています。ご利用を予定されている場合ご相談ください。

#### **NetworkType**

以下の通信タイプから選択することができます。

<table><thead><tr><th width="145.333251953125"></th><th></th></tr></thead><tbody><tr><td>Room &#x26; P2P</td><td>内部的に Room に入室して、 Room 経由の Relay 通信 と P2P を利用して通信します。P2P で通信できる時 P2P で通信し、P2P で通信できにないときは Room 経由で通信します。どの経路で通信しているか意識しないで使用することができます。</td></tr><tr><td>Room</td><td>内部的に Room に入室して、 Room 経由の Relay 通信のみを利用して通信します。</td></tr><tr><td>DGS</td><td>内部的に Room に入室して、別途起動している DGS サーバーに接続して通信します。DGS では、必ず ConnectionMode は Single Authority を選択して頂く必要があります。※2</td></tr><tr><td>OFFLINE</td><td>Diarkis サーバーに接続せず OFFLINE でゲームを動作させるためのモードになります。OFFLINE では、必ずConnectionMode は Single Authority を選択して頂く必要があります。</td></tr></tbody></table>

※2 : DGS を使用する際は、別途 アプリケーションを DGS サーバーで起動しておく必要があります。

#### **MaxMembers**

ゲームインスタンスに参加するクライアントの数を指定します。

## インゲームセッション中のユーザー管理とデータ送受信

#### ゲームインスタンスの開始処理

ゲームインスタンス開始処理の結果は `GameInstStartEvent` で受け取ることができ `ConnectionManager::AddGameInstStartCallback` で呼び出されるコールバックを設定することができます。\
ゲームインスタンス開始が成功すれば自分自身は CSAR への接続が完了した事になります。\
また、ゲームインスタンスに参加した結果、自分がゲームのホスト役となった場合、ゲームサーバの処理の開始を要求する `LaunchHostProcess` イベントが発生します。 このイベントはゲームサーバの処理を新たに開始する必要がある、ゲームインスタンスの開始時と後述するホストマイグレーション時に発生します。 ユーザはこのイベントのコールバックをトリガーとしてゲームサーバの開始処理を行うことができます。 `LaunchHostProcess` イベントは `ConnectionManager::AddLaunchHostProcessCallback` で呼び出されるコールバックを設定することができます。

#### 参加ユーザーの情報取得

ゲームインスタンスに自分以外のユーザーが接続すると `UserConnect` イベントが発生します。`UserConnect` イベントは `ConnectionManager::AddUserConnectCallback` で呼び出されるコールバックを設定することができます。 ゲームインスタンスから自分以外のユーザーが離脱すると `UserDisconnect` イベントが発生します。`UserDisconnect` イベントは `ConnectionManager::AddUserDisconnectCallback` で呼び出されるコールバックを設定することができます。 ゲームインスタンスに参加しているユーザーは `ConnectionManager::GetConnectedClients` もしくは `ConnectionManager::GetConnectedUsers` で取得することが可能です。詳細は API ドキュメントをご参照ください。

#### データの送受信

Single Authority を使用している場合、データの送信には Single Authority 専用の `SendToHost/SendToClient` を使用します。この API で送信したデータは `DataToHost/DataToClient` イベントで受け取ることができ、`ConnectionManager::AddDataToHost/AddDataToClient` で呼び出されるコールバックを設定することができます。

No Authority を使用している場合、データの送信には No Authority 専用の `SendBroadcast/SendMulticast/SendUnicast` を使用します。この API で送信したデータは `DataToUser` イベントで受け取ることができ、`ConnectionManager::AddDataToUserCallback` で呼び出されるコールバックを設定することができます。

#### 終了処理

ゲームインスタンスから離脱するには `ConnectionManager::ExitFromGameInstance` を使用します。  ゲームインスタンスから離脱すると `ConnectionManager::ExitFromGameInstance` を実行したユーザーは `GameInstExit` イベントで離脱したことを受け取ることができ、`ConnectionManager::AddGameInstExitCallback` で呼び出されるコールバックを設定することができます。 Single Authority のクライアントを除くその他のユーザーは `OnUserDisconnect` を受け取ります。

ゲームインスタンスから離脱後、`ConnectionManager::DisconnectDiarkisServer` を使用して Diarkis サーバから切断します。

#### ホスト役かどうかの判断

`ConnectionManager::IsHost` で自身がホスト役になっているかどうかを確認することができます。

## ホストマイグレーションのフロー

CSAR は既存のホスト役が不在となった場合でも新たなホスト役を選任してホスト役を切り替える事 (ホストマイグレーション) でゲームを継続することができます。ホストマイグレーションは CSAR が Single Authority でかつ Host-Cliented タイプで動作している場合にのみ有効です。No Authority や DGS を用いたゲームでは機能しません。ホストマイグレーションは、主に次のような状況で発生します：

* ホストがゲームインスタンスから退出した場合や、アプリのクラッシュなどによりネットワークから切断された場合
* `ConnectionManager::TransferHost` の実行により、ホストの役割が他のクライアントに委譲された場合

&#x20;ホストマイグレーションは以下のフローで実行されます。

#### ホストの状態の保存

ホストマイグレーションに備えて、ホスト役だけが持っているマスターデータの情報を Diarkis サーバー 上に保存することが可能です。 `ConnectionManager::StoreGameState` を呼び出すことで任意のバイト列を 10 KiB まで保存することができます。

#### クライアントの場合

ホスト役が認識できなくなった場合、`HostOffline` イベントが発生します。 `HostOffline` 通知後、CSAR は 自動的に新たなホスト役に接続する処理を開始します。その後、新たなホスト役が決定し、通信の準備が完了すると `HostMigrationComplete` イベントが発生します。

`HostOffline` イベントは `ConnectionManager::AddHostOfflineCallback` で、`HostMigrationComplete` イベントは `ConnectionManager::AddHostMigrationCompleteCallback` でそれぞれ呼び出されるコールバックを設定することができます。

#### 新しいホストの場合

新しくホスト役として選出された場合、クライアントの側のイベントに加えて `LaunchHostProcess` イベントが発生します。 このイベントでは新たにホスト役として選出されたことの通知とともに前任のホスト役が `ConnectionManager::StoreGameState` で保存したデータがあればそのデータを取得することが可能です。このデータをもとにアプリ側でホスト役の状態を復元することで、ホストが他のコンピュータに移動しても保存時の状態を復帰することできます。`ConnectionManager::GetConnectedClients` で取得できるホスト役から見たクライアントのリストも、この時点で接続しているクライアントが返るようになります。

## ゲームインスタンスのライブマイグレーション

Diarkis サーバーがスケールインなどによってシャットダウンされる際に稼働中のゲームインスタンスがある場合、ゲームインスタンスを別な Diarkis サーバーにマイグレートすることでゲームを継続することが可能です。

#### GameInstanceOffline イベント

Diarkis サーバーのプロセスがシャットダウンされる際にサーバーはクライアントに offline になる旨を通知します。クライアント側では `GameInstanceOffline` イベントがトリガーされます。`GameInstanceOffline` イベントはゲームインスタンスを構成するクライアントのうち1人だけにトリガーされます。ホストクライアント型のゲームであればホスト役で、メッシュ型のゲームであれば誰か1人が自動で選ばれそのクライアントでトリガーされます。

`GameInstanceOffline` イベントを受信したクライアントは `ConnectionManager::MigrateGameInstance` を実行することでマイグレーションを開始することができます。マイグレーションが開始されるとゲームインスタンス内の全てのクライアントが自動で新しい Diarkis サーバーに再接続します。

以下は `GameInstanceOffline` イベントのコールバック実装の例です。

```
void OnGameInstanceOffline(const GameInstanceOfflineEventArgs& args)
{
    // Call MigrateGameInstance at the appropriate timing according to the app-side implementation to migrate the GameInstance.
    // アプリ側の実装に合わせて、適したタイミングで MigrateGameInstance を呼び出して GameInstance の移動を行ってください。
    DiarkisUtils::Print("Start migrate game instance...");
    gManager->MigrateGameInstance();
}

```

なお、`ConnectionManager::MigrateGameInstance` を実行して再接続処理が開始すると現在接続しているサーバーと切断されるため一時的に通信できなくなります。 切断が発生するタイミングは `ConnectionManager::MigrateGameInstance` を実行するタイミングでコントロールすることができますので、アプリケーションの都合に合わせて調整してください。

#### GameInstanceMigrationStart イベント

ゲームインスタンスのマイグレーションが開始されると `GameInstanceMigrationStart` イベントが全てのクライアントでトリガーされます。`GameInstanceMigrationComplete` イベントがトリガーされるまで他のクライアントと通信することはできません。

#### GameInstanceMigrationComplete イベント

新しいサーバーに接続しゲームインスタンスのマイグレーションが完了すると `GameInstanceMigrationComplete` イベントが全てのクライアントでトリガーされます。このイベントが成功した場合、他のクライアントとの通信が可能になります。

\* 現在ゲームインスタンスのマイグレーションは Room ベースのゲームでのみ使用可能で、DGS のときは動作しません。

## その他の機能

#### Single Authority と メッシュ型 の共存

ゲームインスタンスをSingle Authority で初期化後に `ConnectionManager::AllowSendDataBetweenClients` を使用することで同時にメッシュ型の通信を使用することができるようになります。 この機能はデフォルトでは無効になっており、明示的に有効化する必要があります。ゲームインスタンスが開始された後に`ConnectionManager::AllowSendDataBetweenClients` を `true` で呼び出すと `SendBroadcast`, `SendMulticast`, `SendUnicast` を実行してクライアント間でデータをやり取りすることができるようになります。これらのデータは`DataToUserCallback` で受信することができます。また、`ConnectionManager::GetConnectedUsers` で他のクライアントの UID を取得することが可能になります。


# Game Engine Integration

## 概要

**Diarkis ランタイム・ライブラリ** をゲームエンジンから使用するために、各ゲームエンジン用のプラグインが提供されています。

## 対応ゲームエンジン

* [Unreal Engine](/diarkis-client/game-engine-integration/ue)
* Unity(準備中)


# Unreal Engine

## 概要

Diarkis Unreal Engine Plugin (以下 UE プラグイン)は [Diarkis クライアント SDK](/getting-started/diarkis-client-sdk) を Unreal Engine から使用するためのプラグインです。<br>

## Diarkis の基本

Diarkis はサーバとクライアントが連携して動作するシステムです。\
Diarkis を使用すると何ができるか、どのような機能があるかについては [Diarkis の概要](/overview) を参照してください。\
UE プラグインは Diarkis クライアントの実装を行うためのプラグインとなっており、クライアント SDK 相当の機能を UE から使用することができます。\
クライアント SDK についての詳細は [Diarkis クライアント SDK](/getting-started/diarkis-client-sdk) を参照してください。

## Unreal Engine Plugin パッケージの構成について

UE プラグインパッケージは「Diarkis プラグイン」と「プラグインの機能を使用したサンプルコード」で構成されています。\
\
「Diarkis プラグイン」部分はクライアント SDK の [Diarkis Module](/diarkis-client/diarkis-module) 相当の機能を提供しており、このプラグインを通して Diarkis のクライアント側の各機能にアクセスすることができます。「Diarkis プラグイン」はパッケージ内の`Plugins/Diarkis` 以下に配置されています。\
\
「プラグインの機能を使用したサンプルコード」では、プラグイン機能へ UE からアクセスする方法や Blueprint からの呼び出し等の方法をサンプルコードで紹介しています。 UE プラグインパッケージの「Diarkis プラグイン」以外の部分全てが「プラグインの機能を使用したサンプルコード」となっており Diarkis プラグインを利用する形で各種サンプルコードが実装されています。\
\
また、「プラグインの機能を使用したサンプルコード」も２つのレイヤーに分かれており、プラグインの機能を利用してより高度な機能を実装したり UE との連携のための機能を実装した「DiarkisExtension」部分と「DiarkisExtension」を使用してサンプルアプリを実装した「DiarkisPluginSample」部分があります。\
「DiarkisExtension」は `Source\DiarkisExtension` にソースコードが格納されています。\
\
「DiarkisExtension」では様々な機能が実装されていますが、これらはあくまでサンプルコードとなりますので、将来的に互換性が無い仕様変更が発生したり、不具合が存在する可能性があります。 プロジェクトでのご利用を検討される場合はこの点をご留意ください。

## UE プラグインの詳細説明

* [UE プラグインの基礎](/diarkis-client/game-engine-integration/ue/ue-plugin-basics)
* [イベント処理について](/diarkis-client/game-engine-integration/ue/tips-for-event-processing)
* [各サンプルの紹介](/diarkis-client/samples/unreal-engine/diarkis-plugin-sample#sanpuru)
* [Diarkis Extension に含まれる機能一覧](/diarkis-client/samples/unreal-engine/diarkis-plugin-sample#nitsuite)


# Diarkis プラグインの基本的な使い方

## 概要

本ページでは UE プラグインを利用して Diarkis のクライアント機能を利用する際の基本を説明します。

## Diarkis クライアント SDK の基本的な使用方法

UE プラグインは [Diarkis クライアント SDK](/getting-started/diarkis-client-sdk) 相当の機能を有したプラグインとなります。 クライアント SDK では Diarkis-Module を利用してランタイムの初期化、終了処理、機能の利用等を行います。\
UE から Diarkis プラグインを利用する際においても基本的には同様の実装が必要となりますので、基本的な使用方法として [Diarkis-Module のドキュメント](/diarkis-client/diarkis-module) を先にご一読いただけるとこの先の説明をスムーズにご理解いただけます。

## サンプルでの実装例紹介

[Diarkis-Module のドキュメント](/diarkis-client/diarkis-module) で説明した点を中心にシンプルなサンプルでどのように実装されるかソースコードを交えて説明します。

### 初期化処理と終了処理

[Diarkis Module の初期化処理と終了処理](/diarkis-client/diarkis-module/how-to-init-deinit) 相当の処理はおおむね以下のクラスに実装されています。

* `Source\DiarkisExtension\Public\DiarkisNetworkManager.h`
* `Source\DiarkisExtension\Private\DiarkisNetworkManager.cpp`

以下が実装内容の詳細となります。

* 初期化処理
  * `UDiarkisNetworkManager::Connect`/`UDiarkisNetworkManager::ConnectAsync` の実行時にまだ未初期化であれば初期化処理を実行します
  * [Diarkis Module の初期化と終了](https://help.diarkis.io/diarkis-client/game-engine-integration/ue/pages/S9LGXROVUDXYkgmWfZH1#diarkis-module-の初期化と終了) に記載されていますように、この処理はアプリ起動後一度だけ実行する必要があります
  * UE では Editor 上での実行も考慮すると呼び出しタイミングには注意が必要となります
* Diarkis サーバへの接続情報取得
  * [Diarkis サーバへの接続情報の取得](https://help.diarkis.io/diarkis-client/game-engine-integration/ue/pages/S9LGXROVUDXYkgmWfZH1#diarkis-サーバーへの接続情報取得) と同様の実装となっています
* Diarkis サーバへの接続
  * [Diarkis サーバへの接続](https://help.diarkis.io/diarkis-client/game-engine-integration/ue/pages/S9LGXROVUDXYkgmWfZH1#diarkis-サーバーへ接続) と同様の実装となっています
* 終了処理
  * `UDiarkisNetworkManager::Disconnect` の実行時に接続先サーバがなくなったときに自動的に実行します
  * [Diarkis Module の初期化と終了](https://help.diarkis.io/diarkis-client/game-engine-integration/ue/pages/S9LGXROVUDXYkgmWfZH1#diarkis-module-の初期化と終了) に記載されていますように `DiarkisDestroy` の呼び出しは `DiarkisInit` の呼び出しに１対１で対応している必要があります

### ロガー関連の実装

#### ログレベルの変更

* UE プラグインでは LoggerFactory.cpp は以下の場所に存在します
  * `Plugins\Diarkis\Source\Diarkis\Client\Private\logging\LoggerFactory.cpp`

#### UE プラグインのロガー関連設定

* ログファイルは以下のフォルダに出力されます。
  * DiarkisPluginSample/logs/○○○/および以下のフォルダ（OOOOはユーザID）。
* Developmentビルドでもログを出力したい場合は、DiarkisInterfaceBaseのコンストラクタで`bOutputLog = true`を調整してください。
* DiarkisInterfaceBase コンストラクタでは、LogOutType でファイル出力とデバッグ出力を切り替えることができます。

### Diarkis Module のカスタマイズ

Diarkis はサーバとクライアントが連携して動作する仕組みとなっており、サーバへリクエストを送信して結果をコールバックで受け取るという形が基本となっています。\
この動作を実現するために [Diarkis Module のカスタマイズ](/diarkis-client/diarkis-module/how-to-customize-module) を行いアプリに合わせて Diarkis Module のコールバック処理等をカスタマイズする必要があります。\
Diarkis プラグインサンプルではサンプルの都合に合わせてこの実装を行っており、これらのファイルが以下のフォルダに格納されています。

* `Source\DiarkisExtension\Private`

Room 用のカスタマイズ実装であれば `DiarkisRoom`といった名前で `DiarkisRoomBase` を継承してサンプルの動作を実装しています。

### 基本となる処理や呼び出しタイミング

サーバへのコマンドの送信は各モジュールのインスタンスを使用して行います。\
ここでは Room モジュールでサーバ上に部屋を作成する処理を例に使用方法を紹介します。\
Room モジュールのインスタンスは `DiarkisRoomBase` を継承した `DiarkisRoom` クラスが実装されています。<br>

* `Source\DiarkisExtension\Public\DiarkisRoom.h`
* `Source\DiarkisExtension\Private\DiarkisRoom.cpp`

#### コマンドの送信

`DiarkisRoomBase` には部屋を作成するコマンドを送信する `DiarkisRoomBase::SendCreateRoom` が用意されており、このメソッドを実行することでサーバへ部屋作成のリクエストコマンドを送信することができます。<br>

サンプルコードでは以下のコードでこの機能を利用して部屋作成コマンドを送信しています。

`Source\FieldWalker\Diarkis\UI\Room\RoomMenu.cpp` の `URoomMenu::OnCreateButtonClicked`

#### サーバでの処理結果などの受け取り

サーバでの処理結果は Room モジュールのコールバックで受け取ることができます。\
例えば、`SendCreateRoom` コマンドの実行結果は `DiarkisRoom::OnRoomCreation` で受け取ることができます。 コマンドの処理結果がどのコールバックで取得できるかは `DiarkisRoomBase` のような Base クラスのヘッダファイルを参照してください。\
イベントコールバックは Diarkis が管理するイベントスレッドで発火するためこのコールバック内で UE 関連の機能を使用することができません。 また、コールバックが発生してからアプリに通知される際のレスポンス向上のためにもイベントスレッドで負荷の高い処理を行うことは避けることが望ましいです。 これらの課題に対応するためにサンプルでは様々なイベント処理が実装されています。\
詳細については [イベント処理について](/diarkis-client/game-engine-integration/ue/tips-for-event-processing) を参照してください。


# イベント処理の注意点

## 概要

UnrealEngine の Diarkis Plugin Sample には、サーバからの Response 通知 / Push 通知 のコールバックを受け取る仕組みとして、以下の３種類の仕組みを用意しております。

1. `DiarkisDispatch` クラスを利用した仕組み
2. `EventHandler` クラスを利用した仕組み
3. `DiarkisSyncData` クラスを利用した仕組み

こちらのコールバックイベントの受け取る仕組みはサンプルのコードになりますので、もし自前の仕組みを使用される場合は置き換えて頂くことが可能です。

## DiarkisDispatch クラスを利用した仕組み

### 概要

* `DiarkisDispatch` クラスは、Diarkis Plugin Sample に用意されたサーバからの Response 通知 / Push 通知 のコールバックを受け取る仕組みの一つです。
* サーバから通知されたコールバックは、`DiarkisXXXXDelegate` の `DiarkisDispatch` にキューイングされます。
* キューイングされた `DiarkisDispatch` は、`UDiarkisNetworkManager::Tick()` から呼び出される `InGameDiarkisInterface::Execute()`で実行されています。
* キューイングされたイベントは、Tick() の呼び出し間隔で処理されますのでご注意ください。もし、タイムラグなく処理されたい場合は、必要に応じて `InGameDiarkisInterface::Execute()` を呼び出す場所を調整ください。
* 現状 `DiarkisDispatch` クラスの仕組みと `EventHandler` クラスを利用した仕組みと混在していますが、今後は `DiarkisDispatch`の仕組みが、`DiarkisPlugin` の Sampleとしてアプリにコールバックイベントを返すメインの仕組みになる予定です。

### 処理シーケンス

ここでは、UDP サーバの Connect コマンドの処理の流れで説明しますが、他のモジュール（Room, P2P, MatchMakerなど）のコマンドのコールバックイベントも流れは一緒になります。それぞれの処理に合わせてクラスや関数を置き換えてご確認ください。

例えば UDP/TCP サーバ接続のコールバックイベントを受ける際の流れは以下になります。

まずは、サーバ からの Response 通知 / Push 通知 のコールバックを `DiarkisDispatch` でキューイングします。

1. アプリから UDP/TCP サーバに Connect します。
2. UDP/TCP サーバからの Response や Push 通知は、`DiarkisUdpBase` クラスで `udp_->GetConnectedEvent()` コールバックイベントにセットされている `DiarkisUdpBase::OnConnect()` が呼び出されます
3. `DiarkisUdpBase` の 子クラス `InGameDiarkisUdp::OnConnect()` が呼び出されます。
4. `InGameDiarkisUdp::OnConnect()` 関数内の `Dispatch()` でイベントをキューイングします。

{% @mermaid/diagram content="sequenceDiagram
actor app
participant gam as InGameDiarkisUdp
participant udp as DiarkisUdpBase
participant lib as libDiarkis
participant ser as UDP server

box client
participant app
participant gam
participant udp
participant lib
end

app ->> udp: 1. DiarkisUdpBase::Connect()
udp ->> lib: IDiarkisUdp::Connect()
lib ->> ser: Request
ser ->> lib: Response
lib ->> udp: 2. udp\_->GetConnectedEvent()
udp ->> gam: 3. InGameDiarkisUdp::OnConnect()" %}

```c++
void InGameDiarkisUdp::OnConnect(const DiarkisConnectionEventArgs& e)
{
    DiarkisUdpBase::OnConnect(e);
    
    ....
    ....

    Dispatch(args, [this](std::shared_ptr<DiarkisDispatch::Args> args)
    {
        delegate_->OnConnect(*this, *std::static_pointer_cast<IDiarkisUdpDelegate::ConnectArgs>(args));
    });
}
```

次に、ゲームのスレッドから `DiarkisDispatch` でキューイングされたイベントを `Execute()` で実行します。

1. ゲームのスレッドの `UDiarkisNetworkManager::Tick()` から `InGameDiarkisInterface::Execute()` で Delegate が処理されます。
2. `InGameDiarkisUdp::OnConnect()` で `Dispatch()` された `delegate_->OnConnect()` が呼び出されます。
3. `DiarkisSampleUdpDelegate::OnConnect()` が呼び出されます。

{% @mermaid/diagram content="sequenceDiagram
actor app
participant del as DiarkisSampleUdpDelegate
participant udp as InGameDiarkisUdp
participant int as InGameDiarkisInterface
participant Net as UDiarkisNetworkManager::Tick

box client
participant app
participant del
participant udp
participant int
participant Net
end

Net ->> int: 1. DiarkisLists\_\[serverType]->Execute()
int ->> udp: 1. InGameDiarkisUdp->Execute()
udp ->> del: 2. delegate\_->OnConnect()
del ->> app: 3. DiarkisSampleUdpDelegate::OnConnect()" %}

### DiarkisDispatch の関連クラス

* 役割の概要
  * アプリレイヤーで Diarkis コールバックイベントを受け取るためのクラス群（新）
* コードの場所
  * DiarkisPluginSample/Source/DiarkisExtension/XXXXX/Delegate
* 各クラス
  * `DiarkisDispatch.h / DiarkisDispatch.cpp` : Diarkis のコールバックイベントをキューイングや実行するクラス
  * `DiarkisUDPDelegate.h` : Diarkis の Group のコールバックイベントをキューに入れるクラス
  * `DiarkisP2PDelegate.h` : Diarkisの P2P のコールバックイベントをキューに入れるクラス
  * `DiarkisMatchMakerDelegate.h` : Diarkis の MatchMaker のコールバックイベントをキューに入れるクラス
  * `DiarkisRoomDelegate.h` : Diarkis の Room のコールバックイベントをキューに入れるクラス
  * `DiarkisSessionDelegate.h` : Diarkis の Session のコールバックイベントをキューに入れるクラス
  * `DiarkisGroupDelegate.h` : Diarkis の Group のコールバックイベントをキューに入れるクラス

## EventHandler クラスを利用した仕組み

### 概要

* `EventHandler` クラスは、Diarkis Plugin Sample に用意されたサーバからの Response 通知 / Push 通知 のコールバックを受け取る仕組みの一つで、BluePrint用 コールバックイベントを返すための仕組みとして用意されています。
* サーバから通知されたコールバックは、`DiarkisNetworkXXXXEventEmitter` の `UDiarkisNetworkEventHandler` にキューイングされます。
* キューイングされた `UDiarkisNetworkEventHandler` は、`UDiarkisNetworkManager::Tick()` から呼び出される `IUDiarkisNetworkEventHandler::Update()` で実行されています。
* キューイングされたイベントは、Tick() の呼び出し間隔で処理されますのでご注意ください。もし、タイムラグなく処理されたい場合は、必要に応じて `UDiarkisNetworkEventHandler::Update()` を呼び出す場所を調整ください。
* 現状 `DiarkisDispatch` クラスの仕組みと `EventHandler` クラスを利用した仕組みと混在していますが、今後は `DiarkisDispatch`の仕組みが、`DiarkisPlugin` の Sampleとしてアプリにコールバックイベントを返すメインの仕組みになる予定です。

### 処理シーケンス

ここでは、UDP サーバの Connect コマンドの処理の流れで説明しますが、他のモジュール（Room, P2P, MatchMakerなど）のコマンドのコールバックイベントも流れは一緒になります。それぞれの処理に合わせてクラスや関数を置き換えてご確認ください。

例えば UDP/TCP サーバ接続のコールバックイベントを受ける際の流れは以下になります。

まずは、サーバ からの Response 通知 / Push 通知 のコールバックを `EventHandler` でキューイングします。

1. アプリから UDP/TCP サーバに Connect します。
2. UDP/TCP サーバからの Response や Push 通知は、`udp_->GetConnectedEvent()` コールバックイベントにセットされている `DiarkisUdpBase::OnConnect()` が呼び出されます。
3. `DiarkisUdpBase OnConnect()` が呼び出され、内部的に `udp_->IsConnected() == true` に変更されます。
4. `UDiarkisNetworkManager::Tick()` から `UpdateConnected()` が定期的に呼び出され、`GetUdpBase()->IsConnected()==true` に切り替わった時に、`EnqueueOnNetworkConnect()` が呼び出されます。
5. `UDiarkisNetworkCoreEventEmitter::EnqueueOnNetworkConnect()` で `OnNetworkConnect` がキューイングされます。

{% @mermaid/diagram content="sequenceDiagram
actor app
participant udp as DiarkisUdpBase
participant lib as libDiarkis
participant ser as UDP server

box client
participant app
participant udp
participant lib
end

app ->> udp: 1. DiarkisUdpBase::Connect()
udp ->> lib: IDiarkisUdp::Connect()
lib ->> ser: Request
ser ->> lib: Response
lib ->> udp: 2. DiarkisUdpBase OnConnect()" %}

次に、ゲームのスレッドから `UDiarkisNetworkEventHandler` でキューイングされたイベントを `Update()` で実行します。

1. ゲームのスレッドの `UDiarkisNetworkManager::Tick()` から `EventHandler->Update()` が呼び出されます。
2. `UDiarkisNetworkEventHandler::Update()` で、キューイングされたイベントが `Event->CallFunction()` で呼び出されます。
3. `IDiarkisNetworkCoreEvent` の派生クラスの `ADiarkisSampleBase` の子クラスの `BPDiarkisPluginSample::OnNetworkConnect()` のイベントが呼び出されます。
4. `BPDiarkisPluginSample` の親クラスの `ADiarkisSampleBase::OnNetworkConnect_Implementation()`が呼び出されます。

{% @mermaid/diagram content="sequenceDiagram
actor app
participant del as ADiarkisSampleBase
participant plg as BPDiarkisPluginSample
participant han as UDiarkisNetworkEventHandler
participant Net as UDiarkisNetworkManager::Tick

box client
participant app
participant del
participant plg
participant han
participant Net
end

Net ->> han: 1. EventHandler->Update();
han ->> plg: 2. Event->CallFunction(Instance);
plg ->> del: 3. BPDiarkisPluginSample::OnNetworkConnect()
del ->> app: 4. ADiarkisSampleBase::OnNetworkConnect\_Implementation()" %}

### 関連クラス

* 役割の概要
  * BluePrint で Diarkis コールバックイベントを受け取るためのクラス群（旧）
* コードの場所
  * DiarkisPluginSample/Source/DiarkisExtension/XXXXX/Events
* 各クラス
  * `UDiarkisNetworkEventHandler.h` / `UDiarkisNetworkEventHandler.cpp` : Diarkis のコールバックイベントをキューイングや実行するクラス
  * `Interfaces` ディレクトリ（Diarkis Plugin のコールバックを受け取るインターフェースクラス）
    * `UDiarkisNetworkCoreEvent.h` : Diarkis Plugin の Core 機能のコールバックを受け取るインターフェースクラス
    * `UDiarkisNetworkRoomEvent.h` : Diarkis Plugin の Room 機能のコールバックを受け取るインターフェースクラス
    * `UDiarkisNetworkGroupEvent.h` : Diarkis Plugin の Group 機能のコールバックを受け取るインターフェースクラス
    * `UDiarkisNetworkFieldEvent.h` : Diarkis Pluginの Field 機能のコールバックを受け取るインターフェースクラス
    * `UDiarkisNetworkP2PEvent.h` : Diarkis Plugin の P2P 機能のコールバックを受け取るインターフェースクラス
    * `UDiarkisNetworkMatchMakerEvent.h` : Diarkis Plugin の MatchMaker 機能のコールバックを受け取るインターフェースクラス
  * `Emitters` ディレクトリ（Diarkis イベントをキューイングするクラス）
    * `UDiarkisNetworkEventEmitterBase.h / UDiarkisNetworkEventEmitterBase.cpp` : 様々なイベントを生成するためのベースクラス
    * `UDiarkisNetworkCoreEventEmitter.h / UDiarkisNetworkCoreEventEmitter.cpp` : Diarkis Core イベントをキューイング用クラス
    * `UDiarkisNetworkRoomEventEmitter.h / UDiarkisNetworkRoomEventEmitter.cpp` : Diarkis Room イベントをキューイング用クラス
    * `UDiarkisNetworkGroupEventEmitter.h / UDiarkisNetworkGroupEventEmitter.cpp` : Diarkis Group イベントのキューイング用クラス
    * `UDiarkisNetworkFieldEventEmitter.h / UDiarkisNetworkFieldEventEmitter.cpp` : Diarkis Field イベントのキューイング用クラス
    * `UDiarkisNetworkP2PEventEmitter.h / UDiarkisNetworkP2PEventEmitter.cpp` : Diarkis P2P イベントのキューイング用クラス
    * `UDiarkisNetworkMatchMakerEventEmitter.h / UDiarkisNetworkMatchMakerEventEmitter.cpp` : Diarkis MatchMaker イベントのキューイング用クラス

## DiarkisSyncData クラスを利用した仕組み

### 概要

* `DiarkisSyncData` クラスは、Diarkis Plugin Sample に用意されたサーバからの Response 通知 / Push 通知 のコールバックを受け取る仕組みの一つで、キャラクターの位置同期など遅延を抑えたいコールバックイベントを処理する仕組みとして用意されています。

まずは、Room のリレー通信、P2P通信で取得した Payload をキューイングします。

1. `DiarkisRoom::OnRoomMemberBroadcast()` が呼び出されます。
2. `syncData_->OnPositionSync()` が呼び出ばれます。
3. `payloadSyncQueue.push()` で Payload をキューイングします。

次に、キューイングし Payload をデキューします。

1. `UDiarkisSyncComponent::TickComponent()` の処理で、 `DiarkisRemoteMovementSync::UpdateMovement()` が呼び出されます。
2. `DequeueLastFrame()` で Payload をデキューします。


# Packet Manipulator の使い方

## 概要

本ページでは UE プラグインから Packet Manipulator を使用する方法を説明します。Packet Manipulator の基本的な使い方については [Packet Manipulator](/diarkis-client/runtime-library/packet-manipulator) をご参照ください。

## インスタンスの取得

UE プラグインで Packet Manipulator のインスタンスを取得するには `DiarkisGetPacketManipulator()` を使用します。libdiarkis の `Diarkis::System::PacketManipulator::DiarkisGetPacketManipulator()` を直接使用すると意図しないインスタンスが返されるためご注意ください。

```cpp
#include "DiarkisFunctions.h"

...

Diarkis::System::PacketManipulator::IPacketManipulator* pacman = DiarkisGetPacketManipulator();
```

## サンプル実装

本セクションでは Diarkis プラグインの Zombie サンプルに Packet Manipulator を追加して効果を確認する方法について説明します。

<figure><img src="/files/LIYklWhBHET1YTRrEupG" alt=""><figcaption></figcaption></figure>

### プリセットフィルタの追加

以下のコードでアプリの開始時にパケット遅延のプリセットフィルタを設定します。

```cpp
#include "DiarkisFunctions.h"

...

void AGameManager::OnGameInstStart(const GameInstStartEventArgs& args)
{
    auto pacman = DiarkisGetPacketManipulator();
    // Diarkis の UDP パケットすべてにフィルタを適用します。
    auto rawUdpRecvFilterSet = pacman->GetOrAllocFilterSet(Diarkis::System::PacketManipulator::FilterApplyPoint::RawUdpReceive);

    if (auto rawUdpRecvFilterSetPtr = rawUdpRecvFilterSet.lock())
    {
        // 新たに設定する前に既存のフィルタをリセットします。
        rawUdpRecvFilterSetPtr->ClearFilters();
        // すべてのパケットに 200 - 300 ms の遅延が発生する設定です。
        // RTT としてはこの設定値の 2 倍の値となります。
        rawUdpRecvFilterSetPtr->AddPacketDelayFilter(1.0f, 200, 300);
    }
    ...
```

### Packet Manipulator の更新処理の追加

Packet Manipulator が管理するフィルタの状態を更新するために定期的に `IPacketManipulator::Update()` を実行する必要があります。本サンプル実装では Tick にて定期的に実行していますが、アプリ全体で一度実行すればよい処理となりますので、ご都合に合わせて適切な場所で実行するようにしてください。

```cpp
void AGameManager::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    DiarkisGetPacketManipulator()->Update();
    ...
```


# サンプル


# C++

## C++ サンプルのビルドと実行方法

## サンプル全般

1. samples 以下に、"サンプル名\プラットフォーム名" というフォルダ構造でサンプルのファイルが含まれていますので 各フォルダの .sln ファイルを Visual Stduio で開きます。
2. プロジェクト>プロパティ>デバック>コマンド引数 を指定します。

   ```
    $(endPoint) $(uid) $(clientKey) 例 192.168.XXX.XXX:7000 2222 5599933
   ```
3. F5 でビルド実行。 出力ウィンドウに実行状況が表示されます。
4. 複数クライアントを実行する際は、Windows ターミナル(コマンドプロンプト)などから、別プロセスで複数クライアントを実行します。

   ```
   > .\x64\Debug\matchmaker_ticket.exe $(endPoint) $(uid) $(clientKey)
   例 : matchmaker_ticket.exe 192.168.XXX.XXX:7000 2222 5599933
   ```

## iOS シミュレーター向けサンプル・ビルド

1. Xcode で `Product` > `Destination` > `Destination Architectures` > `Show Rosetta Destinations` を確認します
2. トップバーの run destination をクリックし、Rosetta simulator を選択します
3. `Build Settings` > `Search Paths` を開きます
4. サーチパスの順番を `../../../platforms/ios/iOS-Simulator/lib_static` が上に来るように調整します

## Android サンプルの実行

1. VisualStudio でプロジェクトを右クリックして `Properties` を開きます
2. `Debugging` に移動します
3. 起動時の引数を `Launch Flags` で設定します。 引数は `--es` オプション(intent arguments) をつけて指定します。
4. e.g.

   ```
   --es host 192.168.55.117:7000 --es uid 1111 --es clientKey AAAA
   ```


# room\_broadcast

## room\_broadcast サンプル

### 概要

room\_broadcast サンプルは Diarkis サーバーの Room と P2P 機能を使用したサンプル・プログラムです。2人のユーザーが同じ Room に接続し、Room 経由のリレー通信を行った後、P2P 接続を行い P2P でも通信を行う内容となっています。\
room\_broadcast サンプルでは以下の機能を確認することができます。

* Room の作成
* Room への参加
* Room メンバーへのメッセージ送信
* P2P の開始
* P2P でのメッセージの送受信

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`room_broadcast.exe 192.168.1.123:7000 1111 AAAA`

## room\_broadcast サンプル解説

1. Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続します。\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。
2. 各機能の初期化処理を行います。 本サンプルでは Room モジュールと P2P モジュールを使用するため、Diarkis Module でそれらの機能を初期化します。

   ```
   // diarkis =  Diarkis::DiarkisAllocShared<DiarkisInterface>(uid); 
   ...
   // P2P モジュールのセットアップ
   diarkis->SetupP2P();

   // Room モジュールのセットアップ
   diarkis->SetupRoom(false);

   ```
3. Room の RandomJoin で Room に入室します。 `DiarkisRoomBase::RandomJoinRoom()` は参加可能な Room がすでに存在すればその Room に参加し、存在しなければ新たに Room を作成します。また、`DiarkisRoomBase::RandomJoinRoom()` でサーバーへリクエストを送信後、実際に Room に参加が完了するまで `DiarkisRoomBase::IsJoin()` を使用して状態をチェックします。

   ```cpp
   diarkis->RandomJoinRoom(10, 60, 0, true);

   // DiarkisRoomBase::OnRoomJoinを待つ
   while (diarkis->GetRoomBase()->IsJoin() == false) {
       std::this_thread::sleep_for(std::chrono::milliseconds(30));
   }
   ```
4. Room に想定されている人数が参加するまで待機します。\
   本サンプルでは 2 ユーザーが同じ Room に参加するまで待機しています。 `DiarkisRoomBase::SendGetMemberIDs()` を呼び出すと Room に参加しているメンバーのリストの取得をサーバーにリクエストすることが可能です。結果は `DiarkisRoomBase::GetRoomMembers()` で取得することができます。

   ```cpp
   while (1)
   {
       Diarkis::StdVector<Diarkis::StdString> members;

       diarkis->GetRoomBase()->SendGetMemberIDs();
       diarkis->GetRoomBase()->GetRoomMembers(members);

       if (members.size() == NumPeer)
       {
           break;
       }
       std::this_thread::sleep_for(std::chrono::milliseconds(2000));
   }
   ```
5. Room の Broadcast で Room に参加しているユーザー全員にメッセージを送信します。

   ```cpp
   diarkis->GetRoomBase()->SendBroadcastToRoom(buff, bReliable);
   ```
6. Room のオーナーは `DiarkisRoomBase::SendStartP2PSync()` を実行してサーバーへ P2P 接続の開始を通知し、Room に参加しているメンバーの接続先のアドレス・リストを取得します。

   ```cpp
   if (Diarkis::StdString(uid.c_str()) == diarkis->GetRoomBase()->GetOwnerUID())
   {
       // 各メンバーの接続先のアドレスリストを取得する
       diarkis->GetRoomBase()->SendStartP2PSync();
   }

   ```
7. Room のオーナーが `DiarkisRoomBase::SendStartP2PSync()` を実行すると Room に参加しているユーザー全員に `DiarkisRoomBase::OnStartP2PSync()` で P2P 接続情報が通知され、これをトリガーにホールパンチを開始します。
8. ホールパンチに成功したら、P2P で通信します。

   ```cpp
   diarkis->GetP2PBase()->SendBroadcast(buff, RudpType::UNRELIABLE_UNORDERED);
   ```
9. `DiarkisRoomBase::SendLeaveRoom()` を実行して Room から退室をサーバーへリクエストします。 本サンプルでは `DiarkisRoomBase::IsLeave()` を使用して Room から Leave が完了するまで待機しています。

   ```cpp
   // Roomから退出
   diarkis->SendLeaveRoom();

   // DiarkisRoomBase::OnRoomLeaveを待つ
   while (diarkis->GetRoomBase()->IsLeave() == false) {
       std::this_thread::sleep_for(std::chrono::milliseconds(30));
   }
   ```
10. 終了処理\
    詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。

## 注意点

* P2P のホールパンチを成功させるためには、P2P するクライアントが同じ Room に入っている状態でアドレスを交換して実施しています。
* 複数のクライアントをご用意頂き、同時に実行して動作を確認してください。


# directmessage\_simple

## directmessage\_simple サンプル

### 概要

DirectMessage モジュールを使用して 2 人のユーザーがメッセージを送受信するサンプルです。 DirectMessage モジュールの特徴については[こちらのページ](/diarkis-modules/dm)を参照してください。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`directmessage_simple.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`directmessage_simple.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは 2 クライアントが既定の UID で接続する前提での実装となっています。起動時は "1111", "2222" の UID でサンプルを起動してください。起動後、"1111" は "2222" からの DirectMessage を受け取ってメッセージのやり取りを開始するため、"1111" を起動後に "2222" を起動してください。

### サンプルコード説明

#### DirectMessage を使用する全体的な流れ

1. Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続します。\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。
2. セットアップ\
   DirectMessage モジュールを初期化します。

   ```
   diarkis = Diarkis::DiarkisAllocShared<DiarkisInterfaceDirectMessageSimple>(uid);
   ...
   // Diarkis Module の DirectMessage をセットアップ
   // DiarkisInterface 内で管理されている DirectMessage モジュールを初期化します。
   // DirectMessage モジュールのインスタンスを確保して、通信に使用する TCP/UDP モジュールと関連付けます。
   // 以降、DirectMessage モジュールのポインタを DiarkisInterface から取得して DirectMessage の機能にアクセスします。
   diarkis->SetupDirectMessage();
   ```
3. メッセージの送信\
   DirectMessage モジュールは Diarkis サーバに接続されていれば送受信することができます。本サンプルではセットアップ処理完了後にすぐにメッセージの送受信処理を行っています。

   ```
   std::shared_ptr<DirectMessageSimple> dm = diarkis->GetDirectMessage();
   ...
   // UID を指定してメッセージを送信
   dm->Send(targetUid, payload.data(), payload.size());
   ```
4. メッセージの受信\
   メッセージを受信すると `DiarkisDirectMessageBase::OnMessage`が発火します。\
   コールバックでの処理をカスタマイズする方法については後述します。
5. 切断処理

   ```
   // 指定したユーザーとの接続を切断します。
   // 切断する際に任意のデータを切断先のユーザーに送信することができます。
   dm->Disconnect(targetUid, payload.data(), payload.size());
   ```
6. 終了処理\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。

#### DirectMessage のイベント処理カスタマイズ

DirectMessage 関連の処理をカスタマイズする方法については [Diarkis Module のカスタマイズ](/diarkis-client/diarkis-module/how-to-customize-module) を参照してください。 サンプルでは以下のクラスでカスタム処理を実装しています。

メッセージ受信時の処理

```
/*
 * DirectMessage 関連の処理をアプリ側でカスタマイズするための実装。
 * DirectMessage 関連のデータを受信した際に On... 系のコールバックが発火するため、アプリが処理したい内容を実装します。
 * 本サンプルでは取得したメッセージをコンソールへ表示しています。
 */
class DirectMessageSimple : public DiarkisDirectMessageBase
{
    ...
    /**
     * @~japanese
     * @brief 他のリモートユーザーから　DirectMessage が送られてきた際に呼ばれるコールバックイベントを取得する。
     * @details DirectMessage Message の通知 ( サーバからのPush ) が送られた際に呼ばれる。
     * @~
     */
    void OnMessage(const DiarkisDirectMessageEventArgs& e) override
    {
        Diarkis::StdString str((const char*)e.GetPayload().data(), e.GetPayload().size());
        DiarkisUtils::Print("DirectMessageSimple::OnMessage called - %s", str.c_str());
        DiarkisDirectMessageBase::OnMessage(e);
        anyMessageReceived_ = true;
    }
    ...

```

カスタマイズしたクラスを `DiarkisInterfaceBase` で使用するには、 `DiarkisInterfaceBase` を継承してカスタマイズした `DirectMessageSimple` クラスのインスタンスを作成するようにしてください。

```
/*
 * DirectInterface が内部で管理する各機能のモジュールをアプリ固有のものに置き換えるために DiarkisInterfaceBase を継承したクラスを実装します。
 */
class DiarkisInterfaceDirectMessageSimple : public DiarkisInterfaceBase
{
public:
    ...
    void SetupDirectMessage(void)
    {
        if (udpBase_ != nullptr && udpBase_->Get() != nullptr)
        {
            // You can use the customized DirectMessage class by instantiating it here.
            // アプリ側でカスタマイズしたクラスを作成します。
            if (dmBase_ == nullptr)
                dmBase_ = Diarkis::DiarkisAllocShared<DirectMessageSimple>();
            // Initialize DirectMessage instance and register callback functions.
            // DirectMessage クラスを初期化してコールバックイベントを登録
            dmBase_->SetupUdp(udpBase_->Get(), this->GetLoggerFactory());
        }
        ...

```


# group\_sample

## group\_sample サンプル

### 概要

Group モジュールを使用して 2 人のユーザーがメッセージを送受信するサンプルコードです。\
Group モジュールの特徴については[こちらのページ](/diarkis-modules/group)を参照してください。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`group_sample.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`group_sample.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは起動すると自動的に Group に接続してメッセージを送信します。 ほぼ同じタイミングで 2 つのサンプルを起動することで、それぞれのユーザーが送信したデータがお互いに受信する様子を確認することができます。

### サンプルコード説明

#### Group を使用する全体的な流れ

1. Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続します。\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。
2. セットアップ\
   Group モジュールを初期化します。

   ```
   diarkis = Diarkis::DiarkisAllocShared<DiarkisInterfaceBase>(uid);
   ...
   // Diarkis Module の Group のセットアップ
   diarkis->SetupGroup(false);
   ```
3. Group への参加

   ```
   // サーバに Group 参加を通知
   std::vector<uint8_t> joinMessage = {};
   diarkis->GetGroupBase()->SendRandomJoinGroup(60, joinMessage, 200, true);

   // Group に参加したか確認
   while (diarkis->GetGroupBase()->IsJoin() == false)
   {
       std::this_thread::sleep_for(std::chrono::milliseconds(100));
       continue;
   }
   ```
4. メッセージの送信\
   Group に参加完了後、Group に対してメッセージを送信します。\
   本サンプルでは `DiarkisGroupBase::SendBroadcastToGroup` を使用し、Group に参加しているメンバー全員にメッセージを送信します。

   ```
   std::vector<uint8_t> message = {};
   ...
   // message に作成したペイロードを送信します。
   // このメソッドを使用して送信したメッセージは DiarkisGroupBase::OnGroupMemberBroadcast で受け取ることができます。
   // Broadcast しているため、自分自身にもメッセージが返ってきます。
   diarkis->GetGroupBase()->SendBroadcastToGroup(message.data(), message.size(), false);
   ```
5. メッセージの受信\
   メッセージを受信すると `DiarkisGroupBase::OnGroupMemberBroadcast` が発火します。\
   コールバックでの処理をカスタマイズする方法については後述します。
6. 切断処理 Group から切断するには `DiarkisGroupBase::SendLeaveGroup` を使用します。

   ```
   std::vector<uint8_t> leaveMessage = {};
   diarkis->GetGroupBase()->SendLeaveGroup(groupId, leaveMessage);
   ```
7. 終了処理\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。

#### Group 関連処理のカスタマイズ

Group 関連の処理をカスタマイズする方法については [Diarkis Module のカスタマイズ](/diarkis-client/diarkis-module/how-to-customize-module) を参照してください。  サンプルでは以下のクラスでカスタム処理を実装しています。

ブロードキャストメッセージ受信時の処理

```
/**
* @~Japanese
* @brief Group モジュールで発生するイベントをアプリ側でカスタマイズするための実装
*/
class DiarkisGroupSample : public DiarkisGroupBase
{
public:
    DiarkisGroupSample() : DiarkisGroupBase() {}
    ~DiarkisGroupSample() override {}

    /**
    * @~japanese
    * @brief Group メンバーからのブロードキャストメッセージを受信した際に呼ばれるコールバック関数
    * @args[in] transportType 通信に使用されたプロトコルタイプ
    * @args[in] e ブロードキャストメッセージのペイロード
    */
    void OnGroupMemberBroadcast(DiarkisTransportType transportType, const DiarkisPayloadEventArgs& e) override
    {
        // sample_main 内で SendBroadcastToGroup に渡されたペイロードをパースしてデータを取得します。
        ...
```

カスタマイズしたクラスを `DiarkisInterfaceBase` で使用するには、 `DiarkisInterfaceBase` を継承してカスタマイズした `DiarkisGroupBase` クラスのインスタンスを作成するようにしてください。

```
/**
 * DiarkisInterface が内部で管理する各機能のモジュールをアプリ固有のものに置き換えるために DiarkisInterfaceBase を継承したクラスを実装します。
 */
class DiarkisInterfaceGroupSample : public DiarkisInterfaceBase
{
public:
    ...
    void SetupGroup(bool bRetry = false) override
    {
        if (groupBase_ == nullptr)
        {
            // You can use the customized Group class by instantiating it here.
            // アプリ側でカスタマイズしたクラスを作成します。
            groupBase_ = Diarkis::DiarkisAllocShared<DiarkisGroupSample>();
        }

        // Initialize Group instance and register callback functions.
        // Group クラスを初期化してコールバックイベントを登録
        DiarkisInterfaceBase::SetupGroup(bRetry);
        ...

```


# matching\_and\_turn

## matching\_and\_turn サンプル

### 概要

MatchMaker モジュールを使用してマッチングを行った後、マッチしたユーザー達が別のサーバに接続してメッセージを送受信する複合的なサンプルです。 このサンプルは、マッチングを経て集まったユーザー同士が別のサーバに移動してゲームの本編部分をプレイする動線を模したものになっています。\
また、本サンプルではマッチング後、別サーバ上でのインゲームデータのやり取りに CSAR を使用しています。 CSAR を利用することによりマッチングしたユーザー同士が集まった場で唯一の Authority を決定しゲーム全体の進行管理役として扱ったり、Room と P2P の透過的な利用ができたりゲームを実装するうえで様々なメリットがあります。CSAR についての詳細は[こちら](/diarkis-client/csar-clustered-server-authoritative-ruler)を参照してください。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

また、本サンプルではマッチングに使用する UDP サーバとインゲームでの通信用の UDP サーバの２つのサーバを用意する必要があります。チュートリアルでは UDP サーバを１台だけ起動する手順になっているため、`DIARKIS_SERVER_TYPE`  に `TURN` を指定してもう１台 UDP サーバを起動しておいてください。

**起動コマンド例**

`DIARKIS_SERVER_TYPE=TURN ./remote_bin/udp`

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`matching_and_turn.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`matching_and_turn.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは起動すると MatchMaker のチケットを発行してマッチング待ち状態に入ります。 サンプルプログラムを 2 つ起動するとチケットがマッチし別サーバ上で CSAR の GameInstance に入る処理に移行します。 GameInstance に接続後、それぞれのユーザーがメッセージを送受信しプログラムが終了します。

### サンプルコード説明

本サンプルはマッチメイキング部分とインゲーム部分に分けて実装されており、マッチメイキングとインゲームで別のサーバへ接続する想定の実装となっています。 タイミングや機能によって接続先サーバを切り替えるには、接続先のサーバ毎に `DiarkisInterfaceBase` を用意して使用する必要があります。 マッチメイキング部分では `DiarkisInterfaceBase` を直接利用していますが、インゲーム部分では CSAR が内部で管理してる `DiarkisInterfaceBase` を間接的に利用しています。\
また、本サンプルでは基礎的な Diarkis サーバへの接続方法等は説明していません。必要に応じて [room\_broadcast ](/diarkis-client/samples/cpp/room-broadcast)等の基礎的なサンプルを参照してください。

#### マッチメイキング処理

`MatchMaking` 関数に実装されています。

**DiarkisInterface について**

マッチメイキングではマッチメイキング用のサーバに接続するために専用の `DiarkisInterfaceBase` のインスタンスを用意して使用しています。

```
bool MatchMaking(const std::string& host, const std::string& uid, const std::string& clientKey, std::string& gameInstanceId)
{
    // DiarkisInterfaceインスタンスの作成
    std::shared_ptr<DiarkisInterface> diarkisMatch = Diarkis::DiarkisAllocShared<DiarkisInterface>(uid);
    ...

```

このインスタンスは `MatchMaking` 関数を抜ける際に破棄されており、このタイミングでマッチメイキング用サーバから切断しています。

**マッチメイキング**

本サンプルでは MatchMaker モジュールのチケットを使用してマッチングを行っています。

```
...
// サーバに MatchMaker IssueTicket を送信
diarkisMatch->GetMatchMakerBase()->SendIssueTicket(0);

while (!diarkisMatch->GetMatchMakerBase()->IsTicketComplete())
{
    ...
```

MatchMaker モジュールのチケットの使用方法については [matchmaker\_ticket](/diarkis-client/samples/cpp/matchmaker-ticket) サンプルに詳細が記載されていますのでそちらを参照してください。

**インゲーム用サーバへ接続する情報の共有**

マッチング完了後、マッチしたユーザー同士でインゲーム用サーバへ接続するために情報を共有しています。

```
// GameInstanceId をマッチングのオーナーから送信する
if (owner)  // matching owner
{
    // マッチングサーバのオーナーである場合、マッチングしたユーザに GameInstanceId を送信する
    // GameInstanceId は一般的にゲーム側で作成する必要がある。
    // 今回はテストのためにマッチングオーナーが作成する。
    gameInstanceId = "TestGameInstanceId";
    std::vector<uint8_t> gameInstanceIdVec(gameInstanceId.begin(), gameInstanceId.end());
    diarkisMatch->GetMatchMakerBase()->SendTicketBroadcast(0, gameInstanceIdVec);
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
else  // not matching owner (member)
{
    // マッチングのオーナーでない場合、マッチングのオーナーから GameInstanceId を待つ
    DiarkisUtils::Print("I am not owner. Receiving GameInstanceId from matching owner.");
    while (true)
    {
        ...
```

CSAR では任意の文字列を使用して同じ GameInstance に参加することができます。 本サンプルではマッチングオーナーとなったユーザーが決定した任意の文字列を他のユーザーに送信することで同じ GameInstance に参加するための情報を共有しています。 この時のメッセージの送信にはチケットでマッチしたユーザー全員にメッセージを送信することができる `DiarkisMatchMakerBase::SendTicketBroadcast` が使用されています。

ここまでのプロセスでマッチングが完了し、マッチしたユーザー間で同じ GameInstance に参加するための情報が共有されました。

#### インゲーム部分

`StartGameInstanceAndSendMessage` 関数に実装されています。 インゲーム部分では CSAR を使用し、同じ GameInstance に参加してメッセージのやり取りを行っています。

**同じ GameInstance に参加**

`StartGameInstanceAndSendMessage` では `MatchMaking` で共有された GameInstanceID を使用して CSAR の GameInstance に接続しています。

```
...
const int numClients = 2;
GameInstanceHostClientConfig config;
// MatchMaking 関数で共有された GameInstanceID を使用する
config.gameInstanceId = gameInstanceId.c_str();
config.networkType = GameInstNetworkType::ROOM_AND_P2P;
config.minMembers = 1;
config.maxMembers = numClients; // Game instance に参加するクライアント数 // Number of clients to join the game instance
manager->StartGameInstance(config);
...
```

各ユーザーが同じ GameInstanceID を使用しているため同じ GameInstance に参加することができます。

**メッセージの送受信**

CSAR の GameInstance に参加後は通常の CSAR の利用方法でメッセージの送受信を行うことができます。

```
...
// メッセージを送信
if (manager->IsAuthority())
{
    msg = "I am host. uid=" + std::string(uid);
    data = std::vector<uint8_t>(msg.begin(), msg.end());
    manager->SendToClients(data.data(), data.size(), reliability);
}

msg = "I am client. uid=" + std::string(uid);
data = std::vector<uint8_t>(msg.begin(), msg.end());
manager->SendToAuthority(data.data(), data.size(), reliability);
...
```

### 備考

本サンプルではサンプルとして試しやすくするために 1 つの Diarkis サーバに接続しています。 しかし、実際のアプリではマッチメイキング用とインゲーム用のサーバが別々に用意されることになります。 インゲーム用のサーバが別に用意された環境では接続先のアドレスが異なりますので、マッチメイキング後に GameInstanceID とともに接続先のインゲームサーバのアドレスを共有して接続先も切り替えてください。


# matchmaker\_ticket

## matchmaker\_ticket サンプル

### 概要

MatchMaker モジュールのチケット機能を使用して 2 人のユーザーをマッチングさせ、マッチしたユーザー同士でメッセージをやり取りするサンプルです。 MatchMaker モジュールの特徴については[こちらのページ](/diarkis-modules/matchmaker)を参照してください。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`matchmaker_ticket.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`matchmaker_ticket.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは起動すると自動的にチケットを発行してマッチング待機状態になります。 サンプルプログラムを 2 つ起動することで、マッチングが成立しメッセージのやり取りに進みます。

### サンプルコード説明

#### MatckMaker のチケットを使用する全体的な流れ

1. Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。
2. セットアップ\
   MatchMaker モジュールを初期化します。

   ```
   diarkis = Diarkis::DiarkisAllocShared<DiarkisInterface>(uid);
   ...
   // Diarkis Module の MatchMaker のセットアップ
   diarkis->SetupMatchMaker();
   ```
3. チケットの発行 `DiarkisMatchMaker::SendIssueTicket` でチケットを発行します。

   ```
   // サーバにMatchMaker IssueTicket を送信
   diarkis->GetMatchMakerBase()->SendIssueTicket(0);
   ```
4. マッチング完了待機\
   `DiarkisMatchMaker::IsTicketComplete` でチケットがマッチしたかどうかを判定することができます。また、`DiarkisMatchMaker::SendTicketCancel` を使用してマッチ完了前にキャンセルをリクエストすることも可能です。

   ```
   bool bCancel = false;
   int wait_cnt = 0;
   // DiarkisRoomBase::OnIssueTicketを待つ
   while (diarkis->GetMatchMakerBase()->IsTicketComplete() == false) {
       std::this_thread::sleep_for(std::chrono::milliseconds(100));
       wait_cnt++;
       if (wait_cnt > 100)
       {
           diarkis->GetMatchMakerBase()->SendTicketCancel(0);
           bCancel = true;
           break;
           }
       }
   ...
   ```
5. メッセージの送信\
   マッチング完了後、マッチしたユーザーに対してメッセージを送信します。`DiarkisMatchMaker::SendTicketBroadcast` を使用するとマッチしたチケットを発行したユーザー全員にメッセージを送信することができます。

   ```
   std::string str = "Goodbye";
   std::vector<uint8_t> message(str.begin(), str.end());
   ...
   diarkis->GetMatchMakerBase()->SendTicketBroadcast(0, message);
   ...
   ```
6. 切断処理\
   `DiarkisMatchMaker::SendTicketLeave` を使用してマッチしたチケットから抜けることができます。

   ```
   diarkis->GetMatchMakerBase()->SendTicketLeave(0);
   ```
7. 終了処理\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。


# p2p\_rudp\_sample

## p2p\_rudp サンプル

### 概要

Room モジュールを使用してユーザー同士が P2P 接続し、RUDP (Reliable UDP) を使用して P2P 通信を行うサンプルです。 Room モジュールの特徴については[こちらのページ](/diarkis-modules/room)を参照してください。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`p2p_rudp_sample.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`p2p_rudp_sample.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは起動すると Room に接続し、Room のメンバー数が 2 人になるまで待機します。 サンプルプログラムを 2 つ起動することで 2 人のユーザーが Room に参加し P2P 接続の処理が開始します。

### サンプルコード説明

#### Room の P2P を使用する全体的な流れ

1. Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続します。\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。
2. セットアップ\
   Room と P2P モジュールを初期化します。

   ```
   diarkis = Diarkis::DiarkisAllocShared<DiarkisInterface>(uid);
   ...
   // Diarkis Module の Room のセットアップ
   diarkis->SetupRoom();
   // Diarkis Module の P2P のセットアップ
   diarkis->SetupP2P();
   ...
   ```
3. Room へ接続\
   `DiarkisRoomBase::SendCreateOrJoinByCustomID` を使用して Room に接続します。Room に接続することが目的のため `DiarkisRoomBase::SendJoinRandomRoom` 等、他の接続用の API を使用しても問題ありません。

   ```
   auto roomBase = diarkis->GetRoomBase();
   roomBase->SendCreateOrJoinByCustomID("sample_room", 4, 200, uid.c_str(), false);
   ...
   ```
4. P2P 接続を開始\
   Room に接続後、`DiarkisRoomBase::SendStartP2PSync` を実行することで P2P 接続を開始します。この API は Room に接続しているメンバーの中で誰か 1 人が実行すれば他のユーザー含めて全員の P2P 接続処理が開始します。本サンプルでは Room のオーナーとなっているユーザーが代表してこの API を実行しています。

   ```
   // Start P2P
   if (uid == ownerUid)
   {
       roomBase->SendStartP2PSync();
   }
   ```
5. P2P 接続完了待ち\
   本サンプルでは全ユーザーの P2P 接続が完了するまで待機します。 `DiarkisP2PBase::GetConnectedUsers` で P2P 接続が完了しているユーザー数をチェックすることができるため、この人数が想定している人数になるまで待機しています。 `DiarkisP2PBase::GetConnectedUsers` は接続しているピアの数となるため参加人数 - 1 でチェックしています。

   ```
   Diarkis::StdVector<Diarkis::StdString> peerUids = p2pBase->GetConnectedUsers();
   while (peerUids.size() < numClients - 1)
   {
       std::this_thread::sleep_for(std::chrono::milliseconds(30));
       peerUids = p2pBase->GetConnectedUsers();
   }
   ...
   ```
6. P2P で RUDP メッセージを送信\
   P2P での接続完了後、`DiarkisP2PBase::SendBroadcast` を使用して P2P 接続している全ユーザーに対してメッセージを送信しています。 この時 Reliablity に `Reliability::RELIABLE_ORDERED` や `Reliability::RELIABLE_UNORDERED` を指定すると RUDP としてメッセージを送信します。 `Reliability::RELIABLE_ORDERED` では到達保証・順番保証となり `Reliability::RELIABLE_UNORDERED` では到達保証のみとなります。\
   また、RUDP では MTU を超える大きなサイズのパケットも送信することができます。簡単に大量のデータを送ることが出来てしまいますので、ネットワーク負荷を考慮してご使用ください。

   ```
   if (num % 10 == 0)
   {
       reliability = Reliability::RELIABLE_ORDERED;
   }

   // RUDP で大きいサイズ (2000 byte) のメッセージを送信
   if (num % 100 == 0)
   {
       reliability = Reliability::RELIABLE_UNORDERED;
       sendSize = PayloadSize;
   }

   p2pBase->SendBroadcast(buff.data(), sendSize, reliability);
   ...
   ```
7. P2P でメッセージを受信\
   P2P でメッセージを受信すると `DiarkisP2PBase::OnP2PMessage` が発火します。 `DiarkisP2PBase::OnP2PMessage` の引数の `DiarkisMessageEventArgs` では送信元 UID やペイロードなど様々な情報を取得することができます。 ユーザーは `DiarkisP2PBase` を継承し `DiarkisP2PBase::OnP2PMessage` をオーバーライドすることで受信したデータに対してアプリ固有の処理を実装することができます。

   ```
   void DiarkisP2PBase::OnP2PMessage(const DiarkisMessageEventArgs& args)
   {
       ...
   ```
8. 切断処理 `DiarkisP2PBase::Disconnect` を使用して P2P 接続を切断します。このとき、Room に接続したままであれば Room 経由で通信を行うことが可能です。

   ```
   p2pBase->Disconnect();
   ```
9. 終了処理\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。


# session\_simple

## session\_simple サンプル

### 概要

4 人のユーザーが Session モジュールを使用してメッセージのやり取りを行います。 Session モジュールの特徴については[こちらのページ](/diarkis-modules/session)を参照してください。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[1. Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`session_simple.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`session_simple.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは 4 クライアントが既定の UID で接続する前提の実装となっています。起動時は "1111", "2222", "3333", "4444" の UID でサンプルを起動してください。\
起動後、"1111" は他のユーザーを招待して Session に参加する形でサンプルが動作しますので "1111" 以外のユーザーを起動後、最後に "1111" を起動してください。

### サンプルコード説明

#### Session を使用する全体的な流れ

1. Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続します。\
   詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。
2. セットアップ\
   Session モジュールを初期化します。

   ```
   diarkis = Diarkis::DiarkisAllocShared<DiarkisInterfaceSessionSimple>(uid);
   ...
   // DiarkisInterface 内で管理されている Session モジュールを初期化します。
   // Session モジュールのインスタンスを確保して、通信に使用する UDP モジュールと関連付けます。
   // 以降、Session モジュールのポインタを DiarkisInterface から取得して Session の機能にアクセスします。
   diarkis->SetupSession();
   ```
3. Session の作成\
   `DiarkisSessionBase::SendCreate` を使用して Session の作成をサーバへリクエストします。`DiarkisSessionBase::HasSession` を使用して、特定の Session が作成済みかをチェックすることができます。

   ```
   session->SendCreate(SessionSimple::SessionType::Global, 3, 60);
   while (!session->HasSession(SessionSimple::SessionType::Global))
   {
       std::this_thread::sleep_for(std::chrono::milliseconds(100));
   }
   ```
4. Session への招待\
   `DiarkisSessionBase::SendInvite` を使用して他のユーザーを Session へ招待します。招待するにはユーザーの UID を知っている必要があるため Session 以外の方法で UID を事前に共有する必要があります。本サンプルではすでに共有済みの状態を想定して実装されています。

   ```
   const char* inviteMembers[2] =
   {
       "2222",
       "4444"
   };
   session->SendInvite(SessionSimple::SessionType::Global, &inviteMembers[0], 2, "Do you want to join us?");
   ```
5. Session への参加\
   他のユーザーから招待を受けると `DiarkisSessionBase::OnSessionInvite` が発火します。 `DiarkisSessionBase::OnSessionInvite` では引数の `DiarkisSessionInviteEventArgs` で Session ID と SendInvite に渡されたメッセージを取得することができます。 本サンプルでは `DiarkisSessionInviteEventArgs` で取得した Session ID を使用して `DiarkisSessionBase::SendJoin` を実行することで Session に参加しています。\
   また、Session モジュールでは複数の Session に参加することが可能です。 本サンプルでも全体 Session とチーム Session に見立てて複数の Session に参加しています。

   ```
   void OnSessionInvite(const DiarkisSessionInviteEventArgs& e) override
   {
       // Invite されたときの session type をチェックして join しています。
       switch (e.GetSessionType())
       {
       case SessionType::Global:
           if (ownUID_ == "2222" || ownUID_ == "4444")
           {
               DiarkisUtils::Print("SendJoin(%d, %s) is called in %s", e.GetSessionType(), e.GetSessionID().c_str(), ownUID_.c_str());
               SendJoin(e.GetSessionType(), e.GetSessionID().c_str());
           }
           break;
       ...
   ```
6. メッセージの送信\
   Session に参加後、`DiarkisSessionBase::SendBroadcast` 等メッセージ送信系の API を使用して Session に参加しているメンバーにメッセージを送ることができます。

   ```
   const char* GlobalBroadcastMessage = "Message to the Global session type";
   session->SendBroadcast(SessionSimple::SessionType::Global, GlobalBroadcastMessage);
   ```
7. メッセージの受信\
   他のユーザーからメッセージを受信すると `DiarkisSessionBase::OnSessionBroadcast` や `DiarkisSessionBase::OnSessionMessageTo` が発火します。ユーザーは `DiarkisSessionBase` のこれらのメソッドをオーバーライドすることで受信したデータに対してアプリ固有の処理を実装することができます。

   ```
   void OnSessionBroadcast(const DiarkisSessionNotificationEventArgs& e) override
   {
       DiarkisSessionBase::OnSessionBroadcast(e);
       DiarkisUtils::Print("SessionSimple::OnSessionBroadcast(%d, %s) is called in %s", e.GetSessionType(), e.GetContent().c_str(), ownUID_.c_str());
   }

   void OnSessionMessageTo(const DiarkisSessionNotificationEventArgs& e) override
   {
       DiarkisSessionBase::OnSessionMessageTo(e);
       DiarkisUtils::Print("SessionSimple::OnSessionMessageTo(%d, %s) is called in %s", e.GetSessionType(), e.GetContent().c_str(), ownUID_.c_str());
   }
   ```
8. ユーザーのキック\
   `DiarkisSessionBase::SendKick` を使用することで特定のユーザーを Session から強制的に追い出すことができます。

   ```
   // "4444" をキックして SessionType::Global(0) から除外します。
   session->SendKick(SessionSimple::SessionType::Global, "4444");
   ```
9. 切断処理\
   `DiarkisSessionBase::SendLeave` を使用することで Session から退出することができます。

   ```
   session->SendLeave(SessionSimple::SessionType::Global);
   ```
10. 終了処理\
    詳細については [Diarkis モジュール利用の全体的な流れ](https://help.diarkis.io/diarkis-client/samples/cpp/pages/r3yF3oFZnxAPw2prL13v#diarkis-モジュール利用の全体的な流れ) を参照してください。


# room\_chat

### room\_chat サンプル

#### 概要

room\_chat サンプルは、Diarkis サーバーの Room Chat 機能を使用したインタラクティブな CLI サンプル・プログラムです。ユーザーは Room に参加し、リアルタイムでカスタムチャットメッセージの送受信や、サーバーへの同期コマンドでチャット履歴の取得を行うことができます。

room\_chat サンプルでは以下の機能を確認することができます。

* Room の参加
* Room メンバーへのカスタムチャットメッセージ送信
* チャット履歴の取得（同期コマンド）
* Room からの退室

#### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

1. [Diarkis サーバーをローカル環境で起動する](/getting-started/tutorial/setup-local-server)

#### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

| 引数         | 説明                            |   |
| ---------- | ----------------------------- | - |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |   |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |   |
| clientKey  | クライアントキーを任意の文字列で指定してください      |   |

起動例：

<pre><code><strong>room_chat.exe 192.168.1.123:7000 1111 AAAA
</strong></code></pre>

#### room\_chat サンプル解説

Diarkis ランタイムおよび Diarkis Module を初期化し、Diarkis サーバーへ接続します。詳細については [Diarkis モジュール利用の全体的な流れ](/diarkis-client/diarkis-module) を参照してください。

Room モジュールをセットアップします。

```
// Room モジュールのセットアップ
diarkis->SetupRoom(false);
```

RandomJoin で Room に入室します。DiarkisRoomBase::RandomJoinRoom() は参加可能な Room が存在すればその Room に参加し、存在しなければ新たに Room を作成します。サーバーへリクエストを送信後、DiarkisRoomBase::IsJoin() で Room 入室完了を待機します。

Room 入室後、サンプルはインタラクティブなコマンドループに入ります。標準入力から以下のコマンドで操作できます。

* /send/<メッセージ> — Room にチャットメッセージを送信する
* /sync — サーバーから全チャット履歴を取得する。OnRoomChatLog コールバックで結果が表示される
* /quit — Room を退室してプログラムを終了する
* /help — 使用可能なコマンドを表示する

DiarkisRoomBase::SendLeaveRoom() で Room からの退室をリクエストします。DiarkisRoomBase::IsLeave() で退室完了を待機します。

終了処理

[Diarkis モジュール利用の全体的な流れ](/diarkis-client/diarkis-module) を参照してください。

#### 注意点

* Windows 環境では、コンソールを UTF-8 エンコーディングに設定して日本語などマルチバイト文字のチャットメッセージを正しく処理します。
* 複数のクライアントを別々のターミナルウィンドウから同時に起動して、Room Chat の動作を確認できます。
* /sync コマンドはサーバーからチャット履歴を取得します。結果は OnRoomChatLog コールバックで表示されます。


# packet\_manipulator\_simple

## packet\_manipulator\_simple サンプル

<figure><img src="/files/GmkfI81xUtZqzgWj9snu" alt=""><figcaption></figcaption></figure>

### 概要

packet\_manipulator\_simple サンプルは Packet Manipulator の基本的な使用方法とプリセットフィルタの設定方法のサンプルとなっています。\
サンプルコードでは Packet Manipulator の実装コードのフォーカスしているため、Diarkis Runtime の基本的な実装については関数化して説明等は記載していません。 Diarkis Runtime の基本的な実装について知りたい場合は [room\_broadcast サンプル](broken://pages/UQBFCMlPKjhZLYEDxmC3) や [Diarkis モジュール利用の全体的な流れ](/diarkis-client/diarkis-module#diarkis-module-noi) を参照してください。\
また、Packet Manipulator 全般については [Packet Manipulator ](/diarkis-client/runtime-library/packet-manipulator)を参照してください。

**本サンプルはデバッグ機能のサンプル実装のため Release ビルドはできません。**

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[Diarkis サーバをローカルで起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`packet_manipulator_simple.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`packet_manipulator_simple.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは起動すると Room に接続し、Room のメンバー数が 2 人になるまで待機します。 2 ユーザーが Room に接続後、Packet Manipulator のフィルタ設定を変えながら通信を行いフィルタ適用の結果を画面に表示します。

## packet\_manipulator\_simple サンプル解説

本セクションではサンプルの実装ポイントについて解説します。

### Packet Manipulator の更新処理実装

Packet Manipulator では内部状態を更新するために定期的に `IPacketManipulator::Update` を実行する必要があります。\
本サンプルではこの処理を専用スレッドを用意して実装しています。具体的には `LaunchPacketManipulatorThread` で専用スレッドを起動し、`ManipulatorLoop` で `IPacketManipulator::Update` を実行しています。 サンプルのコメントにもありますように、この処理の実行頻度が Packet Filter の実行間隔となり、Packet Filter の機能にも影響します。本サンプルではパケット遅延フィルタを利用しており、なるべく遅延の値がぶれないように 30 ms 間隔で `IPacketManipulator::Update` を呼び出すようにしています。\
更新処理に専用スレッドを用意することは必須というわけではなく、ゲームのループで毎フレーム実行するような形でも問題ありません。

### プロファイル情報の表示

本サンプルでは `FilterApplyPoint::RawUdpReceive` へ Packet Filter を適用しています。この Apply Point は Diarkis の UDP 通信すべてに影響する Apply Point で、この Apply Point でパケロスや遅延が発生すると Diarkis のプロファイル情報にも影響するようになっています。本サンプルでは Packet Filter の影響をわかりやすくするためこのプロファイル情報を利用しています。

### Packet Manipulator の利用方法の解説

1. `DiarkisGetPacketManipulator` を使用して Packet Manipulator のインスタンスを Diarkis Runtime から取得します。

```
// Get Packet Manipulator pointer from the runtime.
// You can access the all features of the Packet Manipulator from here.
auto pacman = Diarkis::System::PacketManipulator::DiarkisGetPacketManipulator();
```

2. `IPacketManipulator::GetOrAllocFilterSet` を使用して `FilterApplyPoint::RawUdpReceive` Apply Point の `IFilterSet` を取得します。

```
// Get a filter set for Raw UDP Receive packets.
// Packet Manipulator works by applying filters in the filter set for a specific apply point.
// In this sample, we use the Raw UDP Receive filter set to apply the filters to incoming raw UDP packets in the Diarkis runtime.
auto rawUdpRecvFilterSet = pacman->GetOrAllocFilterSet(Diarkis::System::PacketManipulator::FilterApplyPoint::RawUdpReceive);
```

3. `IFilterSet` は `std::weak_ptr` で返されるため、使用する前に有効性をチェックしてください。\
   また、Filter Set と Apply Point に互換性がない場合は nullptr が返りますのでご注意ください。　　 本サンプルでは `IFilterSet` のポインタを取得後、プリセットのパケロスフィルターを設定しています。

```
if (auto rawUdpRecvFilterSetPtr = rawUdpRecvFilterSet.lock())
{
    // You can specify the packet loss rate (probability) when adding the filter.
    // You can also set a seed value for random number generation.
    // This filter uses the pseudo-random number generator to decide whether to drop a packet or not based on the specified probability.
    // Basically, the order of packet loss is always the same when you use the same seed value.
    // But the order of the incoming packets will be different in different network conditions, so the actual lost packets may differ, even with the same seed.
    rawUdpRecvFilterSetPtr->AddPacketLossFilter(packetLossProbability, 12345);
}
```

4. `FilterApplyPoint::RawUdpReceive` は Diarkis が扱うすべての UDP パケットに対して適用されます。本サンプルでは Room と P2P でパケットを送信してプロファイルのパケロス率に影響していることを確認できます。
5. 以下、パケットの遅延と複数 Packet Filter 適用のサンプル実装となっていますが、基本的な構造はここまでの流れと同じです。


# packet\_manipulator\_custom\_filter

## packet\_manipulator\_custom\_filter サンプル

<figure><img src="/files/cws6yTO7Lpgo7NEcQ0bM" alt=""><figcaption></figcaption></figure>

### 概要

packet\_manipulator\_custom\_filter サンプルは Packet Manipulator で使用する、Custom Filter の実装方法と使用方法のサンプルとなっています。\
サンプルコードでは Packet Manipulator の実装コードのフォーカスしているため、Diarkis Runtime の基本的な実装については関数化して説明等は記載していません。 Diarkis Runtime の基本的な実装について知りたい場合は [room\_broadcast サンプル](broken://pages/UQBFCMlPKjhZLYEDxmC3) や [Diarkis モジュール利用の全体的な流れ](/diarkis-client/diarkis-module#diarkis-module-noi) を参照してください。 また、Packet Manipulator 全般については [Packet Manipulator](/diarkis-client/runtime-library/packet-manipulator) を参照してください。

**本サンプルはデバッグ機能のサンプル実装のため Release ビルドはできません。**

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[Diarkis サーバをローカルで起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

`packet_manipulator_custom_filter.exe serverAddr UID clientKey`

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`packet_manipulator_custom_filter.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは起動すると Room に接続し、Room のメンバー数が 2 人になるまで待機します。 2 ユーザーが Room に接続後、Packet Manipulator のフィルタ設定を Room と P2P で通信を行い Packet Filter の適用結果を表示しています。

## packet\_manipulator\_custom\_filter サンプル解説

本セクションではサンプルの実装ポイントについて解説します。 Packet Manipulator の使用方法の基本については [packet\_manipulator\_simple サンプル](/diarkis-client/samples/cpp/packet_manipulator_simple) をご参照ください。

### Custom Filter の実装の基本

本サンプルでは Room と P2P それぞれのパケットに対してのみフィルタを適用する Custom Filter を実装しています。\
ここでは P2P 用 Custom Filter を例にとり実装方法を解説します。\
以下が P2P メッセージパケット用の Custom Filter の全体となります。

```
/**
* @brief Custom Packet Loss Filter for P2P packets.
* @details A user can implement custom packet filter for P2P packets by inheriting from IP2PMessageReceiveFilterBase.
*          You can access the packet data and related information from the argument passed to the Apply function.
*/
class P2PPacketLossFilter : public Diarkis::System::PacketManipulator::IP2PMessageReceiveFilterBase
{
public:
    P2PPacketLossFilter() : randomGenerator_(12345) {}
    ~P2PPacketLossFilter() override {}

    /**
    * @brief Apply the filter to a P2P packet.
    * @details This method will be called for each received P2P packet.
    */
    Diarkis::System::PacketManipulator::FilterPostProcess Apply(Diarkis::System::PacketManipulator::IP2PMessageReceiveFilterArgument& arg) override
    {
        // In this sample, we drop 30% of the received P2P packets randomly.
        if (randomGenerator_() % 100 < 10)
        {
            DiarkisUtils::Print("Drop P2P packet from %s(%s:%d)", arg.GetPeerUID().c_str(), arg.GetPeerResolvedAddress().c_str(), arg.GetPeerPort());
            // The packet filter will stop processing the packet and drop it when returning Skip.
            return Diarkis::System::PacketManipulator::FilterPostProcess::Skip;
        }
        return Diarkis::System::PacketManipulator::FilterPostProcess::ApplyNextFilter;
    }
private:
    std::mt19937 randomGenerator_;
};
```

[Packet Manipulator の User Custom Filter](/diarkis-client/runtime-library/packet-manipulator#user-custom-filter) にあるように、基本的には各 Apply Point 専用の Filter Base を継承して Custom Filter を実装します。今回は `P2PMessageReceive` Apply Point のため `IP2PMessageReceiveFilterBase` を継承して実装しています。 Packet Filter の実装ではフィルタ適用時に実行される `Apply` メソッドをオーバーライドすることで処理をカスタマイズします。この Filter Base では `IP2PMessageReceiveFilterArgument` を受け取ることのできる `Apply` メソッドが純粋仮想関数として宣言されており、P2P 専用の情報を受け取って `Apply` 処理を実装することが可能となっています。\
また、`Apply` メソッドの返り値でその後のパケットの扱いをコントロールすることができます。`FilterPostProcess::Skip` を返すことでその後、Diarkis Runtime 内ではそのパケットが無かったものとして扱われます。`FilterPostProcess::ApplyNextFilter` を返すと通常通りの処理を継続します。 サンプル実装では単純に確率でパケロスさせるように `Apply` メソッドの返り値をコントロールしています。

### 複雑な Custom Filter 実装

`RoomPacketDelayFilter` では Room の push/response パケットに対して遅延を発生させる仕組みを実装しています。

`RoomPacketDelayFilter::Apply` で Packet Filter 内に受け取ったパケットの情報を保存し、そのタイミングではパケットを無視するように返り値を返します。 `RoomPacketDelayFilter::Apply` に渡されるパケットの情報は一時的なもののため、`IPacketFilterArgument::DeepCopy` を使用してパケットデータのコピーを保存します。

```
/**
* @brief Apply the filter to a room packet.
* @details This method will be called for each received room push and response packet.
*/
Diarkis::System::PacketManipulator::FilterPostProcess Apply(Diarkis::System::PacketManipulator::IRoomPushAndResponseFilterArgument& arg) override
{
    // This method and Update method will be called from the various thread in the Diarkis runtime and the user thread.
    // So we need to protect the internal data with mutex.
    std::lock_guard<std::mutex> lock(mutex_);

    if (randomGenerator_() % 100 < 10)
    {
        ...
        // Store the packet argument into the buffer for delay processing.
        // You can deep copy the argument to hold it for later processing.
        delayPackets_.emplace_back(arg.DeepCopy());

        return Diarkis::System::PacketManipulator::FilterPostProcess::Skip;
    }

    return Diarkis::System::PacketManipulator::FilterPostProcess::ApplyNextFilter;
}

```

その後、`RoomPacketDelayFilter::Update` で時間を計測し、遅延分の時間が経過した後に通常の受信時の処理を行うようになっています。 `RoomPacketDelayFilter::Update` の引数として実際にパケットを処理する関数が渡されるため、その関数を呼び出すことで通常通りのパケットの処理を行います。

```
/**
* @brief Update function called periodically to process any pending operations.
* @details This method will be called periodically from the Packet Manipulator update loop.
*/
void Update(std::function<Result(Diarkis::System::PacketManipulator::IPacketFilterArgument*)> packetHandler) override
{
    // This method and Apply method will be called from the various thread in the Diarkis runtime and the user thread.
    // So we need to protect the internal data with mutex.
    std::lock_guard<std::mutex> lock(mutex_);

    // Process all delayed packets in the buffer.
    auto iter = delayPackets_.begin();
    uint32_t deliveredCount = deliveredPacketIndex_;
    while (iter != delayPackets_.end())
    {
        ...
        // This method expects to be passed the packet filter argument that was passed to the Apply method.
        packetHandler((*iter).get());
    }
    ...
}

```


# custom\_memory\_allocator

## custom\_memory\_allocator サンプル

### 概要

Diarkis C++ ランタイム内で行われるメモリの確保・開放処理をユーザーが実装したアロケータを使用するように変更することができます。 本サンプルではカスタムアロケータの実装と設定方法について紹介します。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[Diarkis サーバをローカルで起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`custom_memory_allocator.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは単体で実行することができ、起動すると指定された Diarkis サーバに接続し切断します。

### サンプルコード説明

カスタムアロケータの実装方法と設定方法に分けて紹介します。 また、本サンプルでは基礎的な Diarkis サーバへの接続方法等は説明していません。必要に応じて room\_broadcast 等の基礎的なサンプルを参照してください。

#### カスタムアロケータの実装方法

カスタムアロケータは `ICustomAllocator` を継承して実装します。

```
/**
* This is a sample implementation of the custom allocator.
* The user can implement their own custom allocator by inheriting Diarkis::ICustomAllocator.
*/
class SampleCustomAllocator : public Diarkis::ICustomAllocator
{
public:
    ...
```

`ICustomAllocator` には以下のインターフェイスが存在し、それらをアプリ側の都合に合わせて実装します。本サンプルでは単純に malloc/free でメモリの確保と開放を行っています。

```
void* Allocate(size_t size, int flag) override
{
    ...
    return ptr;
}

void* AlignedAllocate(size_t size, size_t align, int flag) override
{
    ...
    return ptr;
}

void  Deallocate(void* ptr) override
{
    ...
}
```

#### カスタムアロケータの設定方法

実装したカスタムアロケータのインスタンスを用意し、`SetCustomAllocator` で Diarkis C++ ランタイムへ設定します。異なるアロケータが混在して使用されないように必ずすべての Diarkis 関連の API 呼び出しよりも前に設定するようにしてください。

```
// Replace the default allocator of the Diarkis runtime with the custom allocator user implemented.
// You need to set the custom allocator before calling any Diarkis runtime functions.
Diarkis::SetCustomAllocator(&gCustomAllocator);
```


# runtime\_logger

## runtime\_logger サンプル

### 概要

Diarkis Module では様々なタイプのロガーがプリセットで用意されています。 本サンプルではプリセットロガーとカスタムロガーの実装と設定方法について紹介します。

### ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。

[Diarkis サーバをローカルで起動する](/getting-started/tutorial/setup-local-server)

### サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

#### 起動例：

`runtime_logger.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは単体で実行することができ、起動すると指定された Diarkis サーバに接続し切断します。

### サンプルコード説明

カスタムアロケータの実装方法と設定方法に分けて紹介します。 また、本サンプルでは基礎的な Diarkis サーバへの接続方法等は説明していません。必要に応じて room\_broadcast 等の基礎的なサンプルを参照してください。

#### プリセットロガー

Diarkis Module では様々な種類のプリセットロガーを提供しています。 本サンプルではそれらのロガーを切り替えて実行することで、それぞれのロガーがどのような動きをするか紹介しています。\
以下が DEBUG\_OUT ロガーのサンプル実装例となります。

```
// Logging with DEBUG_OUT
// The runtime will use DebugLoggerBackend if you specify DEBUG_OUT.
// On Windows, the log messages will be output to the Debug Output window.
// On other platforms, the log messages will be output to the console.
DiarkisUtils::Print("************************ Logging with DEBUG_OUT ****************************");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
{
    DiarkisInterface::DiarkisInit(uid, LogOutType::DEBUG_OUT);

    if (RunDiarkis(host, uid, clientKey) != 0)
    {
        DiarkisInterface::DiarkisDestroy();
        return 1;
    }

    DiarkisInterface::DiarkisDestroy();
}
```

#### カスタムロガーの実装方法

カスタムロガーの実装方法についてはこちらのページを参照してください。本サンプルではこの方法に則ってカスタムロガーを実装しています。\
以下がカスタムロガーのサンプル実装となります。

```
/**
* This is a sample implementation of the custom logger.
*/
class AppCustomLoggerBackend : public ILoggerBackend
{
public:
    ...
```

Diarkis が出力するログにアプリ側で任意の文字列を追加して出力しています。

```
Result Log(const Diarkis::StdString& message, bool includeNewLine) override
{
    // Custom log output implementation
    // A log message is passed into this method, so you can implement your app-specific log output here.
    // This method could be called from multiple threads, so make sure your implementation is thread-safe.
    DiarkisUtils::Print("Add log to the app logger: %s", message.c_str());

    return Diarkis::Results::SUCCESS;
}
```


# server\_migration

## server\_migration サンプル

<figure><img src="/files/hLURzARCEF8NthjMTb9q" alt=""><figcaption></figcaption></figure>

## 概要

Diarkis サーバはサーバクラスタとして構成されており、スケールインなどで接続中のサーバが停止した場合、別のサーバへ接続しなおすサーバマイグレーションが発生することがあります。 サーバマイグレーションを実現するにあたりクライアント側でも対応実装を行う必要があるため、本サンプルではどのような実装が必要となるか説明します。\
マイグレーションに関する詳細については[こちらのページ](/diarkis-client/diarkis-module/migration)を参照してください。

## ローカル環境でサーバーを起動する

サンプルで使用するサーバーを起動するためのチュートリアルを実施して、ローカル環境で Diarkis サーバーを起動します。 本サンプルではマイグレーションを発生させるため、サンプル実行中に UDP サーバの起動と停止を行う必要があります。 チュートリアルの手順を参考にサンプルコードのコメントに従って UDP サーバのプロセスを操作してください。

[Diarkis サーバをローカルで起動する](/getting-started/tutorial/setup-local-server)

## サンプルの引数

サンプルの起動時には以下の３つのパラメータを指定してください。

| 引数         | 説明                            |
| ---------- | ----------------------------- |
| serverAddr | Diarkis サーバのエンドポイントを指定してください  |
| UID        | 接続するユーザーの ID を任意の文字列で指定してください |
| clientKey  | クライアントキーを任意の文字列で指定してください      |

**起動例：**

`server_migration.exe 192.168.1.123:7000 1111 AAAA`

### 起動方法

本サンプルは単体で実行することができ、起動すると指定された Diarkis サーバに接続し、マイグレーションが発生するまで待機します。 マイグレーションを発生させるには Diarkis サーバを起動している PC で移動先として新たに UDP サーバを起動したのちに、接続中の UDP サーバを停止します。

## サンプルコード説明

### **UDP サーバのマイグレーション対応実装**

`DiarkisUdpBase` クラスの仮想関数をオーバーライドしてマイグレーション対応コードを実装します。

```
class DiarkisUdpServerMigrationSample : public DiarkisUdpBase
{
    ...
```

以下の実装がマイグレーション対応に関連するコードとなります。`DiarkisUdpBase::OnOffline` でサーバが停止することを検出し、アプリの都合に合わせて `DiarkisUdpBase::SendMigrate` を実行します。 `DiarkisUdpBase::SendMigrate` 実行後、Diarkis ランタイムが自動的に接続中のサーバからの切断と新しいサーバへの再接続処理を実行します。 この再接続処理の中で `DiarkisUdpBase::OnConnect` と `DiarkisUdpBase::OnDisconnect` が発火し、再接続の完了を検出することができます。

```
    void OnConnect(const DiarkisConnectionEventArgs& args) override
    {
        // OnConnect is called when a connection is established to the UDP server.
        // In this sample, this callback is called when connecting to the UDP server initially or reconnecting by the migration process.
        // OnConnect は UDP サーバへの接続が確立したタイミングで呼び出されます。
        // 本サンプルでは UDP サーバへの初回接続時とマイグレーションでの再接続時に呼び出されます。
        DiarkisUdpBase::OnConnect(args);
        DiarkisUtils::Print("Connected to UDP server status=%d reconnect=%d", args.GetStatus(), args.GetReconnect());
        if (isMigrationStarted_)
        {
            isMigrationCompleted_ = true;
        }
        isMigrationStarted_ = false;
    }
    void OnDisconnect(bool isReconnect) override
    {
        // OnDisconnect is called when disconnecting from the UDP server.
        // In this sample, this callback is called when disconnecting by the migration process and disconnecting finally.
        // OnDisconnect は UDP サーバから切断されたタイミングで呼び出されます。
        // 本サンプルではマイグレーションでの再接続時と最終的な切断時に呼び出されます。
        DiarkisUdpBase::OnDisconnect(isReconnect);
        DiarkisUtils::Print("Disconnected from UDP server reconnect=%d", isReconnect);

        // If isReconnect is true, it means the server is reconnecting.
        // サーバへの再接続時の呼び出しの場合、isReconnect が true になります。
        if (isReconnect)
        {
            // There is nothing to do for the application implementation in the reconnecting process because
            // Diarkis client runtime will reconnect to the server automatically.
            // Please be carefull if doing the disconnection process of the application in the reconnecting process.
            // マイグレーションによる再接続処理では自動的にサーバの再接続が行われるため、アプリ側では何も対応する必要はありません。
            // アプリ側の通常の切断時の処理が実行されないようにご注意ください。
        }
    }
    void OnOffline(void) override
    {
        DiarkisUdpBase::OnOffline();
        DiarkisUtils::Print("DiarkisUdpServerMigrationSample::OnOffline is called");
        // This callback is triggered when it is determined that the server will be stopped due to scaling in.
        // Please call SendMigrate at an appropriate timing according to the application's implementation to move a new server.
        // During the server migration process, the connection to the server will be temporarily disconnected,
        // and communication with the server will not be possible until reconnection is complete.
        // In this sample code, SendMigrate is called immediately within this callback for simplicity.
        // サーバがスケールインなどで停止することが決定したタイミングでこのコールバックが発火します。
        // アプリ側の実装に合わせて、適したタイミングで SendMigrate を呼び出してサーバの移動を行ってください。
        // サーバの移動処理中はサーバとの接続が一時的に切断され再接続されるまでサーバと通信できない期間が発生しますので
        // アプリの仕様に合わせて適切なタイミングで SendMigrate を呼び出してください。
        // サンプルコードでは特に考慮せず、このコールバック内で即座に SendMigrate を呼び出しています。
        this->SendMigrate();
        isMigrationStarted_ = true;
    }

```

### **Room のマイグレーション対応実装**

Room を使用している場合、マイグレーションを行うには Room 専用の対応実装を行う必要があります。 `DiarkisRoomBase` クラスの仮想関数をオーバーライドしてマイグレーション対応コードを実装します。

```
class DiarkisRoomServerMigrationSample : public DiarkisRoomBase
{
    ...
```

以下の実装が Room のマイグレーション対応に関連するコードとなります。構造としては UDP サーバのマイグレーション対応実装と同様になっていますが使用するコールバックが異なります。

```
    void OnRoomMigrateStart(void) override
    {
        // This callback is triggered when migration is started on the server side.
        // There is no need to perform any special processing on the application side, but if you want to recognize that migration has started, please use this callback.
        // サーバ側でマイグレーションが開始されたタイミングでこのコールバックが発火します。
        // アプリ側で何か特別な処理を行う必要はありませんが、マイグレーションが開始されたことを認識したい場合はこのコールバックを利用してください。
        DiarkisRoomBase::OnRoomMigrateStart();
        DiarkisUtils::Print("The room migration is started...");
        isMigrationStarted_ = true;
    }

    void OnRoomMigrateComplete(const DiarkisRoomMigrateCompleteEventArgs& e) override
    {
        // This callback is triggered when migration is completed on the server side.
        // There is no need to perform any special processing on the application side, but if you want to recognize that migration has completed, please use this callback.
        // サーバ側でマイグレーションが完了したタイミングでこのコールバックが発火します。
        // アプリ側で何か特別な処理を行う必要はありませんが、マイグレーションが完了したことを認識したい場合はこのコールバックを利用してください。
        DiarkisRoomBase::OnRoomMigrateComplete(e);

        if (e.IsSuccess() == false)
        {
            DiarkisUtils::Print("Failed to Migrate. error code=%d message=%s", e.GetErrorCode(), e.GetErrorMessage().c_str());
            return;
        }

        DiarkisUtils::Print("The room migration is completed...\nsuccess=%d, RoomID=%s", e.IsSuccess(), e.GetRoomID().c_str());
        if (isMigrationStarted_)
        {
            isMigrationCompleted_ = true;
        }
    }

    void OnOffline() override
    {
        DiarkisRoomBase::OnOffline();
        DiarkisUtils::Print("DiarkisRoomServerMigrationSample::OnOffline is called");

        // This callback is triggered when it is determined that the server will be stopped due to scaling in.
        // Please call SendMigrate at an appropriate timing according to the application's implementation to move a new server(Room).
        // During the server migration process, the connection to the server will be temporarily disconnected,
        // and communication with the server will not be possible until reconnection is complete.
        // Also, please note that only the owner of the Room can call SendMigrateRoom.
        // In this sample code, SendMigrate is called immediately within this callback for simplicity.
        // サーバがスケールインなどで停止することが決定したタイミングでこのコールバックが発火します。
        // アプリ側の実装に合わせて、適したタイミングで SendMigrate を呼び出してサーバ(Room)の移動を行ってください。
        // サーバの移動処理中はサーバとの接続が一時的に切断され再接続されるまでサーバと通信できない期間が発生しますので
        // アプリの仕様に合わせて適切なタイミングで SendMigrate を呼び出してください。
        // また、Room のオーナーのみが SendMigrateRoom を呼び出すことができますのでご注意ください。
        // サンプルコードでは特に考慮せず、このコールバック内で即座に SendMigrate を呼び出しています。
        if (GetOwnUID() == GetOwnerUID())
        {
            // Only the owner of the Room calls Migrate
            // Room のオーナーが Migrate を呼ぶ
            this->SendMigrateRoom();
        }
    }
```

### **マイグレーションを発生させる手順**

ローカルで Diarkis サーバを立ち上げている場合、UDP サーバが複数プロセス起動している状態で、クライアントが接続中の UDP サーバを停止させることでマイグレーションを発生させることができます。 サンプル起動時には 1 つだけ UDP サーバを起動しておくとクライアントの接続先サーバプロセスが固定されるため、クライアント接続後に新たに UDP サーバのプロセスを起動した際に、どのプロセスを停止すればよいかが判別しやすくなります。


# Unreal Engine Plugin


# Diarkis Plugin Sample

## DiarkisPluginSample の確認手順

## 目次

* [Diarkis Unreal Engine プラグインについて](#diarkis-unreal-engine-puraguinnitsuite)
* [サンプル概要](#sanpuru)
* [確認手順](#que-ren-shou-shun)
  * [Windowsの確認手順](#windowsno)
  * [選択ビューポート](#bypto)
* [各画面の説明](#no)
  * [ログイン画面](#roguin)
  * [MainMenu 画面](#mainmenu-hua-mian)
  * [Room 画面](#room-hua-mian)
  * [Room InGame 画面](#room-ingame-hua-mian)
  * [LOD Room InGame 画面](#lod-room-ingame-hua-mian)
  * [Field 画面](#field-hua-mian)
  * [MatchMaker Host/Search 画面](#matchmaker-hostsearch-hua-mian)
  * [MatchMaker Host/Search InGame 画面](#matchmaker-hostsearch-ingame-hua-mian)
  * [MatchMaker Ticket 画面](#matchmaker-ticket-hua-mian)
  * [DirectMessage 画面](#directmessage-hua-mian)
  * [Session 画面](#session-hua-mian)
  * [Group 画面](#group-hua-mian)
  * [Zombie 画面](#zombie-hua-mian)
  * [Zombie InGame 画面](#zombie-ingame-hua-mian)
  * [DGS](#dgs)
  * [動作確認（マルチプレーオプション）](#maruchipuropushon)
* [コードについて](#kdonitsuite)
  * [C++ サンプルコード](#c-sanpurukdo)
  * [DiarkisPluginSample 側の主なコード（Diarkis Pluginを利用したサンプル）](#diarkispluginsample-nonakdodiarkis-pluginwoshitasanpuru)
  * [Diarkis Plugin + Sample の主なコード](#diarkis-plugin-sample-nonakdo)
  * [libDiarkis のコードについて](#libdiarkis-nokdonitsuite)
  * [diarkis-module Client のコードについて](#diarkis-module-client-nokdonitsuite)
  * [diarkis-module Extension のコードについて](#diarkis-module-extension-nokdonitsuite)
  * [DiarkisNetwork のコードについて](#diarkisnetwork-nokdonitsuite)
  * [Blueprint用 コールバックイベント キューイング のコードについて](#blueprint-krubakkuibento-kyingu-nokdonitsuite)
  * [イベントの Delegate のコードについて](#ibentono-delegate-nokdonitsuite)
* [クラス図](#kurasu)
* [同期機能について](#nitsuite)
  * [Diarkis Plugin で同期できる機能について](#diarkis-plugin-dedekirunitsuite)
  * [サンプル上で同期を利用している機能](#sanpurudewoshiteiru)
* [動作確認環境](#dong-zuo-que-ren-huan-jing)
  * [UE のバージョン毎の確認環境](#ue-no-bjonno)
* [動作確認手順](#dong-zuo-que-ren-shou-shun)
  * [Windows のパッケージ作成手順](#windows-nopakkji)
  * [Android 端末で動作確認手順（別途AndroidStudio環境のセットアップが必要](#android-deandroidstudionosettoappuga)

## Diarkis Unreal Engine プラグインについて

プラグインの詳細については Diarkis ヘルプセンターの [Diarkis Unreal Engine](https://help.diarkis.io/diarkis-client/game-engine-integration/ue) のページを参照してください。

## サンプル概要

* UnrealEngine 用プラグイン `Diarkis UnrealEngine Plugin` を使用したサンプルプロジェクト (`DiarkisPluginSample`) です。
* `Diarkis`の `Room, P2P, RPC, Field, MatchMaker, DirectMessage, Session, Group` 機能を確認することができます。
* 複数の `DiarkisPluginSample` を起動することで、他のクライアント端末で動作しているキャラクターの位置同期を確認できるサンプルです。
* キャラクターの位置同期は `UDP/TCP` プロトコルまたは `P2P` プロトコルを用いて確認することができます。
* 対応プラットフォームは、Windows10/11, Mac, iOS, Android, Nintendo Switch 2, PS5, Xbox Series X|S です。

## 確認手順

### Windowsの確認手順

1. DiarkisPluginSample.uproject の右クリックコンテキストメニューから「Visual Studio プロジェクトファイルの生成」を選択する。
2. Visual Studio で DiarkisPluginSample.sln を開く。
3. ビルド＆実行
   * Windowsで実行する： VisualStudio で `DebugGame_Editor` と `Win64` を選択し、ビルドと実行を行う。

### 選択ビューポート

1. UE5Editorを起動し、ツールバーの`Play`ボタンをクリックします。
   * `Selected Viewport`と`Multiplayer Option`で`Number of Players: 1`を選択します。

     ![image](/files/A9zW7yHkU3kFYQWyTTxc)

## 各画面の説明

### ログイン画面

* 起動後、以下のログインメニューが最初に表示されます。

  ![image](/files/U6jjXsoY6rgRu6yxIUea)
* Diarkisサーバーの設定を行います。
  * **HostName :** Diarkis HTTP サーバの URL を指定します。
  * **ClientKey :** DiarkisサーバーにClientKeyがある場合は、それを指定します。ない場合は空欄にしてください。
  * **UID :** 他のクライアントと異なるユニークなUIDを指定します。 ※ 他のクライアントと同じUIDを指定すると同期できません。デフォルトで、マシン名とクライアントのプロセスIDの組み合わせになっています。
  * **Protocol :** 通信方式（UDP/TCP）を選択します。
* **Start ボタン :** MainMenu 画面に移動します。
* フレームレート設定
  * **Show Stat :** フレームレートなどの情報を表示します。
  * **Fix FPS :** FPSを固定します。
  * **FPS :** 固定FPSを30FPSと60FPSの間で切り替えます。

### MainMenu 画面

* ログイン後、以下のメインメニューが表示されます。

<figure><img src="/files/h3My9TVQutsjigRqs0RJ" alt=""><figcaption></figcaption></figure>

* サンプルタイプを選びます。
  * **Room :** Room 画面に遷移します。
  * **Field :** Field 画面に遷移します。Field は使用する Pod (UDP / TCP サーバー) の数に応じて Grid で分割されます。
    * デフォルトでは、1Podの UDP サーバーが起動しているため、1Mapが4Gridに分割されます。
    * 他のキャラクターが隣の Grid にいても、そのキャラクターが自分の視界内にいれば、そのキャラクターの位置情報が送信されます。
  * **Host/Search :** MatchMaker Host/Search 画面に遷移します。
  * **Ticket :** MatchMaker Ticket 画面に遷移します。
  * **DirectMessage :** DirectMessage 画面に遷移します。
  * **Session :** Session 画面に遷移します。
  * **Group :** Group 画面に遷移します。
  * **Zombie :** Zombie 画面に遷移します。
  * **Disconnect :** Diarkis から切断してログインメニューに戻ります。

### Room 画面

* MainMenu 画面の Room ボタンをクリックすると、Room の作成や参加が行える Room 画面に遷移します。&#x20;

<figure><img src="/files/I54iC0SdrlR0Gr7V5G0z" alt=""><figcaption></figcaption></figure>

* **Max Members :** Room に参加できる最大人数を指定します。
* **Allow Empty :** クライアントが誰も参加していない場合でも、Room を保持するかどうかを決定します。
* **TTL :** Room が空になった後の生存時間を秒単位で指定します。
* **Interval :** サーバがブロードキャストを処理する間隔をミリ秒単位で指定します。
* **Join Random Room ボタン :** 利用可能な Room がある場合は、参加し、なければ Room を新規作成します。
* **Create Room ボタン :** Room を新規作成します。
* **Join Room ボタン :** 指定した Room ID の Room に参加します。
* **Create Or Join By CustomID ボタン :** 指定した Custom ID を使い、利用可能な Room がある場合は、参加し、なければ Room を新規作成します。
* **LOD Room Sample ボタン :** キャラクターの位置情報で使用する通信データにサーバ側で LOD を適用して通信量を削減するサンプルです。
  * ※ LOD Room Sample の動作確認を行うためには、diarkis-server-template の examples/room/lod の diarkis server を使用する必要があります。
    * diarkis-server-template の examples をインストールするには、下記のドキュメントをご参照ください。 [diarkis-server-template examples のインストール方法](https://github.com/Diarkis/diarkis-server-template/tree/develop/examples/README.md)
    * examples をインストールしたあとは、`examples/room/lod`フォルダに移動し、下記のドキュメントの手順に従ってください。 [LOD Room Sample に対応した diarkis server のビルド方法](https://github.com/Diarkis/diarkis-server-template/tree/develop/examples/room/lod/README.md)
    * この diarkis server では LOD Room Sample の動作のみ対応しております。他の Field サンプルや Session サンプルの実行には対応しておりません。

### Room InGame 画面

* Room 画面でボタンをクリックすると、Room InGame 画面に遷移し、キャラクターの位置が同期されます。&#x20;

  <figure><img src="/files/jRQ8PmlQZjWHux1R8x89" alt=""><figcaption></figcaption></figure>
* **Back ボタン :** Room 画面に戻ります。
* **Copy RoomID ボタン :** Room ID をクリップボードにコピーします。Windows, Mac OS, Linux プラットフォームでのみ動作します。
* **P2P Start ボタン :** P2P 機能を使って各クライアントとの通信を開始します。
* **Recovery ボタン :** RPC機能を使ってHPを回復します。
* アプリケーション操作

```キー
  wキー: 前進
  sキー: 後退
  aキー: 左に移動
  dキー: 右に移動
  スペースキー: ジャンプ
  カメラコントロール: マウス
  弾丸発射: 左マウスボタン
```

* P2P接続の確認方法
  * Room InGame 画面の`P2P Start`ボタンをクリックします。
  * クライアントとの接続(HolePunch)に成功すると、Room InGame 画面に`P2P Connect： 1 Client`と表示され、クライアント間でP2P通信が開始されます。

### LOD Room InGame 画面

* Room 画面で LOD Room Sample ボタンをクリックすると、LOD Room InGame 画面に遷移し、キャラクターの位置が同期されます。

<figure><img src="/files/IYY2OQGuPb9hyvYJCpCd" alt=""><figcaption></figcaption></figure>

* サンプルの動作概要
  * クライアントが送信した位置データをサーバ上で LOD を適用する機能のサンプル実装です
    * クライアントは高頻度で自分の位置をサーバへ送信します
    * サーバではプレイヤーの位置を元に LOD を適用し、近くにいる他のユーザーは高頻度で、遠くにいる他のユーザーは低頻度で位置データを送信するように制御を行い通信量を削減します
  * LOD に関するパラメータはサーバ側に設定を持っており、画面右側に表示しています
    * 設定値
      * Distance(N) cm:
        * サーバの設定項目名: `MaxDistanceForNearby`
        * LOD 処理を適用開始する距離です。
      * Distance(F) cm:
        * サーバの設定項目名: `MaxDistanceForFar`
        * LOD 処理が適用される最大距離です。
        * この距離以上離れるとサーバから位置情報が送られなくなります。
      * SyncIntr(N) ms:
        * サーバの設定項目名: `SyncIntervalForNearby`
        * 更新頻度の最小値です
        * Distance(N) 未満の場合はこの設定で位置情報が送信されます
      * SyncIntr(F) ms:
        * サーバの設定項目名: `SyncIntervalForFar`
        * 更新頻度の最大値です
  * プレイヤー毎の情報
    * 各リモートキャラクターの頭上にはそのキャラクターの通信状況に関する情報が表示されています
    * TotalSyncs:
      * 総受信パケット数
    * SyncRate:
      * 秒間何パケット受信したか
  * 位置情報全体の通信状況
    * デバッグメッセージの領域に位置同期全体の情報が表示されています
      * 青文字のテキストが実際に計測された値で総パケット数と通信量
      * 黄文字のテキストで LOD を適用しない場合の総パケット数と通信量の概算値

### Field 画面

* MainMenu 画面の Field ボタンをクリックすると、Field 画面に遷移します。
* Field は Join や Create のような事前コマンドを必要しません。
* Field は1つのサーバタイプに1つしか存在せず、ユーザーは一度に1つの位置にしか存在できません。

| Item          | Description                                             | Example |
| ------------- | ------------------------------------------------------- | ------- |
| Grid Size     | Field のサイズ。全体の Field のサイズとそれを分割する Grid のサイズが変更されます。     | 10000   |
| Server Count  | メッシュネットワーク内の Diarkis サーバーの数は、Field を分割する Grid の数に影響します。 | 4       |
| FOV Halfwidth | 視界の範囲。視界の範囲に入るリモートキャラクターが同期されます。                        | 1800    |
| Main Menu     | MainMenu 画面に戻ります。                                       |         |

![image](/files/oZVhwirbaBnuG4np2lD7)

### MatchMaker Host/Search 画面

* ※ この一連のサンプルを実行するには、マッチメイキング用の Diarkis サーバとは別に、TURN というサーバタイプ名で起動している Diarkis サーバが必要です。 環境変数`DIARKIS_SERVER_TYPE`に TURN を設定し、Diarkis サーバを立ち上げてください。

* MainMenu 画面の MatchMaker ボタンをクリックすると、MatchMaker 画面に遷移し、MatchMakerでマッチングした相手とInGame(Room)に参加することができます。&#x20;

  <figure><img src="/files/XTQ0POgqvoy7PbaEfdzN" alt=""><figcaption></figcaption></figure>

* **Matching Type :**
  * **Rank :**
    * 1-5、6-10、11-15、16-20、...のランクのユーザー同士でマッチングされます。

* **Tag :** マッチング用のタグ（文字列）。同じタグ同士がマッチングするようになります。

* **Max Players :** 作成する Room の最大人数。この機能はホストに対してのみ有効です。

* **Room Name :** 作成する Room の名前（文字列）。この機能はホストに対してのみ有効です。

* **Pass :** 作成する Room にロックを掛けたい時に指定するパスワード。この機能はホストに対してのみ有効です。

* **Host ボタン:** マッチング用の Room を作成する場合は、このボタンをクリックします。

* **Search ボタン:** 既に作成されている Room を検索します。検索が成功すると、Search Matching に Room のリストが表示されます。

* **OwnUID :** あなたのUIDが表示されます。

* **Main Menuボタン :** メインメニューに戻ります。

* **Search Matching :**

  * **Room Lists:** 検索して条件にあった Room があった場合、入室できる Room が表示されます。
  * **Join ボタン :** 入室したい Room を選択して、Join ボタンで Room に入室します。Room が既に一杯の場合や、すでにサーバーから Room が消えている場合など入室に失敗する場合があります。
  * **Disband ボタン :** Room を解散します。この機能はホストのみ有効です。

  <figure><img src="/files/EXV4pX5o9flV520bEKMA" alt=""><figcaption></figcaption></figure>

* **Room MemberLists :** Room に入室しているメンバーの UID リストを表示します。

  * **RoomID :** 入室している Room の Room ID が表示されます。
  * **Leave ボタン :** 入室している Room から退出します。
  * **Kick ボタン :** 入室しているメンバーを Room から Kick します。この機能はホストのみ有効です。
  * **In Game ボタン :** Room に居るメンバーと InGame に移動します。この機能はホストのみ有効です。

  <figure><img src="/files/G5aJkoDxO5trQnuWwQzV" alt=""><figcaption></figcaption></figure>

* **Sync**
  * **Sync ボタン :** SendMessage に入力されたメッセージがマッチングしたメンバーに送信されます。
  * **Send Message :** 送信するメッセージを入力します。
  * **Recv Message :** 受信したメッセージが表示されます。&#x20;

    <figure><img src="/files/oIOrm0u6WmGl5nfGdlf2" alt=""><figcaption></figcaption></figure>

### MatchMaker Host/Search InGame 画面

* MatchMaker Host/Search 画面の InGame ボタンをクリックすると、InGame 画面に遷移します。&#x20;

  <figure><img src="/files/CQKrvHja5Aa5Tdm1BJCH" alt=""><figcaption></figcaption></figure>
* **RoomID :** TURN サーバの RoomID が表示されます。 MatchMaker に使用されるRoomとは別のRoomのため、異なるRoomIDが表示されます。
* **Room Member :** Room に参加しているメンバーの一覧が表示されます。自分のUIDは青色で表示されます。
* **Receive Message :** 受信したメッセージが表示されます。
* **Send Message :** 送信するメッセージを入力します。Enter キーを入力するとメッセージが送信されます。
* **Send Message ボタン :** SendMessage に入力されたメッセージが Room 内のすべてのユーザーに送信されます。
* **Back ボタン :** MatchMaker Host/Search 画面に戻ります。

### MatchMaker Ticket 画面

* MainMenu 画面の Ticket ボタンをクリックすると、MatchMaker Ticket 画面に遷移します。

<figure><img src="/files/Pnf4GI2n8glz6CW5MfZK" alt=""><figcaption></figcaption></figure>

* **Ticket Type :** Ticket Type を指定します。
* **Issue Ticket ボタン :** 指定した Ticket Type でマッチングを開始します。
* マッチングが完了すると以下の画面が表示されます。

<figure><img src="/files/UDGggZh4lNsktT77QASW" alt=""><figcaption></figcaption></figure>

* **Cancel ボタン :** マッチング中の場合、マッチングをキャンセルします。
* **Leave ボタン :** マッチングした Ticket から退出します。
* **Send Message :** 送信するメッセージを入力します。Enter キーを入力するとメッセージが送信されます。
* **Send Message ボタン :** SendMessage に入力されたメッセージがマッチングしたすべてのユーザーに送信されます。
* **Matched Member :** マッチングしたユーザーの一覧が表示されます。
* **Main Menu ボタン :** MainMenu 画面に戻ります。

### DirectMessage 画面

* MainMenu 画面の DirectMessage ボタンをクリックすると、DirectMessage 画面に遷移します。&#x20;

  <figure><img src="/files/yS7kwFrOZ81za6LoUjK9" alt=""><figcaption></figcaption></figure>
* **My UID :** 自分のユーザーIDが表示されます。
* **クリップボードボタン :** 右側のクリップボードのアイコンのボタンをクリックすることで、自分のユーザーIDをクリップボードにコピーします。Windows, Mac OS, Linux プラットフォームでのみ動作します。
* **Recipient ID :** 送信したいユーザーIDを入力します。
* **Receive Message :** 受信したメッセージが表示されます。
* **Send Message :** 送信するメッセージを入力します。Enter キーを入力するとメッセージが送信されます。
* **Send Message ボタン :** SendMessage に入力されたメッセージが Recipient ID で指定したユーザーに送信されます。
* **Main Menu ボタン :** MainMenu 画面に戻ります。

### Session 画面

* MainMenu 画面の Session ボタンをクリックすると、Session 画面に遷移します。&#x20;

  <figure><img src="/files/FR39gQm8oJhdyjXRGAWt" alt=""><figcaption></figcaption></figure>
* **Joined Sessions :** 参加中の Session のリストが表示されます。クリックすることで Session を選択できます。選択中の Session が青色で表示されます。
* **Invited Sessions :** 招待された Session のリストが表示されます。クリックすることで招待された Session Type と Session ID が入力されます。
* **Create ボタン :** 入力された Session Type で Session を新規作成します。
* **Join ボタン :** 入力された Session Type と Session ID の Session に参加します。
* **Leave ボタン :** 入力された Session Type の参加中の Session から退出します。
* **Receive Message :** 受信したメッセージが表示されます。
* **Send Message :** 送信するメッセージを入力します。Enter キーを入力するとメッセージが送信されます。
* **Send Message ボタン :** SendMessage に入力されたメッセージが 選択中の Session 内のすべてのユーザーに送信されます。
* **Joined Member :** 選択中の Session に参加しているユーザーの一覧が表示されます。クリックすることで Kick ボタンの入力にユーザーID が入力されます。
* **Kick ボタン :** 入力されたユーザーIDを選択中の Session から Kick します。
* **Invite ボタン :** 入力されたユーザーIDを選択中の Session に Invite します。
* **Main Menu ボタン :** MainMenu 画面に戻ります。

### Group 画面

* ※ Group サンプルは一部の機能が動作していません。
* MainMenu 画面の Group ボタンをクリックすると、Group 画面に遷移します。&#x20;

  <figure><img src="/files/E8wDMkPFRmi5fA8krWNA" alt=""><figcaption></figcaption></figure>
* **Joined Group :** 参加中の Group のリストが表示されます。クリックすることで Group を選択できます。選択中の Group が青色で表示されます。
* **RandomJoin ボタン :** 利用可能な Group がある場合は、参加し、なければ Group を新規作成します。
* **Create ボタン :** Group を新規作成します。
* **Join ボタン :** 入力された Group ID の Group に参加します。
* **Leave ボタン :** 入力された Group ID の参加中の Group から退出します。Group ID を指定せず、ボタンをクリックした場合は、参加中のすべての Group から退出します。
* **Receive Message :** 受信したメッセージが表示されます。
* **Send Message :** 送信するメッセージを入力します。Enter キーを入力するとメッセージが送信されます。
* **Send Message ボタン :** SendMessage に入力されたメッセージが 選択中の Group 内のすべてのユーザーに送信されます。
* **Joined Member :** ※この機能は動作していません。
* **Main Menu ボタン :** MainMenu 画面に戻ります。

### Zombie 画面

* MainMenu 画面の Zombie ボタンをクリックすると、CSAR モジュールの動作を確認できる Zombie 画面に遷移します。

<figure><img src="/files/fmJROOiyhP0peeRUi7Yw" alt=""><figcaption></figcaption></figure>

* **Min Members :** Game Instance に参加できる最少人数を指定します。
* **Max Members :** Game Instance に参加できる最大人数を指定します。
* **Game Instance Name :** Game Instance Name を指定します。
* **Connection Mode :** Game Instance の Connection Mode を指定します。
  * SINGLE\_AUTHORITY : ホストクライアント型や DGS など、1 台のマシンが権限を持つモード
* **Network Type :** Game Instance の Network Type を指定します。
  * ROOM\_AND\_P2P : P2P が利用可能であれば P2P で通信し、P2P が利用可能でない場合は Room 経由で通信するタイプ
  * ROOM : Room 経由で通信するタイプ
  * DGS : DGS と通信するタイプ
* **Create/Join Game Instance ボタン:** 指定した設定で Game Instance を開始し、Zombie InGame 画面に遷移します。
* **Main Menu ボタン :** MainMenu 画面に戻ります。

### Zombie InGame 画面

* Zombie 画面の Create/Join Game Instance ボタンをクリックすると、Zombie InGame 画面に遷移します。
* 指定したConnection Mode, Network Type でキャラクターの位置を同期します。
* キャラクターの位置はホストやDGSなどの Authority が管理しています。
* Zombie は自動でランダムに移動します。
* **Back ボタン:** Zombie 画面に戻ります。

<figure><img src="/files/fHvK5XuqUdRnWuph33F6" alt=""><figcaption></figcaption></figure>

* アプリケーション操作

```
  wキー: 前進
  sキー: 後退
  aキー: 左に移動
  dキー: 右に移動
```

### DGS

* ※ DGS の動作確認を行うためには、diarkis-server-template の examples/csar/dgs の diarkis server を使用する必要があります。
  * diarkis-server-template の examples をインストールするには、下記のドキュメントをご参照ください。 [diarkis-server-template examples のインストール方法](https://github.com/Diarkis/diarkis-server-template/tree/develop/examples/README.md)
  * examples をインストールしたあとは、`csar/dgs`フォルダに移動し、下記のドキュメントの手順に従ってください。 [DGS に対応した diarkis server のビルド方法](https://github.com/Diarkis/diarkis-server-template/tree/develop/examples/csar/dgs/README.md)
  * この diarkis server では DGS の動作のみ対応しております。他の Field サンプルや Session サンプルの実行には対応しておりません。

1. まず、サーバービルドターゲット用のサンプルプロジェクトをパッケージ化します（これにはソースコード版のUEが必要です）。パッケージ化前に、ビルドターゲットでDiarkisPluginSampleServerがチェックされていることを確認してください

<figure><img src="/files/kCgWQsbDO6FXoKGpKnXm" alt=""><figcaption></figcaption></figure>

1. パッケージ化後、パッケージ化されたファイルのBinariesフォルダに移動します。その中にdgsConfigというフォルダがあります。その中にlog.jsonとmesh.jsonファイルがあります。必要に応じて内容を変更してください。

   log.json:

   ```json
   {
       "level": "sys",     // Log level (verbose, network, sys, debug, info, notice, warn, error, fatal)
       "timeZone": "local" // Timezone (utc, local)
   }
   ```

   mesh.json:

   ```json
   {
       "marsAddress": "127.0.0.1", // MARS Server Address
       "marsPort": "6779"          // MARS Server Port
   }
   ```
2. ターミナルで、パッケージ化されたフォルダのルートに移動し、下記コマンドでDGSを起動します。

   Windows:

   ```powershell
   ./DiarkisPluginSampleServer.exe -log -WorkingDir="$(Get-Location)" -LogConfigPath="DiarkisPluginSample\Binaries\Win64\dgsConfig\log.json" -MeshConfigPath="DiarkisPluginSample\Binaries\Win64\dgsConfig\mesh.json" -Host="0.0.0.0" -Port="8888" -DiarkisCloudEnv="127.0.0.1"
   ```

   Linux:

   ```shellscript
   ./DiarkisPluginSampleServer.sh -log -WorkingDir="$(pwd)" -LogConfigPath="DiarkisPluginSample/Binaries/Linux/dgsConfig/log.json" -MeshConfigPath="DiarkisPluginSample/Binaries/Linux/dgsConfig/mesh.json" -Host="0.0.0.0" -Port="8888" -DiarkisCloudEnv="127.0.0.1"
   ```

* **log :** サーバープロセスがUEログを出力するように設定します。これにより、サーバーが正常に動作しているかどうかを簡単に確認できます。
* **WorkingDir :** Windowsで実行する場合は「$(Get-Location)」に、Linuxで実行する場合は「$(pwd)」に設定してください。
* **LogConfigPath :** ログ設定ファイルのパスを指定します。
* **MeshConfigPath :** メッシュ設定ファイルのパスを指定します。
* **Host :** DGS がバインドするホストを指定します。全てのネットワークインターフェースで listen する場合は 0.0.0.0 を指定します。
* **Port :** DGS がバインドするポートを指定します。
* **DiarkisCloudEnv :** Diarkis は、主要なパブリッククラウドサービスに対応しています。DiarkisCloudEnv は、設定された値に基づいて、DGS サーバーがパブリックアドレスを取得する際のクラウド環境を指定します。この値は、Diarkis DGS を実行しているクラウドサービスと一致している必要があります。

  | Cloud Service   | Env Value |
  | --------------- | --------- |
  | Google Cloud    | GCP       |
  | AWS             | AWS       |
  | Microsoft Azure | AZURE     |
  | Tencent         | TENCENT   |
  | Alibaba Cloud   | ALIBABA   |
  | Linode          | LINODE    |

  ローカルで実行する場合はホスト名またはIPアドレスを設定することが可能です。
* (任意) **FPSOverride :** ゲームサーバーのFPS。指定しない場合、この値は60になります

<figure><img src="/files/gRGA2bxwiyJnpF7Nl2jb" alt=""><figcaption></figcaption></figure>

4. クライアント側でゾンビメニューに移動し、ネットワークタイプをDGSに設定した後、Create/Join Game Instanceをクリックしてください。少なくとも2人のクライアントが参加するまでキャラクターは生成されません。

<figure><img src="/files/PFIL7y5Dq3QrNr2yyOB7" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/CR7x7Pi207AdwVXi1Kr1" alt=""><figcaption></figcaption></figure>

### 動作確認（マルチプレーオプション）

1. UEEditorを起動したら、ツールバーの`Play`ボタンを押します。
   * `Standalone Game`を選択します。
   * 「Multiplayer Option」 で `Number of Players: 2 ～ 4`を選択します。
   * ネットモード`Net Mode`で`スタンドアロンプレイ(Play Standalone)`を選択する。

     ![image](/files/SfvvUFGukicmg10U3Fs1)
2. 複数のWindowが表示されるので、ゲームの `Start` ボタンを押す。
   * UIDが同じでないことを確認してください。そうしないと意図しない動作が起こります。
   * 複数人でプレイする場合、PCのスペックによってはキャラクターの同期に時間がかかる場合があります。
3. 以下の手順は、選択したビューポートの場合と同じです。

   ![](/files/whtxrBXtonP1zhEtzVDi)

## コードについて

### C++ サンプルコード

* `Plugins\Diarkis\Source\SDK\samples` フォルダに、Diarkis Plugin の C++ サンプルが含まれています。
* 詳細については、[Plugins\Diarkis\Source\SDK\SAMPLE\_README.md](https://github.com/Diarkis/UE_PluginSample/blob/release/v1.0.3/Plugins/Diarkis/Source/SDK/SAMPLE_README.md) を参照してください。

### DiarkisPluginSample 側の主なコード（Diarkis Pluginを利用したサンプル）

* `DiarkisPluginSample/Source/DiarkisPluginSample`
  * `DiarkisPluginSampleGameMode.h / DiarkisPluginSampleGameMode.cpp` : ゲームを管理するメインクラス ( AGameModeBase )
  * `DiarkisPluginSampleCharacter.h / DiarkisPluginSampleCharacter.cpp` : キャラクターのクラス ( ADiarkisCharacter )
* `DiarkisPluginSample/Source/DiarkisPluginSample/Diarkis`
  * `DiarkisSampleBase.h / DiarkisSampleBase.cpp` : Diarkisプラグインを使用するためのベースクラス
  * `DiarkisSampleInterface.h / DiarkisSampleInterface.cpp` : Diarkis Pluginを使用するためのインターフェースクラス
  * `DiarkisSample.h / DiarkisSample.cpp` : Diarkis Pluginを使用するサンプルクラス

### Diarkis Plugin + Sample の主なコード

| メソッド                     | 範囲             | 役割                                            |
| ------------------------ | -------------- | --------------------------------------------- |
| libDiarkis               | Diarkis Plugin | Diarkis ライブラリ                                 |
| diarkis-module Client    | Diarkis Plugin | Diarkisライブラリを制御するインターフェースの親クラス                |
| diarkis-module Extension | Sample コード     | Diarkisライブラリを制御するインターフェースの子クラス群               |
| DiarkisNetwork           | Sample コード     | Diarkisプラグインのインターフェースクラス                      |
| EventEmitter             | Sample コード     | BluePrint で Diarkis コールバックイベントを受け取るためのクラス群（旧） |
| Delegate                 | Sample コード     | アプリレイヤーで Diarkis コールバックイベントを受け取るためのクラス群（新）    |

### libDiarkis のコードについて

* * 役割の概要
    * Diarkis C++ライブラリ
  * コードの場所
    * DiarkisPluginSample/Plugins/Diarkis/Source/SDK/platforms/
  * 各ライブラリのファイル
    * win-x64 で使用されるライブラリ `(win-vs2019/lib/x86_64-MD/<ビルドの種類>/lib_static)`
    * macos-x64 で使用されるライブラリ `(macos/lib/macos-arm64/<ビルドの種類>/lib_static)`
    * ios で使用されるライブラリ `(ios/lib/iOS/<ビルドの種類>/lib_static)`
    * android で使用されるライブラリ `(android/lib/<ABI>/<ビルドの種類>/lib_static)`
    * ps5 で使用されるライブラリ `(ps5/lib/<ビルドの種類>/lib_static)`
    * xbox series で使用されるライブラリ `(xbox-series/lib/MD/<ビルドの種類>/lib_static)`
    * Nintendo Switch 2 で使用されるライブラリ `(Ounce-ounce-a64/lib/<ビルドの種類>/lib_static)`
  * ビルドの種類
    * debug : `Debug ビルド` x `Diarkis ログ出力 有り`
    * develop : `Release ビルド` x `Diarkis ログ出力 有り`
    * release : `Release ビルド` x `Diarkis ログ出力 無し`

### diarkis-module Client のコードについて

* 役割の概要
  * Diarkisライブラリを制御するインターフェースの親クラス
* コードの場所
  * DiarkisPluginSample/Plugins/Diarkis/Source/SDK/diarkis-module/Client
* 各クラス
  * `DiarkisInterfaceBase.h / DiarkisInterfaceBase.cpp` : Diarkis をコントロールするインターフェースクラス（ libDiarkis をコントロール）
  * `DiarkisUdpBase.h / DiarkisUdpBase.cpp` : UDP 機能の Base クラス
  * `DiarkisTcpBase.h / DiarkisTcpBase.cpp` : TCP 機能の Base クラス
  * `DiarkisP2PBase.h / DiarkisP2PBase.cpp` : P2P 機能の Base クラス
  * `DiarkisRoomBase.h / DiarkisRoomBase.cpp` : Room 機能の Base クラス
  * `DiarkisMatchMakerBase.h / DiarkisMatchMakerBase.cpp` : MatchMaker 機能の Base クラス
  * `DiarkisGroupBase.h / DiarkisGroupBase.cpp` : Group 機能の Base クラス
  * `DiarkisFieldBase.h / DiarkisFieldBase.cpp` : Field 機能の Base クラス
  * `DiarkisSessionBase.h / DiarkisSessionBase.cpp` : Session 機能の Base クラス
  * `DiarkisDirectMessageBase.h / DiarkisDirectMessageBase.cpp` : DirectMessage 機能の Base クラス
  * `DiarkisRPCBase.h / DiarkisRPCBase.cpp` : RPC 機能の Base クラス
  * `LoggerFactory.h / LoggerFactory.cpp` : Logger 機能の Main クラス
  * `ConnectionManager.h / ConnectionManager.cpp` : CSAR 機能の Main クラス
  * `DiarkisServerBase.h / DiarkisServerBase.cpp` : CSAR DGS 機能の Main クラス

### diarkis-module Extension のコードについて

* 役割の概要
  * Diarkisライブラリを制御するインターフェースの子クラス群
* コードの場所
  * DiarkisPluginSample/Source/DiarkisExtension
* 各クラス
  * `Character`（キャラクターデータ同期クラス）
    * `DiarkisCharacter.h / DiarkisCharacter.cpp` : Diarkisのキャラクター同期用のクラス。
  * `Component` (位置同期用の Diarkis コンポーネントクラス)
    * `DiarkisSyncComponent.h / DiarkisSyncComponent.cpp` : Diarkis位置同期コンポーネントクラス
    * `DiarkisCharacterSyncComponent.h / DiarkisCharacterSyncComponent.cpp` : アクタの作成時にカスタムデータを送受信するためのサンプル実装コンポーネントクラス
  * `Movement`（Diarkisの位置同期クラス）
    * `DiarkisMovementController.h` : 位置同期コンポーネントのインターフェースクラス
    * `DiarkisLocalMovementSync.h / DiarkisLocalMovementSync.cpp` : ローカル用の位置同期コンポーネントクラス。
    * `DiarkisRemoteMovementSync.h / DiarkisRemoteMovementSync.cpp` : リモート用のロケーション同期コンポーネントクラス。
  * `Diarkis/Utils` （ユーティリティクラス）
    * `DiarkisUtils.h / DiarkisUtils.cpp` : ライブラリを制御するためのインターフェースクラス
  * ライブラリを制御するためのインターフェースクラス（XXXBaseから派生）
    * `DiarkisInterface.h / DiarkisInterface.cpp` (XXXBase から派生したクラス)
    * `DiarkisRoom.h / DiarkisRoom.cpp` : Room 機能を制御するためのクラス
    * `DiarkisGroup.h / DiarkisGroup.cpp` : Group 機能を制御するクラス
    * `DiarkisField.h / DiarkisField.cpp` : Field 機能を制御するクラス
    * `DiarkisTcp.h / DiarkisTcp.cpp` : TCP 機能を制御するクラス
    * `DiarkisUdp.h / DiarkisUdp.cpp` : UDP 関数を制御するクラス
    * `DiarkisP2P.h / DiarkisP2P.cpp` : P2P 機能を制御するクラス
    * `DiarkisMatchMaker.h / DiarkisMatchMaker.cpp` : MatchMaker機能を制御するクラス
    * `DiarkisRPC.h / DiarkisRPC.cpp` : RPC機能を制御するクラス
    * `DiarkisSyncData.h / DiarkisSyncData.cpp` : Diarkisのロケーション同期を処理するクラス
    * `DiarkisReplication.h / DiarkisReplication.cpp` : 部屋のプロパティを使用した変数のレプリケーションを行うクラス
    * `DiarkisActorManagement.h / DiarkisActorManagement.cpp` : Diarkisが管理するアクターを識別するID

### DiarkisNetwork のコードについて

* 役割の概要
  * Diarkis プラグインのインターフェースクラス群
* コードの場所
  * DiarkisPluginSample/Source/DiarkisExtension/XXXXX/
* 各クラス - `DiarkisNetworkManager.h / DiarkisNetworkManager.cpp` : Diarkis Plugin を管理するメインのクラス - `DiarkisNetworkSubsystem.h / DiarkisNetworkSubsystem.cpp` : UDiarkisNetworkManager インスタンスを保持するクラス - `DiarkisNetworkBlueprintLibrary.h / DiarkisNetworkBlueprintLibrary.cpp` : ブループリント関数として Diarkis プラグインのコールバックイベントを登録するクラス。 - `Modules`ディレクトリ（Diarkis のステータスをチェックし、コールバックイベントをキューに入れるクラス）
  * `DiarkisNetworkModuleBase.h / DiarkisNetworkModuleBase.cpp` : Diarkis のステータスをチェックしてコールバックイベントをキューに溜める基底クラス
  * `DiarkisNetworkRoom.h / DiarkisNetworkRoom.cpp` : Diarkis の Room のステータスをチェックし、コールバックイベントをキューに入れるクラス。
  * `DiarkisNetworkGroup.h / DiarkisNetworkGroup.cpp` : Diarkis の Group のステータスをチェックし、コールバックイベントをキューに入れるクラス。
  * `DiarkisNetworkField.h / DiarkisNetworkField.cpp` : Diarkis の Field のステータスをチェックし、コールバックイベントをキューに入れるクラス。
  * `DiarkisNetworkP2P.h / DiarkisNetworkP2P.cpp` : Diarkisの P2P のステータスをチェックし、コールバックイベントをキューに入れるクラス。
  * `DiarkisNetworkMatchMaker.h / DiarkisNetworkMatchMaker.cpp` : Diarkis の MatchMaker のステータスをチェックし、コールバックイベントをキューに入れるクラス。

### Blueprint用 コールバックイベント キューイング のコードについて

* 役割の概要
  * BluePrint で Diarkis コールバックイベントを受け取るためのクラス群（旧）
* コードの場所
  * DiarkisPluginSample/Source/DiarkisExtension/XXXXX/Events
* 各クラス
  * `Interfaces` ディレクトリ（Diarkis Plugin のコールバックを受け取るインターフェースクラス）
    * `DiarkisNetworkCoreEvent.h` : Diarkis Plugin の Core 機能のコールバックを受け取るインターフェースクラス
    * `DiarkisNetworkRoomEvent.h` : Diarkis Plugin の Room 機能のコールバックを受け取るインターフェースクラス
    * `DiarkisNetworkGroupEvent.h` : Diarkis Plugin の Group 機能のコールバックを受け取るインターフェースクラス
    * `DiarkisNetworkFieldEvent.h` : Diarkis Pluginの Field 機能のコールバックを受け取るインターフェースクラス
    * `DiarkisNetworkP2PEvent.h` : Diarkis Plugin の P2P 機能のコールバックを受け取るインターフェースクラス
    * `DiarkisNetworkMatchMakerEvent.h` : Diarkis Plugin の MatchMaker 機能のコールバックを受け取るインターフェースクラス
  * `Emitters` ディレクトリ（Diarkis イベントをキューイングするクラス）
    * `DiarkisNetworkCoreEventEmitter.h / DiarkisNetworkCoreEventEmitter.cpp` : Diarkis Core イベントをキューイング用クラス
    * `DiarkisNetworkEventEmitterBase.h / DiarkisNetworkEventEmitterBase.cpp` : 様々なイベントを生成するためのベースクラス
    * `DiarkisNetworkRoomEventEmitter.h / DiarkisNetworkRoomEventEmitter.cpp` : Diarkis Room イベントをキューイング用クラス
    * `DiarkisNetworkGroupEventEmitter.h / DiarkisNetworkGroupEventEmitter.cpp` : Diarkis Group イベントのキューイング用クラス
    * `DiarkisNetworkFieldEventEmitter.h / DiarkisNetworkFieldEventEmitter.cpp` : Diarkis Field イベントのキューイング用クラス
    * `DiarkisNetworkP2PEventEmitter.h / DiarkisNetworkP2PEventEmitter.cpp` : Diarkis P2P イベントのキューイング用クラス
    * `DiarkisNetworkMatchMakerEventEmitter.h / DiarkisNetworkMatchMakerEventEmitter.cpp` : Diarkis MatchMaker イベントのキューイング用クラス

### イベントの Delegate のコードについて

* 役割の概要
  * アプリレイヤーで Diarkis コールバックイベントを受け取るためのクラス群（新）
* コードの場所
  * DiarkisPluginSample/Source/DiarkisExtension/XXXXX/Delegate
* 各クラス
  * `DiarkisDispatch.h / DiarkisDispatch.cpp` : Diarkis のコールバックイベントをキューイングや実行するクラス
  * `DiarkisRoomDelegate.h` : Diarkis の Room のコールバックイベントをキューに入れるクラス
  * `DiarkisUDPDelegate.h` : Diarkis の UDP のコールバックイベントをキューに入れるクラス
  * `DiarkisP2PDelegate.h` : Diarkisの P2P のコールバックイベントをキューに入れるクラス
  * `DiarkisMatchMakerDelegate.h` : Diarkis の MatchMaker のコールバックイベントをキューに入れるクラス

## クラス図

* Diarkisプラグインのクラス図

  ![イメージ](/files/9hNhO39XSeSNH4N4l2gs)
* Diarkis Pluginをカスタマイズするには
  * Diarkisライブラリを制御するインターフェースクラス（図の上段赤枠）から派生したクラスを用意し、その処理をカスタマイズする。
  * `ADiarkisPluginSample`を参考にカスタマイズしてください。
* キャラクター同期に関連するクラス図

  ![image](/files/0yfqSt7h1ok4AlqyD3W3)
* 同期方法をカスタマイズする
  * `DiarkisLocalMovementSync`、`DiarkisRemoteMovementSync`から派生したクラスを用意して処理をカスタマイズするか、`ADiarkisCharacter`から派生したクラスを用意して処理をカスタマイズする。

## 同期機能について

### Diarkis Plugin で同期できる機能について

Field Walkerのサンプルには、Diarkis Pluginを使って同期できる以下の機能のサンプルが含まれています。

* アクターインスタンス管理
* アクターロケーション同期
* 変数のレプリケーション
* RPC

これらの機能は、標準的なUEの通信処理とは異なる実装となっているため、通常の通信処理とは別に設定・実装する必要があります。また、Diarkis Room機能を用いて同期を実装するため、Roomを用いた通信が可能な状態である必要があります。

#### アクターのインスタンス管理

* `DiarkisSyncComponent` を持つアクターがローカルで作成または削除されると、アクターインスタンス管理のための情報がリモートに送信され、各ホスト上に同じアクターインスタンスが存在するように同期されます。マップ上に最初に配置されたアクタは、既に作成されたインスタンスから再利用され、動的に作成されたアクタはリモート上で動的に生成されます。
* `DiarkisSyncComponent`は、Diarkisネットワーク上のアクタを識別するために使用される`DiarkisアクタID`を持っています。また、`DiarkisSyncComponent`のオーナーかどうかを判断し、この情報を使ってローカルとリモートの動作を切り替えることができる。例えば、`ThirdPerson_AnimSyncBP` はこのフラグを使用して、ローカルの `CharacterMovement` から情報を取得するか、通信によって取得した情報を使用するかを切り替える。
* `Actor ID`と所有者フラグは、Diarkisネットワークに接続して必要な情報が利用可能になった後に利用可能になる。`DiarkisSyncComponent`には `OnDiarkisActorIDDecided` イベントがあり、このイベントが発生すると知ることができます。例えば、`ThirdPersonCharacter`の`Begin Play`はこのイベントを使用して、オーナーが決定した後にレプリケーションとRPC登録処理を行います。
* リモートアクターの作成時にカスタムデータを追加することも可能です。 `UDiarkisCharacterSyncComponent::SerializeSpawnActorCustomPayload()` でリモートアクター作成データにカスタムデータを追加し、`UDiarkisCharacterSyncComponent::DeserializeSpawnActorCustomPayload()` を呼び出して受信データから必要な情報を取得します。

#### アクターの位置同期

* アクターに `DiarkisSyncComponent` を追加することで、これらの機能が有効になります。同じRoomに接続されている他のホストにも同じActorが自動的に作成されます。リモートの `ThirdPersonCharacter` は位置、姿勢、ジャンプ状態などを自動的に同期します。リモートホストが Room を離れると、ローカルに存在するリモートアクターも自動的に削除されます。

#### 変数のレプリケーション

* アクターが持つ変数をネットワーク経由で同期します。対象となる変数には `UPROPERTY()` を指定する必要があります。`Register Replicated Variable`で対象のActorと変数名を登録し、`Send Replicated Variable`で必要なタイミングでデータを送信します。ただし、送信間隔は `DiarkisReplication::replicationMinimumInterval` が最も短いタイミングとなる。サンプルでは、`ThirdPersonCharacter` ブループリントの `Register Replicated Variables` と `Send Replicated Variable` に実装があります。

#### RPC

* RPCはネットワーク経由でActorの関数を呼び出す関数です。対象の関数には `UFUNCTION()` を指定する必要があります。`RegisterRpcUEFuncName`で対象のActorと変数名を指定して関数を登録し、`SendRpcUEFuncAll`などで送信します。リモート側で RPC を受信すると、同じ Actor ID を持つ Actor の指定した名前のメソッドを呼び出します。サンプルでは `ThirdPersonCharacter` のブループリントの `RPC` を登録し、`ADiarkisPluginSampleCharacter::HandleFire()` で `RPC` を送信しています。

### サンプル上で同期を利用している機能

本サンプルでは、上記の関数を用いて以下の処理を実装しています。

#### プレイヤーキャラクターの生成・位置の同期

* ローカルで`ThirdPersonCharacter`を作成すると、同じRoomに接続している他のホストでも自動的に同じアクターが作成されます。リモートの `ThirdPersonCharacter` は位置、姿勢、ジャンプ状態などを自動的に同期します。リモートホストが Room を離れると、ローカルに存在するリモートアクターも自動的に削除されます。また、リモートのプレイヤーキャラクターを作成する際には、オーナーが決定したアクターの色が初期データとして渡され、すべてのホストで同じ外見を再現します。

#### 弾の発射

* 弾の発射処理はリモートホスト側で `RPC` によって呼び出されます。ローカルで弾を発射する場合、あなたがプレイヤーキャラクターのオーナーであれば、 `RPC` コールがルームに参加している他のホストに送られます。この`RPC`呼び出しによって、リモートホストも同じ弾の発射処理を行います。弾丸の衝突とダメージ処理は、アクタのオーナーであるホストによって決定されます。

  ![image](/files/liMyoN4AHN68GTG9Xvo3) ![image](/files/7XeL2IhGZCWLMnK9Trtp)

#### 体力同期

* `ThirdPersonCharacter`の変数 `Health` はレプリケーションによって同期されます。`ThirdPersonCharacter`の所有者であるホストで被弾時に `Health` が減少すると、自動的にリモート側の `ThirdPersonCharacter` の `Health` 変数に同期される。また、`Recovery`ボタンによって`Health`が回復すると、その変化は自動的にリモート側の`ThirdPersonCharacter`に反映される。

## 動作確認環境

* 対応プラットフォーム
  * Windows 10/11
  * Mac OS X
  * iOS
  * Android
  * PS5
  * Xbox Series X|S
  * Nintendo Switch 2
* UnrealEngine バージョン
  * 5.6.1

### UE の バージョン毎の確認環境

* UE 5.6.1
  * VisualStudio 2022
    * WindowsSDK バージョン 10.0.22621.0
    * MSVC 14.38.33130
  * Mac/iOS 環境
    * macOS 14 Sonoma
    * Xcode 16.2
  * Android 環境
    * Android Studio Koala Feature Drop | 2024.1.2 Patch 1
    * Android NDK 27.2.12479018
    * Android 14.0 (API 34)
  * PS5 環境
    * UE5 ベースコード
      * 5.6.1 リリース タグ
    * PS5 SDK バージョン 11.00.00.46
  * Xbox Series X|S 環境
    * UE5 ベースコード
      * 5.6.1 リリース タグ
    * GDK: 250402
  * Nintendo Switch 2 環境
    * Nintendo SDK 20.5.6
* UE 5.5.4
  * VisualStudio 2022
    * WindowsSDK バージョン 10.0.22621.0
    * MSVC 14.38.33130
  * PS5 環境
    * UE5 ベースコード
      * 5.5.4 リリース タグ
    * PS5 SDK バージョン 10.000
  * Xbox Series X|S 環境
    * UE5 ベースコード
      * 5.5.4 リリース タグ
    * GDK: 240302

## 動作確認手順

### Windows のパッケージ作成手順

1. `Platform` メニュー => `Windows` => `Package Project` を選択する。
2. パッケージの出力先フォルダを指定する。
3. ビルドに成功したら、出力先に指定したフォルダから DiarkisPluginSample.exe を起動する。

### Android 端末で動作確認手順（別途AndroidStudio環境のセットアップが必要）

1. UnrealEditorのタスクバーから、`設定`⇒`プロジェクト設定`をクリックします。
2. Platformの項目で、`Android`をクリックします。

   ![image](/files/NocWl5mQ6ERF7JXpBurQ)

* APKパッケージ
  * Androidパッケージ
  * 最小SDKバージョン
  * ターゲットSDKバージョン
* ビルド
  * armv7サポートのチェックを外す（armv7は順次サポートされます）
  * arm64のサポートをチェックする

3. Platformの項目で `Android SDK` をクリックします。

   ![image](/files/5wkHwR3OZKwFmMNSSMfs)

* 以上の各項目を環境に合わせて設定してください。

4. Android プラットフォーム用にプロジェクトをパッケージングします。

   ![image](/files/AKMnNS8nRlXBtsK4Hnaz)
5. USBデバッグを有効にした Android デバイスを PC に接続します。
6. Android プロジェクトの出力ディレクトリにあるインストールスクリプト(Install\_\*\*\*.bat)を実行し、アプリをインストールします。
7. アプリを起動すると、ログイン画面が表示されます。


# Unity Plugin


# FieldWalker

## Diarkis Unity SDKサンプルアプリケーション

ネットワークミドルウェアである Diarkis が提供する Unity Engine ベースのサンプルアプリケーションです。MatchMake、DirectMessage、異なるプロトコル（UDP、TCP、P2P）を使用したプレイヤー間のデータ同期など、さまざまな機能が含まれています。 Diarkis 製品の詳細については [Diarkis Website](https://help.diarkis.io/) を参照してください。

### フォルダの説明

* `Core` Diarkis `C#` SDKをインストールします
  * `Client` Diarkis Module (Diarkis Core Library を使用するための `C#` クラスとヘルパー)
    * `Data` データ構造関連クラス
    * `Events` イベント関連クラス
    * `Logging` ロギングシステムに関連するクラス
    * `Modules` モジュール・ベース・クラス (Diarkis Core Library と相互作用するコードを含む)
    * `System` システム関連クラス (メモリ管理など)
    * `Utils` Diarkis型を使用するための各種ヘルパー
  * `Interop` Diarkis Native Code を呼び出すために自動生成された `C#` ファイル (これらのファイルを変更することは推奨されません)
  * `CustomCommands` 開発者は、ここのファイルを使用してカスタムコマンドを定義できます
    * `json\_definitions` カスタムコマンドの構造を定義する json ファイル
    * `custom` カスタムコマンドの構造を定義する json ファイル
  * `Libraries` `C++` で作られた Diarkis Native Code (Diarkis Core API)
* `Documentation` 本書
* `Plugin` Diarkis プラグイン スクリプト
  * `Callbacks` Diarkis イベントへのコールバックを登録するスクリプト
  * `Common` 各シーン共通のスクリプト(Diarkis SDK とのインタフェースを持つシングルトンクラスの DiarkisNetworkManager を含む)
  * `MainGame` MainGame シーン用スクリプト(3D環境におけるプレイヤーの同期)
* `Sample` Diarkis サンプルスクリプト
  * `Misc` その他ファイル（ライト、入力システムなど）
  * `Models` 3D モデルファイル
    * `Animations` キャラクター用アニメーションファイル
    * `Materials` マテリアルファイル
  * `Prefabs` プレハブファイル (例: DiarkisPlayer、Grid など)
  * `Scenes` Unity のシーンファイル。
  * `Scripts` Unity 関連スクリプト
    * `MainGame` MainGame シーン用スクリプト(3D環境におけるプレイヤーの同期)
    * `SceneManagers` シーン固有のクラス
    * `UI` ユーザーインターフェイススクリプト (ボタン、トグルなど)
* `Textures` 画像ファイル

### プロジェクトをロードする

Unityプロジェクトで、`Window>Package Manager`でパッケージマネージャを開き、`+`をクリックし、`Add Package From Disk`で、Diarkisサンプルフォルダのルートにあるpackage.jsonファイルを選択します。

![](/files/UA5deN8Bev8Ltr9uzOpj)

LTS の Unity バージョンを使用することをお勧めします（最終アップデートでは 6000.0.38f1）。\
これで、プロジェクトを `Unity Editor` で開くことができます。

![](/files/rusz9MOnMfgpgHPxPYZ7)

タイトルシーンファイル (`Assets/Scenes/DiarkisSample_1_Title.unity`) を選択します。\
ツールバーから再生ボタンをクリックすることでサンプルを実行することができます。

### MacOSにおけるライブラリのセキュリティエラー

MacOSでプロジェクトをロードしているユーザは、ライブラリが信頼できないというポップアップ通知が表示されることがあります。\
この場合、ウィンドウを閉じずに`システム設定>プライバシーとセキュリティ`を開き、`とにかく許可`ボタンを押してください。各ライブラリに対してこの操作を行う必要があるかもしれません（ただし、各ライブラリにつき1回のみ）。この操作を行わないと、Diarkisのライブラリが実行できなくなり、サンプルにランタイムエラーが表示されます。

![](/files/BXfPNbNNpdABgorW6PkZ)

## シーン解説

以下のように Scene List に Scene を選択してください。

![](/files/NQUIcZLCALYeizYL2sQx)

### タイトル

このシーンはサンプルの初期シーンで、Diarkisサーバーとの接続を設定するために様々な入力を提供することができます。

| Item       | Description                          | Example                                                             |
| ---------- | ------------------------------------ | ------------------------------------------------------------------- |
| Host       | Diarkis Http サーバの EndPoint URL       | asia-northeast1.diarkis.io (http\:// は不要です ) / 192.168.XXX.XXX:7000 |
| ClientKey  | Client Key （必要な場合は指定する）              | XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX                                |
| UID        | ユニークな User ID                        | Diarkis203                                                          |
| RandomUID  | ユニークな User IDをランダムに自動生成（unique GUID) | 0f0b66aa-8d45-4505-9208-c41d975a0c65                                |
| ServerType | 使用する Diarkis サーバの通信プロトコルを指定する        | UDP or TCP                                                          |

![](/files/Za4PdaHDKOYKeRQyGY2c)

### SelectFeature

この画面から、各 Diarkis モジュール に関する機能を選択できます。

![](/files/FJ9BDTyoOKvk2IKjycXb)

### Room

この画面は、`Diarkis Room Module` を使用します。`Room` は、この画面から Create、Join、RandomJoin で部屋に入室することができます。

| Item               | Description                                                 | Example |
| ------------------ | ----------------------------------------------------------- | ------- |
| MaxMember          | Room の最大入室人数                                                | 4       |
| AllowEmpty         | ユーザーが部屋にいない間、部屋の存在を許可する。                                    | true    |
| TTL                | 部屋に誰も居なくなった後の部屋の存続時間。                                       | 60      |
| Interval           | Broadcast, MessageTo のメッセージのサーバでの送信間隔。この間隔分メッセージはバッファリングする。 | 200     |
| Join Existing Room | RoomID を指定して入室する。 (上記の設定は使用しない)                             |         |
| Join Random Room   | 上記の設定を使ってランダムなルームに参加し、既存のルームが見つからない場合は新しいルームを作成する。          |         |
| Create Room        | 上記の設定を使用してルームルームを作成し、参加する。                                  |         |

![](/files/NU29xnZXfLlu35URLmoT)

### MainGame - Room

この画面は `Room` が参加に成功すると表示されます。`Diarkis Room Module` を通じてメッセージを送受信することで、プレイヤーやオブジェクトの位置を同期されます。

| Item               | Description                                                                                                                   | Example                      |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| My UID             | 自分のローカルキャラクタの UserID。                                                                                                         | Diarkis-Test                 |
| Room ID            | 入室している Room の ID。                                                                                                             | 17b39a74e5fff52c7f0000011fa4 |
| Owner UID          | Room オーナーの User ID 。                                                                                                          | Diarkis-Test                 |
| Members            | 現在 Room に入室しているメンバー。                                                                                                          | Diarkis-Test, ...            |
| Broadcast/Send     | Room 内の全員にメッセージを送信する。User ID を指定した場合は、そのユーザーのみにメッセージが送信する                                                                     |                              |
| Leave              | Room から退室して、前の画面に戻る。                                                                                                          |                              |
| Start P2P          | 他の Room メンバーとのP2P通信を初期化する。一度設定すると、メッセージの送受信は `Diarkis Room Module` ではなく `Diarkis P2P Module` を使って行われます。これにより、通信速度が向上する可能性がある。 |                              |
| Create Sphere/Cube | プレイヤーの近くに新しい object を生成して位置を同期する。                                                                                             |                              |
| Clear All Objects  | Room 内の作成された Object をすべて削除する。                                                                                                 |                              |
| Clear My Objects   | Room 内の自分が作成した Object をすべて削除する。                                                                                               |                              |

![](/files/UjndODw6eC27uZaFRSCB)

### MainGame - Field

この画面は `Diarkis Field Module` の3D表現を表示している。Field 通信は Join や Create のような事前コマンドを必要しません。Feild は1つのサーバタイプに1つしか存在せず、ユーザーは一度に1つの位置にしか存在できません。

| Item            | Description                                        | Example      |
| --------------- | -------------------------------------------------- | ------------ |
| My UID          | 自分のローカルキャラクタの UserID。                              | Diarkis-Test |
| Server Count    | メッシュネットワーク内の Diarkis サーバーの数は、地面に分割する Grid の数に影響する。 | 4            |
| Field Size      | Field のサイズ。全体の Field のサイズとそれを分割する Grid のサイズを変更される。 | 10000        |
| Field of Vision | 視界の範囲. 視界の範囲に入るリモートキャラクタが同期される。                    | 1800         |
| Grid Size       | 1つの Grid サイズ. Field Size と Server Count から算出される。   | 5000         |
| Leave           | Field を離れ、前のシーンに戻る。                                |              |

![](/files/AzQa9IJ96UDkXcwUIq0k)

### Ticket MatchMaking

この画面では `Diarkis MatchMaker Module` を使って `IssueTicket` を発行し、サーバから他のユーザとのマッチングを待つことができます。マッチングが完了すると、他の `Ticket members` にメッセージを送ることができます。

| Item            | Description                                                                                            | Example                                             |
| --------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
| TicketType      | 発行するチケットの種類を指定。 Diarkis Template Serverの基本バージョンでは、チケットタイプ0のみが実装されています。他のチケットタイプを扱うには、サーバ側で何らかの開発が必要です。 | 0                                                   |
| Issue Ticket    | サーバーにMatchMaking の IssueTicket を発行する。                                                                  |                                                     |
| Cancel          | IssueTicket をキャンセル。                                                                                    |                                                     |
| Status          | マッチング状況。                                                                                               | Not Started / Waiting For Match / Complete / Failed |
| Ticket Owner ID | 完了したチケットのオーナー UserID。                                                                                  |                                                     |
| Members         | 完成したチケットのメンバー。                                                                                         |                                                     |
| Message         | 他の Ticket の Member にメッセージを Broadcast する。                                                               |                                                     |

![](/files/9m9YdF66OZeAADH9GQfv)

### Search MatchMaking

ここでは、`Diarkis MatchMaker Module` の `Host/Search` を利用したサンプルがご確認頂けます。 ユーザーは、マッチング条件に基づいて一致するルームのリストを取得し、参加するルームを選択できます。 `Mode` のドロップダウンを使用して `Host` と `Search` の機能を切り替えることができます。

| **Item**              | **Description**                                                                            |
| --------------------- | ------------------------------------------------------------------------------------------ |
| **Mode Dropdown**     | マッチングモードを選択します（例：Host、Search）。Host モードでは、ホスト役として新しい 部屋 を作成できます。                            |
| **ProfileID Field**   | 入力フィールド（通常は事前入力済みまたは静的）で、マッチングプロファイル（例：*RankMatch*）を指定するものです。                              |
| **Rule Dropdown**     | マッチングの ルール を選択します。例えば `1v1` や、マッチングルールセットでサポートされている他のモードなどです。ルールを追加する場合サーバーのカスタムズが必要となります。 |
| **Rank Field**        | マッチメイキングに使用するランクまたはスキルレベルを設定し、ランクベースのフィルタリングを有効にします。                                       |
| **Max Players Field** | ホストする 部屋 の最大プレイヤー数を設定します。                                                                  |
| **Room Name Field**   | 作成する 部屋 の名前を指定するためのテキスト入力欄です。                                                              |
| **Password Field**    | 部屋 を ロック（プライベート）にするためのパスワードを設定するための入力欄です。                                                  |
| **Host Button**       | 指定されたパラメーターで 部屋 の作成を確認し、Host モードに入ります。                                                     |
| **Disband Button**    | ホストが作成後に部屋を解散（削除）する機能です。                                                                   |
| **Status Label**      | 現在のステータスを表示します。例：*Host*（Host モード時）または*Not started*（ Search モード時のみ有効）。                      |
| **My UID Label**      | マッチメイキングシステムにおける現在のユーザーの一意の識別子を表示し、デバッグやユーザー追跡に役立ちます。                                      |
| **Room List Table**   | 通常は利用可能なルームを表示し、列はルーム名、オーナー、プレイヤー数、ロック状態です。Host モードでは非表示または無効化されます。                        |
| **Join Room Button**  | Host モードではこのボタンは無効化されます。Search モードで見つかった 部屋 に参加するために使用されます。                                |
| **Password Field**    | ロックされた 部屋 に参加する際のパスワードを入力するためのフィールド Search モード時のみ有効）。                                      |

![](/files/uSWRSDqalzx4EGCLidi5)

マッチングした 部屋 に参加すると、メンバーははメッセージを交換でき、ホストはメンバーを Kick などが行えます

| **Item**             | **Description**                         |
| -------------------- | --------------------------------------- |
| **Room Info Panel**  | 現在入室している 部屋 の名前を表示します。例：*ROOM42*。       |
| **Participant List** | 現在入室している 部屋 のすべての参加者の UID の一覧を表示します。    |
| **Kick Button**      | ホストが選択した参加者を強制的に 部屋 から追い出します。           |
| **Chat Panel**       | 部屋参加者の間のチャットメッセージを表示し、追跡可能性のためUIDを含みます。 |
| **Chat Input Field** | 部屋内でチャットメッセージを入力するための入力ボックスです。          |
| **Send Button**      | 部屋参加者に、入力したチャットメッセージを送信します。             |

![](/files/KmHBJ4GLyNLDmK4SAfyu)

### Group

`Diarkis Group Module`は、この画面でランダムな Group へのCreaat や Join、他の Group メンバーにメッセージの Broadcast をおこなうことができます。

| Item           | Description                       |
| -------------- | --------------------------------- |
| Group ID       | 参加している Group ID                   |
| Message Button | 他の Group メンバーにメッセージを Broadcast する |

![](/files/9xAIx66x684aDSUJ74iA)

### Direct Message

`Diarkis DM Module`は、この画面で、他のメンバーに直接 Unicast メッセージを送信するために使用することができます。

| Item           | Description                                    |
| -------------- | ---------------------------------------------- |
| My UID         | 自分の UserID                                     |
| Recipient ID   | メッセージを送信するユーザーの UserID                         |
| Message Button | Recipient ID で指定したユーザーに Direact Message を送信する。 |

![](/files/pGNv84AsqLJ9x5yGTL4Z)

### Authoritative Network

こちらの画面は、`Connection Manager`を利用したサンプルになります。\
HostCliented 型のパケット通信機能 (SendToHost, SendToClients, SendBroadcast ) を使用したサンプルになります。

| Item                             | Description                                                                                         |
| -------------------------------- | --------------------------------------------------------------------------------------------------- |
| MinMembers                       | こちらで指定した人数を超えると DGS サーバーに接続します。この設定は `Network Type=DGS` のときのみ有効です。指定する人数は MaxMembers よりは小さい必要があります。 |
| MaxMembers                       | 作成する部屋の 最大人数を指定します。                                                                                 |
| Connection Mode                  | 以下の Connection Mode リストから選択してください。                                                                  |
| Network Type                     | 以下の Network Type リストから選択してください。                                                                     |
| Create/Join Game Instance Button | GameInstance の一意の名前を指定して、ボタン押下で同名の部屋が無い場合は作成され、既に存在する場合のその部屋に入室します。                                 |

![](/files/Z5x6s9wxQMFOuJ7ks5bB)

### Connection Mode

| Mode             | Description                                          |
| ---------------- | ---------------------------------------------------- |
| Single Authority | Authority となるホスト役が存在し、そのホスト役に対してクライアントが接続するタイプになります。 |
| No Authority     | Authority となるホスト役が存在せず、各クライアントが対等な関係で接続するタイプです。      |

### Network Type

| Type       | Description                                                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Room & P2P | 内部的に Room に入室して、 Room 経由の Relay 通信 と P2P を利用して通信します。P2P で通信できる時 P2P で通信し、P2P で通信できにないときは Room 経由で通信します。どの経路で通信しているか意識しないで使用することができます。 |
| Room       | 内部的に Room に入室して、 Room 経由の Relay 通信のみを利用して通信します。                                                                                       |

| Item           | Description                                           |
| -------------- | ----------------------------------------------------- |
| My UID         | 自分の UserID                                            |
| Message Button | ホスト役かクライアント役かでボタンのタイトルが変わります。ボタンを押下することでメッセージが送信されます。 |
| Leave Button   | 一つ前の画面に戻ります。                                          |

![](/files/eNgJDVjQuXHOIk3PWi26)

### HostClient Game Demo

こちらの画面は、`Connection Manager`を利用したサンプルになります。\
HostCliented 型や DGS 型のゲーム向けのサンプルになります。

| Item                             | Description                                                                                         |
| -------------------------------- | --------------------------------------------------------------------------------------------------- |
| MinMembers                       | こちらで指定した人数を超えると DGS サーバーに接続します。この設定は `Network Type=DGS` のときのみ有効です。指定する人数は MaxMembers よりは小さい必要があります。 |
| MaxMembers                       | 作成する部屋の 最大人数を指定します。                                                                                 |
| Connection Mode                  | 以下の Connection Mode リストから選択してください。                                                                  |
| Network Type                     | 以下の Network Type リストから選択してください。                                                                     |
| Create/Join Game Instance Button | GameInstance の一意の名前を指定して、ボタン押下で同名の部屋が無い場合は作成され、既に存在する場合のその部屋に入室します。                                 |

![](/files/C5BTNKAfrYpFHl0iuEWD)

### Connection Mode

| Mode             | Description                                          |
| ---------------- | ---------------------------------------------------- |
| Single Authority | Authority となるホスト役が存在し、そのホスト役に対してクライアントが接続するタイプになります。 |

### Network Type

| Type       | Description                                                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Room & P2P | 内部的に Room に入室して、 Room 経由の Relay 通信 と P2P を利用して通信します。P2P で通信できる時 P2P で通信し、P2P で通信できにないときは Room 経由で通信します。どの経路で通信しているか意識しないで使用することができます。 |
| Room       | 内部的に Room に入室して、 Room 経由の Relay 通信のみを利用して通信します。                                                                                       |
| DGS        | 内部的に Room に入室して、別途起動している DGS サーバーに接続して通信します。DGS を使用するときは、ConnectionMode は強制的に Single Authority になります。                                 |
| OFFLINE    | Diarkis サーバーに接続せず OFFLINE でゲームを動作させるためのモードになります。OFFLINE では、必ずConnectionMode は Single Authority を選択して頂く必要があります。                        |

| Item         | Description  |
| ------------ | ------------ |
| My UID       | 自分の UserID   |
| Leave Button | 一つ前の画面に戻ります。 |

![](/files/MKEVswrfW7HhvGyNAAlR)

Zombi : 自動で動き回るキャラクターが Zombi です。Zombi は ホスト役 が計算して動かしています。

\\

## クラス説明

### DiarkisNetworkManager

このクラスは Monobehaviour シングルトンクラスで、ユーザーが Unity Project で Diarkis の機能を使用したい限り有効である必要があります。\
Diarkis モジュールオブジェクトを公開するために静的に呼び出すことができます。

使用例:

```csharp
if (!DiarkisNetworkManager.IsConnected()) // check if the client is not yet connected to Diarkis
{
    DiarkisNetworkManager.Instance.ServerType = Diarkis.ServerType.UDP; // set the protocol to UDP
    DiarkisNetworkManager.Instance.HttpHost = "127.0.0.1:7000"; // set the server address
    DiarkisNetworkManager.Instance.ClientKey = _clientKeyInputField.text; // set the client Key
    DiarkisNetworkManager.Instance.UID = "John Doe"; //set the user ID
    DiarkisNetworkManager.Connect(); // initate Connection to Diarkis
}
```

インスペクタUIには、エディタからDiarkisのオプションを設定するための様々なオプションが含まれています：

![](/files/kM1StHbGtqw9Vbk6LC8M)

### イベント

Diarkis APIは、独自のイベントシステムを使用しています。Diarkisモジュールで特定のアクションが完了すると、関連モジュールによってイベントがトリガーされ、登録されているすべてのコールバックが [EventHandler](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Core/Client/Events/DiarkisEventHandler.cs) に呼び出されます。各イベントとそのパラメータのシグネチャは、このクラスにあります。

イベント登録の例:

```csharp
protected override void Start()
{
  DiarkisEventHandler handler = DiarkisNetworkManager.GetEventHandler();
  handler.RegisterCallback(DiarkisEventType.UDPConnect, (System.Action<DiarkisConnectionEventArgs>)((args) => { OnConnect(args); }), this);
}

public void OnConnect(DiarkisConnectionEventArgs args)
{
    if (args.GetStatus() == DiarkisConnectStatus.DCS_Success)
    {
        Logger.Debug("Connection Success");
    }
    else
    {
        Logger.Error("Connection Failed");
    }
}
```

[DiarkisCallbackDispatcher](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Scripts/Plugin/Callbacks/DiarkisCallbackDispatcher.cs) Monobehaviour クラスを使用すると、コードを1行も記述することなく、Editor ユーザーインターフェイスからイベントを登録できます。\
また、Event の型シグネチャを覚えておくのにも便利です。

例えば以下の設定は上のコードと同様の動作です。

![](/files/fMjnZlfAwk0HaeTR8Nra)

### Logging

Diarkis の Logging システムは、以下の2つのクラスに基づいています：[DiarkisLogger](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Core/Client/Logging/DiarkisLogger.cs) と [DiarkisLoggingFactory](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Core/Client/Logging/DiarkisLoggerFactory.cs)

`DiarkisLogger` インスタンスは `DiarkisLoggerFactory` によって作成され、異なるログレベルの情報を記録するために使用されます。これらの Logger は、アプリケーションの実行に関するメッセージ、警告、エラー、その他の関連情報を記録するためのメソッドを提供します。

`DiarkisLoggerFactory` はネイティブコード内でロガーを作成する役割を担い、Diarkis フレームワーク内の異なるモジュールに Logger インスタンスを提供します。LoggerFactory のコンストラクタで提供されるログ関数は、出力先、ログレベルフィルタリング、およびフォーマットオプションを制御するように設定することができ、開発者は特定のニーズに合わせてロギング動作を調整することができます。

使用例:

```csharp
    class Program
    {
        static void Main(string[] args)
        {
            var logSeverity = DiarkisLoggerSeverity.Debug;
            var logFileStream = DiarkisUtils.GetFileStreamWriter("./", "diarkis-cs-sample-logs.txt");
            var logAction = new Action<string>((str) =>
            {
                if (str.Contains("\n"))
                {
                    Console.Write(str);
                    logFileStream.Write(str);
                }
                else
                {
                    Console.WriteLine(str);
                    logFileStream.WriteLine(str);
                }
            });
            var loggerFactory = new Diarkis.DiarkisLoggerFactory(logSeverity, logAction);
            DiarkisLogger = loggerFactory.CreateLogger();
            var logger1 = (DiarkisLogger)loggerFactory.CreateLogger("ExampleLogger1");
            var logger2 = (DiarkisLogger)loggerFactory.CreateLogger("ExampleLogger2");
            logger1.Error("this is an error");
            logger2.Debug("this is a Debug log");
            logger2.Verbose("the Debug Log is not displayed because severity level is too high");
            var eventHandler = new DiarkisEventHandler();
            Diarkis.DiarkisInterface diarkis = new Diarkis.DiarkisInterface(loggerFactory, ServerType.UDP, eventHandler);
        }
    }
```

出力例：

```
2024-02-01 15:39:32[FAIL][ExampleLogger1]this is an error
2024-02-01 15:39:32[DEBUG][ExampleLogger2]this is a Debug log
```

注意: 上記のコードは、`./diarkis-cs-sample-logs.txt`にもファイルを作成し、その出力を登録します。

ログレベルの階層:

```csharp
public enum DiarkisLoggerSeverity {
  Trace = 0,
  Verbose,
  Debug,
  Info,
  Warning,
  Error,
  Fatal,
  None
}
```

### Logger Manager Inspector

`NetworkManager` インスペクタには、ロガーを簡単に設定するためのさまざまなフィールドもあります。たとえば、ロガーのカテゴリに応じて異なるログの重大度を設定したり、ゲーム画面にログを表示したりできます。

![](/files/R73rDfYf30FpP3VGrxUE)

### Diarkis Core Library

Diarkis Core Library は、`C++` で作成された DLL です。自動生成される .cs クラスのラッパーファイル `Core-wrapped` により、`C++` のネイティブコードを `C#` で使用することができます。

### Diarkis Module

Diarkis Module は Diarkis `C#` SDK の一部であり Unity とは独立しており、純粋な `C#` コンソールアプリケーションで使用できます。

### DiarkisInterface

これは実際にはモジュールそのものではなく、すべての Diarkis Module を一度に含むインターフェースとして機能するクラスです。`DiarkisNetworkManager` Unity クラスのメンバーですが、基本的な `C#` コンテキストでも使用できます。`DiarkisInterface` は、1つの Diarkis サーバ との接続につき1つ用意する必要があります。MatchMake 用のサーバと TURN 用のサーバを用意する必要がある場合は、`DiarkisInterface` のインスタンスも 2つ用意する必要があります。

使用例:

```csharp
  class Program
  {
      static void Main(string[] args)
      {
          var eventHandler = new DiarkisEventHandler(); // Event Handler object (cf: Events)
          var logSeverity = DiarkisLoggerSeverity.Error;
          var logAction = new Action<string>((str) => {Console.WriteLine(str); });
          var loggerFactory = new Diarkis.DiarkisLoggerFactory(logSeverity, logAction); // Logger Factory Object (cf: Logging)

          // DiarkisInterface object is created here (The ServerType is passed as parameter of the constructor UDP/TCP)
          Diarkis.DiarkisInterface diarkis = new Diarkis.DiarkisInterface(loggerFactory, ServerType.UDP, eventHandler);
          diarkis.HttpHost = "192.168.100.12:7000";

          // StartRuntimeThread will initiate the update loop using multiThreading,
          // Since Unity is not a thread safe Engine, it is not recommended to use this function in Unity
          // The recommended behavior is to call diarkis.UpdateComponents(); inside the FixedUpdate function of the
          // DiarkisNetworkManager object
          diarkis.StartRuntimeThread();
          Console.WriteLine("Connecting to " + diarkis.HttpHost);

          // Send the Http Authentication get request to the Http Diarkis server, if the authentication is sucessful
          // the server will reply
          diarkis.sendHttpAuth();

          // note: DiarkisUtils.WaitFor() is using threads will block the main thread until the condition is met.
          // It is not recommended to use it in a Unity Project since it will make the game freeze.
          // Instead it is recommended to use the DiarkisAsync.WaitFor() function which is using the Unity Coroutine mechanism
          if (!DiarkisUtils.WaitFor(() => { return diarkis.IsConnected(); }))
          {
              Console.WriteLine("Connection Timeout");
              return;
          }
          Console.WriteLine("UDP Connection success");

          // listen to the Room Join event (just an example)
          eventHandler.RegisterCallback(Diarkis.DiarkisEventType.RoomJoin,
          (DiarkisRoomJoinEventArgs eventArgs) => { Console.WriteLine("Room Joined!"); });

          // Join a random room
          diarkis.Room.SendJoinRandomRoom(4, 60, 100, true);

          // do any required more actions ...
          return;
      }
  }
```

### UDP

このモジュールは、Diarkis サーバとの `UDP` 接続を作成および管理するために使用され、Room や P2P などの他のいくつかのモジュールはこれに依存しています。

### TCP

このモジュールは、Diarkis サーバとの `TCP` 接続を作成および管理するために使用されます。Room や P2P などの他のモジュールは、`UDP` または `TCP` のいずれかを使用して設定できますが、一部のモジュールは `UDP` のみに依存します。

### Room

このモジュールは、複数のクライアントが `Room` に参加し、データ、メッセージ、またはオブジェクト情報を交換できるようにするために使用します。`P2P` 接続は `Room` が `UDP` 接続を使用していることを条件として、グループのメンバと開始することができる。一度に参加できる `Room` は1つだけで、同じ `Room` に入室するには同じサーバに接続している必要があります。

### P2P

このモジュールは、2つのクライアント間の直接ピアツーピア接続の作成と管理に使用します。オプション機能として `Room` モジュールで使用する。`Room` に参加したクライアントは `P2P` 同期を開始し、中間サーバを使用せずにデータを送信できるようになります。

### Group

`Group` モジュールは、複数のクライアントがグループに参加し、メッセージを交換するために使用します。クライアントが同時に参加できる Group 数は無制限で、同じ Group に入室するのに、同じサーバに接続する必要はありません。

### Field

このモジュールは一見 `Room` に似ていますが、いくつかの点で異なります。`Field` は1つのサーバネットワークに1つしか存在しません。Diarkis のサーバロジックでは、複数の `UDP` サーバを同じメッシュネットワークの一部として設定することができます。`Field` はいくつかの `Grid` に分割され、それぞれのサーバに割り当てられます。各サーバはそれぞれの `Grid` 内にいるクライアントの情報を伝える役割を担います。\
このモジュールは、同時に多数のプレイヤーが参加する可能性のある大規模な環境に最適です。

### Direct Message

この `Direct Message` ( DM )モジュールは、別サーバ間にいるメンバーに直接ユニキャストメッセージを送信するために使用されます。`DM` は `Room` や `Group` とは異なり 同じ部屋に入室せず、 `UerID` が分かっていれば直接送信できます。

### MatchMaker

`MatchMaker` モジュールには、マッチメイキングチケットを発行するための関数とイベントが含まれており、理想的には、マッチメイキング条件はサーバ側で定義されます。クライアントは `TicketType` を設定して `IssueTicket()` でチケットを発行するだけです。チケットの発券が完了すると(マッチング条件を満たすプレイヤーが全て見つかると)、 `TicketComplete`イベントが発生し、プレイヤーは`TicketBroadcast`コマンドでマッチングした他のメンバーと通信できるようになります。

### Session

`Session` は、同じメッシュネットワーク内の異なる Diarki サーバ上に同じセッションを存在させることができるという点で、`Field`モジュールと似ています。しかし、その目的は座標データを共有することではなく、限られたプレイヤーグループに対して標準的なデータを交換することです。

## カスタムコマンド

Diarkis モジュールを通して送受信されるコマンドのほとんどは `組み込みコマンド` です。つまり、その内容はコアライブラリによって把握され、例えば `DiarkisRoomJoinEventArgs` のような特定の型に解析されます。

しかし、独自のコマンドを開発したい開発者は、サーバ側だけでなくクライアント側にもコマンドを追加する必要があります。このようなコマンドの作成と解析を容易にするために、Diarkisは `Puffer` というスタンドアロンのバイナリを提供しており、linuxまたはmacのバイナリとして `Diarkis Server Template` (別のリポジトリ)にあります。

サンプルで使用されているいくつかのペイロードとコマンドを含むjson定義ファイルは[CustomCommands.json](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Core/CustomCommands/json_definitions/CustomCommands.json)にあります。

カスタムコマンドがサーバ側でどのように定義されるかによりますが、ほとんどの場合、カスタムコマンドの解析は `OnResponse` または `OnPush` イベント（`UDP` または `TCP` の場合）で行われます。\
上で定義したコマンド `Zoo` の使用例：

```csharp
        public void OnResponse(DiarkisResponseEventArgs args)
        {
            if (args.GetVersion() == Zoo.VER && args.GetCommand() == Zoo.CMD)
            {
                Zoo zooCommand = new Zoo();
                if (!Zoo.Unpack(args.GetPayload().ToArray()))
                {
                    DiarkisNetworkManager.Instance.Logger.Error("Failed to parse Zoo command");
                    return;
                }
                long lion = zooCommand.Lion;
                byte[] penguins = zooCommand.Penguins;
            }
        }
```

## 複数のDiarkisInterfaceインスタンスの使用

DiarkisNetworkManagerは、1つのdiarkis Serverへの単一の接続を使用するように設計されていますが、同時に複数の接続を処理することも可能です。\
そのためには、この例のように、DiarkisNetworkManagerオブジェクトを介して、複数の代替DiarkisInterfaceオブジェクトを作成することができます：

```csharp
        var diarkisLobby = DiarkisNetworkManager.GetDiarkisInterface("Lobby");
        var diarkisGame = DiarkisNetworkManager.GetDiarkisInterface("Game");
        diarkisLobby.EventHandler.RegisterCallback(DiarkisEventType.UDPConnect, new Action<DiarkisConnectionEventArgs>((args) => { Debug.Log("CONNECTED TO LOBBY SERVER."); }));
        diarkisLobby.EventHandler.RegisterCallback(DiarkisEventType.UDPConnect, new Action<DiarkisConnectionEventArgs>((args) => { Debug.Log("CONNECTED TO GAME SERVER."); }));

        DiarkisNetworkManager.Connect("Lobby", DiarkisTransportType.UDP, "LBY");
        DiarkisNetworkManager.Connect("Game", DiarkisTransportType.UDP, "GAME");
```

## 非同期コード

イベントは同期的にアクションを実行する方法ですが、開発者は特定のシナリオを非同期的に定義された順序で実行したい場合があります。そのために、[DiarkisUtils](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Core/Client/Utils/DiarkisUtils.cs) インターフェースで定義されている `WaitFor()` 関数があります。

使用例：

```csharp
class Program
  {
      static void Main(string[] args)
      {
          Diarkis.DiarkisInterface diarkis = new Diarkis.DiarkisInterface(loggerFactory, ServerType.UDP, eventHandler);
          diarkis.HttpHost = "192.168.100.12:7000";
          diarkis.StartRuntimeThread();
          Console.WriteLine("Connecting to " + diarkis.HttpHost);
          diarkis.sendHttpAuth();
          if (!DiarkisUtils.WaitFor(() => { return diarkis.IsConnected(); }))
          {
              Console.WriteLine("Connection Timeout");
              return;
          }
          Console.WriteLine("UDP Connection success");
          Console.WriteLine("Joining a Room...");
          diarkis.Room.SendJoinRandomRoom(4, 60, 100, true);
          if (!DiarkisUtils.WaitFor(() => { return diarkis.Room.ID != ""; }))
          {
              Console.WriteLine("Room Join Timeout");
              return;
          }
          Console.WriteLine("Room Joined!");
          return;
      }
  }
```

注意：`DiarkisUtils.WaitFor()` は `Threading.Sleep()` 関数を使っています。\
これは、条件が満たされるまでメインスレッドをブロックしていることを意味します。\
代わりに、 [DiarkisAsync](https://github.com/Diarkis/diarkis-help-center/blob/main/gitbook/renewal/ja/diarkis-client/samples/unity/field-walker/Scripts/Plugin/Common/DiarkisAsync.cs)インターフェイスの`DiarkisAsync.WaitFor()`バージョンを使用することをお勧めします。このインターフェイスでは、 `Threads` の代わりにUnityの `Coroutine` メカニズムを使用します。

Unity 非同期コードの使用例：

```csharp
void Start()
{
    StartCoroutine(ConnectAndJoinRoomAsync())
}

public IEnumerator ConnectAndJoinRoomAsync()
{
    DiarkisNetworkManager.Connect();
    DiarkisAsyncResult res = new DiarkisAsyncResult();
    yield return DiarkisAsync.WaitFor(() =>
    {
        return IsConnected();
    }, res);
    if (!res.completed)
    {
        if (Logger != null)
        {
            Logger.Error("Connection Time Out");
        }
        yield break;
    }

    Logger.Debug("Connection Success");
    Logger.Debug("Joining Random Room...");
    DiarkisNetworkManager.GetRoom().SendJoinRandomRoom(4, 60, 100, true);
    yield return DiarkisAsync.WaitFor(() =>
    {
        return DiarkisNetworkManager.GetRoom().ID != "";
    }, res);
    if (!res.completed)
    {
        if (Logger != null)
        {
            Logger.Error("Room Join Time Out");
        }
        yield break;
    }
    Logger.Debug("Room succesfully joined");
    yield return null;
}
```

## Diarkis イベントを非同期にキャッチする

Diarkisライブラリは、[イベントセクション](#Events)で説明したように、コールバック関数を使用してイベントを同期的に呼び出すように設計されています。しかし、前述の関数とイベントコールバックを組み合わせることで、イベントコールバックを非同期のコンテキストで使用することができます。

以下は、イベントコールバックを使って非同期にメンバーの入室を待つ関数の例です：

```csharp
        void Start()
        {
            StartCoroutine(WaitForRoomMemberJoin())
        }

        IEnumerator WaitForRoomMemberJoin()
        {
            DiarkisAsyncResult res = new DiarkisAsyncResult();
            DiarkisPayloadEventArgs eventArgs = null;
            var callback = (Action<DiarkisPayloadEventArgs>)((args) => { eventArgs = args; });
            DiarkisNetworkManager.GetEventHandler()?.RegisterCallback(Diarkis.DiarkisEventType.RoomMemberJoin, callback, this);
            yield return DiarkisAsync.WaitFor(() =>
            {
                return eventArgs != null;
            }, res);

            DiarkisNetworkManager.GetEventHandler()?.UnregisterCallback(callback);
            if (res.completed)
            {
                // callback triggered
            }
            else
            {
                //callback trigger timeout
            }
            yield return null;
        }
```

## Switch Between Release or Debug Native Libraries

Diarkis の Unity パッケージには、プラットフォームごとに Debug / Release のネイティブライブラリを簡単に切り替えられる便利な Editor ツールが含まれています。これにより、開発中（デバッグ用シンボル付き）と本番ビルドで、適切なバイナリを使い分けることができます。

メニューの場所：

**Unity Editor → Tools → Diarkis → Native Library Type → Use Debug / Use Release**

**Use Debug** を選択すると次の操作を行います：

* `Core/Libraries/Debug/{platform}` 以下のネイティブプラグインを有効化
* 対応する Release のネイティブプラグインを無効化
* Unity のプラグインインポート設定を自動で調整（プラットフォーム互換性やアーキテクチャなどを含む）

同様に **Use Release** を選ぶと：

* `Core/Libraries/Release/{platform}` 以下のネイティブプラグインを有効化
* Debug のネイティブプラグインを無効化
* 適切なインポート設定を適用

![](/files/4LG6pryuZhtWId0poCzS)

* ビルドの種類
  * Debug : `Debug ビルド` x `Diarkis ログ出力 有り`
  * Develop : `Release ビルド` x `Diarkis ログ出力 有り`
  * Release : `Release ビルド` x `Diarkis ログ出力 無し`

## カスタムアロケータ

Diarkis Native Libraryのコード全体で使用するアロケータサイズを任意に設定することができます。そのために、静的関数 `SetCustomAllocatorSize` と `SetCustomAllocator` が用意されています。

このサイズは使用可能なメモリー・サイズを超えてはならず、そうでない場合は例外がスローされます。

以下の例では、アロケーターのサイズを16MBに設定しています。通常、Diarkisライブラリーは500Kb以上使用することはありませんが、少なくとも4Mbは使用することをお勧めします。\
使用例:

```csharp
  class Program
  {
      static void Main(string[] args)
      {
          //Parameters are the Size of the Buffer, and the alignment.
          ICustomAllocator _originalAllocator = DiarkisLib.SetCustomAllocatorSize(1024 * 1024 * 16, 16);

          // code using Diarkis ...

          // When Diarkis resources are done to be used and have been disposed, the Allocator can be safely reset
          DiarkisLib.SetCustomAllocator(_originalAllocator);
          return;
      }
  }
```

## メモリー安全性に関する注意事項

Diarkis `C#` SDKは、もともと`C++`で作成され、ネイティブライブラリにコンパイルされたコア Diarkis Library に基づいています。自動生成される .cs クラスのラッパーファイルにより、`C++` のネイティブコードを `C#` で使用することができます。しかし、 `C++` と `C#` は明らかに同じ言語ではなく、最も重要な違いはメモリの管理方法です。`C#` はヒープ管理がガーベジコレクタ (GC) によって処理されるため、「メモリセーフ」です。`C++` は GC を持たないので、データの割り当て/解放を管理するのは開発者の責任です。

そのため、Native Diarkis Library の各ラップクラスは `C#` 型の `IDisposable` から派生しています。これらのオブジェクトは `Garbage Collector` にデータを気にしないように指示する機能を持っており、 `Dispose()` 関数を使用することで、管理されていないリソースを開発者が任意に解放することができます。

いったん `Dispose()` 関数が呼び出されると、このオブジェクトに割り当てられたアンマネージドリソースに依存するすべてのコードは `Memory Exception` や `Unity Editor` のクラッシュ、`Memory Leaks` を引き起こす可能性があることを理解しておくことが重要です。\
このような問題が発生した場合、リソースの解放が早すぎたり、使用時期が遅すぎたりした可能性があります

## visionOS と visionOS-Simulator でのビルド

visionOSでのビルドはiOSでのビルドと似ていますが、xcode.˶でいくつかの追加ステップが必要です。\
最初に知っておくべきことは、Unityのビルドウィンドウで、プラットフォームをvisionOSかvisionOS-Simulatorの間で切り替えることができるということです。

![](/files/S3TvBJW3VvImuHoPxWHR)

また、シミュレータでビルドするか、実際のvisionOSデバイスでビルドするかによって、エディタでライブラリのパラメータを変更する必要があります。\
例えば、`VisionOS-Simulator-arm64`でビルドしたい場合は、`Packages>Diarkis Plugin Sample>Core>Libraries>visionOS-Simulator-arm64` フォルダで2つのライブラリを選択し、プラットフォームを`visionOS`に設定して、競合するライブラリを無効にする必要があります。

![](/files/vx5PBi1mmIQcaHsvBFeX)

ビルドが完了したら、xcodeのプロジェクトファイルをxcodeで開きます（visionOSをサポートするにはバージョン15.2以上が必要です）。そして、iOSビルドと同様に、ターゲット・プラットフォームに対応するライブラリを左パネルのプロジェクトに追加します。

![](/files/lZszSRXbzUL2vfLYQOqv)

`Add to target`に必ずチェックを入れます： Unity-visionOS\`にチェックを入れてください。

![](/files/ETRDBp1CHjTEhHP3zKMN)

次に、`Unity-VisionOS > General > Frameworks, Libraries, and Embedded Content`で、2つのdiarkisライブラリを`Embed & Sign`に設定します。

![](/files/MLhtmsdooROEkOpStk6d)

(このステップはシミュレータに特有のものです)。\
`Unity-VisionsOS > Build Settings > Linking - General` で、`Other Linker Flags` に値 `-ld64` を追加します。

これでリンカが正しく設定され、アプリをvisionOS用にビルドできるようになりました。

![](/files/pbKWXSrwF7nWvoomPxQ2)

## 難読化

難読化ツールはDiarkisで安全に使用することができます。例として、[Beebyte Obfuscator from the Unity Asset Store](https://assetstore.unity.com/packages/tools/utilities/obfuscator-48919)はDiarkisサンプルの難読化バージョンをビルドするために使用されています。バイナリファイルにはアプリケーションのロジックを隠すアセンブリが含まれており、ハッカーがソースコードをリバースエンジニアリングすることを困難にします。

Beebyte Obfuscator を使用するには、まずライセンスを購入し、`Window > Package Manager` ウィンドウからダウンロードしてプロジェクトにインポートする必要があります。

![](/files/5DDSpHKlbBW2uAnUi7cX)

パッケージがインポートされると、`Assets/Editor/Beebyte/Obfuscator/ObfuscsatorOptions`.key に `settings prefab` が作成されます。\
この設定を変更することで、難読化をより積極的にしたり、そうでなかったりすることができます。\
デフォルト設定で問題なく動作するはずです。

![](/files/QetZkIPqTF9nscyQ20uy)

そして、`File > Build Settings` ウィンドウからプロジェクトをビルドし、`Clean Build` オプションを使用して、難読化設定が各ビルド間で適用されていることを確認します。

![](/files/U2DlzUmXdtyof2ajZJNR)


# HowToReplicatePosition.md

ここでは、Diarkis プラグインを使って GameObject の位置を同期させる方法を説明します。

## プロジェクトのセットアップ

まず、新しい Unity プロジェクトを作成します。\
次に、Unity Package Manager 経由で Diarkis Sample Package をインポートします（詳細は [Readme](/diarkis-client/samples/unity/field-walker) を参照してください）。\
最後に、シーンに `DiarkisNetworkManager` オブジェクトを追加し、 Diarkis Server への接続情報を設定します。\
これでセットアップは完了です。

## アセットを追加する

キャラクタを同期させる方法を確認するために、標準的な Unity アセットを使用した同期プロセスを説明します。\
まず、新しい空のシーンを作成し、マップ、ネットワークロジック、制御可能なキャラクターを作成するために、以下の手順に従ってください。\
それでは、いくつかの要素を追加しましょう：

* キーボード入力を処理するために InputSystem プレハブを追加します（`Packages>Diarkis Plugin Sample>Prefabs>Common>InputSystem` から）。
* MainCamera オブジェクトを `Packages>Diarkis Plugin Sample>Prefabs>CommonMainCamera` の MainCamera で置き換えます。
* `SpawnPosition` という空のオブジェクトを作成し、プレイヤーをスポーンさせたい場所を設定します。
* `DiarkisNetworkManager`オブジェクトを追加し（`Packages>Diarkis Plugin Sample>Prefabs>Common>NetworkManager` から）、Diarkisサーバーの設定に合わせて設定します（例：Http Host: 192.168.100.12:7000、Auto Start Connect: true）。
* `RoomManager`プレハブを追加します（`Packages>Diarkis Plugin Sample>Prefabs>SceneManagers>RoomManager` から）。\
  すべての手順が正しく行われていれば、シーンの構造は次のようになります。

![Sceneコピー](/files/FnsFu4tGLeDb69ur68WH)

ここで、RoomManager オブジェクトに、どのプレイヤープレファブをどこにスポーンするかを設定する必要があります。\
まず、`Packages>Diarkis Plugin Sample>Prefabs>MainGame>Player` からプレハブをコピーし、アセットフォルダ内の `Assets>Prefabs` のようにコピーします。例えば、メッシュ、スケルトン、アニメーション、コード（クラスが `DiarkisSynchronizedCharacter` を継承している限り）は開発者が作り直すことができます。\
リジッドボディ・アニメーター・カメラとユーザー入力ハンドラを含む完全なサンプルスクリプトは `Packages>Diarkis Plugin Sample>Scripts>Sample>MainGame>DiarkiSampleSynchronizedCharacter` にあります。Diarkis Playerプレハブはデフォルトでこのスクリプトを使用していますが、スクリプトが継承する関数 `GetLocalFrameDataPayload` と `EnqueueRemoteFrameData` を実装していれば、開発者が独自の実装を作成することも可能です。

![Sceneコピー](/files/4o4iAkhMZNG8PVNWlef0)

`DiarkisSynchronizedPlayer` クラスと同様に、 Diarkis Room モジュールの同期オブジェクトを簡単に作成するために継承できる`DiarkisRoomObject`クラスがあります。基本的なルームオブジェクトの実装例が `Packages>Diarkis Plugin Sample>Scripts>Sample>MainGame>DiarkiSampleRoomObject` にあります。このクラスは、2つの形（球体か立方体）とランダムな色を持つ単純なオブジェクトの作り方を示しています。開発者は、`UpdateProperties`関数と`GetObjectProperties`関数をオーバーライドすることで、同じようなクラスを再作成し、独自のオブジェクトプロパティを実装することができます。オブジェクトのプロパティ `Object Properties` は `Dictionary<string, double>` 型であります。`SetWaitForSyncPush(true)`を呼び出すと、次に `RoomObjectSync Push` を呼び出したときにプロパティが送信されます。

![Sceneコピー](/files/Qz5yTDeIa8qew4Rq3sdm)

あとは `RoomManager Inspector` の `Instance Handler` Parameter に `Player` プレハブ、`RoomObject` プレハブ、`SpawnPosition` を追加するだけです。

![Sceneコピー](/files/erZr8ZuCdUwxM1R7PmbV)

いくつかの設定はインスペクタで変更でき、キャラクターの動きをどのように同期させるかを決めることができます。\
以下はそれぞれの説明です：

* MaxRemoteQueueLength：リモートでキューに入れることができるペイロードの数（この数を50以上に保つことを推奨。）
* MaxFramePerPayload: 1つのペイロードが含むことができるフレーム数（最大時）、キャラクターが動いている場合、次に送信されるペイロードはこのフレーム数に達するまで待ってから送信される（または`Minimum Local Payload Send Frequency`の遅延の後に送信されます）
* MiniumLocalPayload Send Frequency : ペイロードを送信するための最小遅延（ペイロードの最後の送信からこの遅延に達した場合、最大フレーム値でペイロードが満たされるのを待たずに送信します）
* LocalFrameEnqueue Frequency（ローカル・フレーム・エンキュー頻度）：0に設定するとすべてのフレームが同期され、そうでなければこの値に基づいて遅延ごとに1フレームが同期されます。
* MaxRemoteInterpolation Duration In seconds: プレイヤーの動きを受け取ったときに許容される最大補間時間。
* MaxAllowedDelayInSeconds: 何らかの理由で古いリモートペイロードを受信した場合、自動的にスキップされます。

DeepL.com（無料版）で翻訳しました。

注：例えば、補間するローカルフレーム数が4で、ペイロードあたりの最大フレーム数が5である場合、各ペイロードは5\*5=25フレーム送信されることを意味するので、FPSが60であれば、60/25=\~0.42秒ごとに送信されます。

![Sceneコピー](/files/hyoVzcgJbj2OQvbqDaMn)

これでシーンはキャラクタを複製し、リモート接続されたプレイヤーを表示する準備ができました。

![Sceneコピー](/files/bNKMNV1EcQybbl5bKw0n)


# チュートリアル

ここでは、Unity または Unreal Engine を使用して Diarkis Plugin をゲームプロジェクトに導入するための手順をお伝えいたします。

### ゲームエンジン

#### Unity

Unityプラグインを使用したDiarkis C# SDKの統合方法を段階的に説明するガイド。\
Unityチュートリアルには以下が含まれます：

* はじめに（Diarkisサーバーへの接続）

#### Unreal Engine

Diarkis クライアントの基本的な機能を Unreal Engine から使うために、Diarkis Unreal Engine Plugin をプロジェクトに導入する手順を段階的に説明するガイド。\
Unreal Engine チュートリアルには以下が含まれます：

* Diarkis サーバーへの接続と切断
* Diarkis Module のカスタマイズ
* Diarkis Extension を使用したゲームスレッドでのコールバック


# Unity チュートリアル

このフォルダには、Diarkis Unity SDK をステップバイステップで学ぶための自己完結型チュートリアルが収録されています。各チュートリアルは前の内容を前提として構成されています。

| チュートリアル                             | テーマ                                |
| ----------------------------------- | ---------------------------------- |
| Tutorial 1 - Minimal Setup          | Diarkis サーバーへの接続 — HTTP 認証と UDP 接続 |
| Tutorial 2 - Ticket MatchMaker      | チケット方式マッチメイキング                     |
| Tutorial 3 - Host/Search MatchMaker | ホスト/サーチ方式マッチメイキング                  |
| Tutorial 4 - Room                   | ルーム参加・メンバー管理・ブロードキャスト              |
| Tutorial 5 - Direct Message         | UID を指定した特定クライアントへのメッセージ送信         |
| Tutorial 6 - Group                  | 文字列 ID で識別される軽量な pub/sub チャンネル     |

### 前提条件

* Unity 6000.0.64f1 以降
* Diarkis Unity SDK がプロジェクトにインポート済み
* 稼働中の Diarkis サーバー（ホストアドレスとクライアントキー）

### はじめ方

各チュートリアルには `Tutorials/Scenes/` 以下に専用シーン、`Tutorials/Scripts/` 以下に対応するマネージャースクリプトがあります。シーンを開き、チュートリアルの Markdown を読みながら Play モードに入ってください。

各チュートリアルスクリプトの先頭に、実行前に設定が必要な定数があります。

```csharp
private const string HOST       = "127.0.0.1:7000"; // サーバーアドレス
private const string CLIENT_KEY = "";               // クライアントキー
private const string UID        = "";               // 空文字の場合はランダム生成
```

### チュートリアルスクリプトは自由に改変できます

`Tutorials/Scripts/` 内のスクリプトは、ドキュメントと並行して読むことを想定した、意図的にシンプルな実装です。**自由に編集・実験してください**。また、独自機能を実装する際の出発点としても活用できます。

オリジナルを残しつつ実験したい場合は、変更前にスクリプトとシーンを複製してください。


# Tutorial 1 - Minimal Setup

このチュートリアルでは、Diarkis Unity SDK を使ってサーバーに接続するための最小限のコードを実装します。余分な機能は一切持たせず、接続の仕組みそのものに集中できる構成です。

終了時には以下のことが身についています:

* コアオブジェクト（`DiarkisNetworkManager` / `DiarkisInterface` / `DiarkisEventHandler`）の取得と使い方
* イベントコールバックの登録と解除
* HTTP 認証と UDP 接続を **2 ステップに分けて** 呼び出す方法
* 接続イベント（成功 / 失敗 / 切断）を受け取って状態表示する方法

Room・MatchMaker・Field などの機能モジュールは次のチュートリアル以降で扱います。

SDK の構成

コードを書く前に、主要コンポーネントの関係を把握しておきましょう。

#### DiarkisNetworkManager

Unity プロジェクトにおける Diarkis SDK のエントリーポイントです。シングルトン `MonoBehaviour` であり、`DiarkisNetworkManager.Instance` に初めてアクセスしたとき自動的に生成されます。手動で作成する必要はありません。

```
DiarkisNetworkManager（シングルトン MonoBehaviour）
  └─ DiarkisInterface（名前で管理、複数持てる）
       ├─ ConnectionManager（接続フロー管理）
       ├─ EventHandler（イベントコールバックのハブ）
       ├─ DiarkisUdp / DiarkisTcp（トランスポート）
       ├─ DiarkisRoom（ルーム機能）
       ├─ DiarkisMatchMaker（マッチメイキング）
       └─ DiarkisField / DiarkisP2P / ...（その他機能モジュール）
```

```mermaid
flowchart TD
    TM["Tutorial1MinimalSetupManager
あなたのゲームコード"]

    subgraph PLUGIN["Diarkis Unity Plugin"]
        DNM["DiarkisNetworkManager
シングルトン MonoBehaviour
C++ ランタイム初期化"]
    end

    subgraph SDK["Diarkis C# SDK  —  Core/Client/"]
        DI["DiarkisInterface
1サーバー接続
= 1インスタンス"]
        CM["ConnectionManager
接続フロー管理"]
        EH["EventHandler
コールバックハブ"]
        TR["DiarkisUdp / DiarkisTcp
トランスポート"]
        RM["DiarkisRoom
ルーム機能"]
        MM["DiarkisMatchMaker
マッチメイキング"]
        DI --- CM
        DI --- EH
        DI --- TR
        DI --- RM
        DI --- MM
    end

    subgraph INTEROP["SWIG バインディング  —  Core/Interop/"]
        SW["自動生成 C# バインディング
P/Invoke で
ネイティブ呼び出し"]
    end

    subgraph NATIVE["ネイティブ"]
        LIB["libdiarkis（C++）
メモリ管理
パケット処理
プロトコル実装"]
    end

    TM -->|"Connect · Disconnect
GetDiarkisInterface"| DNM
    DNM -->|"名前で辞書管理
毎フレーム Update"| DI
    EH -.->|"コールバック
OnUDPConnect など"| TM
    CM -->|"P/Invoke"| SW
    TR -->|"P/Invoke"| SW
    SW --> LIB
```

`DiarkisNetworkManager` の主な責務:

* **C++ ランタイムの初期化・終了** — `Awake()` で Diarkis コアを起動します
* **複数インターフェースの管理** — 名前をキーとした辞書で `DiarkisInterface` を管理します。ゲームサーバーとチャットサーバーに同時接続する場合など、複数の接続を持てます
* **毎フレームのポーリング** — `Update()` で全インターフェースの `UpdateComponents()` を呼び出し、C++ 側で受信したパケットを C# のコールバックへ届けます
* **シーンをまたいで保持** — `DontDestroyOnLoad` で永続化されます

#### DiarkisInterface

1 つの `DiarkisInterface` は、1 つのサーバー接続とそのすべての機能を束ねたオブジェクトです。`DiarkisNetworkManager.GetDiarkisInterface(name)` で取得または生成し、名前を省略するとデフォルト（空文字列）になります。

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface("");

// 各機能モジュールへのアクセス
DiarkisRoom       room       = diarkis.Room;
DiarkisMatchMaker matchMaker = diarkis.MatchMaker;
```

各機能チュートリアルでは、この `diarkis.Room` / `diarkis.MatchMaker` を起点に操作します。

`DiarkisInterface` は Diarkis の C++ コアライブラリへの C# ラッパーです。内部では **SWIG** によって自動生成された C# バインディング（`Core/Interop/`）を通じて P/Invoke でネイティブライブラリを呼び出しています。毎フレームの `UpdateComponents()` が不可欠ですが、`DiarkisNetworkManager` が代行するので開発者が意識する必要はありません。

#### DiarkisEventHandler

`DiarkisEventHandler` は `DiarkisInterface` の**コールバックハブ**です。接続成功・失敗・受信メッセージ・マッチメイキング結果など、SDK が発生させるすべてのイベントはここを通じて登録します。

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;
```

**コールバックの登録**

```csharp
handler.OnUDPConnect(OnConnect, this);
```

* 第 1 引数はコールバックメソッドです。
* 第 2 引数は**オーナー** — 任意のオブジェクトで、通常は `this` を渡します。オーナーは後で一括解除するときに使います（クリーンアップ 参照）。

**コールバックのシグネチャ**

各イベントには決まったシグネチャがあります。

| イベント                 | コールバックシグネチャ                          |
| -------------------- | ------------------------------------ |
| `OnEndpointReceived` | `Action<DiarkisHTTPAuthDataModel>`   |
| `OnUDPConnect`       | `Action<DiarkisConnectionEventArgs>` |
| `OnUDPDisconnect`    | `Action<bool>`（再接続フラグ）               |
| `OnUDPFail`          | `Action<string>`（エラーメッセージ）           |
| `OnHttpError`        | `Action<string>`（エラーメッセージ）           |

シンプルな処理はラムダで書くこともできます。

```csharp
handler.OnUDPFail(_ => SetState("UDP 接続失敗", ColorRed, false), this);
```

#### ConnectionManager

`DiarkisInterface` の内部コンポーネントで、接続シーケンスを管理します。Diarkis への接続は 2 ステップで構成されています。

```mermaid
flowchart TD
    S1["ステップ 1: HTTP 認証<br/>SendGetServerEndpointRequest()"]
    S2["ステップ 2: UDP 接続<br/>ConnectWithAuthData()"]
    EV1[OnEndpointReceived]
    EV2[OnUDPConnect]
    ERR1[OnHttpError]
    ERR2[OnUDPFail]

    S1 --> EV1
    S1 --> ERR1
    EV1 -->|"Connect UDP を押す"| S2
    S2 --> EV2
    S2 --> ERR2
```

### なぜ 2 ステップに分けるのか

実際のゲームでは、クライアントが Diarkis HTTP サーバーへ直接リクエストを送ることは推奨されません。**クライアントキーなどのシークレットを保護する**ため、HTTP 認証は認証機能を持つゲームサーバーなどを経由して行うことをご検討ください。

```mermaid
sequenceDiagram
    participant C as クライアント
    participant G as ゲームサーバー
    participant D as Diarkis HTTP サーバー
    participant U as Diarkis UDP サーバー

    Note over C,D: 推奨フロー（本番）
    C->>G: 接続リクエスト（ログイン済みセッション）
    G->>D: HTTP 認証（ClientKey はサーバー側で管理）
    D-->>G: エンドポイント情報（host, port, 暗号化キー）
    G-->>C: エンドポイント情報を転送

    Note over C,U: ステップ 2（クライアントが直接実行）
    C->>U: UDP 接続（エンドポイント情報を使用）
    U-->>C: OnUDPConnect
```

このサンプルはその **2 ステップを明示的に分けて呼び出す**実装を示しています。`connectOnAuthResponse = false` を設定することで、HTTP レスポンス受信後も UDP 接続を自動開始せず、明示的に制御できます。

#### 本番での AuthData の受け取り方

このサンプルでは `SendGetServerEndpointRequest` を使ってクライアントが直接 Diarkis HTTP サーバーへリクエストを送り、`OnEndpointReceived` でパース済みの認証データを受け取っています。

本番では、ゲームサーバーが Diarkis HTTP サーバーへリクエストを送り、その結果を JSON 文字列としてクライアントへ転送します。クライアントはその JSON を `DiarkisAuthResponse.ParseFromJson()` でパースして `ConnectWithAuthData` に渡します。

```csharp
// ゲームサーバーから JSON 文字列を受け取ったコールバック内で（例）
// {"serverType":"UDP","serverHost":"10.0.0.1","serverPort":"7200",
//  "sid":"abc123...","encryptionKey":"...","encryptionIV":"...","encryptionMacKey":"..."}
void OnJsonReceivedFromGameServer(string json)
{
    DiarkisAuthResponse authResponse = new DiarkisAuthResponse();
    if (authResponse.ParseFromJson(json))
    {
        DiarkisHTTPAuthDataModel authData = new DiarkisHTTPAuthDataModel(authResponse);
        diarkis.ConnectWithAuthData(authData, useConnectionManager: true);
    }
}
```

`SendGetServerEndpointRequest` はサンプル・開発時のショートカットです。本番コードでは使わないことを推奨します。

#### ショートカット: DiarkisNetworkManager の Connect()

2 ステップを分けずに 1 回の呼び出しで接続したい場合（簡単なプロトタイプなど）、`DiarkisNetworkManager` の `Connect` メソッドを使うと HTTP 認証と UDP 接続を自動でまとめて実行できます。

```csharp
DiarkisNetworkManager.Connect(INTERFACE_NAME, HOST, CLIENT_KEY, uid, "UDP");
```

手軽ですが各ステップのタイミングを細かく制御することはできません。HTTP 認証をサーバー側で行う本番環境では、このチュートリアルで示した 2 ステップ方式を推奨します。

### シーンのセットアップ

**前提条件**: Unity 6000.0.64f1 以降、Diarkis Unity SDK がプロジェクトにインポート済み

`Tutorials/Scenes/Tutorial1-MinimalSetup.unity` を開きます。シーンには `Tutorial1-Minimal-UI`（Canvas / ボタン / Network State テキスト）と `TutorialManager`（`Tutorial1MinimalSetupManager` アタッチ済み）が含まれています。

#### シーンへの DiarkisNetworkManager の配置

前述のとおり `DiarkisNetworkManager` は実行時に自動生成されますが、**シーンに `DiarkisNetworkManager` GameObject をあらかじめ配置しておくことを推奨します**。

> **Inspector フィールドとコードの違い:** `DiarkisNetworkManager` は `PreStoredHttpHost`・`PreStoredClientKey`・`UseRandomUID` を Inspector に公開しています。これは**デバッグ用の便宜機能**であり、再コンパイルなしにエンドポイントを素早く切り替えるのに便利です。このチュートリアルのコードは同じプロパティをランタイムで明示的に設定しており、これが本番環境で推奨されるアプローチです。コードは Inspector の値が適用された後に実行されるため、コードによる設定が常に優先されます。

<figure><img src="/files/HVzZUjIjJyjRbgQyCIMQ" alt=""><figcaption></figcaption></figure>

`Tutorials/Scripts/Tutorial1MinimalSetupManager.cs` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000"; // 本番では自社ゲームサーバーのアドレス
private const string CLIENT_KEY = "";               // 発行済みのクライアントキー
private const string UID        = "";               // 空文字の場合はランダム生成
```

UID はサーバー上でこのクライアントを一意に識別する値です。空文字にすると `Guid.NewGuid()` でランダム生成されます。衝突が起きないため手軽なテストに便利です。ダイレクトメッセージや特定のルーム操作など、2 つのインスタンスがお互いを既知の ID で参照する必要がある場合は、`"player-1"` のような固定値を設定してください。

<figure><img src="/files/H8YV4dsByyG1Fjtsdj0W" alt=""><figcaption></figcaption></figure>

### コードの解説

#### Start() — 初期化とイベント登録

SDK オブジェクトを取得し、すべてのコールバックを登録する場所です。Diarkis のイベントはすべて `DiarkisEventHandler` を通じて届くので、これが最初にやるべきことです。

```csharp
private void Start()
{
    DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
    DiarkisEventHandler handler = diarkis.EventHandler;

    // ステップ 1 の結果
    handler.OnEndpointReceived(OnEndpointFetched, this);
    handler.OnHttpError(_ => SetState("HTTP 認証エラー", ColorRed, false), this);

    // ステップ 2 の結果
    handler.OnUDPConnect(OnConnect, this);
    handler.OnUDPDisconnect(OnDisconnect, this);
    handler.OnUDPFail(_ => SetState("UDP 接続失敗", ColorRed, false), this);
}
```

各登録呼び出しの第 2 引数に渡している `this` が**オーナー**です。これにより `OnDestroy` で一括解除できます（クリーンアップ 参照）。

`OnEndpointReceived` は SDK が HTTP レスポンスをパースして `DiarkisHTTPAuthDataModel` を組み立てたタイミングで発火します。`connectOnAuthResponse = true` の場合は SDK が自動で UDP 接続を開始するため、**このコールバック内で `ConnectWithAuthData` を呼ばないでください**（二重接続が発生します）。`connectOnAuthResponse = false`（このサンプルの設定）の場合のみ、任意のタイミングで手動呼び出しします。

#### ステップ 1: エンドポイント取得

```csharp
private void OnFetchEndpointClicked()
{
    DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);

    // false にすることで、HTTP レスポンス受信時に UDP 接続を自動開始しない
    diarkis.connectOnAuthResponse = false;

    diarkis.SetClientKey(CLIENT_KEY);
    diarkis.HttpHost = HOST;

    // レスポンスは OnEndpointReceived → OnEndpointFetched で受け取る
    diarkis.SendGetServerEndpointRequest("UDP", useConnectionManager: false);
}

private void OnEndpointFetched(DiarkisHTTPAuthDataModel authData)
{
    // authData には接続に必要な全情報が入っている:
    //   authData.ServerHost / ServerPort — 接続先
    //   authData.Sid                     — セッション ID
    //   authData.EncryptionKey / IV / MacKey — 暗号化情報
    //
    // この authData をそのままステップ 2 の ConnectWithAuthData に渡す。
    _authData = authData;
    _endpointFetched = true;
    SetState("エンドポイント取得済み — UDP 接続可能", ColorBlue, false);
}
```

`connectOnAuthResponse = false` が 2 ステップ分割の鍵です。これを設定しないと HTTP レスポンスを受け取った瞬間に UDP 接続が始まり、ステップ 2 を手動で呼ぶ意味がなくなります。

#### ステップ 2: UDP 接続

```csharp
private void OnConnectUdpClicked()
{
    // ステップ 1 の OnEndpointFetched で受け取った authData を使って UDP 接続を開始する
    DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME)
        .ConnectWithAuthData(_authData, useConnectionManager: true);
}
```

`ConnectWithAuthData` は authData の SID・EncryptionKey・EncryptionIV・EncryptionMacKey を使って UDP 接続を開始します。結果は `OnUDPConnect` または `OnUDPFail` で届きます。

#### コールバック

`Start()` で登録したコールバックに接続イベントが届きます。

```csharp
private void OnConnect(DiarkisConnectionEventArgs args)
{
    if (args.GetStatus() == DiarkisConnectStatus.DCS_Success)
        SetState("接続済み", ColorGreen, true);
    else
        SetState("接続失敗", ColorRed, false);
}

private void OnDisconnect(bool reconnecting)
{
    // reconnecting が true の場合は自動再接続の試みが進行中
    if (!reconnecting)
        SetState("未接続", ColorRed, false);
}
```

`DiarkisConnectionEventArgs.GetStatus()` は `DiarkisConnectStatus` 列挙型を返します。`OnUDPConnect` は成功・失敗どちらでも発火するため、必ずステータスを確認してください。

`OnUDPDisconnect` の `reconnecting` フラグが `true` のとき、SDK はすでに自動再接続を試みています。この場合は「再接続中...」などの UI 表示にとどめ、最終切断として扱わないようにしましょう。

#### OnDestroy() — クリーンアップ

```csharp
private void OnDestroy()
{
    DiarkisNetworkManager.GetEventHandler(INTERFACE_NAME)?.UnregisterCallbacks(this);
}
```

`UnregisterCallbacks(this)` は `this` をオーナーとして登録した**すべての**コールバックを一括解除します。イベントごとに個別に解除する必要はありません。シーン遷移やオブジェクト破棄時に必ず呼んでください。これを怠ると、破棄済みオブジェクトへの参照がコールバックとして残り続けます。

### 動作確認

シーンの設定とコードの理解が整ったら、Play モードに入り **Fetch Endpoint** → **Connect UDP** の順にボタンを押してください。Network State ラベルが緑色になり「接続済み」と表示されれば成功です。**Disconnect** ボタンで初期状態に戻ります。

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlFJ89PMX2ike3NyauXNM%2Fuploads%2Fgit1V968Z28muiEhzYl2%2FRecording%202026-03-12%20084623.webm?alt=media&token=6e6c8ad6-9668-4cbf-a8b8-302965532f88>" %}

### 接続フロー

```mermaid
flowchart TD
    B1([Fetch Endpoint]) --> F1["SendGetServerEndpointRequest()"]
    F1 -->|成功| R1[OnEndpointReceived]
    F1 -->|失敗| E1[OnHttpError]

    R1 --> B2([Connect UDP])
    B2 --> F2["ConnectWithAuthData()"]
    F2 -->|成功| R2[OnUDPConnect]
    F2 -->|失敗| E2[OnUDPFail]
```

接続の基本が確認できたら、次のチュートリアルに進みましょう。

* **Tutorial 2 - Ticket MatchMaker**: チケット方式のマッチメイキング
* **Tutorial 3 - Host/Search MatchMaker**: ホスト/サーチ方式のマッチメイキング
* **Tutorial 4 - Room**: 入室・メンバー管理・ブロードキャスト


# Tutorial 2 - Ticket MatchMaker

このチュートリアルでは、Diarkis のチケット方式マッチメイキングを実装します。各クライアントがチケットを発行するだけで、サーバーが同じチケットタイプを持つクライアントを自動的にグループ化します。ホストを選ぶ必要も、ルームを検索する必要もありません。

終了時には以下のことが身についています:

* `DiarkisInterface` から `DiarkisMatchMaker` モジュールを取得する方法
* `DiarkisEventHandler` にマッチメイキング用コールバックを登録する方法
* チケットの発行・キャンセルと各レスポンスの処理
* SDK からメンバーリストとオーナー情報を取得して表示に反映する方法
* チケット内でブロードキャストメッセージを送受信する方法

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続するため、手動の接続ステップはありません。

### チケット方式とは

Diarkis のマッチメイキングには主に 2 つの方式があります。

| 方式                 | 概要                                        |
| ------------------ | ----------------------------------------- |
| **Ticket（チケット方式）** | 各クライアントがチケットを発行し、サーバーが条件の合うクライアントをグループ化する |
| **Host/Search**    | 誰かがホストとなってルームを作り、他のクライアントがそのルームを検索して参加する  |

チケット方式ではホストを意識する必要がありません。クライアントは `SendIssueTicket()` を呼ぶだけで、サーバーがグループ化を管理します。

`ticketType`（byte 0〜255）は**マッチングのカテゴリ**を表します。同じ `ticketType` を持つチケット同士だけがグループ化されるので、ランクマッチ・カジュアルマッチなどモードごとに異なる値を使います。

### マッチングの流れ

```mermaid
flowchart TD
    A([開始]) --> B[SendIssueTicket]
    B --> C{OnMMTicketIssueResponse}
    C -->|失敗| Z([エラー終了])
    C -->|成功 - 発行者| D[チケット参加済み]
    C -->|成功 - 参加者には| DJ[OnMMTicketJoin]
    DJ --> D
    D --> E[OnMMTicketMemberJoin]
    E --> D
    D -->|必要人数が揃った| G[OnMMTicketComplete]
    D -->|キャンセル| H[SendTicketCancel]
    H --> I[OnMMTicketCancel]
    I --> Z2([終了])
```

重要な非対称性: チケットを**発行した**クライアントは `OnMMTicketIssueResponse` でチケット参加を確認しますが、`OnMMTicketJoin` は**受け取りません**。`OnMMTicketJoin` は既存のチケットに後から参加した側（2 人目以降）にのみ届きます。コードはこの両方のケースに対応しています。

### シーンのセットアップ

`Tutorials/Scenes/Tutorial2-TicketMatchMaker.unity` を開き、接続先を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
```

Play モードに入るとシーンが自動接続します。**Network State** ラベルが緑色になり「接続済み」と表示されると、**Issue Ticket** ボタンが有効になります。

> **Tip — 同一マシンで 2 クライアントをテストする場合:** **Edit > Project Settings > Player** の **Run In Background** を有効にしてください。これを有効にしないと、フォーカスのない Unity インスタンスはネットワークイベントを処理せず、UI がウィンドウをアクティブにしたときしか更新されません。

<figure><img src="/files/idAAQl6SzzTCqpW9JYmB" alt=""><figcaption></figcaption></figure>

### コードの解説

#### MatchMaker モジュールの取得

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
_matchMaker = diarkis.MatchMaker;
```

機能モジュールはすべて `DiarkisInterface` を通じて取得します。Tutorial 4 の `diarkis.Room` も同じパターンです。

#### イベントの登録

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnMMTicketIssueResponse(OnIssueTicketResponse, this);
handler.OnMMTicketJoin(OnTicketJoin, this);
handler.OnMMTicketMemberJoin(args => OnTicketMemberJoin(args), this);
handler.OnMMTicketMemberLeave(args => OnTicketMemberLeave(args), this);
handler.OnMMTicketComplete(OnTicketComplete, this);
handler.OnMMTicketCancel(_ => OnTicketCancelled(), this);
handler.OnMMTicketBroadcast(OnTicketBroadcast, this);
```

このチュートリアルで登録するイベントの一覧です。

| イベント                      | 発火タイミング             | コールバックシグネチャ                            |
| ------------------------- | ------------------- | -------------------------------------- |
| `OnMMTicketIssueResponse` | サーバーがチケット発行を応答      | `Action<DiarkisMMResponseEventArgs>`   |
| `OnMMTicketJoin`          | 既存チケットに参加した（参加者側のみ） | `Action<DiarkisMMTicketJoinEventArgs>` |
| `OnMMTicketMemberJoin`    | 他のプレイヤーがチケットに参加     | `Action<DiarkisMMTicketJoinEventArgs>` |
| `OnMMTicketMemberLeave`   | プレイヤーがチケットから離脱      | `Action<DiarkisMMResponseEventArgs>`   |
| `OnMMTicketComplete`      | 必要人数が揃いマッチング成立      | `Action<DiarkisMMResponseEventArgs>`   |
| `OnMMTicketCancel`        | チケットがキャンセルされた       | `Action<DiarkisMMResponseEventArgs>`   |
| `OnMMTicketBroadcast`     | ブロードキャストメッセージを受信    | `Action<DiarkisMMSyncEventArgs>`       |

`OnDestroy` での `UnregisterCallbacks(this)` はお忘れなく。オーナーパターンの詳細は Tutorial 1 を参照してください。

#### SendIssueTicket() — チケットの発行

```csharp
private void OnTicketClicked()
{
    byte ticketType = GetTicketType();
    SetStatus("チケット待機中...");
    _matchMaker.SendIssueTicket(ticketType);
}
```

`SendIssueTicket` は非同期です。結果は `OnMMTicketIssueResponse` で届きます。

#### OnIssueTicketResponse — チケットに参加した（発行者側）

```csharp
private void OnIssueTicketResponse(DiarkisMMResponseEventArgs args)
{
    if (!args.IsSuccess())
    {
        SetStatus($"チケット発行失敗 (code: {args.GetErrorCode()})");
        return;
    }
    // 発行者は OnMMTicketJoin を受け取らないため、ここで UI を更新する
    SetStatus("チケット参加済み");
    RefreshTicketUI();
    RefreshButtons();
}
```

`OnMMTicketIssueResponse` の成功が、発行者がチケット待機状態に入ったことの確認です。`OnMMTicketJoin` は後から参加した側（2 人目以降）にのみ届きます。どちらのパスも `RefreshTicketUI()` を呼んでメンバーリストとオーナー表示を更新します。

#### メンバーリストとオーナーの更新

イベント引数からメンバー情報を読み取るのではなく、各イベント後に SDK から直接取得します。

```csharp
private void RefreshTicketUI()
{
    byte ticketType = GetTicketType();

    if (_ownerIDValueText != null)
        _ownerIDValueText.text = _matchMaker.GetTicketOwnerUID(ticketType) ?? "";

    if (_memberListText != null)
    {
        var members = _matchMaker.GetMembers(DiarkisMatchMakerType.Ticket, ticketType);
        _memberListText.text = members != null && members.Count > 0
            ? string.Join("\n", members)
            : "";
    }
}
```

`GetTicketOwnerUID()` と `GetMembers()` は常にサーバー側の最新状態を反映しているため、参加・離脱・完了のすべてのコールバックからこのヘルパーを呼んでいます。

#### OnTicketComplete — マッチング成立

```csharp
private void OnTicketComplete(DiarkisMMResponseEventArgs args)
{
    if (!args.IsSuccess())
    {
        SetStatus($"チケット完了失敗 (code: {args.GetErrorCode()})");
        return;
    }
    SetStatus("マッチング成立！");
}
```

`OnMMTicketComplete` はチケット内の**全クライアントに同時に届きます**。これをゲームセッション開始のシグナルとして使います。

<figure><img src="/files/OVuWzJ50vArMb5KOTDE4" alt=""><figcaption></figcaption></figure>

#### SendTicketBroadcast — チケット内メッセージ

マッチング待機中にチケット内のメンバー全員へメッセージを送れます。`SendTicketBroadcast` は送信者自身にも届くため、ペイロードに自分の UID を含めることで送受信側を区別します。

```csharp
private void OnMessageClicked(string message)
{
    // "uid:message" 形式でエンコードして送信者を識別できるようにする
    string myUID = GetMyUID();
    byte[] payload = Encoding.UTF8.GetBytes($"{myUID}:{message}");
    _matchMaker.SendTicketBroadcast(GetTicketType(), new ArraySegment<byte>(payload));
}

private void OnTicketBroadcast(DiarkisMMSyncEventArgs args)
{
    // "uid:message" を分解して自分か他者かを判別する
    DiarkisByteVector payload = args.GetPayload();
    string raw = Encoding.UTF8.GetString(/* payload のバイト列 */);
    int sep = raw.IndexOf(':');
    string senderUID = raw[..sep];
    string message   = raw[(sep + 1)..];

    string prefix = senderUID == GetMyUID() ? "[自分]" : $"[{senderUID}]";
    AddChatLine($"{prefix} {message}");
}
```

ペイロードはバイト配列です。形式はゲームに合わせて自由に決めてください。このサンプルでは UID プレフィックス付き UTF-8 テキストを使っています。

#### TicketState によるボタン制御

`MatchMakerTicketState` でクライアントの現在位置を把握し、ボタンと入力フィールドの有効/無効を切り替えます。

```mermaid
stateDiagram-v2
    [*] --> None
    None --> TicketJoined : SendIssueTicket()
    TicketJoined --> TicketComplete : OnMMTicketComplete
    TicketJoined --> None : SendTicketCancel()
```

```csharp
MatchMakerTicketState state = _matchMaker.GetTicketState(ticketType);

bool notStarted = state == MatchMakerTicketState.None;
bool inTicket   = state == MatchMakerTicketState.TicketJoined;
bool complete   = state == MatchMakerTicketState.TicketComplete;

_ticketButton.interactable    = notStarted && _connected;
_cancelButton.interactable    = inTicket;
_ticketTypeInput.interactable = !inTicket;  // チケット中は変更不可
```

### Cancel と Leave の違い

この 2 つのメソッドは似ていますが、使うタイミングが異なります。

| メソッド               | 呼ぶタイミング            | 呼べるのは誰か                         |
| ------------------ | ------------------ | ------------------------------- |
| `SendTicketCancel` | マッチング待機中（チケット未完了）  | チケットオーナーのみ — 全メンバーのチケットをキャンセルする |
| `SendTicketLeave`  | チケット完了後、ルームが生成された後 | 任意のメンバー — 他のメンバーに影響せずルームから退出する  |

このチュートリアルの **Cancel** ボタンは `SendTicketCancel` を呼び、オーナーが待機中のチケットをキャンセルします。非オーナーのメンバーがマッチング後のルームから抜けたい場合は、代わりに `SendTicketLeave` を使います。

### バックフィル（上級）

このチュートリアルでは使用しませんが、バックフィルの概要を紹介します。

チケットが完了してゲームセッションが始まった後、プレイヤーが途中で離脱することがあります。**バックフィル**を使うと、チケットオーナーがサーバーに空きスロットへの新規プレイヤー補充をリクエストできます。マッチメイキングプロセス全体をやり直す必要はありません。

```csharp
// プレイヤーが抜けた後、オーナーが代替プレイヤーをリクエスト
_matchMaker.SendTicketBackfill(ticketType);
// → 補充が見つかると OnTicketBackfillComplete が届く

// 補充待ちをやめる場合
_matchMaker.SendTicketCancelBackfill(ticketType);
```

マッチメイキングのロジック自体はサーバー側で処理されます。クライアントはリクエストを送り、`OnTicketBackfillComplete` コールバックを待つだけです。

次は **Tutorial 3 - Host/Search MatchMaker** で、明示的なホスト/サーチャーロールを使うマッチメイキングを学びます。


# Tutorial 3 - Host/Search MatchMaker

このチュートリアルでは、Diarkis の **Host/Search 方式マッチメイキング**を実装します。一方のクライアントがマッチングルームを「ホスト」し、他のクライアントがサーバーを検索して参加します。Tutorial 2 のチケット方式と対比しながら読むと理解が深まります。

終了時には以下のことが身についています:

* `HostMatchMaking()` でマッチングルームをホストする方法
* `Search(joinFlag: true)` でルームを検索して自動参加する方法
* `OnMMHost`、`OnMMResult`、`OnMMJoin` を別々に処理する方法
* ホストとして `SendDisbandMatchmaking()` でルームを解散する方法
* メンバーとして `SendLeaveMatchmaking()` でルームから離脱する方法
* `MatchMakerHostSearchState` に基づいてボタン状態を制御する方法

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続します。

### チケット方式との違い

| 項目       | Ticket（Tutorial 2）                      | Host/Search（Tutorial 3）     |
| -------- | --------------------------------------- | --------------------------- |
| ホストの概念   | なし（サーバーが管理）                             | あり（クライアントが明示的に選択）           |
| 主なイベント   | `OnMMTicketJoin` / `OnMMTicketComplete` | `OnMMHost` / `OnMMJoin`     |
| 状態の型     | `MatchMakerTicketState`                 | `MatchMakerHostSearchState` |
| ルーム検索    | 不要                                      | `Search()` で検索              |
| 向いているケース | 対等なマッチング                                | ロビー方式・ルームブラウザ               |

### 仕組み

```mermaid
sequenceDiagram
    participant H as Host クライアント
    participant S as Searcher クライアント
    participant SV as Diarkis サーバー

    H->>SV: HostMatchMaking(profileID, maxMembers)
    SV-->>H: OnMMHost (roomID)
    Note over H: ルーム作成完了、参加者を待つ

    S->>SV: Search(profileID, joinFlag=true)
    SV-->>S: OnMMResult（ルーム一覧）
    SV-->>S: OnMMJoin（自動参加完了）
    SV-->>H: OnMMMemberJoin（メンバーが増えた）
```

### 状態遷移

```mermaid
stateDiagram-v2
    [*] --> None
    None --> Host : HostMatchMaking() → OnMMHost
    None --> Search : Search() 発行
    Search --> MatchingRoomJoined : OnMMJoin
    Host --> None : SendDisbandMatchmaking()
    MatchingRoomJoined --> None : SendLeaveMatchmaking()
```

### シーンのセットアップ

`Tutorials/Scenes/Tutorial3-HostSearchMatchMaker.unity` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
private const string UID        = ""; // 空文字の場合はランダム生成
```

マッチング条件も必要に応じて変更できます。

```csharp
private const string PROFILE_ID  = "RankMatch"; // Host 側と Search 側で一致させる
private const int    MAX_MEMBERS = 4;
```

Play モードに入るとシーンが自動接続します。**Network State** ラベルが緑色になったら、一方のクライアントで **Host**、もう一方で **Search** を押してください。

### コードの解説

#### MatchMaker モジュールの取得

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
_matchMaker = diarkis.MatchMaker;
```

Tutorial 2 と同じパターンです。

#### イベントの登録

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnMMHost(OnMMHost, this);
handler.OnMMResult(OnMMResult, this);
handler.OnMMJoin(OnMMJoin, this);
handler.OnMMMemberJoin(_ => RefreshMemberList(), this);
handler.OnMMMemberLeave(_ => RefreshMemberList(), this);
handler.OnMMDisband(_ => OnMMDisband(), this);
handler.OnMMLeave(_ => OnMMLeave(), this);
```

このチュートリアルで登録するイベントの一覧です。

| イベント              | 発火タイミング                              | コールバックシグネチャ                              |
| ----------------- | ------------------------------------ | ---------------------------------------- |
| `OnMMHost`        | `HostMatchMaking()` が成功してルームが作成された   | `Action<DiarkisMMHostEventArgs>`         |
| `OnMMResult`      | `Search()` の結果が返ってきた                 | `Action<DiarkisMMResultEventArgs>`       |
| `OnMMJoin`        | `Search(joinFlag=true)` による自動参加が完了した | `Action<DiarkisMMJoinResponseEventArgs>` |
| `OnMMMemberJoin`  | 他のメンバーがルームに参加した                      | `Action<DiarkisMMJoinEventArgs>`         |
| `OnMMMemberLeave` | メンバーがルームから離脱した                       | `Action<DiarkisMMLeaveEventArgs>`        |
| `OnMMDisband`     | ホストがルームを解散した                         | `Action<DiarkisMMDisbandEventArgs>`      |
| `OnMMLeave`       | 自分がルームから離脱した                         | `Action<DiarkisMMLeaveEventArgs>`        |

`OnDestroy` での `UnregisterCallbacks(this)` はお忘れなく。オーナーパターンの詳細は Tutorial 1 を参照してください。

#### HostMatchMaking()

```csharp
private void OnHostClicked()
{
    _matchMaker.HostMatchMaking(
        maxMembers: (ushort)MAX_MEMBERS,
        ttl: 60,           // ルームの有効時間（秒）
        profileID: PROFILE_ID,
        tag: PROFILE_ID);  // グルーピング用タグ。ここでは profileID と同じ値を使用
}
```

成功すると `OnMMHost` が届き、ルーム ID を取得できます。状態は `MatchMakerHostSearchState.Host` に移行します。

#### Search()

```csharp
private void OnSearchClicked()
{
    var props = new Dictionary<string, uint> { { "rank", 10 } };
    _matchMaker.Search(
        profileID: PROFILE_ID,
        tag: PROFILE_ID,
        props: props,       // サーバー側でフィルタされる数値条件
        joinFlag: true,     // true なら見つかり次第自動参加
        howmany: 1,
        message: "");
}
```

`props` はサーバー側のフィルタ条件です。ホスト側のプロパティが条件を満たすルームだけが返ります。`joinFlag: true` にすると最初のルームに自動参加して `OnMMJoin` が届きます。参加前に一覧を確認したい場合は `joinFlag: false` にしてください。

#### OnMMHost と OnMMJoin

```csharp
private void OnMMHost(DiarkisMMHostEventArgs args)
{
    if (!args.IsSuccess())
    {
        AddChatLine($"[エラー] ホスト失敗 (code: {args.GetErrorCode()})");
        return;
    }

    string roomID = args.GetRoomID();
    if (_statusText != null)   _statusText.text   = roomID;
    if (_ownerUIDText != null) _ownerUIDText.text = /* 自分の UID */;

    RefreshMemberList();
    RefreshButtons();
}

private void OnMMJoin(DiarkisMMJoinResponseEventArgs args)
{
    if (!args.IsSuccess())
    {
        AddChatLine($"[エラー] 参加失敗 (code: {args.GetErrorCode()})");
        return;
    }

    // _matchMaker.GetHostUID() でホストの UID を取得できます
    if (_ownerUIDText != null) _ownerUIDText.text = _matchMaker.GetHostUID();

    RefreshMemberList();
    RefreshButtons();
}
```

#### Disband と Leave の違い

| 操作        | メソッド                       | 呼び出せる人     | 他メンバーに届くイベント      |
| --------- | -------------------------- | ---------- | ----------------- |
| ルームを解散する  | `SendDisbandMatchmaking()` | ホストのみ      | `OnMMDisband`     |
| ルームから離脱する | `SendLeaveMatchmaking()`   | 参加者（サーチャー） | `OnMMMemberLeave` |

ホストが終了したいときは必ず **解散**（Disband）を使います。ホストが離脱すると TTL まで空のルームがサーバーに残ってしまいます。

#### メンバーリストの更新

`GetMembers(DiarkisMatchMakerType.HostSearch)` はサーバーへのリクエストなしでローカルキャッシュからメンバー一覧を返します。

```csharp
private void RefreshMemberList()
{
    List<string> members = _matchMaker?.GetMembers(DiarkisMatchMakerType.HostSearch);
    _memberListText.text = members != null && members.Count > 0
        ? string.Join("\n", members)
        : "";
}
```

`OnMMHost`、`OnMMJoin`、`OnMMMemberJoin`、`OnMMMemberLeave`、`OnMMDisband`、`OnMMLeave` から `RefreshMemberList()` を呼んで表示を最新に保ちます。

#### HostSearchState によるボタン制御

```csharp
private void RefreshButtons()
{
    MatchMakerHostSearchState state = _matchMaker.HostSearchState;

    bool notStarted  = state == MatchMakerHostSearchState.None;
    bool isHost      = state == MatchMakerHostSearchState.Host;
    bool isInRoom    = state == MatchMakerHostSearchState.MatchingRoomJoined;
    bool isSearching = state == MatchMakerHostSearchState.Search;

    if (_hostButton)    _hostButton.interactable    = notStarted && _connected;
    if (_searchButton)  _searchButton.interactable  = notStarted && _connected;
    if (_leaveButton)   _leaveButton.interactable   = isInRoom || isSearching;
    if (_disbandButton) _disbandButton.interactable = isHost;

    bool canMessage = isHost || isInRoom;
    if (_messageButton1) _messageButton1.interactable = canMessage;
}
```

`HostSearchState` の 4 状態（`None` / `Host` / `Search` / `MatchingRoomJoined`）が UI 制御の唯一の基準です。

### 次のステップ

Tutorial 4 では **Room** を学びます — マッチング後にプレイヤーをグループ化してメッセージを交換するセッション空間です。

```mermaid
flowchart LR
    MM["MatchMaker<br/>（Tutorial 2 / 3）"] -->|マッチング成立| RM["Room<br/>（Tutorial 4）"]
```


# Tutorial 4 - Room

このチュートリアルでは、Diarkis の **Room** 機能を実装します。Room はゲームセッションの基本単位であり、プレイヤーをグループ化してメッセージの交換を可能にします。マッチメイキング後に移行する先として一般的に使われます。

終了時には以下のことが身についています:

* `DiarkisInterface` から `DiarkisRoom` モジュールを取得する方法
* `SendJoinRandomRoom` でランダムなルームに参加（なければ作成）する方法
* `OnRoomCreation` と `OnRoomJoin` を別々に処理する方法
* ルーム内全員にブロードキャストメッセージを送る方法
* `GetRoomMembers()` でメンバーリストを最新状態に保つ方法
* ルームから退出する方法

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続します。

### Room とは

Room は最大人数と TTL（有効時間、秒単位）を持つ仮想空間です。メンバー同士が自由にメッセージを交換できます。

```mermaid
flowchart LR
    A[Client A] -- join --> R[(Room)]
    B[Client B] -- join --> R
    C[Client C] -- join --> R
    R -- Broadcast --> A
    R -- Broadcast --> B
    R -- Broadcast --> C
```

`SendJoinRandomRoom()` は参加可能なルームがあればそこに入室し、なければ新規作成します。結果は `OnRoomCreation`（新規作成）または `OnRoomJoin`（既存参加）で届きます。

```mermaid
flowchart TD
    S([Join Room]) --> J[SendJoinRandomRoom]
    J --> C{Room available?}
    C -->|no| CR[OnRoomCreation]
    C -->|yes| JE[OnRoomJoin]
    CR --> IN[In room]
    JE --> IN
    IN --> BC[SendBroadcastToRoom]
    BC --> RCV[OnRoomMemberBroadcast]
    IN --> LV([Leave Room])
    LV --> EV[OnRoomLeave]
```

### シーンのセットアップ

`Tutorials/Scenes/Tutorial4-Room.unity` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
private const string UID        = ""; // 空文字の場合はランダム生成
```

Play モードに入るとシーンが自動接続します。**Network State** ラベルが緑色になったら **Join Room** ボタンが有効になります。

### コードの解説

#### Room モジュールの取得

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
_room = diarkis.Room;
```

Tutorial 2・3 の `diarkis.MatchMaker` と同じパターンです。

#### イベントの登録

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnRoomCreation(OnRoomCreation, this);
handler.OnRoomJoin(OnRoomJoin, this);
handler.OnRoomMemberJoin(_ => RefreshRoomUI(), this);
handler.OnRoomMemberLeave(_ => RefreshRoomUI(), this);
handler.OnRoomLeave(_ => OnRoomLeave(), this);
handler.OnRoomMemberBroadcast(OnRoomMemberBroadcast, this);
```

このチュートリアルで登録するイベントの一覧です。

| イベント                    | 発火タイミング             | コールバックシグネチャ                               |
| ----------------------- | ------------------- | ----------------------------------------- |
| `OnRoomCreation`        | 新規ルームを作成した（最初のメンバー） | `Action<DiarkisRoomCreationEventArgs>`    |
| `OnRoomJoin`            | 既存ルームに参加した          | `Action<DiarkisRoomJoinEventArgs>`        |
| `OnRoomMemberJoin`      | 他のメンバーがルームに参加した     | `Action<DiarkisRoomMemberJoinEventArgs>`  |
| `OnRoomMemberLeave`     | メンバーがルームから退出した      | `Action<DiarkisRoomMemberLeaveEventArgs>` |
| `OnRoomLeave`           | 自分がルームから退出した        | `Action<DiarkisRoomLeaveEventArgs>`       |
| `OnRoomMemberBroadcast` | ブロードキャストメッセージを受信した  | `Action<DiarkisPayloadEventArgs>`         |

`OnDestroy` での `UnregisterCallbacks(this)` はお忘れなく。オーナーパターンの詳細は Tutorial 1 を参照してください。

#### SendJoinRandomRoom()

```csharp
private void OnJoinClicked()
{
    _room.SendJoinRandomRoom(
        maxMembers: 4,
        ttl: 120,      // ルームの有効時間（秒）
        interval: 100, // オブジェクト同期の送信間隔（ミリ秒）
        allowEmpty: true);
}
```

`allowEmpty: true` にすると、空のルームにも参加します。結果は `OnRoomCreation` または `OnRoomJoin` で届きます。

#### OnRoomCreation と OnRoomJoin の違い

発火状況と取得できるデータが異なります。

```csharp
private void OnRoomCreation(DiarkisRoomCreationEventArgs args)
{
    if (!args.IsSuccess()) { /* エラー処理 */ return; }

    // args.GetRoomID() を使う — この時点では _room.RoomID がまだ未設定の場合がある
    string roomID = args.GetRoomID();
    if (_roomIDText != null)   _roomIDText.text   = roomID;
    if (_ownerUIDText != null) _ownerUIDText.text = GetMyUID(); // 自分がオーナー
}

private void OnRoomJoin(DiarkisRoomJoinEventArgs args)
{
    if (!args.IsSuccess()) { /* エラー処理 */ return; }

    // _room.RoomID と _room.OwnerUID はここで利用可能
    if (_roomIDText != null)   _roomIDText.text   = _room.RoomID;
    if (_ownerUIDText != null) _ownerUIDText.text = _room.OwnerUID;
}
```

> **注意:** `OnRoomCreation` 内では `_room.RoomID` ではなく `args.GetRoomID()` でルーム ID を取得してください。コールバック時点でモジュールの内部状態がまだ更新されていない場合があります。

#### メンバーリストの更新

`DiarkisMatchMaker` と異なり、`DiarkisRoom` には `GetRoomMembers()` というローカルキャッシュから即時にメンバー一覧を返すメソッドがあります。サーバーへのリクエストは不要です。

```csharp
private void RefreshRoomUI()
{
    if (_memberListText == null) return;

    DiarkisStringVector members = _room.GetRoomMembers();
    _memberListText.text = members != null && members.Count > 0
        ? string.Join("\n", members)
        : "";
}
```

`OnRoomMemberJoin`・`OnRoomMemberLeave`・`OnRoomJoin`・`OnRoomCreation` から `RefreshRoomUI()` を呼ぶことで表示を常に最新に保ちます。

#### ブロードキャスト — 送信と受信

`SendBroadcastToRoom` は送信者**以外**の全メンバーにメッセージを届けます。送信者自身の画面に表示するには `AddChatLine` を直接呼びます。ペイロードに UID を含めることで受信側が送信者を識別できます。

```csharp
// 送信側
private void OnMessageClicked(string message)
{
    string myUID = GetMyUID();
    byte[] payload = Encoding.UTF8.GetBytes($"{myUID}:{message}");
    _room.SendBroadcastToRoom(payload, reliable: true);
    AddChatLine($"[自分] {message}"); // ローカル表示（ブロードキャストは自分に届かない）
}

// 受信側
private void OnRoomMemberBroadcast(DiarkisPayloadEventArgs args)
{
    DiarkisByteVector payload = args.GetPayload();
    // バイト列をデコード...
    string raw = Encoding.UTF8.GetString(bytes);
    int sep = raw.IndexOf(':');
    string senderUID = raw[..sep];
    string message   = raw[(sep + 1)..];
    AddChatLine($"[{senderUID}] {message}");
}
```

`reliable: true` は TCP 相当の信頼性保証付き送信です。位置同期など多少のロストが許容できる高頻度更新には `false` を使います。

#### ボタン状態の管理

```csharp
private void RefreshButtons()
{
    bool inRoom = _room != null && _room.IsJoin();

    if (_joinButton)      _joinButton.interactable      = !inRoom && _connected;
    if (_leaveButton)     _leaveButton.interactable     = inRoom;
    if (_messageButton1) _messageButton1.interactable  = inRoom;
    if (_messageButton2) _messageButton2.interactable  = inRoom;
    if (_messageButton3) _messageButton3.interactable  = inRoom;
}
```

`_room.IsJoin()` が「現在ルームに参加しているか」の唯一の判断基準です。

### Room と MatchMaker を組み合わせる

マッチメイキング後にプレイヤーを Room に移行するのが一般的な本番フローです。

```mermaid
flowchart LR
    MM["MatchMaker<br/>(Tutorial 2 / 3)"] -->|マッチング成立| RM["Room<br/>(Tutorial 4)"]
```

Tutorial 1〜4 が完了すれば、接続 → マッチメイキング → ルーム参加という完全なフローを実装できます。Tutorial 5 では **Direct Messaging** — UID を指定した特定プレイヤーへのメッセージ送信 — を学びます。


# Tutorial 5 - Direct Message

このチュートリアルでは、Diarkis の **Direct Message (DM)** 機能を実装します。DM は UID を指定して特定のクライアントへサーバー経由で 1 対 1 メッセージを届けます。

終了時には以下のことが身についています:

* `DirectMessage.Send()` で特定 UID へ DM を送信する方法
* `OnDMMessage` で受信コールバックを登録する方法
* `DiarkisDirectMessageEventArgs` から送信者 UID とペイロードを取得する方法
* `OnDMMessageResponse` で配信確認（Ack）を受け取る方法（省略可）

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続します。

### Direct Message とは

Room のブロードキャストはルーム内の全員（自分を除く）に届きます。DM は **UID を明示的に指定した 1 クライアントにだけ**届きます。相手がルームに参加しているかどうかは関係ありません。

```mermaid
flowchart LR
    A[クライアント A] -->|"DM: targetUID=B"| SRV[(Diarkis サーバー)]
    SRV -->|中継| B[クライアント B]
    SRV -.->|届かない| C[クライアント C]
```

| メソッド                  | 宛先             | 用途例                 |
| --------------------- | -------------- | ------------------- |
| `SendBroadcastToRoom` | ルーム内の全員（自分を除く） | ゲーム状態の同期            |
| `DirectMessage.Send`  | 指定した 1 クライアント  | フレンドへのチャット、プライベート通知 |

DM は P2P ではなく**サーバー経由**です。送信先の UID さえわかれば、同じルームに参加していなくても送信できます。

### シーンのセットアップ

`Tutorials/Scenes/Tutorial5-DirectMessage.unity` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
private const string UID        = ""; // 空文字の場合はランダム生成
```

Play モードに入るとシーンが自動接続します。一方のクライアントの **My UID** をコピーし、もう一方の **Target UID** フィールドに貼り付けてメッセージボタンを押してください。

### コードの解説

#### イベントの登録

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnDMMessage(OnMessageReceived, this);
handler.OnDMMessageResponse(_ => { /* 配信確認：省略可 */ }, this);
```

`OnDMMessage` を登録するだけで、自分の UID 宛に送られたすべての DM を受け取れます。

| イベント                  | 発火タイミング                 | コールバックシグネチャ                             |
| --------------------- | ----------------------- | --------------------------------------- |
| `OnDMMessage`         | 自分の UID 宛の DM が届いた      | `Action<DiarkisDirectMessageEventArgs>` |
| `OnDMMessageResponse` | 送信した DM がサーバーに到達した（省略可） | `Action<DiarkisDMResponseEventArgs>`    |

#### DM の送信

```csharp
private void OnSendClicked(string message)
{
    string targetUID = _targetUIDInput.text.Trim();
    if (string.IsNullOrEmpty(targetUID)) return;

    byte[] bytes = Encoding.UTF8.GetBytes(message);
    DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME).DirectMessage.Send(targetUID, bytes);

    // DM は送信者自身には届かないため、ローカルに表示する
    AddChatLine($"[自分 → {targetUID}] {message}");
}
```

ペイロードは任意のバイト列です。ここでは UTF-8 エンコードしたテキストを送っていますが、MessagePack や JSON などで構造化しても構いません。

#### DM の受信

```csharp
private void OnMessageReceived(DiarkisDirectMessageEventArgs e)
{
    string senderID = e.GetSenderID();

    DiarkisByteVector payload = e.GetPayload();
    if (payload == null || payload.Count == 0) return;

    byte[] bytes = new byte[payload.Count];
    for (int i = 0; i < payload.Count; i++)
        bytes[i] = payload[i];

    string message = Encoding.UTF8.GetString(bytes);
    AddChatLine($"[{senderID}] {message}");
}
```

`GetPayload()` は `DiarkisByteVector` を返します。インデックスアクセスで `byte[]` に変換してから UTF-8 デコードします。このパターンは Tutorial 4 の `OnRoomMemberBroadcast` と同じです。

#### 配信確認（省略可）

```csharp
handler.OnDMMessageResponse(_ => {
    // Diarkis サーバーがメッセージを受信したことの確認
}, this);
```

サーバーへの到達確認が必要な場合に使います。多くのユースケースでは不要です。

### メッセージフロー

```mermaid
sequenceDiagram
    participant A as クライアント A
    participant S as Diarkis サーバー
    participant B as クライアント B

    A->>S: DirectMessage.Send(targetUID=B, payload)
    S-->>A: OnDMMessageResponse（省略可）
    S->>B: OnDMMessage（senderID=A, payload）
```

### 次のステップ

Tutorial 6 では **Group** を学びます — 文字列 ID で識別される軽量な pub/sub チャンネルで、複数のクライアントが同じチャンネルにブロードキャストできます。


# Tutorial 6 - Group

このチュートリアルでは、Diarkis の **Group** 機能を実装します。Group は文字列 ID で識別される軽量な pub/sub チャンネルです。同じ ID を知っているクライアントが全員同じチャンネルに参加してブロードキャストし合えます。

終了時には以下のことが身についています:

* `Group.SendJoin()` で Group に参加する（存在しなければ自動作成）方法
* `OnGroupCreate`（新規作成）と `OnGroupJoin`（既存参加）を区別する方法
* `Group.SendBroadcast()` で全メンバーにブロードキャストする方法
* `OnGroupMemberBroadcast` でブロードキャストを受信する方法
* `Group.SendLeave()` で退出する方法

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続します。

### Group と Room の違い

| 項目          | Room            | Group                          |
| ----------- | --------------- | ------------------------------ |
| 識別子         | サーバーが生成する ID    | 任意の文字列（例: `"global-chat"`）     |
| 作成          | 明示的な作成またはランダム参加 | `SendJoin` 時に存在しなければ自動作成       |
| サーバー側メンバー管理 | あり（最大メンバー数、TTL） | なし（軽量）                         |
| 複数同時参加      | 1 つ             | 複数の Group に同時参加可能              |
| 用途例         | ゲームセッション、マッチ    | グローバルチャット、チームチャンネル、話題別 pub/sub |

Group ID は自分で決める文字列なので、同じ ID を知っているクライアントが自動的に同じチャンネルを共有します。事前の発見ステップは不要です。

```mermaid
flowchart LR
    A[クライアント A] -->|SendJoin global-chat| SRV[(Diarkis サーバー)]
    B[クライアント B] -->|SendJoin global-chat| SRV
    C[クライアント C] -->|SendJoin team-red| SRV
    SRV -->|Broadcast global-chat| A
    SRV -->|Broadcast global-chat| B
    SRV -.->|届かない| C
```

### シーンのセットアップ

`Tutorials/Scenes/Tutorial6-Group.unity` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
private const string UID        = ""; // 空文字の場合はランダム生成
```

Play モードに入るとシーンが自動接続します。2 つのクライアントで同じ Group ID（例: `global-chat`）を入力して **Join** を押してください。メッセージボタンを押すと相手のチャットログに表示されます。

### コードの解説

#### イベントの登録

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnGroupCreate(OnGroupCreated, this);
handler.OnGroupJoin(OnGroupJoined, this);
handler.OnGroupLeave(OnGroupLeft, this);
handler.OnGroupMemberJoin(_ => AddChatLine("[システム] メンバーが参加しました"), this);
handler.OnGroupMemberLeave(_ => AddChatLine("[システム] メンバーが退出しました"), this);
handler.OnGroupMemberBroadcast(OnBroadcastReceived, this);
```

このチュートリアルで登録するイベントの一覧です。

| イベント                     | 発火タイミング                              | コールバックシグネチャ                           |
| ------------------------ | ------------------------------------ | ------------------------------------- |
| `OnGroupCreate`          | `SendJoin` で新規 Group が作成された（最初のメンバー） | `Action<DiarkisGroupEventArgs>`       |
| `OnGroupJoin`            | `SendJoin` で既存の Group に参加した          | `Action<DiarkisGroupEventArgs>`       |
| `OnGroupLeave`           | 自分が Group から退出した                     | `Action<DiarkisGroupEventArgs>`       |
| `OnGroupMemberJoin`      | 他のメンバーが参加した                          | `Action<DiarkisGroupMemberEventArgs>` |
| `OnGroupMemberLeave`     | メンバーが退出した                            | `Action<DiarkisGroupMemberEventArgs>` |
| `OnGroupMemberBroadcast` | 他メンバーからブロードキャストが届いた                  | `Action<DiarkisPayloadEventArgs>`     |

#### SendJoin()

```csharp
private void OnJoinClicked()
{
    string groupID = _groupIDInput.text.Trim();
    if (string.IsNullOrEmpty(groupID)) return;

    DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME).Group.SendJoin(groupID);
}
```

Group が存在しなければ自動作成します。結果は `OnGroupCreate` または `OnGroupJoin` で届きます。

#### OnGroupCreate と OnGroupJoin

両方とも同じシグネチャと同じデータアクセスパターンです。

```csharp
private void OnGroupCreated(DiarkisGroupEventArgs e)
{
    if (!e.IsSuccess()) { AddChatLine($"[エラー] {e.GetErrorMessage()}"); return; }
    _currentGroupID = e.GetGroupID();
    _inGroup        = true;
    AddChatLine($"[システム] グループを作成しました: {_currentGroupID}");
    RefreshButtons();
}

private void OnGroupJoined(DiarkisGroupEventArgs e)
{
    if (!e.IsSuccess()) { AddChatLine($"[エラー] {e.GetErrorMessage()}"); return; }
    _currentGroupID = e.GetGroupID();
    _inGroup        = true;
    AddChatLine($"[システム] グループに参加しました: {_currentGroupID}");
    RefreshButtons();
}
```

`e.GetGroupID()` で参加した Group の ID を確認します。

#### SendBroadcast()

```csharp
private void OnMessageClicked(string message)
{
    if (!_inGroup) return;

    byte[] bytes = Encoding.UTF8.GetBytes(message);
    DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME).Group.SendBroadcast(_currentGroupID, bytes);

    // ブロードキャストは送信者自身には届かないため、ローカルに表示する
    AddChatLine($"[自分] {message}");
}
```

`Room.SendBroadcastToRoom` と同様に、Group のブロードキャストも**送信者自身には届きません**。ローカルに直接表示します。

#### ブロードキャストの受信

```csharp
private void OnBroadcastReceived(DiarkisPayloadEventArgs e)
{
    DiarkisByteVector payload = e.GetPayload();
    if (payload == null || payload.Count == 0) return;

    byte[] bytes = new byte[payload.Count];
    for (int i = 0; i < payload.Count; i++)
        bytes[i] = payload[i];

    string message = Encoding.UTF8.GetString(bytes);
    AddChatLine($"[受信] {message}");
}
```

`DiarkisByteVector` → `byte[]` の変換パターンは Tutorial 4・5 と同じです。

#### 参加・送受信フロー

```mermaid
flowchart TD
    J["SendJoin(groupID)"] -->|新規作成| C["OnGroupCreate\ne.GetGroupID()"]
    J -->|既存参加| V["OnGroupJoin\ne.GetGroupID()"]
    C --> B["SendBroadcast(groupID, bytes)"]
    V --> B
    B -->|送信者以外の全メンバー| R["OnGroupMemberBroadcast\ne.GetPayload()"]
    L["SendLeave(groupID)"] --> GL["OnGroupLeave"]
```


# Unreal Engine チュートリアル

ここでは、Diarkis クライアント の Unreal Engine チュートリアルについて説明します。\
これらのガイドでは、Diarkis クライアントの基本的な機能を Unreal Engine から使うために、Diarkis Unreal Engine Plugin をプロジェクトに導入する手順を段階的に説明します。

***

## What You’ll Learn

Unreal Engine チュートリアル内容：

* Diarkis サーバーへの接続と切断
* Diarkis Module のカスタマイズ
* Diarkis Extension を使用したゲームスレッドでのコールバック

これらのチュートリアルは段階的に構成されています。まずは「Diarkis サーバーへの接続と切断」に進んでください。


# Diarkis サーバーへの接続と切断

ここでは簡単なチュートリアルを通じて Diarkis Unreal Engine Plugin (以降、UE Plugin) のインストール手順や Unreal Engine から Diarkis Module の初期化と終了方法や Diarkis サーバーへの接続方法などの基本的な使い方について解説します。

{% hint style="info" %}
このチュートリアルは UE 5.6.1 を使用しています。異なるバージョンをお使いの場合、UI や挙動が異なる可能性があるため、同じバージョンの使用をお勧めします。
{% endhint %}

#### プロジェクトの作成

Blank テンプレートから新規プロジェクトを作成します。

このチュートリアルでは C++ を選択します。

<figure><img src="/files/hV4y2qZ4iYAG2gZIJp6j" alt=""><figcaption></figcaption></figure>

#### UE Plugin のインストール手順

1. **DiarkisPluginSample の解凍**
   * DiarkisPluginSample を解凍します。
2. **Plugins フォルダに Diarkis フォルダを移動**&#x20;
   * 解凍した DiarkisPluginSample の Plugins フォルダ内の Diarkis フォルダをチュートリアルプロジェクトの Plugins フォルダにコピーします。<br>

     ```
     .
     ├── Config
     ├── Content
     ├── Source
     ├── Plugins ← (無ければフォルダを作成)
     │   └── Diarkis ← (ここにプラグインを配置)
     │       ├── Config
     │       ├── Content
     │       ├── Source
     │       └── Diarkis.uplugin
     └── Tutorial.uproject
     ```
3. **UE Plugin をプロジェクトの依存関係に追加**
   * `Source/Tutorial/Tutorial.Build.cs` を開き、 以下のようにプラグインを依存関係に追加します。<br>

     ```csharp
     // Tutorial.Build.cs

     public class Tutorial : ModuleRules
     {
     	public Tutorial(ReadOnlyTargetRules Target) : base(Target)
     	{
     		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
     	
     		PublicDependencyModuleNames.AddRange(new string[] {
     			"Core",
     			"CoreUObject",
     			"Engine",
     			"InputCore",
     			"EnhancedInput",
     			"Diarkis" // <-- 追加
     		});
     ```
4. **プロジェクトを再ビルド**
   * UE Plugin を追加した後、プロジェクトを再ビルドします。

#### **GameInstanceSubsystem の子クラスの作成**

Diarkis サーバへの接続、切断を行うために GameInstanceSubsystem の子クラスを作成します。

Tools -> New C++ Class をクリックし、親クラスに GameInstanceSubsystem を指定します。

<figure><img src="/files/tBrwnsTRrP2UsPNQ5IB1" alt=""><figcaption></figcaption></figure>

TutorialSubsystem という名前でクラスを作成します。

<figure><img src="/files/ZCvSSqbJYFZ1lUCQunwm" alt=""><figcaption></figcaption></figure>

このチュートリアルではここで作成した TutorialSubsystem クラスを使用して、UE Plugin の基本的な使い方を解説します。

#### Diarkis Module の初期化と終了

はじめに **Diarkis ランタイム・ライブラリ** および **Diarkis Module** を初期化するために `DiarkisInterfaceBase::DiarkisInit()` を呼び出します。 この処理はアプリケーション全体で最初に一度だけ実行する必要があります。\
また、アプリケーションの終了時に `DiarkisInterfaceBase::DiarkisDestroy()` を呼び出して Diarkis 全体の終了処理を行います。この処理は `DiarkisInterfaceBase::DiarkisInit()` と対になっていて、`DiarkisInit` 同様にアプリケーションのライフサイクル全体で一度だけ呼び出してください。

このチュートリアルでは GameInstanceSubsystem の子クラスを使い、`Initialize` 関数の中で `DiarkisInterfaceBase::DiarkisInit()` を呼び出し、`Deinitialize` 関数の中で `DiarkisInterfaceBase::DiarkisDestroy()` を呼び出します。

```cpp
// TutorialSubsystem.h

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "TutorialSubsystem.generated.h"

UCLASS()
class UTutorialSubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

public:
    UTutorialSubsystem();

    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
};
```

```cpp
// TutorialSubsystem.cpp


#include "TutorialSubsystem.h"
#include "DiarkisInterfaceBase.h"

UTutorialSubsystem::UTutorialSubsystem()
{
    UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Constructor"));
}

void UTutorialSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);

    UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Initialize - Calling DiarkisInit"));
    Diarkis::DiarkisInterfaceBase::DiarkisInit("logs", Diarkis::LogOutType::FILE_OUT, true);
}

void UTutorialSubsystem::Deinitialize()
{
    UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Deinitialize - Calling DiarkisDestroy"));
    Diarkis::DiarkisInterfaceBase::DiarkisDestroy();

    Super::Deinitialize();
}

```

ここでは 1 クライアントからの利用を前提とした簡単なチュートリアルのため、DiarkisInit 関数の logDirName 引数で "logs" を指定していますが、実際には uid を指定するなど複数クライアントでもログが上書きされないように設定してください。

```cpp
Diarkis::DiarkisInterfaceBase::DiarkisInit("logs", Diarkis::LogOutType::FILE_OUT, true);
```

LogTemp カテゴリーのみ表示するようにフィルタを設定します。

<figure><img src="/files/GoK7CMQFVTB46kE1Msr9" alt=""><figcaption></figcaption></figure>

ゲームを実行し、ゲーム起動時に `LogTemp: UTutorialSubsystem: Initialize - Calling DiarkisInit` が表示され、ゲーム終了時に `LogTemp: UTutorialSubsystem: Deinitialize - Calling DiarkisDestroy` が表示されることを確認します。

#### Diarkis サーバーへの接続と切断

TutorialSubsystem クラスに処理を追加し、ゲーム起動時に Diarkis サーバーへの接続、ゲーム終了時に切断を行います。

最終的なコードは下記になります。

```cpp
// TutorialSubsystem.h

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "DiarkisInterfaceBase.h"
#include "TutorialSubsystem.generated.h"

UCLASS()
class TUTORIAL_API UTutorialSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()
	
public:
	UTutorialSubsystem();

	void Initialize(FSubsystemCollectionBase& Collection) override;
	void Deinitialize() override;

private:
	std::shared_ptr<Diarkis::DiarkisInterfaceBase> DiarkisInterfaceInstance;
};

```

```cpp
// TutorialSubsystem.cpp


#include "TutorialSubsystem.h"
#include "Kismet/KismetSystemLibrary.h"

UTutorialSubsystem::UTutorialSubsystem()
{
	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Constructor"));
}

void UTutorialSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// Diarkis ライブラリの初期化
	// Initialize Diarkis library
	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Initialize - Calling DiarkisInit"));
	Diarkis::DiarkisInterfaceBase::DiarkisInit("logs", Diarkis::LogOutType::FILE_OUT, true);

	// DiarkisInterfaceインスタンスの作成
	// Create DiarkisInterface instance
	DiarkisInterfaceInstance = Diarkis::DiarkisAllocShared<Diarkis::DiarkisInterfaceBase>("AAAA");
	if (!DiarkisInterfaceInstance)
	{
		UE_LOG(LogTemp, Error, TEXT("DiarkisInterfaceInstance creation failed"));
		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();

		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	// Diarkis UDP クラスを初期化
	// Initialize Diarkis UDP class
	DiarkisInterfaceInstance->SetupUdp();

	// Http サーバーから UDP サーバーの Endpoint を取得
	// Get the endpoint of the UDP server from the HTTP server
	char endpoint[256];
	if (!DiarkisInterfaceInstance->GetEndpoint("127.0.0.1:7000", "", "UDP", endpoint, 256))
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to get UDP endpoint"));

		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	UE_LOG(LogTemp, Log, TEXT("Endpoint: %s"), UTF8_TO_TCHAR(endpoint));

	// UDP サーバーに接続
	// Connect to the UDP server
	UE_LOG(LogTemp, Log, TEXT("Connecting to the UDP server..."));
	bool result = DiarkisInterfaceInstance->ConnectUdp(endpoint);

	if (!result)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to connect to the UDP server"));
		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server"));
}

void UTutorialSubsystem::Deinitialize()
{
	if (!DiarkisInterfaceInstance)
	{
		UE_LOG(LogTemp, Warning, TEXT("DiarkisInterfaceInstance is null during Deinitialize"));

		DiarkisInterfaceBase::DiarkisDestroy();
		Super::Deinitialize();
		return;
	}

	// UDP サーバーから切断
	// Disconnect from the UDP server
	UE_LOG(LogTemp, Log, TEXT("Disconnecting from the UDP server..."));
	DiarkisInterfaceInstance->Disconnect();
	UE_LOG(LogTemp, Log, TEXT("Disconnected from the UDP server."));

	DiarkisInterfaceInstance.reset();

	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Deinitialize - Calling DiarkisDestroy"));
	Diarkis::DiarkisInterfaceBase::DiarkisDestroy();

	Super::Deinitialize();
}

```

処理を追加した後は、再度ビルドし、ゲーム起動時に `"Connected to the UDP server"` というログが表示され、ゲーム終了時に `"Disconnected from the UDP server."` というログが表示されることを確認します。

このチュートリアルでは UID を固定の文字列  `"AAAA"` として指定していますが、実際にはユーザー情報に合わせて設定します。

```cpp
DiarkisInterfaceInstance = Diarkis::DiarkisAllocShared<Diarkis::DiarkisInterfaceBase>("AAAA");
```

このチュートリアルではエンドポイントに `"127.0.0.1:7000"` を指定し、クライアントキーに空文字  `""`  を指定していますが、ご利用の環境に合わせて変更してください。

```cpp
if (!DiarkisInterfaceInstance->GetEndpoint("127.0.0.1:7000", "", "UDP", endpoint, 256))
```

ここでは `ConnectUdp` 関数の戻り値で接続できたかどうかを判断していますが、エンドポイントに接続できない場合、タイムアウトされるまで処理が止まります。実際のゲームでは応答性のために `ConnectAsync` 関数と コールバック関数を使い、接続できたかどうかを判断することをお勧めします。詳細については次のページを参照してください。

```cpp
	bool result = DiarkisInterfaceInstance->ConnectUdp(endpoint);

	if (!result)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to connect to the UDP server"));
		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server"));
```

ここまでで UE Plugin を使い、Diarkis サーバーへの接続と切断が実行できました。


# Diarkis Module のカスタマイズ

Diarkis クライアントではコールバック関数をカスタマイズすることで Diarkis サーバーに接続完了した時や Room に参加した時など様々なイベントに合わせて任意の処理を実行することができます。

ここでは、クイックスタート で作成した TutorialSubsystem クラスを変更し、ConnectUdp 関数から ConnectUdpAsync 関数に変更し、コールバック関数を使って接続状態を判断するように変更する方法について解説します。

#### ConnectUdpAsync 関数への変更

`ConnectUdp` 関数を実行するとサーバーに接続できない場合、タイムアウトするまで動作が停止します。そこで応答性を高めるために `ConnectUdpAsync` 関数に変更します。

```cpp
// TutorialSubsystem.cpp
	
	...
	// UDP サーバに接続
	// Connect to the UDP server
	UE_LOG(LogTemp, Log, TEXT("Connecting to the UDP server..."));
	bool result = DiarkisInterfaceInstance->ConnectUdpAsync(endpoint);
```

また、実際にサーバーへの接続処理が完了する前に `ConnectUdpAsync` 関数の次の処理が実行されるため、下記のログの処理を削除します。

```cpp
UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server")); // <-- 削除
```

#### コールバック関数の実装

1. DiarkisUdpBase クラスを継承したクラスを作成します。

   ```cpp
   // TutorialSubsystem.h

   ...
   #include "DiarkisUdpBase.h"

   class DiarkisUdpTutorial;
   ...
   // Diarkis Module の Udp 機能をアプリ側でカスタマイズするために DiarkisUdpBase を継承したクラスを実装します。
   // Implement a class that inherits from DiarkisUdpBase to customize the Udp functionality of the Diarkis Module on the application side.
   class DiarkisUdpTutorial : public Diarkis::DiarkisUdpBase
   {
   private:
       void OnConnect(const DiarkisConnectionEventArgs& args) override;
   };
   ```

   ```cpp
   // TutorialSubsystem.cpp

   ...
   void DiarkisUdpTutorial::OnConnect(const DiarkisConnectionEventArgs& args)
   {
   	if (args.GetStatus() == Diarkis::DiarkisConnectStatus::DCS_Timeout)
     {
   		UE_LOG(LogTemp, Error, TEXT("Timeout occurred while connecting to the UDP server"));
   		return;
   	}
       UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server"));
   }
   ```
2. DiarkisIntefaceBase クラスを継承したクラスを作成します。

   ```cpp
   // TutorialSubsystem.h
    
   ...
   #include "DiarkisUdpBase.h"
   #include "DiarkisInterfaceBase.h"
   ...
   class DiarkisUdpTutorial;
   class DiarkisInterfaceTutorial;
   ...
   // DiarkisInterface が内部で管理する各機能のモジュールをアプリ固有のものに置き換えるために DiarkisInterfaceBase を継承したクラスを実装します。
   // Implement a class that inherits from DiarkisInterfaceBase to replace each functional module managed internally by DiarkisInterface with application-specific ones.
   class DiarkisInterfaceTutorial : public Diarkis::DiarkisInterfaceBase
   {
   public:
       DiarkisInterfaceTutorial(const std::string& uid) : Diarkis::DiarkisInterfaceBase(uid) {};

       bool SetupUdp() override;
   };
   ```

   ```cpp
   // TutorialSubsystem.cpp

   bool DiarkisInterfaceTutorial::SetupUdp()
   {
       // アプリ側でカスタマイズした DiarkisUdpTutorial クラスを作成します
       // Create a DiarkisUdpTutorial class customized on the application side
       if (udpBase_ == nullptr)
       {
           udpBase_ = Diarkis::DiarkisAllocShared<DiarkisUdpTutorial>();
       }

       DiarkisInterfaceBase::SetupUdp();

       return true;
   }
   ```
3. DiarkisInterfaceBase クラスを作成していた部分を書き換えます。

   ```cpp
   // TutorialSubsystem.h

   UCLASS()
   class TUTORIAL_API UTutorialSubsystem : public UGameInstanceSubsystem
   {
   ...
   private:
       std::shared_ptr<DiarkisInterfaceTutorial> DiarkisInterfaceInstance;
   ```

   ```cpp
   // TutorialSubsystem.cpp 

   void UTutorialSubsystem::Initialize(FSubsystemCollectionBase& Collection)
   {
       ...
       // DiarkisInterfaceインスタンスの作成
       // Create DiarkisInterface instance
       DiarkisInterfaceInstance = Diarkis::DiarkisAllocShared<DiarkisInterfaceTutorial>("AAAA");
   ```

最終的なコードは下記のようになります。

```cpp
// TutorialSubsystem.h

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "DiarkisUdpBase.h"
#include "DiarkisInterfaceBase.h"
#include "TutorialSubsystem.generated.h"

class DiarkisUdpTutorial;
class DiarkisInterfaceTutorial;

UCLASS()
class TUTORIAL_API UTutorialSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()
	
public:
	UTutorialSubsystem();

	void Initialize(FSubsystemCollectionBase& Collection) override;
	void Deinitialize() override;

private:
	std::shared_ptr<DiarkisInterfaceTutorial> DiarkisInterfaceInstance;
};

// Diarkis Module の Udp 機能をアプリ側でカスタマイズするために DiarkisUdpBase を継承したクラスを実装します。
// Implement a class that inherits from DiarkisUdpBase to customize the Udp functionality of the Diarkis Module on the application side.
class DiarkisUdpTutorial : public Diarkis::DiarkisUdpBase
{
private:
    void OnConnect(const DiarkisConnectionEventArgs& args) override;
};

// DiarkisInterface が内部で管理する各機能のモジュールをアプリ固有のものに置き換えるために DiarkisInterfaceBase を継承したクラスを実装します。
// Implement a class that inherits from DiarkisInterfaceBase to replace each functional module managed internally by DiarkisInterface with application-specific ones.
class DiarkisInterfaceTutorial : public Diarkis::DiarkisInterfaceBase
{
public:
	DiarkisInterfaceTutorial(const std::string& uid) : Diarkis::DiarkisInterfaceBase(uid) {};

    bool SetupUdp() override;
};

```

```cpp
// TutorialSubsystem.cpp


#include "TutorialSubsystem.h"
#include "Kismet/KismetSystemLibrary.h"

UTutorialSubsystem::UTutorialSubsystem()
{
	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Constructor"));
}

void UTutorialSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// Diarkis ライブラリの初期化
	// Initialize Diarkis library
	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Initialize - Calling DiarkisInit"));
	Diarkis::DiarkisInterfaceBase::DiarkisInit("logs", Diarkis::LogOutType::FILE_OUT, true);

	// DiarkisInterfaceインスタンスの作成
	// Create DiarkisInterface instance
	DiarkisInterfaceInstance = Diarkis::DiarkisAllocShared<DiarkisInterfaceTutorial>("AAAA");
	if (!DiarkisInterfaceInstance)
	{
		UE_LOG(LogTemp, Error, TEXT("DiarkisInterfaceInstance creation failed"));
		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();

		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	// Diarkis UDP クラスを初期化
	// Initialize Diarkis UDP class
	DiarkisInterfaceInstance->SetupUdp();

	// Http サーバから UDP サーバの Endpoint を取得
	// Get the endpoint of the UDP server from the HTTP server
	char endpoint[256];
	if (!DiarkisInterfaceInstance->GetEndpoint("127.0.0.1:7000", "", "UDP", endpoint, 256))
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to get UDP endpoint"));

		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	UE_LOG(LogTemp, Log, TEXT("Endpoint: %s"), UTF8_TO_TCHAR(endpoint));

	// UDP サーバに接続
	// Connect to the UDP server
	UE_LOG(LogTemp, Log, TEXT("Connecting to the UDP server..."));
	bool result = DiarkisInterfaceInstance->ConnectUdpAsync(endpoint);

	if (!result)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to connect to the UDP server"));
		Diarkis::DiarkisInterfaceBase::DiarkisDestroy();
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
	}

	UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server"));
}

void UTutorialSubsystem::Deinitialize()
{
	if (!DiarkisInterfaceInstance)
	{
		UE_LOG(LogTemp, Warning, TEXT("DiarkisInterfaceInstance is null during Deinitialize"));

		DiarkisInterfaceBase::DiarkisDestroy();
		Super::Deinitialize();
		return;
	}

	// UDP サーバから切断
	// Disconnect from the UDP server
	UE_LOG(LogTemp, Log, TEXT("Disconnecting from the UDP server..."));
	DiarkisInterfaceInstance->Disconnect();
	UE_LOG(LogTemp, Log, TEXT("Disconnected from the UDP server."));

	DiarkisInterfaceInstance.reset();

	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Deinitialize - Calling DiarkisDestroy"));
	Diarkis::DiarkisInterfaceBase::DiarkisDestroy();

	Super::Deinitialize();
}

void DiarkisUdpTutorial::OnConnect(const DiarkisConnectionEventArgs& args)
{
	if (args.GetStatus() == Diarkis::DiarkisConnectStatus::DCS_Timeout)
	{
		UE_LOG(LogTemp, Error, TEXT("Timeout occurred while connecting to the UDP server"));
		return;
	}
	UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server"));
}

bool DiarkisInterfaceTutorial::SetupUdp()
{
	// アプリ側でカスタマイズした DiarkisUdpTutorial クラスを作成します
	// Create a DiarkisUdpTutorial class customized on the application side
	if (udpBase_ == nullptr)
	{
		udpBase_ = Diarkis::DiarkisAllocShared<DiarkisUdpTutorial>();
	}

	DiarkisInterfaceBase::SetupUdp();

	return true;
}

```

処理を追加した後は、再度ビルドし、ゲーム起動時に `"Connected to the UDP server"` というログが表示され、ゲーム終了時に `"Disconnected from the UDP server."` というログが表示されることを確認します。

ここまでで UE Plugin を使い、Diarkis サーバーへの非同期接続とコールバック関数を使って接続状態を判断することができました。

今回は、コールバック関数の中でスレッドセーフな UE\_LOG 関数しか実行していませんが、実際のアプリケーションではコールバック内で様々な処理を実行することが想定されます。しかし、コールバックは Diarkis が管理するイベントスレッドで発火され、ゲームスレッドで実行されるわけではないため、このコールバック内で `SpawnActor` 関数の実行などの UE 関連の機能を使用することができません。\
これらの課題に対応するために DiarkisPluginSample では様々なイベント処理が実装されています。

次のページで DiarkisPluginSample に含まれる DiarkisExtension プラグインを使用して、ゲームスレッドでコールバック関数を実行する方法を説明します。


# DiarkisExtension を使用したゲームスレッドでのコールバック

ここでは、DiarkisPluginSample に含まれる DiarkisExtension の `DiarkisDispatch` クラスを使用して、ゲームスレッドでコールバック関数を実行する方法を説明します。

`DiarkisDispatch` クラスを使用したイベント処理については、[イベント処理の注意点](/diarkis-client/game-engine-integration/ue/tips-for-event-processing#diarkisdispatch-kurasuwoshitami) を参照してください。

{% hint style="info" %}
「DiarkisExtension」では様々な機能が実装されていますが、「UE Plugin」と異なりこれらはあくまでサンプルコードとなりますので、将来的に互換性が無い仕様変更が発生したり、不具合が存在する可能性があります。 ここではチュートリアルのため、「DiarkisExtension」に含まれる NetworkManager を使用し、ゲームスレッドでコールバック関数を実行しておりますが、コールバック処理を受け取る仕組みについては自前で実装されることをご検討ください。
{% endhint %}

#### DiarkisExtension のインストール

1. DiarkisExtension のコピー

   * 解凍した DiarkisPluginSample の Source フォルダ内の DiarkisExtension フォルダをチュートリアルプロジェクトの Source フォルダにコピーします。

   ```txt
   .
   ├── Config
   ├── Content
   ├── Source
   │   └── DiarkisExtension ← (ここに配置)
   ├── Plugins
   └── Tutorial.uproject
   ```
2. DiarkisExtension をプロジェクトの依存関係に追加

   * Source/Tutorial/Tutorial.Build.cs を開き、 以下のように DiarkisExtension を依存関係に追加します。

   ```csharp
   // Tutorial.Build.cs

   public class Tutorial : ModuleRules
   {
       public Tutorial(ReadOnlyTargetRules Target) : base(Target)
       {
           PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
       
           PublicDependencyModuleNames.AddRange(new string[] {
               "Core",
               "CoreUObject",
               "Engine",
               "InputCore",
               "EnhancedInput",
               "Diarkis",
               "DiarkisExtension" // <-- 追加
           });
   ```
3. プロジェクトを再ビルド
   * DiarkisExtension を追加した後、プロジェクトを再ビルドします。

#### DiarkisUdpDelegate の子クラスの作成

DiarkisUdpBase クラスのコールバックを受け取るために、 `IDiarkisUdpDelegate` の子クラスを作成します。

Tools -> New C++ Class をクリックし、親クラスに None を指定します。&#x20;

<figure><img src="/files/BBTpKIHMJSB5f50pMmrr" alt=""><figcaption></figcaption></figure>

DiarkisUdpTutorialDelegate という名前でクラスを作成します。&#x20;

<figure><img src="/files/j49u3oiWpc7V0V7CE6iq" alt=""><figcaption></figcaption></figure>

`IDiarkisUdpDelegate` クラスを継承します。

```cpp
// DiarkisUdpTutorialDelegate.h

#pragma once

#include "CoreMinimal.h"
#include "DiarkisExtension/Public/Delegate/DiarkisUdpDelegate.h"

class TUTORIAL_API DiarkisUdpTutorialDelegate : public IDiarkisUdpDelegate
{
public:
	DiarkisUdpTutorialDelegate();
	~DiarkisUdpTutorialDelegate();
};
```

#### コールバックの実装

接続が完了したときのコールバックを実装します。

```cpp
// DiarkisUdpTutorialDelegate.h

...
private:
    void OnConnect(DiarkisUdpBase& udp, const ConnectArgs& args) override;
};
```

```cpp
// DiarkisUdpTutorialDelegate.cpp

...
void DiarkisUdpTutorialDelegate::OnConnect(DiarkisUdpBase& udp, const ConnectArgs& args)
{
    if (!IsInGameThread())
    {
        UE_LOG(LogTemp, Warning, TEXT("OnConnect called outside of game thread"));
        return;
    }
    
    if (!GEngine)
    {
        UE_LOG(LogTemp, Warning, TEXT("GEngine is null"));
        return;
    }

    if (args.connectStatus == DiarkisConnectStatus::DCS_Timeout)
    {
        GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Timeout occurred while connecting to the UDP server"));
        return;
    }

    GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, TEXT("Connected to the UDP server"));
    UE_LOG(LogTemp, Log, TEXT("Connected to the UDP server"));
}
```

Delegate クラスを継承したこのクラスでは、ゲームスレッドでコールバック関数が実行されるため、`AddOnScreenDebugMessage` 関数などの UE 関連の機能を安全に使用することができます。

#### DiarkisNetworkManager を使用した Diarkis サーバーへの接続

DiarkisDispatch クラスを使用したイベント処理を行うためには、DiarkisNetworkManager クラスを使用して DiarkisInterface インスタンスを管理する必要があります。そこで、TutorialSubsystem クラスを書き換え、DiarkisNetworkManager クラスの `ConnectAsync` 関数を使います。 `DiarkisInterfaceBase::DiarkisInit` 関数の呼び出しは DiarkisNetworkManager 内で行われるため、TutorialSubsystem クラス内で呼び出す必要はありません。詳細については、DiarkisNetworkManager クラスの実装を参照してください。

```cpp
// TutorialSubsystem.h

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "DiarkisExtension/Public/DiarkisNetworkManager.h"
#include "TutorialSubsystem.generated.h"

UCLASS()
class TUTORIAL_API UTutorialSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	UTutorialSubsystem();

	void Initialize(FSubsystemCollectionBase& Collection) override;
	void Deinitialize() override;

private:
	UPROPERTY()
	UDiarkisNetworkManager* NetworkManager;
};

```

```cpp
// TutorialSubsystem.cpp


#include "TutorialSubsystem.h"
#include "Kismet/KismetSystemLibrary.h"
#include "DiarkisExtension/Public/DiarkisNetworkManager.h"

UTutorialSubsystem::UTutorialSubsystem()
{
	UE_LOG(LogTemp, Log, TEXT("UTutorialSubsystem: Constructor"));
}

void UTutorialSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// UDiarkisNetworkManager インスタンスの取得
	// Get UDiarkisNetworkManager instance
	NetworkManager = UDiarkisNetworkManager::Get(GetWorld());
	if (!NetworkManager)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to get UDiarkisNetworkManager instance"));
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
		return;
	}

	// DiarkisNetworkManager を使用した、UDP サーバへの接続
	// Connect to UDP server using DiarkisNetworkManager
	std::string endpoint = "127.0.0.1:7000";
	std::string clientKey = "";
	std::string uid = "AAAA";
	std::string serverType = "UDP";

	std::shared_ptr<DiarkisInterface> diarkis = NetworkManager->ConnectAsync(endpoint, clientKey, uid, serverType, true);
	if (!diarkis)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to get endpoint."));
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
		return;
	}
}

void UTutorialSubsystem::Deinitialize()
{
	if (!NetworkManager)
	{
		UE_LOG(LogTemp, Warning, TEXT("NetworkManager is null during Deinitialize"));

		DiarkisInterfaceBase::DiarkisDestroy();
		Super::Deinitialize();
		return;
	}

	// UDP サーバから切断
	// Disconnect from the UDP server
	UE_LOG(LogTemp, Log, TEXT("Disconnecting from the UDP server..."));
	NetworkManager->Disconnect();
	UE_LOG(LogTemp, Log, TEXT("Disconnected from the UDP server."));

	Super::Deinitialize();
}

```

#### DiarkisUdpTutorialDelegate クラスの登録

ゲームスレッドでコールバックを実行するには、`DiarkisUdp` クラスの `SetDelegate` 関数を実行し、`IDiarkisUdpDelegate` を継承したクラスを登録する必要があります。

`DiarkisUdpTutorialDelegate` クラスのインスタンスを作成し、DiarkisUdp に登録します。

```cpp
// TutorialSubsystem.h
...
#include "DiarkisExtension/Public/DiarkisNetworkManager.h"
...
private:
    std::shared_ptr<DiarkisUdp> DiarkisUdpInstance_;
    std::shared_ptr<DiarkisUdpTutorialDelegate> UdpDelegate_;
```

```cpp
// TutorialSubsystem.cpp
...
	std::shared_ptr<DiarkisInterface> diarkis = NetworkManager->ConnectAsync(endpoint, clientKey, uid, serverType, true);
	if (!diarkis)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to get endpoint."));
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
		return;
	}

	// Udp モジュール用のDelegateを設定
	DiarkisUdpInstance_ = static_pointer_cast<DiarkisUdp>(diarkis->GetUdpBase());
	if (!DiarkisUdpInstance_)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to get DiarkisUdp instance."));
		UKismetSystemLibrary::QuitGame(GetWorld(), nullptr, EQuitPreference::Quit, false);
		return;
	}
	UdpDelegate_ = std::make_shared<DiarkisUdpTutorialDelegate>();
	if (UdpDelegate_)
	{
		DiarkisUdpInstance_->SetDelegate(UdpDelegate_.get());
	}
...
```

Deinitialize 関数内で UdpDelegate\_ をリセットします。

```cpp
void UTutorialSubsystem::Deinitialize()
{
	if (!NetworkManager)
	{
		UE_LOG(LogTemp, Warning, TEXT("NetworkManager is null during Deinitialize"));

		DiarkisInterfaceBase::DiarkisDestroy();
		Super::Deinitialize();
		return;
	}

	// UDP モジュールの Delegate をリセット
	if (DiarkisUdpInstance_)
	{
		DiarkisUdpInstance_->SetDelegate(nullptr);
	}
	if (UdpDelegate_)
	{
		UdpDelegate_.reset();
	}

	// UDP サーバから切断
	// Disconnect from the UDP server
	UE_LOG(LogTemp, Log, TEXT("Disconnecting from the UDP server..."));
	NetworkManager->Disconnect();
	UE_LOG(LogTemp, Log, TEXT("Disconnected from the UDP server."));

	Super::Deinitialize();
}
```

#### 動作確認

コールバックを実装した後は、再度ビルドし、ゲーム起動時に 画面上に "Connected to the UDP server" というデバッグメッセージとログが表示され、ゲーム終了時に "Disconnected from the UDP server." というログが表示されることを確認します。&#x20;

<figure><img src="/files/dCQ4sJwX5mnxmwlYsosN" alt=""><figcaption></figcaption></figure>

ここまでで、UDP サーバーに非同期的に接続し、ゲームスレッドでコールバック関数を実行することができました。


# Diarkis ツール


# Diarkis CLI

## 概要

Diarkis CLI は Diarkis サーバーをビルドするために必要なツールです。Diarkis サーバーテンプレートに同梱されています。

[Diarkis サーバーテンプレート](/getting-started/diarkis-server-template)を利用することで特に Diarkis CLI を意識することなくビルドすることが可能です。

また、ビルドするためには `project ID` と `builder token` の情報が必要となります。エンタープライズライセンスが必要となります。詳しくは [ライセンスと購入](/support/license-and-billing)をご覧ください。


# cgo を利用するプロジェクトをビルドする方法

## cgo

cgo は Go のビルドツールチェーンに組み込まれた仕組みで、Go コードから C/C++ の関数やライブラリを呼び出すためのブリッジです。\
diarkis-cli では cgo がデフォルトでは無効化されているので、明示的に有効化する必要があります。

## ビルド方法

`build.yml` 内の env セクションに下記を追加することで cgo と gcc を使って C プログラムを呼び出す go のプログラムをビルドすることができます。

```yaml
CGO_ENABLED: 1
CC: gcc
```




---

[Next Page](/llms-full.txt/1)

