Keywords: UE4, Networking

Replication Cases

Disconnect Actor’s replication at Run-time

Case:
A Character is replicated at first, then we want to disable replication when it dies.
E.g. Ragdoll, if we continue to keep replicating when character dies, performance overhead can be costly on server and server not care the result of Ragdoll, Ragdoll is just visual effect on client, so we need disconnect replication before performing Ragdoll computing.

Solution:
Execute AActor::ForceNetUpdate() to replicate current status to client before Ragdoll computing fired on server, then execute AActor::TearOff on server, from now on, Actor will not be replicaed forever.
AActor::TornOff() can be fired on client after executing AActor::TearOff on server, it means that Actor can be modified arbitrarily and it will not be corrected by server.

Replication of bullet path in shooting game

Suggestion:

  1. bReplicateMovement = true; in constructor of BulletActor;
  2. ProjectileMovement->InitialSpeed = 0.f; and ProjectileMovement->MaxSpeed = 0.f; in constructor of BulletActor;
  3. Set real time speed of BulletActor in Server.

If you set a large value for ProjectileMovement->InitialSpeed in bullet constructor, and then set Velocity in server, bullet path would jitter at the beginning.

LogNet: UChannel::ReceivedSequencedBunch: Bunch.bClose

Error of dedicated server:

LogNet: UChannel::ReceivedSequencedBunch: Bunch.bClose == true. ChIndex == 0. Calling ConditionalCleanUp.
LogNet: UChannel::CleanUp: ChIndex == 0. Closing connection. [UChannel] ChIndex: 0, Closing: 0 [UNetConnection] RemoteAddr: 10.0.51.19:58395, Name: IpConnection_2147456657, Driver: GameNetDriver IpNetDriver_2147482490, IsServer: YES, PC: BP_PlayerController_C_2147456649, Owner: BP_PlayerController_C_2147456649, UniqueId: NULL:63YQHN7321-E05ECCF9407D3A5479CE6C8A16AA9F77
LogNet: UNetConnection::Close: [UNetConnection] RemoteAddr: 10.0.51.19:58395, Name: IpConnection_2147456657, Driver: GameNetDriver IpNetDriver_2147482490, IsServer: YES, PC: BP_PlayerController_C_2147456649, Owner: BP_PlayerController_C_2147456649, UniqueId: NULL:63YQHN7321-E05ECCF9407D3A5479CE6C8A16AA9F77, Channels: 12, Time: 2021.11.10-09.03.02
LogNet: UChannel::Close: Sending CloseBunch. ChIndex == 0. Name: [UChannel] ChIndex: 0, Closing: 0 [UNetConnection] RemoteAddr: 10.0.51.19:58395, Name: IpConnection_2147456657, Driver: GameNetDriver IpNetDriver_2147482490, IsServer: YES, PC: BP_PlayerController_C_2147456649, Owner: BP_PlayerController_C_2147456649, UniqueId: NULL:63YQHN7321-E05ECCF9407D3A5479CE6C8A16AA9F77

Caused by:
Client initiate to disconnect from server.

UE4 Networking Documents

Online Beacons Docs

Online Beacons are a special type of Actor that provide a lightweight way to contact a server and interact with it (via RPCs) without committing to a normal game connection. While the built-in classes can be used as they are in some cases, they are intended to be extended into custom classes that perform project-specific interactions, logic, and information requests.

Docs: Online Beacons
https://docs.unrealengine.com/en-US/Gameplay/Networking/OnlineBeacons/index.html

Online Beacons Usage:
e.g. If there’s a requirement that make a decision to tell Client which Server to login, Client need to know the current player count of peer Server, now you can use Online Beacon to connect to Server before Replication connection created.

What are “online beacons” and how do they work?

The idea behind beacons was to provide a lightweight way to contact a server before actually connecting “for real”.

Ideally you make a connection, send an RPC, get a response, and disconnect. You can ask things like “who is in the game” or “please make room for me, I’m about to join”. Traditionally, connecting to a server involved a full map load which can be frustrating to users if they do all that work only to be denied entry to an almost full game because someone got into the game first. Now you can make contact while still in the main menu, for example, and test multiple servers to pick the best one before joining. Or to inquire about information about a game in progress to “spectate” without being actually connected with an APlayerController and all that entails.

Gears 3 used beacons to reserve space in game. That way we could test many servers for space and travel to only one, knowing that you were definitely going to get into the game. It avoided contention when thousands of users all received the same 20-50 search results but only 10 could join per match. However, in UE3, this was TCP/IP and the protocol had to be implemented manually not using any reflection by UObject.

In UE4, we decided that our already existing network code was so robust and powerful, why reinvent the wheel? So I made the AOnlineBeacon class to leverage all the RPC and replication power of AActor.

