We tracked the issue to UMassLODSubsystem::SynchronizeViewers(). The issue seems to be that with ServerStreaming every PlayerController is also a StreamingSource. If the very first call to AddPlayerViewer(*PlayerController) early outs, that same PlayerController will be added as a StreamingSourceViewer, “poisoning” the LocalViewerMap logic and preventing that viewer from being “upgraded” to a PlayerControllerViewer (setting ViewerInfo.ActorViewer = PlayerController) on subsequent calls to SynchronizeViewers(). With that field unset we’ll not call UMassReplicationSubsystem::AddClient() later, and not replicate low-fidelity entities.
The following logic avoid this, but will try to upgrade “ignored players” every call.
`if (bGatherPlayerControllers)
{
// Now go through all current player controllers and add if they do not exist
for (FConstPlayerControllerIterator PlayerIterator = World->GetPlayerControllerIterator(); PlayerIterator; ++PlayerIterator)
{
APlayerController* PlayerController = (*PlayerIterator).Get();
check(PlayerController);
// Check if the controller already exists by trying to remove it from the map which was filled up with controllers we were tracking
if (LocalViewerMap.Remove(GetTypeHash(PlayerController->GetFName())) == 0)
{
// If not add it to the list
AddPlayerViewer(*PlayerController);
}
// If ServerStreaming is enabled, every PlayerController is also a StreamingSource. If the very first
// AddPlayerViewer() no-ops due to the early out that “ignores players that don’t have a pawn nor a camera”,
// then we still add that PlayerController as a StreamingSource viewer below.
// Subsequent calls to UMassLODSubsystem::SynchronizeViewers() will add an entry to LocalViewerMap for that
// StreamingSource viewer, preventing the call to AddPlayerViewer, which would “upgrade” the viewer to a
// PlayerController viewer by setting ViewerInfo.ActorViewer.
// Without that upgrade we won’t replicate the MassBubble to the client, and Mass LOD will not work: Agents
// don’t show on the client as low LOD until they are rehydrated as High-LOD and pop in as replicated actors.
// This change attempts to upgrade every PlayerController every time, which isn’t great but avoids the issue.
else if (WorldPartition->IsServerStreamingEnabled())
{
const int32 HashValue = GetTypeHash(PlayerController->GetFName());
FMassViewerHandle& ViewerHandle = ViewerMap.FindOrAdd(HashValue, FMassViewerHandle());
if (ViewerHandle.IsValid())
{
// We are only interested to set the player controller if it was not already set.
const int32 ViewerHandleIdx = GetValidViewerIdx(ViewerHandle);
check(ViewerHandleIdx != INDEX_NONE);
FViewerInfo& ViewerInfo = Viewers[ViewerHandleIdx];
if(ViewerInfo.ActorViewer == nullptr)
{
AddPlayerViewer(*PlayerController); // We call AddPlayerViewer() to handle all the early out checks.
}
}
}
//
}
}`