void MultiplayerLobbyClient::AdvertiseGameSession()

in Source/Services/Multiplayer/Manager/multiplayer_lobby_client.cpp [1272:1466]


void MultiplayerLobbyClient::AdvertiseGameSession() noexcept
{
    std::shared_ptr<XblContext> primaryContext = m_multiplayerLocalUserManager->GetPrimaryContext();
    if (primaryContext == nullptr || GameSession() == nullptr)
    {
        return;
    }

    // This operation performs several sub-steps as follows:
    // 1. If a pending commit is currently in progress, wait until it is completed
    // 2. Establish a lobby session & commit any pending lobby changes
    // 3. Create an MPSD transfer handle from the lobby session to the game session
    // 4. Update the lobby session properties (custom transfer handle & joinability)
    struct AdvertiseGameSessionOperation : public std::enable_shared_from_this<AdvertiseGameSessionOperation>
    {
        AdvertiseGameSessionOperation(
            std::shared_ptr<MultiplayerLobbyClient> lobbyClient,
            std::shared_ptr<XblContext> primaryContext
        ) noexcept
            : m_lobbyClient{ std::move(lobbyClient) },
            m_primaryContext{ std::move(primaryContext) }
        {
        }

        void Run() noexcept
        {
            bool expected{ false };
            if (!m_lobbyClient->m_pendingCommitInProgress.compare_exchange_strong(expected, true))
            {
                // Wait until there isn't commit in progress before continuing.
                // Reschedule op with no delay (1806 XDK behavior)
                HRESULT hr = m_lobbyClient->m_queue.RunWork([op{ shared_from_this() }]
                    {
                        op->Run();
                    });

                if (FAILED(hr))
                {
                    Complete(hr);
                }
            }
            else
            {
                EstablishLobbySession();
            }
        }

    private:
        void EstablishLobbySession() noexcept
        {
            auto lobbySession{ m_lobbyClient->Session() };
            if (!lobbySession)
            {
                if (m_lobbyClient->m_multiplayerLocalUserManager->GetLocalUserMap().empty())
                {
                    // There are no remaining local users. Complete the operation
                    return Complete(S_OK);
                }

                m_lobbyClient->m_multiplayerLocalUserManager->ChangeAllLocalUserLobbyState(MultiplayerLocalUserLobbyState::Add);
                auto hr = m_lobbyClient->CommitPendingLobbyChanges(Vector<uint64_t>{}, false, XblMultiplayerSessionReference{},
                    [
                        op{ shared_from_this() }
                    ]
                (Result<MultiplayerEventQueue> joinLobbyResult)
                {
                    op->m_lobbyClient->m_pendingCommitInProgress = false;
                    op->m_lobbyClient->JoinLobbyCompleted(joinLobbyResult, op->m_primaryContext->Xuid());
                    if (Failed(joinLobbyResult))
                    {
                        op->Complete(joinLobbyResult);
                    }
                    else
                    {
                        op->CreateTransferHandle(op->m_lobbyClient->Session());
                    }
                });

                if (FAILED(hr))
                {
                    Complete(hr);
                }
            }
            else
            {
                m_lobbyClient->m_pendingCommitInProgress = false;
                CreateTransferHandle(lobbySession);
            }
        }

        void CreateTransferHandle(std::shared_ptr<XblMultiplayerSession> lobbySession) noexcept
        {
            JsonDocument lobbyProperties;
            {
                XblMultiplayerSessionReadLockGuard lobbySessionSafe(lobbySession);
                lobbyProperties.Parse(lobbySessionSafe.SessionProperties().SessionCustomPropertiesJson);
            }

            if (!lobbyProperties.HasMember(MultiplayerLobbyClient_TransferHandlePropertyName) ||
                (m_lobbyClient->IsTransferHandleState("pending") && m_lobbyClient->GetTransferHandle() == utils::uint64_to_internal_string(m_primaryContext->Xuid())))
            {
                auto gameSession{ m_lobbyClient->GameSession() };
                if (!gameSession)
                {
                    return Complete(Result<void>{E_ABORT, "GameSession null"});
                }

                auto hr = m_primaryContext->MultiplayerService()->SetTransferHandle(
                    gameSession->SessionReference(),
                    lobbySession->SessionReference(),
                    AsyncContext<Result<String>>{ m_lobbyClient->m_queue,
                    [
                        op{ shared_from_this() },
                        lobbySession
                    ]
                (Result<String> result)
                {
                    if (Succeeded(result))
                    {
                        op->UpdateLobbySession(MakeShared<XblMultiplayerSession>(*lobbySession), result.ExtractPayload());
                    }
                    else
                    {
                        if (result.Hresult() == HTTP_E_STATUS_FORBIDDEN)
                        {
                            // By MPSD design, if the game session doesn't exist on transfer handle creation, it throws a 403.
                            op->m_lobbyClient->ClearGameSessionFromLobby();
                        }
                        op->Complete(result);
                    }
                }
                });

                if (FAILED(hr))
                {
                    return Complete(hr);
                }
            }
        }

        void UpdateLobbySession(
            std::shared_ptr<XblMultiplayerSession> lobbySession,
            String&& transferHandle
        ) noexcept
        {
            if (m_lobbyClient->m_joinability == XblMultiplayerJoinability::DisableWhileGameInProgress)
            {
                lobbySession->SetClosed(true);
            }

            Stringstream jsonHandleValue;
            jsonHandleValue << "completed~" << transferHandle;

            JsonDocument jsonValue;
            jsonValue.SetString(jsonHandleValue.str().data(), jsonValue.GetAllocator());
            lobbySession->SetSessionCustomPropertyJson(
                MultiplayerLobbyClient_TransferHandlePropertyName,
                jsonValue
            );

            HRESULT hr = m_lobbyClient->m_sessionWriter->WriteSession(
                m_primaryContext,
                lobbySession,
                XblMultiplayerSessionWriteMode::UpdateExisting,
                true,
                [
                    op{ shared_from_this() }
                ]
            (Result<std::shared_ptr<XblMultiplayerSession>> result)
            {
                op->Complete(result);
            });

            if (FAILED(hr))
            {
                Complete(hr);
            }
        }

        void Complete(Result<void>&& result)
        {
            // This operation is strange in that nothing directly looks at the result.
            // The result is exposed to title via events, but MPM doesn't seem to handle failures.
            // Trying to keep the purpose of each operation as consistent with 1806 XDK as possible, so leaving as is for now.

            LOGS_DEBUG << __FUNCTION__ << ": HRESULT=" << result.Hresult() << ", ErrorMessage=" << result.ErrorMessage();
        }

        std::shared_ptr<MultiplayerLobbyClient> m_lobbyClient;
        std::shared_ptr<XblContext> m_primaryContext;
    };

    auto operation = MakeShared<AdvertiseGameSessionOperation>(shared_from_this(), primaryContext);
    operation->Run();
}