Description

A high UWorld::Tick DeltaSeconds (i.e. GameThread hitch) results in a high UNetDriver::TickFlush DeltaSeconds. This DeltaSeconds can high (many seconds) if the hitch is long. This value then affects NetDriver's scheduling of an actor's next update via 

ActorInfo->NextUpdateTime = World->TimeSeconds + UpdateDelayRandomStream.FRand() * ServerTickTime + NextUpdateDelta;  

where the value for ServerTickTime == DeltaSeconds.

This issue was reported by a user when showing a loading screen configured to let the engine tick, but did not reset the tick measurement. That is an issue originating in MoviePlayer and will be ticketed separately. However, since server hitches can occur for any other reason I believe this logic for calculating NextUpdateTime should be reconsidered not to use measured DeltaSeconds, but either use expected DeltaSeconds from the target frame rate or cap the value off at a maximum.

Steps to Reproduce
  • Create a IpNetDriver that overrides TickFlush(DeltaSeconds) and print out DeltaSeconds. Also have it print errors when an actor next net update is scheduled far into the future:

 

void UMyIpNetDriver::TickFlush(float DeltaSeconds)
{
    if (DeltaSeconds > 5.0f)
    {
        UE_LOG(LogTemp, Error, TEXT("AAA | Long NetDriver tick delta seconds: %.2f"), DeltaSeconds);
    }
    Super::TickFlush(DeltaSeconds);

    if (UWorld* MyWorld = GetWorld())
    {
        const float WorldTime = MyWorld->GetTimeSeconds();
        for (const TSharedPtr<FNetworkObjectInfo>& ObjectInfo : GetNetworkObjectList().GetActiveObjects())
        {
            FNetworkObjectInfo* ActorInfo = ObjectInfo.Get();
            if (ActorInfo->NextUpdateTime > WorldTime + 5.0f)
            {
                UE_LOG(LogTemp, Error, TEXT("AAA | Actor '%s' NextUpdateTime = %.2f seconds into the future!"), *ActorInfo->Actor->GetPathName(), ActorInfo->NextUpdateTime - WorldTime);
            }
        }
    }
}

 

  • Set it as NetDriver via DefaultEngine.ini:
[/Script/Engine.Engine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/NetDriverTick53R.MyIpNetDriver",DriverClassNameFallback="OnlineSubsystem.IpNetDriver") 
  • Create a BlueprintCallable PlayerController function to cause a hitch:
void AMyPlayerController::ManuallyTriggerHitch()
{
    UE_LOG(LogTemp, Display, TEXT("Triggering 10 second hitch!"));
    FPlatformProcess::Sleep(10.f);
} 
  • Start a listen server and call ManuallyTriggerHitch. It should print out the LogTemp errors from above.{}

Observe: After the hitch, NetDriver has scheduled actor's next update far into the future. This is because measured DeltaSeconds passed into TickFlush directly becomes the high 'ServerTickTIme' value in ServerReplicateActors_BuildConsiderList:

ActorInfo->NextUpdateTime = World->TimeSeconds + UpdateDelayRandomStream.FRand() * ServerTickTime + NextUpdateDelta; // ServerTickTime will be same as hitch time

Expected: A server GameThread hitch should not cause actor's next net update to be scheduled far into the future. It should be capped somehow to expected DeltaSeconds.

Callstack

  UnrealEditor-Engine.dll!UNetDriver::ServerReplicateActors_BuildConsiderList(TArray<FNetworkObjectInfo *,TSizedDefaultAllocator<32>> & OutConsiderList, const float ServerTickTime) Line 4340 C++
  UnrealEditor-Engine.dll!UNetDriver::ServerReplicateActors(float DeltaSeconds) Line 5278 C++
  UnrealEditor-Engine.dll!UNetDriver::TickFlush(float DeltaSeconds) Line 955 C++
  UnrealEditor-NetDriverTick53R.dll!UMyIpNetDriver::TickFlush(float DeltaSeconds) Line 9 C++
  UnrealEditor-Engine.dll!UNetDriver::InternalTickFlush(float DeltaSeconds) Line 1603 C++
  [Inline Frame] UnrealEditor-Engine.dll!Invoke(void(UNetDriver::*)(float)) Line 66 C++
  [Inline Frame] UnrealEditor-Engine.dll!UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(UNetDriver::*)(float) &) Line 311 C++
  UnrealEditor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float),FDefaultDelegateUserPolicy>::ExecuteIfSafe(float <Params_0>) Line 667 C++
  [Inline Frame] UnrealEditor-Engine.dll!TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast(float) Line 254 C++
  UnrealEditor-Engine.dll!TMulticastDelegate<void __cdecl(float),FDefaultDelegateUserPolicy>::Broadcast(float <Params_0>) Line 956 C++
> UnrealEditor-Engine.dll!UWorld::Tick(ELevelTick TickType, float DeltaSeconds) Line 1675 C++
  UnrealEditor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 1924 C++
  UnrealEditor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 531 C++
  UnrealEditor.exe!FEngineLoop::Tick() Line 5825 C++
  [Inline Frame] UnrealEditor.exe!EngineTick() Line 61 C++
  UnrealEditor.exe!GuardedMain(const wchar_t * CmdLine) Line 188 C++
  UnrealEditor.exe!LaunchWindowsStartup(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow, const wchar_t * CmdLine) Line 247 C++
  UnrealEditor.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * pCmdLine, int nCmdShow) Line 298 C++
 [External Code]
 

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-207161 in the post.

0
Login to Vote

Unresolved
ComponentUE - Networking
Affects Versions5.15.25.3
Target Fix5.6
CreatedFeb 16, 2024
UpdatedOct 25, 2024
View Jira Issue