in Source/Services/Multiplayer/Manager/multiplayer_lobby_client.cpp [739:1097]
HRESULT MultiplayerLobbyClient::CommitLobbyChanges(
_In_ const Vector<uint64_t>& xuidsInOrder,
_In_ std::shared_ptr<XblMultiplayerSession> lobbySessionToCommit,
_In_ Callback<Result<void>> callback
) noexcept
{
// For each User in xuidsInOrder, prepare an XblMultiplayerSession with their pending lobby changes
// and write it to MPSD. Will also update the user's multiplayer activity & set the session host token
// as neeeded. Consistent with 1806 XDK behavior, if any individual MPSD call fails unexpectedly,
// we abort the entire operation and return that result.
struct CommitLobbyChangesOperation : public std::enable_shared_from_this<CommitLobbyChangesOperation>
{
CommitLobbyChangesOperation(
std::shared_ptr<MultiplayerLobbyClient> lobbyClient,
const Vector<uint64_t>& xuidsInOrder,
std::shared_ptr<XblMultiplayerSession> lobbySessionToCommit,
Callback<Result<void>>&& callback
) noexcept :
m_lobbyClient{ std::move(lobbyClient) },
m_lobbySessionToCommit{ std::move(lobbySessionToCommit) },
m_callback{ std::move(callback) }
{
auto& localUsers{ m_lobbyClient->GetLocalUserMap() };
if (xuidsInOrder.empty())
{
for (auto pair : localUsers)
{
m_users.push_back(pair.second);
}
}
else
{
for (auto xuid : xuidsInOrder)
{
auto iter = localUsers.find(xuid);
if (iter != localUsers.end())
{
m_users.push_back(iter->second);
}
}
}
}
void Run() noexcept
{
CommitChangesForNextUser();
}
private:
void CommitChangesForNextUser() noexcept
{
if (m_users.empty())
{
Complete(S_OK);
}
else
{
auto user{ m_users.front() };
m_users.pop_front();
CommitChangesForUser(user);
}
}
void CommitChangesForUser(
std::shared_ptr<MultiplayerLocalUser> user
) noexcept
{
// Prepare a session with changes specific to this user.
// Ok to use MPSD 'unsafe' methods here since we created the session locally, thus it is the only reference
auto session = MakeShared<XblMultiplayerSession>(user->Xuid(), &m_lobbySessionToCommit->SessionReference(), nullptr);
switch (user->LobbyState())
{
case MultiplayerLocalUserLobbyState::Add:
{
session->Join();
if (!user->ConnectionAddress().empty())
{
session->CurrentUserInternalUnsafe()->SetSecureDeviceBaseAddress64(user->ConnectionAddress());
}
session->SetSessionChangeSubscription(XblMultiplayerSessionChangeTypes::Everything);
// Only set this for the first user. Could cause a race condition if XblMultiplayerJoinability was changed during the second write.
if (m_setJoinability)
{
m_setJoinability = false;
session->SetJoinRestriction(XblMultiplayerSessionRestriction::Followed);
session->SetReadRestriction(XblMultiplayerSessionRestriction::Followed);
String jsonValueStr = MultiplayerManagerUtils::ConvertJoinabilityToString(XblMultiplayerJoinability::JoinableByFriends);
JsonDocument jsonValue;
jsonValue.SetString(jsonValueStr.c_str(), jsonValue.GetAllocator());
session->SetSessionCustomPropertyJson(MultiplayerLobbyClient_JoinabilityPropertyName, jsonValue);
}
break;
}
case MultiplayerLocalUserLobbyState::Join:
{
session->Join();
if (!user->ConnectionAddress().empty())
{
session->CurrentUserInternalUnsafe()->SetSecureDeviceBaseAddress64(user->ConnectionAddress());
}
session->SetSessionChangeSubscription(XblMultiplayerSessionChangeTypes::Everything);
break;
}
case MultiplayerLocalUserLobbyState::InSession:
{
// Forces to set the current user to yourself.
session->Join();
break;
}
case MultiplayerLocalUserLobbyState::Leave:
{
if (XblMultiplayerSession::IsPlayerInSession(user->Xuid(), m_lobbySessionToCommit))
{
session->Leave();
}
else
{
// In a scenario where the user was removed before he could have been added.
m_lobbyClient->UserStateChanged({ xbl_error_code::logic_error, "The user was removed before they could be added" }, MultiplayerLocalUserLobbyState::Add, user->Xuid());
m_lobbyClient->UserStateChanged({ xbl_error_code::no_error }, MultiplayerLocalUserLobbyState::Leave, user->Xuid());
m_removeStaleUsers = true;
user->SetLobbyState(MultiplayerLocalUserLobbyState::Remove);
// Since the user has been removed, no work to be done for this user
this->CommitChangesForNextUser();
return;
}
break;
}
default:
{
break;
}
}
bool isGameInProgress = m_lobbyClient->GameSession() != nullptr;
// Update any pending local user or lobby session properties.
for (auto& request : m_lobbyClient->m_processingQueue)
{
request->AppendPendingChanges(session, user, isGameInProgress);
}
// Now write the session to MPSD
HRESULT hr{ S_OK };
if (user->LobbyHandleId().empty())
{
hr = m_lobbyClient->m_sessionWriter->WriteSession(
user->Context(),
session,
XblMultiplayerSessionWriteMode::UpdateOrCreateNew,
true,
[
op{ shared_from_this() },
user
]
(Result<std::shared_ptr<XblMultiplayerSession>> result)
{
op->HandleWriteSessionResult(user, std::move(result));
});
}
else
{
hr = m_lobbyClient->m_sessionWriter->WriteSessionByHandle(
user->Context(),
session,
XblMultiplayerSessionWriteMode::UpdateOrCreateNew,
user->LobbyHandleId(),
true,
[
op{ shared_from_this() },
user
]
(Result<std::shared_ptr<XblMultiplayerSession>> result)
{
op->HandleWriteSessionResult(user, std::move(result));
});
user->SetLobbyHandleId(String{});
}
if (FAILED(hr))
{
Complete(hr);
}
}
void HandleWriteSessionResult(
std::shared_ptr<MultiplayerLocalUser> user,
Result<std::shared_ptr<XblMultiplayerSession>>&& sessionResult
) noexcept
{
m_lobbyClient->UserStateChanged(sessionResult, user->LobbyState(), user->Xuid());
m_lobbyClient->HandleLobbyChangeEvents(sessionResult, user, m_lobbyClient->m_processingQueue);
if (Failed(sessionResult))
{
m_lobbyClient->JoinLobbyCompleted(sessionResult, user->Xuid());
// If we failed to join the lobby, make sure the local user context is cleaned up
switch (user->LobbyState())
{
case MultiplayerLocalUserLobbyState::Add:
case MultiplayerLocalUserLobbyState::Join:
{
m_removeStaleUsers = true;
user->SetLobbyState(MultiplayerLocalUserLobbyState::Remove);
}
default:
{
break;
}
}
Complete(sessionResult);
return;
}
switch (user->LobbyState())
{
case MultiplayerLocalUserLobbyState::Add:
case MultiplayerLocalUserLobbyState::Join:
{
auto updatedSession = sessionResult.ExtractPayload();
m_lobbyClient->UpdateSession(updatedSession);
auto oldLobbyState = user->LobbyState();
user->SetLobbyState(MultiplayerLocalUserLobbyState::InSession);
if (oldLobbyState == MultiplayerLocalUserLobbyState::Join)
{
m_lobbyClient->HandleJoinLobbyCompleted(sessionResult, user->Xuid());
}
if (oldLobbyState == MultiplayerLocalUserLobbyState::Add && m_lobbyClient->ShouldUpdateHostToken(user, updatedSession))
{
UpdateHostDeviceToken(user, updatedSession);
}
else
{
SetActivityForUser(user, updatedSession);
}
return;
}
case MultiplayerLocalUserLobbyState::Leave:
{
m_removeStaleUsers = true;
// If you leave the session you were advertising, you don't need to clear the activity.
user->SetLobbyState(MultiplayerLocalUserLobbyState::Remove);
// Intentional fallthrough
}
default:
{
// Work done for this user, continue operation
CommitChangesForNextUser();
return;
}
}
}
void UpdateHostDeviceToken(
std::shared_ptr<MultiplayerLocalUser> user,
std::shared_ptr<XblMultiplayerSession> session
) noexcept
{
XblMultiplayerSessionReadLockGuard sessionSafe{ session };
session->SetHostDeviceToken(sessionSafe.CurrentUser()->DeviceToken);
auto hr = m_lobbyClient->m_sessionWriter->WriteSession(
user->Context(),
session,
XblMultiplayerSessionWriteMode::UpdateExisting,
true,
[
op{ shared_from_this() },
user
]
(Result<std::shared_ptr<XblMultiplayerSession>> writeHostTokenResult)
{
if (Failed(writeHostTokenResult))
{
op->Complete(writeHostTokenResult);
}
else
{
op->SetActivityForUser(user, writeHostTokenResult.ExtractPayload());
}
});
if (FAILED(hr))
{
Complete(hr);
}
}
void SetActivityForUser(
std::shared_ptr<MultiplayerLocalUser> user,
std::shared_ptr<XblMultiplayerSession> session
) noexcept
{
auto hr = user->Context()->MultiplayerService()->SetActivity(
session->SessionReference(),
AsyncContext<Result<void>>{ m_lobbyClient->m_queue,
[
op{ shared_from_this() }
]
(Result<void> setActivityResult)
{
if (Failed(setActivityResult))
{
op->Complete(std::move(setActivityResult));
}
else
{
op->CommitChangesForNextUser();
}
}
});
if (FAILED(hr))
{
Complete(hr);
}
}
void Complete(Result<void>&& result)
{
LOGS_DEBUG << __FUNCTION__ << ": HRESULT=" << result.Hresult() << ", ErrorMessage=" << result.ErrorMessage();
if (m_removeStaleUsers)
{
m_lobbyClient->RemoveStaleXboxLiveContextFromMap();
}
m_callback(result);
}
std::shared_ptr<MultiplayerLobbyClient> m_lobbyClient;
List<std::shared_ptr<MultiplayerLocalUser>> m_users;
std::shared_ptr<XblMultiplayerSession> m_lobbySessionToCommit;
bool m_setJoinability{ true };
bool m_removeStaleUsers{ false };
Callback<Result<void>> m_callback;
};
auto operation = MakeShared<CommitLobbyChangesOperation>(
shared_from_this(),
xuidsInOrder,
lobbySessionToCommit,
std::move(callback)
);
operation->Run();
return S_OK;
}