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();
}