非同期操作と通知
低速または計算コストの高い操作の場合、PlayFab ロビーとマッチメイキング SDK は非同期 API を公開します。 非同期 API は、メイン スレッドからコストの高い操作や低速な操作を開始し、選択したスレッドでそれらの操作の完了をポーリングする機能を提供します。 この同じポーリング メカニズムは、SDK の更新の非同期通知をタイトル コードに配信するためにも使用されます。 このページでは、PlayFab ロビーとマッチメイキング SDK の非同期 API パターンの概要と、それらに対するプログラミングのベスト プラクティスについてご説明します。
基本的な API パターン
PlayFab ロビーとマッチメイキング SDK では、次の 2 種類の非同期 API パターンに注意する必要があります。
非同期操作
SDK の非同期 API を使用するのは簡単です。 非同期操作を開始および完了するための一般的なパターンは次のとおりです:
選択した適切な非同期 API に対して、通常のメソッド呼び出しを行います。 一般的な非同期操作としては、以下のようなものがあります:
API の HRESULT 戻り値を SUCCEEDED() マクロまたは FAILED() マクロで確認します。 この同期的に返された値は、操作が正常に開始されたかどうかを示します。
Warning
非同期 API 呼び出しからの同期戻り値は、操作が正常に完了したかどうかを示しません。 同期エラーと非同期エラーの詳細については、SDK の「エラー処理」をご覧ください。
PFMultiplayerStartProcessingLobbyStateChanges() または PFMultiplayerStartProcessingMatchmakingStateChanges によって提供される、関連付けられた操作の "完了状態の変更" を探して、非同期操作の完了をポーリングします。 PartyManager::CreateNewNetwork() に関連付けられている "完了状態の変更" の例として、PartyCreateNewNetworkCompletedStateChange があります。 "状態の変更" の詳細と動作の詳細については、「状態の変更」セクションをご覧ください。
完了状態の変更の結果値を確認して、操作が成功したか失敗したかを判断します。 これらのエラー値の詳細については、SDK の「エラー処理」をご覧ください。
非同期通知
一部の機能では、ロビーとマッチメイキング SDK への変更の非同期通知が生成されます。
一般的な通知は次のとおりです:
これらの非同期通知は、PFMultiplayerStartProcessingLobbyStateChanges() と PFMultiplayerStartProcessingMatchmakingStateChanges を介して "状態の変更" として SDK によって提供されます。
"状態の変更" の詳細と動作の詳細については、「状態の変更」セクションをご覧ください。
状態の変更
ロビーとマッチメイキング SDK の非同期 API モデルは、PFLobbyStateChange と PFMatchmakingStateChange を中心に構築されています。 PFLobbyStateChanges はロビーのサブシステムの変更について通知し、PFMatchmakingStateChanges はマッチメイキングのサブシステムの変更について通知します。
これらの "状態の変更" は、SKDK からのイベントの非同期通知です。 これらの通知は内部のキューに登録され、PFMultiplayerStartProcessingLobbyStateChanges() と PFMultiplayerStartProcessingMatchmakingStateChanges を呼び出して処理します。 これらの関数は、個別に反復処理および処理できるリストとして、キューに登録されたすべての状態変更 (それぞれの API サブシステム用) を返します。 各状態変更には stateChangeType フィールドがあり、通知される特定の状態変更を調べることができます。 与えられた状態変更を認識したら、汎用 PFLobbyStateChange または PFMatchmakingStateChange 構造体をより具体的な種類の状態変更構造体にキャストして、そのイベントの特定のデータを調べることができます。
通常、状態変更処理は、各状態変更をハンドラーに委任する単純な switch ステートメントとして実装されます。
状態変更の一覧が PFMultiplayerStartProcessingLobbyStateChanges または PFMultiplayerStartProcessingMatchmakingStateChanges から処理されたら、それぞれ PFMultiplayerFinishProcessingMatchmakingStateChanges() または PFMultiplayerFinishProcessingMatchmakingStateChanges() に返す必要があります。
//
// Process Lobby state changes
//
uint32_t lobbyStateChangeCount;
const PFLobbyStateChange * const * lobbyStateChanges;
HRESULT hr = PFMultiplayerStartProcessingLobbyStateChanges(m_pfmHandle, &lobbyStateChangeCount, &lobbyStateChanges);
if (FAILED(hr))
{
return hr;
}
for (uint32_t i = 0; i < lobbyStateChangeCount; ++i)
{
const PFLobbyStateChange* stateChange = lobbyStateChanges[i];
switch (stateChange->stateChangeType)
{
case PFLobbyStateChangeType::CreateAndJoinLobbyCompleted:
{
HandleCreateAndJoinLobbyCompleted(
static_cast<const PFLobbyCreateAndJoinLobbyCompletedStateChange*>(stateChange));
break;
}
// add other state change handlers here
}
}
hr = PFMultiplayerFinishProcessingLobbyStateChanges(m_pfmHandle, lobbyStateChangeCount, lobbyStateChanges);
if (FAILED(hr))
{
return hr;
}
//
// Process Match state changes
//
uint32_t matchStateChangeCount;
const PFMatchmakingStateChange * const * matchStateChanges;
hr = PFMultiplayerStartProcessingMatchmakingStateChanges(m_pfmHandle, &matchStateChangeCount, &matchStateChanges);
if (FAILED(hr))
{
return hr;
}
for (uint32_t i = 0; i < matchStateChangeCount; ++i)
{
const PFMatchmakingStateChange* stateChange = matchStateChanges[i];
switch (stateChange->stateChangeType)
{
case PFMatchmakingStateChangeType::TicketStatusChanged:
{
HandleMatchmakingTicketStatusChanged(
static_cast<const PFMatchmakingTicketStatusChangedStateChange*>(stateChange));
break;
}
// add other state change handlers here
}
}
hr = PFMultiplayerFinishProcessingMatchmakingStateChanges(m_pfmHandle, matchStateChangeCount, matchStateChanges);
if (FAILED(hr))
{
return hr;
}
非同期操作コンテキスト
各非同期 API には、void* asyncContext
パラメーターが含まれています。 この値は、PFMultiplayerStartProcessingLobbyStateChanges() または PFMultiplayerStartProcessingMatchmakingStateChanges() によって提供されると、この API 呼び出しに関連付けられた完了状態の変更に設定されるパススルー パラメーターです。
この値により、非同期 API 呼び出しに任意のポインターサイズのコンテキストを関連付けるメカニズムが提供されます。 これらのコンテキストは、次のようなさまざまなシナリオで使用できます:
- タイトル固有のデータを SDK 呼び出しに関連付けます
- 複数の非同期操作と共有識別子との結び付け
これらの非同期コンテキストは、SDK の使用には必要ありませんが、一部のタイトル ロジックを簡単に記述できるようにします。
操作キュー
非同期 API を取り扱う場合、より大きな非同期フローの一部として、複数の非同期オペレーションを次々に実行する必要があることがよくあります。
ロビーとマッチメイキング SDK では、1 つの例としてロビーを作成し、そのロビーのフレンドに招待を送信します。 シリアル化されると、このフローは次のようになります:
- PFMultiplayerCreateAndJoinLobby() を呼び出し、PlayFab ロビーを作成して参加します。
- PFLobbyCreateAndJoinLobbyCompletedStateChange にロビーが正常に作成され、参加したことが反映されるまで待ちます。
- 招待されたフレンドごとに PFLobbySendInvite() を呼び出します。
- PFLobbySendInviteCompletedStateChange に招待が正常に送信されたことが反映されるまで待ちます。
より複雑なフローとタイトル ロジックの場合は、このシリアル化されたパターンが適切な場合があります。 ただし、より単純なフローの場合、SDK ではタイトル コードを簡略化する代替手段を提供しています:
SDK の非同期の API の多くは、前の操作が完全に完了する前に、依存する操作のキューイングをサポートします。 前の例に照らすと、ロビーが正常に作成されたことを確認する前にも、そのロビーに招待を送ることができます。
実際に、キューを使用すると、非同期操作のコレクションをバンドルし、それらを一度に開始し、エラー処理を 1 つの障害点に結合することができます。
PFLobbyHandle newLobby;
HRESULT hr = PFMultiplayerCreateAndJoinLobby(m_pfmHandle, myPlayerEntityId, newLobbyConfiguration, nullptr, nullptr, &newLobby);
if (SUCCEEDED(hr))
{
for (size_t i = 0; SUCCEEDED(hr) && i < friends.size(); ++i)
{
hr = PFLobbySendInvite(m_pfmHandle, myPlayerEntityId, friends[i], nullptr);
}
if (SUCCEEDED(hr))
{
m_lobby = newLobby;
Log("Created lobby and invited %zu friends!", friends.size());
}
else
{
// For the purposes of demonstration, we could have a policy that we shouldn't bother with any lobbies where
// we couldn't invite all of our friends.
(void) PFLobbyLeave(newLobby, myPlayerEntityId, nullptr);
}
}
非同期作業の制御
ライブラリとそのタイトルのコア CPU ワークロード間の CPU 競合を回避するために、タイトルがどこで非同期作業を行うのかを制御することが必要な場合もあります。
ロビーとマッチメイキング SDK を使用すると、スレッド アフィニティの制御によって非同期作業の実行方法を制御できます
スレッド アフィニティの制御
既定では、非同期の SDK 作業は、慎重に制御されたバックグラウンド スレッドで実行されます。 一部のタイトルでは、CPU 競合を回避するために、これらのバックグラウンド スレッドがスケジュールされている場所を粗い粒度で制御する必要があります。
これらのタイトルの場合、ロビーとマッチメイキング SDK には PFMultiplayerSetThreadAffinityMask() が用意されています。 サポートされているプラットフォームでは、これにより SDK のバックグラウンド スレッドに使用する CPU コアを制限することができます。 これにより、特定のコアが独自の CPU ワークロード用に競合することなく予約されていることを保証できます。