HRESULT MultiplayerGameClient::JoinGameForAllLocalMembers()

in Source/Services/Multiplayer/Manager/multiplayer_game_client.cpp [629:770]


HRESULT MultiplayerGameClient::JoinGameForAllLocalMembers(
    _In_ const XblMultiplayerSessionReference& sessionRefToJoin,
    _In_ const String& handleId,
    _In_ bool createGameIfFailedToJoin,
    _In_ MultiplayerSessionCallback callback
) noexcept
{
    RETURN_HR_INVALIDARGUMENT_IF(!XblMultiplayerSessionReferenceIsValid(&sessionRefToJoin) && handleId.empty());

    std::shared_ptr<XblContext> primaryContext = m_multiplayerLocalUserManager->GetPrimaryContext();
    RETURN_HR_IF_LOG_DEBUG(primaryContext == nullptr, E_UNEXPECTED, "Call add_local_user() before joining.");

    // Leave existing game without updating the latest as the leave may comeback after the actual join and overwrite it.
    auto cachedSession = Session();
    if (cachedSession != nullptr)
    {
        LeaveRemoteSession(cachedSession, false, false);
    }
    UpdateSession(nullptr);

    // Join either an existing or new game session. If attempting to join an existing session fails,
    // optionally creates a new game session from the lobby. When all users have joined the game, a JoinGameCompleted
    // event will be raised.
    struct JoinGameOperation : public std::enable_shared_from_this<JoinGameOperation>
    {
        JoinGameOperation(
            std::shared_ptr<MultiplayerGameClient> gameClient,
            const XblMultiplayerSessionReference& sessionRefToJoin,
            String handleIdToJoin,
            bool createGameIfFailedToJoin,
            MultiplayerSessionCallback&& callback
        ) noexcept :
            m_gameClient{ std::move(gameClient) },
            m_sessionRefToJoin{ sessionRefToJoin },
            m_handleIdToJoin{ std::move(handleIdToJoin) },
            m_createGameIfFailedToJoin{ createGameIfFailedToJoin },
            m_localUsers{ m_gameClient->m_multiplayerLocalUserManager->GetLocalUserMap() },
            m_callback{ std::move(callback) }
        {
        }

        void Run() noexcept
        {
            JoinGameWithNextUser();
        }

    private:
        void JoinGameWithNextUser() noexcept
        {
            assert(!m_localUsers.empty());
            auto localUser{ m_localUsers.begin()->second };
            m_localUsers.erase(m_localUsers.begin());

            auto sessionToCommit = MakeShared<XblMultiplayerSession>(localUser->Xuid(), &m_sessionRefToJoin, nullptr);
            HRESULT hr = m_gameClient->JoinHelper(localUser, sessionToCommit, true, m_handleIdToJoin,
                [
                    this,
                    sharedThis{ shared_from_this() },
                    localUser
                ]
            (Result<std::shared_ptr<XblMultiplayerSession>> joinSessionResult)
            {
                if (Succeeded(joinSessionResult))
                {
                    localUser->SetGameState(MultiplayerLocalUserGameState::InSession);
                }

                if (Failed(joinSessionResult) || m_localUsers.empty())
                {
                    OnJoinCompleted(std::move(joinSessionResult));
                }
                else
                {
                    JoinGameWithNextUser();
                }
            });

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

        void OnJoinCompleted(Result<std::shared_ptr<XblMultiplayerSession>>&& joinResult) noexcept
        {
            if (auto lobbyClient{ m_gameClient->LobbyClient() })
            {
                if (joinResult.Hresult() == HTTP_E_STATUS_NOT_FOUND && !m_handleIdToJoin.empty())
                {
                    // We tried to join an existing session but it didn't exist. Create a new game session
                    // from the lobby and clear the existing handleId since it must be invalid.
                    if (m_createGameIfFailedToJoin)
                    {
                        m_callback(lobbyClient->CreateGameFromLobby());
                        return;
                    }
                    lobbyClient->ClearGameSessionFromLobby();
                }
                else if (Failed(joinResult) && m_handleIdToJoin.empty())
                {
                    // Tried to create a new game and we failed. Clear the transfer handle pending state (GameSessionTransferHandle=pending~xuid).
                    lobbyClient->ClearGameSessionFromLobby();
                }
            }

            if (Succeeded(joinResult))
            {
                m_gameClient->UpdateSession(joinResult.Payload());
                m_gameClient->m_multiplayerLocalUserManager->ChangeAllLocalUserGameState(MultiplayerLocalUserGameState::InSession);
            }

            {
                std::lock_guard<std::mutex> lock(m_gameClient->m_clientRequestLock);
                m_gameClient->m_multiplayerEventQueue.AddEvent(
                    XblMultiplayerEventType::JoinGameCompleted,
                    XblMultiplayerSessionType::GameSession,
                    MakeShared<XblMultiplayerEventArgs>(),
                    joinResult
                );
            }
            m_callback(joinResult);
        }

        std::shared_ptr<MultiplayerGameClient> m_gameClient;
        XblMultiplayerSessionReference m_sessionRefToJoin;
        String m_handleIdToJoin;
        bool m_createGameIfFailedToJoin;
        Map<uint64_t, std::shared_ptr<MultiplayerLocalUser>> m_localUsers;
        MultiplayerSessionCallback m_callback;
    };

    auto operation = MakeShared<JoinGameOperation>(
        shared_from_this(),
        sessionRefToJoin,
        handleId,
        createGameIfFailedToJoin,
        std::move(callback)
    );

    operation->Run();
    return S_OK;
}