[UE4]Networking in Advanced - Engine Source Analysis & High-Level Cases
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:
bReplicateMovement = true;
in constructor of BulletActor;ProjectileMovement->InitialSpeed = 0.f;
andProjectileMovement->MaxSpeed = 0.f;
in constructor of BulletActor;- 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
Main source files for replication
Runtime\Engine\Private\ActorReplication.cpp
Runtime\Engine\Private\Replication.cpp
Runtime\Engine\Public\Net\DataReplication.cpp
Engine\Plugins\Messaging\TcpMessaging\Source\TcpMessaging\Private\TcpMessagingModule.cpp
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.
RPC Related Engine API
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
我是个过分认真的人,总想给生命一个交代。这种愚蠢的努力简直成了我的噩梦,当然也是最终的救赎。——《像我这样笨拙地生活》