非同期 PlayFab パーティーの操作と通知

低速または計算コストの高い操作の場合、PlayFab Party は非同期 API を公開します。 非同期 API は、タイトルがメイン スレッドからコストの高い操作や低速な操作を開始し、選択したスレッドでそれらの操作が完了したかどうかを確認する能力を提供します。 この同じポーリング メカニズムは、PlayFab Party ライブラリの更新の非同期通知をタイトルに配信するためにも使用されます。 このページでは、PlayFab Party の非同期 API パターンの概要と、それらに対するプログラミングのベスト プラクティスについて説明します。

基本的な API パターン

PlayFab Party では、次の 2 種類の非同期 API パターンに注意する必要があります。

  1. 非同期操作
  2. 非同期通知

非同期操作

PlayFab Party の非同期 API を使用するのは簡単です。 非同期操作を開始および完了するための一般的なパターンは次のとおりです:

  1. 選択した適切な非同期 API に対して、通常のメソッド呼び出しを行います。 一般的な非同期パーティーの操作としては、以下のようなものがあります:

  2. PARTY_SUCCEEDED() または PARTY_FAILED() マクロを使用して、非同期 API の PartyError 戻り値を確認します。 この同期的に返された値は、操作が正常に開始されたかどうかを示します。

Warning

非同期パーティー API 呼び出しからの同期戻り値は、操作が正常に完了したかどうかを示しません同期エラーと非同期エラーの詳細については、後のセクションを参照してください。

  1. PartyManager::StartProcessingStateChanges() によって提供される、関連付けられた操作の "完了状態の変更" を探して、非同期操作の完了をポーリングします。 PartyManager::CreateNewNetwork() に関連付けられている "完了状態の変更" の例は、PartyCreateNewNetworkCompletedStateChange です。 "状態の変更" の詳細と動作の詳細については、以下の 「状態の変更」 セクションを参照してください。

  2. 完了状態の変更 結果errorDetail 値を確認して、操作が成功したか失敗したかを判断します。 これらのエラー値の詳細については、以下の「同期エラーと非同期エラー」のセクションをご覧ください。

非同期通知

一部の機能では、PlayFab Party ライブラリに変更のタイトル非同期通知が送信されます。

一般的な通知は次のとおりです:

  1. 新しいエンドポイントがパーティー ネットワークに作成されたときの EndpointCreated
  2. チャット コントロールがパーティー ネットワークに参加する場合の ChatControlJoinedNetwork
  3. ローカル チャット コントロールのオーディオ入力が何らかの形で変更されたことをパーティーが登録する際の LocalChatAudioInputChanged

これらの非同期通知は、 PartyManager::StartProcessingStateChanges() を介して"状態変更" として PlayFab Party によってタイトルに提供されます。

"状態の変更" の詳細と動作の詳細については、以下の 「状態の変更」 セクションを参照してください。

状態の変更

PlayFab Party の非同期 API モデルは、PartyStateChange 構造体を中心に構築されています。

これらの "状態の変更" は、PlayFab パーティー ライブラリからのイベントの非同期通知です。 これらの通知は内部的にキューに登録され、タイトルは PartyManager::StartProcessingStateChanges() を呼び出して処理します。 StartProcessingStateChanges() は、タイトルが個別に反復処理および処理できるリストとして、キューに登録されているすべての状態変更を返します。 各状態変更には PartyStateChange::stateChangeType フィールドがあり、タイトルに通知される特定の状態変更を調べることができます。 タイトルは、与えられた状態変更を認識したら、汎用 PartyStateChange 構造体をより具体的な種類の状態変更構造体にキャストして、そのイベントの特定のデータを調べることができます。

通常、状態変更処理は、各状態変更をハンドラーに委任する単純な switch ステートメントとして実装されます。

状態の変更の一覧が処理されたら、 PartyManager::FinishProcessingStateChanges() に返す必要があります。

uint32_t stateChangeCount;
Party::PartyStateChangeArray stateChanges;
PartyError error = Party::PartyManager::GetSingleton().StartProcessingStateChanges(&stateChangeCount, &stateChanges);
if (PARTY_FAILED(error))
{
    return error;
}

