Joining players don't see existing members in an EOS lobby

I believe the issue is that the existing lobby members don’t get added the Lobby Details in this call in LobbiesEOSGSTypes.cpp

TFuture<TDefaultErrorResultInternal<TSharedRef<FLobbyDataEOS>>> FLobbyDataEOS::Create(
  TSharedRef<FLobbyPrerequisitesEOS> Prerequisites,
  FLobbyId LobbyId,
  const TSharedRef<FLobbyDetailsEOS>& LobbyDetails,
  FUnregisterFn UnregisterFn)
{
  TPromise<TDefaultErrorResultInternal<TSharedRef<FLobbyDataEOS>>> Promise;
  TFuture<TDefaultErrorResultInternal<TSharedRef<FLobbyDataEOS>>> Future = Promise.GetFuture();

  LobbyDetails->GetLobbySnapshot(false)

Since the existing members aren’t there, they don’t get added the lobby snapshot in the subsequent code

    // Fetch member data and apply them to the lobby.
    TMap<FAccountId, FLobbyMemberServiceSnapshot> MemberSnapshots;
    for (FAccountId MemberAccountId : LobbySnapshot.Members)
    {
      TDefaultErrorResultInternal<FLobbyMemberServiceSnapshot> LobbyMemberSnapshotResult = LobbyDetails->GetLobbyMemberSnapshot(MemberAccountId);
      if (LobbyMemberSnapshotResult.IsError())
      {
        UE_LOG(LogOnlineServices, Warning, TEXT("[FLobbyDataEOS::Create] FLobbyDetailsEOS::GetLobbyMemberSnapshot Failed: Lobby[%s], User[%s], Result[%s]"),
          *ToLogString(LobbyId), *ToLogString(MemberAccountId), *Result.GetErrorValue().GetLogString());
        Promise.EmplaceValue(MoveTemp(LobbyMemberSnapshotResult.GetErrorValue()));
        return;
      }

      MemberSnapshots.Emplace(MemberAccountId, MoveTemp(LobbyMemberSnapshotResult.GetOkValue()));
    }

    TOnlineResult<FLobbyClientDataPrepareServiceSnapshot> PrepareServiceLobbySnapshotResult =
      NewLobbyClientData->PrepareServiceSnapshot({ MoveTemp(LobbySnapshot), MoveTemp(MemberSnapshots), {} });

where it prepares the lobby snapshot. Because of this, even though they are in the Lobby Data members in some later parts of the code, they don’t get added to the lobby snapshot which is what gets bubbled up to the player. As far as I can tell, they don’t get added any time later, so, player C never sees them in the lobby snapshot.

I wanted to change that call in the FLobbyDataEOS::Create from

LobbyDetails->GetLobbySnapshot(false)

to

LobbyDetails->GetLobbySnapshot(true)

Does that make sense to do? When I tried it out, it appears to be working as expected

[Attachment Removed]

Steps to Reproduce

  1. The host (Player A) creates an EOS lobby
  2. Player B joins the EOS lobby
    1. When they join, Player A gets a new member added notification, they see Player A, and Player B in the lobby
    2. When they join, Player B, gets a new Lobby creation call, and sees themselves, and the host in the lobby
  3. Player C joins the EOS Lobby
    1. When they join, Player A and Player B, get a new member added notification. They see Player A, B, and C in the lobby
    2. When they join, Player C, gets a new Lobby creation call, and sees themselves and the host in the lobby

[Attachment Removed]

Hey,

Thanks for the details here. This is a known issue, and we have an existing Jira. When a lobby is discovered via search or invite before joining, its client-side entry is built from a snapshot that only contains the lobby owner. Full member data is not available until the local user is actually in the lobby. On join, that already-registered entry is reused and never refreshed, so the joining player never sees the other existing members.

Your suggestion is correct in spirit, but on its own, it does not run in the normal search/invite-then-join flow, because the join path reuses the already-registered lobby entry instead of recreating it. I think the fix below should work for all cases.

All changes are in this file:

Engine/Plugins/Online/OnlineServicesEOSGS/Source/Private/Online/LobbiesEOSGSTypes.cpp

CHANGE 1 - in function FLobbyDataEOS::Create
This copies member data when the lobby entry is first created (your suggestion).
Change this line:
 
	LobbyDetails->GetLobbySnapshot(false)
 
to:
 
	// Copy member data so members are captured when the caller has member visibility; falls back to owner-only otherwise.
 
	LobbyDetails->GetLobbySnapshot(true)
 
CHANGE 2 - in function FLobbyDataRegistryEOS::FindOrCreateFromLobbyDetails
 
This is the load-bearing fix. In the branch that runs when the lobby data already exists, refresh the member snapshot from the incoming (now member-visible) details. The refresh only ever adds members, it never removes them.
 
Replace this line:
 
		return MakeFulfilledPromise<TDefaultErrorResultInternal<TSharedRef<FLobbyDataEOS>>>(FindResult.ToSharedRef()).GetFuture();
 
with:
		TSharedRef<FLobbyDataEOS> LobbyData = FindResult.ToSharedRef();
		TPromise<TDefaultErrorResultInternal<TSharedRef<FLobbyDataEOS>>> Promise;
		TFuture<TDefaultErrorResultInternal<TSharedRef<FLobbyDataEOS>>> Future = Promise.GetFuture();
 
		// The registry entry may have been created from a search/invite snapshot that only contained the owner.
		LobbyDetails->GetLobbySnapshot(true)
		.Next([Promise = MoveTemp(Promise), LobbyData, LobbyDetails, LocalAccountId](TDefaultErrorResultInternal<FLobbyServiceSnapshot>&& Result) mutable
		{
			if (Result.IsError())
			{
				UE_LOGF(LogOnlineServices, Warning, "[FLobbyDataRegistryEOS::FindOrCreateFromLobbyDetails] GetLobbySnapshot refresh Failed: User[%ls], Lobby[%ls], Result[%ls]",
					*ToLogString(LocalAccountId), *LobbyData->GetLobbyIdString(), *Result.GetErrorValue().GetLogString());
				Promise.EmplaceValue(LobbyData);
				return;
			}
 
			FLobbyServiceSnapshot LobbySnapshot = MoveTemp(Result.GetOkValue());
 
			// Only refresh when the snapshot reveals more members than currently known; never shrink the member list.
			if (LobbySnapshot.Members.Num() <= LobbyData->GetLobbyClientData()->GetPublicData().Members.Num())
			{
				Promise.EmplaceValue(LobbyData);
				return;
			}
 
			TMap<FAccountId, FLobbyMemberServiceSnapshot> MemberSnapshots;
			for (FAccountId MemberAccountId : LobbySnapshot.Members)
			{
				TDefaultErrorResultInternal<FLobbyMemberServiceSnapshot> MemberSnapshotResult = LobbyDetails->GetLobbyMemberSnapshot(MemberAccountId);
				if (MemberSnapshotResult.IsError())
				{
					UE_LOGF(LogOnlineServices, Warning, "[FLobbyDataRegistryEOS::FindOrCreateFromLobbyDetails] GetLobbyMemberSnapshot refresh Failed: User[%ls], Member[%ls], Lobby[%ls], Result[%ls]",
						*ToLogString(LocalAccountId), *ToLogString(MemberAccountId), *LobbyData->GetLobbyIdString(), *MemberSnapshotResult.GetErrorValue().GetLogString());
					Promise.EmplaceValue(LobbyData);
					return;
				}
 
				MemberSnapshots.Emplace(MemberAccountId, MoveTemp(MemberSnapshotResult.GetOkValue()));
			}
 
			TOnlineResult<FLobbyClientDataPrepareServiceSnapshot> PrepareResult =
				LobbyData->GetLobbyClientData()->PrepareServiceSnapshot({ MoveTemp(LobbySnapshot), MoveTemp(MemberSnapshots), {} });
			if (PrepareResult.IsError())
			{
				UE_LOGF(LogOnlineServices, Warning, "[FLobbyDataRegistryEOS::FindOrCreateFromLobbyDetails] PrepareServiceSnapshot refresh Failed: User[%ls], Lobby[%ls], Result[%ls]",
					*ToLogString(LocalAccountId), *LobbyData->GetLobbyIdString(), *PrepareResult.GetErrorValue().GetLogString());
				Promise.EmplaceValue(LobbyData);
				return;
			}
 
			LobbyData->GetLobbyClientData()->CommitServiceSnapshot({});
			Promise.EmplaceValue(LobbyData);
		});
 
		return Future;

After applying both changes, please rebuild the OnlineServicesEOSGS module and confirm that a joining player now sees the existing members. Please test both a direct join and a search-then-join if your title supports it, since they exercise different code paths.

If you run into issues, or even if this works, can you share full logs?

Thanks,

Seb

[Attachment Removed]

That appears to have fixed both issues. Thank you

[Attachment Removed]