Assertion for !ClientVisibleLevelNames.Contains in UNetConnection::UpdateLevelVisibilityInternal

Hello!

We’ve been experiencing an issue during where an assertion is triggered in UNetConnection::UpdateLevelVisibilityInternal for the branch to try and make a streaming level visible. The repro rate is quite low.

Few bits of key information upfront:

  • We’re on UE5.7.4.
  • We’re running a vanilla version of UNetConnection.
  • The issue’s been observed both in LAN MP and also with Steam’s P2P Net Driver present.
  • We’ve got some CVars turned on for World Partition & Level Streaming.
  • We’ve got Seamless Travel turned on.

The CVar changes we’ve got enabled in our project’s DefaultEngine.ini:

[SystemSettings]
wp.runtime.UpdateStreaming.EnableAsyncUpdate=True
wp.runtime.EnableServerStreaming=1
wp.Runtime.EnableServerStreamingOut=True
 
LevelStreaming.DefaultAllowClientUseMakingVisibleTransactionRequests=True
LevelStreaming.DefaultAllowClientUseMakingInvisibleTransactionRequests=True
LevelStreaming.ShouldServerUseVisibleTransactionRequest=True
wp.Runtime.UseMakingVisibleTransactionRequests=True
wp.Runtime.UseMakingInvisibleTransactionRequests=True
 
LevelStreaming.AllowIncrementalPreRegisterComponents=True
LevelStreaming.AllowIncrementalPreUnregisterComponents=True
LevelStreaming.AsyncRegisterLevelContext.Enabled=True

The assertion is the check in this snippet:

else if (LevelVisibility.bTryMakeVisible)
{
	if (FLevelUtils::SupportsMakingVisibleTransactionRequests(GetWorld()))
	{
		const FNetLevelVisibilityTransactionId VisibilityRequestId = LevelVisibility.VisibilityRequestId;
		check(VisibilityRequestId.IsValid() && VisibilityRequestId.IsClientTransaction());
 
		// Only consider visible levels with their streaming level in the LoadedVisible state and returning ShouldBeVisible()
		ULevelStreaming* ServerVisibleStreamingLevel = FLevelUtils::GetServerVisibleStreamingLevel(GetWorld(), LevelVisibility.PackageName);
		if (ServerVisibleStreamingLevel && ServerVisibleStreamingLevel->ShouldBeVisible() && (ServerVisibleStreamingLevel->GetLevelStreamingState() == ELevelStreamingState::LoadedVisible))
		{
			ClientMakingVisibleLevelNames.Add(LevelVisibility.PackageName);
			check(!ClientVisibleLevelNames.Contains(LevelVisibility.PackageName));
		}
	}
}

Specifically, it’s this assertion:

ClientMakingVisibleLevelNames.Add(LevelVisibility.PackageName);
check(!ClientVisibleLevelNames.Contains(LevelVisibility.PackageName));

It’s worth noting that whilst looking through the source code in NetConnection.cpp, I stumbled upon a comment for when level visibility requests are scheduled to be deferred during Seamless Travel -- that they need to be deferred until after level loading has completed on the server. Looking at the code, there’s no skipping of the call to UpdateLevelVisibilityInternal when this happens.

void UNetConnection::UpdateLevelVisibility(const FUpdateLevelVisibilityLevelInfo& LevelVisibility)
{
	if (Driver && Driver->GetWorld())
	{
		// If we are doing seamless travel we need to defer visibility updates until after the server has completed loading the level
		// otherwise we might end up in a situation where visibility is not correctly updated
		if (Driver->GetWorld()->IsInSeamlessTravel())
		{
			PendingUpdateLevelVisibility.FindOrAdd(LevelVisibility.PackageName) = LevelVisibility;
		}
	}
	UpdateLevelVisibilityInternal(LevelVisibility);
 
	NotifyConnectionUpdated();
}

Later in UNetConnection::ResetGameWorldState, any pending requests are processed with the same internal function after clearing various containers.

Few questions:

  • Is it intended for the request to be processed immediately after it’s queued it to be pending during Seamless Travel or is this a mistake and the code should be exiting early instead?
  • Are any of the CVars we’re using outdated / do they happen to be used with succession on any of Epic Games’ active projects?
  • Have there been any reports on this assertion being triggered for others?

Any insight would be much appreciated, thank you!

[Attachment Removed]

Hi,

To answer your questions:

Is it intended for the request to be processed immediately after it’s queued it to be pending during Seamless Travel or is this a mistake and the code should be exiting early instead?

After speaking to a dev more familiar with the original change, it does seem that the immediate call to UpdateLevelVisibilityInternal is intentional, although the exact reason is unclear. However, this change was made several years ago, before visibility transactions were implemented. It’s possible that there is now a bug here with how the level visibility updates are handled during seamless travel with the transactions enabled.

Are any of the CVars we’re using outdated / do they happen to be used with succession on any of Epic Games’ active projects?

I don’t believe any of these CVars are outdated. However, some of them, such as the EnableAsyncUpdate and AllowIncrementalPreRegister/UnregisterComponents, are fairly new and may still be considered experimental. You can find the latest info on many of the engine’s world building features here: https://dev.epicgames.com/community/learning/knowledge\-base/r6wl/unreal\-engine\-world\-building\-guide

Have there been any reports on this assertion being triggered for others?

I don’t believe we’ve received any other reports of this specific assert. There was one internal report of this being hit, but it turned out to be caused by project code calling ULevelStreaming::CanMakeVisible, which was triggering visibility transactions to occur outside of the usual level streaming handling.

That being said, we do not use seamless travel much internally, so again, it’s possible that we just haven’t run into the issue. I haven’t been able to reproduce this in my own test project, so if you’re able to provide a basic test project reproducing the issue, that would be greatly appreciated. You can also try increasing the verbosity of the LogPlayerController and LogLevelStreaming categories to get more information in your logs.

As you continue looking into this, please note that Epic will be on summer break from 6/29-7/10, so support will be limited during that time.

Thanks,

Alex

[Attachment Removed]