External data layers are not activated and visible in replays made in NetMode Standalone.

Hello everyone,

in our game we use the Game Features plugin. Among other things, we use game features to add World Partition content into our maps via external data layers (EDLs). These are runtime data layers which have the load filter set to None, so they should be considered both by the client and the server. One thing we noticed is that if we make a replay using the Replay System in the singleplayer (standalone) mode, the EDLs don’t load in the replay. After investigation, we concluded that the issue is caused by the array of active data layers being empty in replays.

The effective runtime state of a data layer is resolved in FWorldDataLayersEffectiveStates::GetDataLayerEffectiveRuntimeStateByName(). The logic is simple - if a data layer is contained by one of the replicated arrays, the runtime state is set to either Loaded or Activated. Updating of these arrays (RepEffectiveActiveDataLayerNames, RepEffectiveLoadedDataLayerNames, RepActiveDataLayerNames and RepLoadedDataLayerNames) is done in AWorldDataLayers::ResolveEffectiveRuntimeState() and AWorldDataLayers::SetDataLayerRuntimeState(). Upon closer inspection, we can see that an update is done only if the data layer is not local and the net mode is set to DedicatedServer or ListenServer:

    // Update Replicated Properties
    if (!bIsLocalDataLayer && (NetMode == NM_DedicatedServer || NetMode == NM_ListenServer))
    {
      FlushNetDormancy();
      AWORLDDATALAYERS_UPDATE_REPLICATED_DATALAYERS(RepEffectiveActiveDataLayerNames, EffectiveStates.GetReplicatedEffectiveActiveDataLayerNames().Array());
      AWORLDDATALAYERS_UPDATE_REPLICATED_DATALAYERS(RepEffectiveLoadedDataLayerNames, EffectiveStates.GetReplicatedEffectiveLoadedDataLayerNames().Array());
    }

This means that in the Standalone net mode, these arrays are never updated. If we add NM_Standalone in the condition, the issue goes away.

    // Update Replicated Properties
    if (!bIsLocalDataLayer && (NetMode == NM_DedicatedServer || NetMode == NM_ListenServer || NetMode == NM_Standalone))
    {
      FlushNetDormancy();
      AWORLDDATALAYERS_UPDATE_REPLICATED_DATALAYERS(RepEffectiveActiveDataLayerNames, EffectiveStates.GetReplicatedEffectiveActiveDataLayerNames().Array());
      AWORLDDATALAYERS_UPDATE_REPLICATED_DATALAYERS(RepEffectiveLoadedDataLayerNames, EffectiveStates.GetReplicatedEffectiveLoadedDataLayerNames().Array());
    }

Is this a passable fix? Is there a better way to fix our issue? Are there any side effects that we’re not aware of?

Thanks in advance for your insight!

Hi Dominik,

I did a test in standalone with regular Runtime Data Layer and I was able to repro.

I fixed the issue by changing the condition to :

// Update Replicated Properties
const UWorld* ActorWorld = GetWorld();
if (!bIsLocalDataLayer && (NetMode == NM_DedicatedServer || NetMode == NM_ListenServer || (NetMode == NM_Standalone && ActorWorld && ActorWorld->IsRecordingReplay())))

Both in :

  • AWorldDataLayers::SetDataLayerRuntimeState
  • AWorldDataLayers::ResolveEffectiveRuntimeState

Let me know if this fixes your problem and I’ll submit the fix.

Richard

Hey Richard,

thanks for your help. Unfortunately, I believe that this is not going to work for us (I’ve tried). The reason being that in our case the runtime state of data layers is only set and resolved once during map load. This condition would only be fulfilled if recording was active during that. But that’s usually not our use case. We typically start recording after the world is loaded. Therefore, the arrays don’t get updated in our case.

Kind regards,

Dominik

Hi Dominik,

Can you also try these changes ? (look for >>> in the code)

static bool GAllowChangingDataLayerRuntimeStateOnClient = false;
 
void AWorldDataLayers::RewindForReplay()
{
	Super::RewindForReplay();
 
	// Same as PostRegisterAllComponents when rewinding we want to reset our state to the initial state and rely on the Replay/Replication.
>>>	check(!GAllowChangingDataLayerRuntimeStateOnClient);
>>>	TGuardValue<bool> Scope(GAllowChangingDataLayerRuntimeStateOnClient, true);
	ResetDataLayerRuntimeStates();
	InitializeDataLayerRuntimeStates();
}
 
void AWorldDataLayers::PostRegisterAllComponents()
{
	Super::PostRegisterAllComponents();
 
	// When running a Replay we want to reset our state to the initial state and rely on the Replay/Replication.
	// Unfortunately this can't be tested in the PostLoad as the World doesn't have a demo driver yet.
	if (GetWorld()->IsPlayingReplay())
	{
>>>		check(!GAllowChangingDataLayerRuntimeStateOnClient);
>>>		TGuardValue<bool> Scope(GAllowChangingDataLayerRuntimeStateOnClient, true);
		ResetDataLayerRuntimeStates();
		InitializeDataLayerRuntimeStates();
	}
}
 
bool AWorldDataLayers::CanChangeDataLayerRuntimeState(const UDataLayerInstance* InDataLayerInstance, AWorldDataLayers::ESetDataLayerRuntimeStateError* OutReason) const
{
	if (!InDataLayerInstance->IsRuntime())
	{
		if (OutReason)
		{
			*OutReason = ESetDataLayerRuntimeStateError::NotRuntime;
		}
		return false;
	}
 
	const ENetMode NetMode = GetNetMode();
 
	if (InDataLayerInstance->IsClientOnly())
	{
		if (NetMode != NM_Standalone && NetMode != NM_Client)
		{
			if (OutReason)
			{
				*OutReason = ESetDataLayerRuntimeStateError::ClientOnlyFromServer;
			}
			return false;
		}
	}
	else if (InDataLayerInstance->IsServerOnly())
	{
		if (NetMode == NM_Client)
		{
			if (OutReason)
			{
				*OutReason = ESetDataLayerRuntimeStateError::ServerOnlyFromClient;
			}
			return false;
		}
	}
>>>	else if (NetMode == NM_Client && !GAllowChangingDataLayerRuntimeStateOnClient)
	{
		if (OutReason)
		{
			*OutReason = ESetDataLayerRuntimeStateError::AuthoritativeFromClient;
		}
		return false;
	}
 
	return true;
}

Richard

Hey Richard,

sadly these changes don’t fix our issue either. I’ve tried both in combination with the previous changes and separately.

Dominik

Dominik, do you think you could provide a small sample that reproduces the issue?

I suspect the root cause is that AWorldDataLayers::PostRegisterAllComponents() and/or AWorldDataLayers::RewindForReplay() is causing you to loose the correct state of your EDL.

Richard

Hey Richard,

I haven’t tried reproducing this outside of our project yet. I’ll give it a try as soon as I can. Unfortunately, I’m a little preoccupied at the moment, so it might take a little bit. Thanks for everything so far.

Dominik

Hi Dominik,

I will submit a fix in our main branch.

The fixes are :

  • add the condition “|| NetMode == NM_Standalone” in AWorldDataLayers::SetDataLayerRuntimeState and AWorldDataLayers::ResolveEffectiveRuntimeState.
  • remove AWorldDataLayers::RewindForReplay() and AWorldDataLayers::PostRegisterAllComponents()

Let me know if this works for you.

Richard

Hey Richard,

yes, this fix works for us, thanks a lot for the help!

Dominik

Thanks Dominik for the feedback.

The fix was submitted (CL 45186049 in our main branch).

Richard