for (uin32_t i = 0; i < stateChangeCount; ++i)
{
    const Party::PartyStateChange* stateChange = stateChanges[i];
    switch (stateChange->stateChangeType)
    {
        case Party::PartyStateChangeType::CreateNewNetworkCompleted:
        {
            auto createNewNetworkStateChange = static_cast<const Party::PartyCreateNewNetworkCompletedStateChange*>(stateChange);
            HandlePartyCreateNewNetworkCompleted(createNewNetworkStateChange);
            break;
        }
        // add other state change handlers here.
    }
}

error = Party::PartyManager::GetSingleton().FinishProcessingStateChanges(stateChangeCount, stateChanges);
if (PARTY_FAILED(error))
{
    return error;
}

注意

ほとんどのタイトルでは、すべての状態変更を直ちに処理し、それらを同時に FinishProcessingStateChanges() に返することをお勧めします。 高度なシナリオでは、一部の状態変更を長期間保持し、後で返すのが適切な場合があります。 これについては、以下の 「状態変更の保留」で詳しく説明します。

同期と非同期

非同期 PlayFab パーティー API を使用する場合、処理する必要があるエラーには 2 種類があることに注意してください:

  1. 同期エラー
  2. 非同期エラー

"同期エラー" は、非同期 API 呼び出しの戻り値として提供されるエラーであり、非同期操作を開始するためのエラーを表します。 これらのエラーは、一般に、正しくないパラメーターを使用して API を呼び出したり、ライブラリが無効な状態のときに API を呼び出したり、メモリを割り当てるための内部ライブラリが失敗したりすることが原因です。

"非同期エラー" は、各操作の非同期完了に関連付けられた 状態変更 のデータとして提供されます。 状態変更構造体には、エラー処理に関連する 2 つのフィールドがあります。

  1. result - すべての完了状態の変更で使用できる PartyStateChangeResult 列挙値。 この値の目的は、操作が失敗した理由をタイトルに広く説明することです。 この値は、タイトルが非同期エラーにプログラムによって対応し、処理するために使用できます。
  2. errorDetail - すべての完了状態の変更と一部の通知状態の変更で使用できる PartyError 値。 これらのエラーの詳細値はきわめて不透明であり、プログラミングしないでください。 これらは主に、操作が失敗した理由をより詳細に理解するために、テレメトリやエラー ログなどの診断で記述することを目的としています。 これらのエラーの詳細は、PartyManager::GetErrorMessage() を呼び出すことによって、人間が判読できる形式に変換できます。 これらのエラー メッセージは、開発者のみが確認することを目的としています。 これらはローカライズされていないか、エンド ユーザーによる使用を意図していません。 また、PlayFab パーティー SDK のエラー コードとそのエラー メッセージの一覧については、「PlayFab パーティーエラー コード」をご覧ください。

Important

状態変更エラー (開発者ログ、テレメトリ、PlayFab Developer Community エラー レポートなど) をキャプチャする場合は、診断の一部として、関連するすべての状態変更の result 値と errorDetail 値の両方をキャプチャすることを強くお勧めします。 2 つの値を組み合わせると、どちらの値よりも多くの情報が分離してキャプチャされます。

非同期操作識別子

各非同期 API には、void* asyncIdentifer パラメーターが含まれています。 この値は、 StartProcessingStateChanges() によって提供されると、この API 呼び出しに関連付けられた完了状態の変更に設定されるパススルー パラメーターです。

この値の目的は、タイトルに、任意のポインター サイズのコンテキストを非同期 API 呼び出しにアタッチするメカニズムを提供することです。 これらのコンテキストは、次のようなさまざまなシナリオで使用できます:

  1. タイトル固有のデータの PlayFab Party API 呼び出しとの関連付け
  2. 複数の非同期操作と共有識別子との結び付け

これらの非同期識別子は、PlayFab Party の使用には必要ありませんが、一部のタイトル ロジックを簡単に記述できるようにします。

操作キュー

非同期 API を取り扱う場合、より大きな非同期フローの一部として、複数の非同期オペレーションを次々に実行する必要があることがよくあります。

PlayFab Party では、1 つの例としてパーティー ネットワークに参加し、チャット コントロールをそれに接続します。 シリアル化されると、このフローは次のようになります:

  1. PartyManager::ConnectToNetwork() を呼び出して、デバイスをパーティー ネットワークに接続します。
  2. PartyConnectToNetworkCompletedStateChange が接続に成功したことを反映するまで待ちます。
  3. PartyNetwork::AuthenticationLocalUser() を呼び出して、ユーザーを認証してパーティー ネットワークに参加させます。
  4. PartyAuthenticateLocalUserCompletedStateChange が認証に成功したことが反映されるまで待ちます。
  5. PartyNetwork::ConnectChatControl() を呼び出して、チャット コントロールをパーティー ネットワークに接続します。
  6. PartyConnectChatControlCompletedStateChange がチャット コントロール接続に成功したことが反映されるまで待ちます。