Traditionally, actor replication and spawning has always been server initiated, meaning that you had to connect before you got a copy of an actor that the server also knew about. I wanted clients to be able to create an actor that would initiate contact with the server and fire a delegate when a connection has been made so that higher level code could make an RPC. Otherwise you’d need to make a connection with the server, wait for the server to send you an actor you could RPC with, then switch to that. Unwieldy in my opinion.

Origin:
https://answers.unrealengine.com/questions/467973/what-are-online-beacons-and-how-do-they-work.html

Unreal Engine 4 Network Compendium

Unreal Engine 4 Network Compendium Made by: Cedric ’eXi’ Neukirchen
http://cedric-neukirchen.net/Downloads/Compendium/UE4_Network_Compendium_by_Cedric_eXi_Neukirchen.pdf

Custom Serialization (Send streaming message / bunch replication)

Custom Struct Serialization for Networking in Unreal Engine
http://www.aclockworkberry.com/custom-struct-serialization-for-networking-in-unreal-engine/

Engine Source Analysis for Networking

Properties used for Replication in Common
/** Square of the max distance from the client's viewpoint that this actor is relevant and will be replicated. */
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category=Replication)
float NetCullDistanceSquared;   

/** Internal - used by UNetDriver */
UPROPERTY(Transient)
int32 NetTag;

/** How often (per second) this actor will be considered for replication, used to determine NetUpdateTime */
UPROPERTY(Category=Replication, EditDefaultsOnly, BlueprintReadWrite)
float NetUpdateFrequency;

/** Used to determine what rate to throttle down to when replicated properties are changing infrequently */
UPROPERTY(Category=Replication, EditDefaultsOnly, BlueprintReadWrite)
float MinNetUpdateFrequency;

/** Priority for this actor when checking for replication in a low bandwidth or saturated situation, higher priority means it is more likely to replicate */
UPROPERTY(Category=Replication, EditDefaultsOnly, BlueprintReadWrite)
float NetPriority;
How does engine check if Actor is relevant to connection

Critical Code:
Engine\Source\Runtime\Engine\Private\ActorReplication.cpp

bool AActor::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
    if (bAlwaysRelevant || IsOwnedBy(ViewTarget) || IsOwnedBy(RealViewer) || this == ViewTarget || ViewTarget == Instigator)
    {
        return true;
    }
    else if ( bNetUseOwnerRelevancy && Owner)
    {
        return Owner->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
    }
    else if ( bOnlyRelevantToOwner )
    {
        return false;
    }
    else if ( RootComponent && RootComponent->GetAttachParent() && RootComponent->GetAttachParent()->GetOwner() && (Cast<USkeletalMeshComponent>(RootComponent->GetAttachParent()) || (RootComponent->GetAttachParent()->GetOwner() == Owner)) )
    {
        return RootComponent->GetAttachParent()->GetOwner()->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
    }
    else if( bHidden && (!RootComponent || !RootComponent->IsCollisionEnabled()) )
    {
        return false;
    }

    if (!RootComponent)
    {
        UE_LOG(LogNet, Warning, TEXT("Actor %s / %s has no root component in AActor::IsNetRelevantFor. (Make bAlwaysRelevant=true?)"), *GetClass()->GetName(), *GetName() );
        return false;
    }

    return !GetDefault<AGameNetworkManager>()->bUseDistanceBasedRelevancy ||
            IsWithinNetRelevancyDistance(SrcLocation);
}

Call Stack:

UE4Editor-Engine.dll!AActor::IsNetRelevantFor(const AActor * RealViewer, const AActor * ViewTarget, const FVector & SrcLocation) Line 320    C++
[Inline Frame] UE4Editor-Engine.dll!IsActorRelevantToConnection(const AActor *) Line 3661    C++
UE4Editor-Engine.dll!UNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection * Connection, const TArray<FNetViewer,FDefaultAllocator> & ConnectionViewers, const TArray<FNetworkObjectInfo *,FDefaultAllocator> ConsiderList, const bool bCPUSaturated, FActorPriority * & OutPriorityList, FActorPriority * * & OutPriorityActors) Line 3780    C++
UE4Editor-Engine.dll!UNetDriver::ServerReplicateActors(float DeltaSeconds) Line 4359    C++
UE4Editor-Engine.dll!UNetDriver::TickFlush(float DeltaSeconds) Line 506    C++
[Inline Frame] UE4Editor-Engine.dll!TMemberFunctionCaller<UNetDriver,void (__cdecl UNetDriver::*)(float)>::operator()(float &) Line 156    C++
[Inline Frame] UE4Editor-Engine.dll!UE4Tuple_Private::TTupleImpl<TIntegerSequence<unsigned int> >::ApplyAfter(TMemberFunctionCaller<UNetDriver,void (__cdecl UNetDriver::*)(float)> &&) Line 498    C++
[Inline Frame] UE4Editor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,TTypeWrapper<void> __cdecl(float)>::Execute(float) Line 617    C++
UE4Editor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float)>::ExecuteIfSafe(float <Params_0>) Line 679    C++
UE4Editor-Engine.dll!TBaseMulticastDelegate<void,float>::Broadcast(float <Params_0>) Line 977    C++
UE4Editor-Engine.dll!UWorld::Tick(ELevelTick TickType, float DeltaSeconds) Line 1720    C++
UE4Editor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 1618    C++
UE4Editor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 403    C++
UE4Editor-Win64-DebugGame.exe!FEngineLoop::Tick() Line 3967    C++
[Inline Frame] UE4Editor-Win64-DebugGame.exe!EngineTick() Line 62    C++
UE4Editor-Win64-DebugGame.exe!GuardedMain(const wchar_t * CmdLine, HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, int nCmdShow) Line 168    C++
UE4Editor-Win64-DebugGame.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow) Line 261    C++