より複雑なフローとタイトル ロジックの場合は、このシリアル化されたパターンが適切な場合があります。 ただし、より単純なフローの場合、PlayFab Party ではタイトル コードを簡略化する代替手段を提供しています:

非同期の PlatFab Party API の多くは、前の操作が完全に完了する前に、依存する操作のキューイングをサポートします。 前の例のとおり、接続に成功する前でも、ローカル ユーザーのパーティー ネットワークへの認証を開始することができます。また、関連付けられたローカル ユーザーの認証がまだ完了していない状態でも、チャットの接続手続きを開始することができます。

実際に、キューを使用すると、非同期操作のコレクションをバンドルし、それらを一度に開始し、エラー処理を 1 つの障害点に結合することができます。

Party::PartyNetwork* newPartyNetwork;
PartyError err = PartyManager::GetSingleton().ConnectToNetwork(networkDescriptor, nullptr, &newPartyNetwork);
if (PARTY_SUCCEEDED(err))
{
    err = newPartyNetwork->AuthenticateLocalUser(m_localUser, networkInvitation, nullptr);
    if (PARTY_SUCCEEDED(err))
    {
        err = newPartyNetwork->ConnectChatControl(m_chatControl, nullptr);
        if (PARTY_SUCCEEDED(err))
        {
            Log("Connecting chat control to new party network!");
            m_network = newPartyNetwork;

            // After this point, we should log any failures reported in PartyConnectToNetworkCompletedStateChange,
            // PartyAuthenticateLocalUserCompletedStateChange, and PartyConnectChatControlCompletedStateChange for
            // diagnostic purposes, but all final failure logic and any retry logic can be coalesced into
            // PartyConnectChatControlCompletedStateChange.
        }
    }

    // If we experienced any unexpected failures to start the authentice or connect-chat-control operations, queue
    // a leave operation so we don't join the network in a half-state.
    if (PARTY_FAILED(err))
    {
        (void) newPartyNetwork->LeaveNetwork(nullptr);
    }
}

状態の変更を保持する

通常、StartProcessingStateChanges() によって提供される状態変更は直ちに処理され、FinishProcessingStateChanges() に直接返されます。

uint32_t stateChangeCount;
Party::PartyStateChangeArray stateChanges;
PartyError error = Party::PartyManager::GetSingleton().StartProcessingStateChanges(&stateChangeCount, &stateChanges);
if (PARTY_FAILED(error))
{
    return error;
}


// process the state changes
// ...

error = Party::PartyManager::GetSingleton().FinishProcessingStateChanges(stateChangeCount, stateChanges);
if (PARTY_FAILED(error))
{
    return error;
}

これは推奨されるフローです。これは、タイトル シナリオの大部分に対してプログラミングが簡単で、サポートされているためです。 ただし、一部の高度なシナリオでは、状態変更のサブセットを保持し、後で元の順序から返すのが適切な場合があります。 通常、これは、その状態変更に関連付けられているリソースの有効期間を延長するために行われます。

例えば、タイトルはメイン スレッドで状態の変更を処理しますが、受信したエンドポイント メッセージはネットワーク更新に基づいてタイトル状態を更新するバックグラウンド スレッドで処理することができます。 一般的な状態変更処理フローでは、状態変更が FinishProcessingStateChanges() に返されたときに状態変更のバッファーが再利用されるため、PartyEndpointMessageReceivedStateChange からエンドポイント メッセージを有効期間の長いバッファーにコピーする必要があります。 PartyEndpointMessageReceivedStateChange を確保し、メッセージを処理するまで戻り値を延期することで、頻繁なコピーの必要性を回避できます。

uint32_t endpointMessageCount = 0;
for (uint32_t i = 0; i < stateChangeCount; ++i)
{
    switch (stateChanges[i]->stateChangeType)
    {
        //
        // ... Process all state change types except for EndpointMessageReceived here...
        //
    }
}

// if there were any endpoint messages in the queue, separate them out, queue them, and return the remaining state
// changes in a separate buffer

// NOTE: this sample uses local std::vectors for brevity, but avoiding heap allocations by reusing vectors or
// using a fixed sized buffer tuned for your game may be appropriate.
std::vector<const Party::PartyStateChange*> nonEndpointStateChanges;
std::vector<const Party::PartyStateChange*> endpointStateChanges;
for (uint32_t i = 0; i < stateChangeCount; ++i)
{
    if (stateChanges[i]->stateChangeType == Party::PartyStateChangeType::EndpointMessageReceived)
    {
        endpointStateChanges.push_back(stateChanges[i]);
    }
    else
    {
        nonEndpointStateChanges.push_back(stateChanges[i]);
    }
}

// Return all non-endpoint message state changes
err = PartyManager::GetSingleton().FinishProcessingStateChanges(
    static_cast<uint32_t>(nonEndpointStates.size()),
    nonEndpointStateChanges.data());

// Title-defined function to queue all endpoint message state changes for later processing.
// QueueEndpointMessagesForLaterProcessing will hold any locks necessary to write to the endpoint message queue
MyGame::QueueEndpointMessagesForLaterProcessing(endpointStateChanges.size(), endpointStateChanges.data());

//
// Elsewhere in another thread/execution context...
//
std::vector<const Party::PartyStateChange*> endpointStateChanges;
// Title-defined function to copy all endpoint message state change pointers that we've so far queued.
// TakeEndpointMessagesOutOfQueue will hold any locks necessary to drain the endpoint message queue.
MyGame::TakeEndpointMessagesOutOfQueue(&endpointStateChanges);

for (const Party::PartyStateChange* stateChange : endpointStateChanges)
{
    auto endpointMessage = static_cast<const Party::PartyEndpointMessageReceivedStateChange*>(stateChange);
    // process the endpoint message
}

err = PartyManager::GetSingleton().FinishProcessingStateChanges(
    static_cast<uint32_t>(endpointStateChanges.size()),
    endpointStateChanges.data());

状態の変更を保持すると、タイトルは、より複雑なタイトル ロジックを犠牲にして、PlayFab Party のメモリとリソースの一部をより厳密に制御できます。

非同期作業の制御

PlayFab Party などのライブラリとそのタイトルのコア CPU ワークロード間の CPU 競合を回避するために、タイトルがいつどこで非同期作業を行うのかを制御することが必要な場合もあります。

PlayFab Party には、非同期の PlayFab パーティー作業の実行方法を制御するための 2 つのオプションがタイトルに用意されています:

  1. スレッド アフィニティの制御
  2. 非同期作業を手動でディスパッチする

スレッド アフィニティの制御

既定では、非同期の PlayFab Party 作業は、慎重に制御されたバックグラウンド スレッドで実行されます。 一部のタイトルでは、CPU 競合を回避するために、これらのバックグラウンド スレッドがスケジュールされている場所を粗い粒度で制御するだけで済みます。

こうしたタイトルには、PlayFab Party は PartyManager::SetThreadAffinityMask() を提供します。 サポートされているプラットフォームでは、これにより PlayFab Partyのバックグラウンドスレッドに使用する CPU コアをタイトルで制限することができます。 これにより、タイトルは、特定のコアが独自の CPU ワークロード用に競合することなく予約されていることを保証できます。

非同期作業を手動でディスパッチする

一部のタイトルでは、PlayFab Party が非同期作業を実行する場所を制御するだけでは十分ではありません。 PlayFab Party が非同期作業を実行するタイミングも制御する必要があります。 こうしたタイトルでは、PlayFab Party は PartyManager::SetWorkMode()PartyManager::D oWork() を提供します。 PartyWorkMode::Manual を使用すると、タイトルはバックグラウンド スレッドをまったく使わずに PlayFab Party ライブラリ を使用できます。 タイトルは、その作業がいつ行われるか直接制御して、選択した実行コンテキストで Party の非同期バックグラウンド作業を手動で駆動します。

Warning

非同期パーティー作業を手動でディスパッチすることは高度な機能です。 PartyWorkMode::Manual でも、非同期パーティー作業は周期的かつ定期的にディスパッチする必要があります。 非同期作業がタイムリーにディスパッチされない場合、PlayFab Party が正しく機能しない可能性があります。

この作業を正しくディスパッチする方法については、 PartyManager::D oWork() のドキュメントをよくお読みください。

手動ディスパッチが必要ない場合は、代わりに Party の既定の非同期作業構成、またはスレッド アフィニティ コントロールを使用することをお勧めします。