Related Properties:

/** Always relevant for network (overrides bOnlyRelevantToOwner). */
UPROPERTY(Category=Replication, EditDefaultsOnly, BlueprintReadWrite)
uint8 bAlwaysRelevant:1;
How does engine check if strip animation data

Strip Animation Data On Dedicated Server was used in UAnimSequence::Serialize():

const bool bCookingTargetNeedsCompressedData = bIsCooking 
    && (!UAnimationSettings::Get()->bStripAnimationDataOnDedicatedServer 
        || !bIsCookingForDedicatedServer 
        || bEnableRootMotion);

AnimSequence with RootMotion enabled will not be stripped when cooking for server.

Functions:

/** Called on client when updated AttachmentReplication value is received for this actor. */
UFUNCTION()
virtual void OnRep_AttachmentReplication();

Properties:

//Priority for this actor when checking for replication in a low bandwidth or saturated situation, higher priority means it is more likely to replicate
UPROPERTY(Category=Replication, EditDefaultsOnly, BlueprintReadWrite)
float NetPriority
How to change RemoteRole when spawning Actor
/** This function should only be used in the constructor of classes that need to set the RemoteRole for backwards compatibility purposes */
void SetRemoteRoleForBackwardsCompat(const ENetRole InRemoteRole) { RemoteRole = InRemoteRole; }
Callstack on client from UNetDriver to constructor of Actor
UE4Editor-MyProj-Win64-DebugGame.dll!MyCharacter::MyCharacter() Line 7
    at E:\MyProj\Source\MyProj\Character\MyCharacter.cpp(7)
UE4Editor-MyProj-Win64-DebugGame.dll!InternalConstructor<AFoliageActor>(const FObjectInitializer & X={...}) Line 3307
    at E:\unrealengine-4.26\Engine\Source\Runtime\CoreUObject\Public\UObject\Class.h(3307)
UE4Editor-CoreUObject.dll!StaticConstructObject_Internal(const FStaticConstructObjectParameters & Params={...}) Line 3195
    at E:\unrealengine-4.26\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectGlobals.cpp(3195)
[Inline Frame] UE4Editor-Engine.dll!NewObject(UObject *) Line 1160
    at E:\unrealengine-4.26\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectGlobals.h(1160)
UE4Editor-Engine.dll!UWorld::SpawnActor(UClass * Class=0x000001b8ab457200, const FTransform * UserTransformPtr, const FActorSpawnParameters & SpawnParameters={...}) Line 508
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\LevelActor.cpp(508)
UE4Editor-Engine.dll!UWorld::SpawnActorAbsolute(UClass * Class=0x000001b8ab457200, const FTransform & AbsoluteTransform, const FActorSpawnParameters & SpawnParameters={...}) Line 289
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\LevelActor.cpp(289)
UE4Editor-Engine.dll!UPackageMapClient::SerializeNewActor(FArchive & Ar={...}, UActorChannel * Channel=0x000001b8a9235f00, AActor * & Actor=0x0000000000000000) Line 609
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\PackageMapClient.cpp(609)
UE4Editor-Engine.dll!UActorChannel::ProcessBunch(FInBunch & Bunch={...}) Line 2764
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\DataChannel.cpp(2764)
UE4Editor-Engine.dll!UActorChannel::ReceivedBunch(FInBunch & Bunch={...}) Line 2734
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\DataChannel.cpp(2734)
UE4Editor-Engine.dll!UChannel::ReceivedSequencedBunch(FInBunch & Bunch={...}) Line 412
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\DataChannel.cpp(412)
UE4Editor-Engine.dll!UChannel::ReceivedNextBunch(FInBunch & Bunch={...}, bool & bOutSkipAck=false) Line 833
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\DataChannel.cpp(833)
UE4Editor-Engine.dll!UChannel::ReceivedRawBunch(FInBunch & Bunch={...}, bool & bOutSkipAck=false) Line 517
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\DataChannel.cpp(517)
UE4Editor-Engine.dll!UNetConnection::ReceivedPacket(FBitReader & Reader, bool bIsReinjectedPacket=64) Line 2783
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\NetConnection.cpp(2783)
UE4Editor-Engine.dll!UNetConnection::ReceivedRawPacket(void * InData, int Count=907) Line 1316
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\NetConnection.cpp(1316)
UE4Editor-OnlineSubsystemUtils.dll!UIpNetDriver::TickDispatch(float DeltaTime) Line 1370
    at E:\unrealengine-4.26\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\IpNetDriver.cpp(1370)
[Inline Frame] UE4Editor-Engine.dll!Invoke(void(UNetDriver::*)(float)) Line 65
    at E:\unrealengine-4.26\Engine\Source\Runtime\Core\Public\Templates\Invoke.h(65)
[Inline Frame] UE4Editor-Engine.dll!UE4Tuple_Private::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(UNetDriver::*)(float) &) Line 299
    at E:\unrealengine-4.26\Engine\Source\Runtime\Core\Public\Templates\Tuple.h(299)
UE4Editor-Engine.dll!TBaseUObjectMethodDelegateInstance<0,UNetDriver,void __cdecl(float),FDefaultDelegateUserPolicy>::ExecuteIfSafe(float <Params_0>) Line 611
    at E:\unrealengine-4.26\Engine\Source\Runtime\Core\Public\Delegates\DelegateInstancesImpl.h(611)
UE4Editor-Engine.dll!TMulticastDelegate<void __cdecl(float),FDefaultDelegateUserPolicy>::Broadcast(float <Params_0>) Line 955
    at E:\unrealengine-4.26\Engine\Source\Runtime\Core\Public\Delegates\DelegateSignatureImpl.inl(955)
UE4Editor-Engine.dll!UWorld::Tick(ELevelTick TickType=-562797776, float DeltaSeconds=0.00000000) Line 1374
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\LevelTick.cpp(1374)
UE4Editor-Engine.dll!UGameEngine::Tick(float DeltaSeconds, bool bIdleMode=false) Line 1794
    at E:\unrealengine-4.26\Engine\Source\Runtime\Engine\Private\GameEngine.cpp(1794)
UE4Editor-Win64-DebugGame.exe!FEngineLoop::Tick() Line 4836
    at E:\unrealengine-4.26\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp(4836)
[Inline Frame] UE4Editor-Win64-DebugGame.exe!EngineTick() Line 62
    at E:\unrealengine-4.26\Engine\Source\Runtime\Launch\Private\Launch.cpp(62)
UE4Editor-Win64-DebugGame.exe!GuardedMain(const wchar_t * CmdLine) Line 169
    at E:\unrealengine-4.26\Engine\Source\Runtime\Launch\Private\Launch.cpp(169)
UE4Editor-Win64-DebugGame.exe!WinMain(HINSTANCE__ * hInInstance=0x000000000000000a, HINSTANCE__ * hPrevInstance=0x0000000000000000, char * __formal=0x0000000000000000, int nCmdShow=0) Line 257
    at E:\unrealengine-4.26\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp(257)
[External Code]

Command & Configuration

net.AllowAsyncLoading & net.DelayUnmappedRPCs
static TAutoConsoleVariable<int32> CVarAllowAsyncLoading(
    TEXT("net.AllowAsyncLoading"),
    0,
    TEXT("Allow async loading of unloaded assets referenced in packets."
        " If false the client will hitch and immediately load the asset,"
        " if true the packet will be delayed while the asset is async loaded."
        " net.DelayUnmappedRPCs can be enabled to delay RPCs relying on async loading assets.")
);

static TAutoConsoleVariable<int32> CVarDelayUnmappedRPCs(
    TEXT("net.DelayUnmappedRPCs"),
    0,
    TEXT("If true delay received RPCs with unmapped object references until they are received or loaded, ")
    TEXT("if false RPCs will execute immediately with null parameters. ")
    TEXT("This can be used with net.AllowAsyncLoading to avoid null asset parameters during async loads."),
    ECVF_Default);

Reference

Documents

Function call replication - Sending messages between server and client

《Exploring in UE4》网络同步原理深入(上)[原理分析]
https://zhuanlan.zhihu.com/p/34723199

《Exploring in UE4》网络同步原理深入(下)[原理分析]
https://zhuanlan.zhihu.com/p/55596030

Networking Insights (Experimental) (included in 4.25).
Unreal Insights now includes Networking Insights to visualize network gameplay data and identify performance bottlenecks or faulty code.

Push Model Networking (4.25)
https://forums.unrealengine.com/t/push-model-networking/510684/13


我是个过分认真的人,总想给生命一个交代。这种愚蠢的努力简直成了我的噩梦,当然也是最终的救赎。——《像我这样笨拙地生活》