클라이언트에 보내는 대역폭(NetBandwidth)은 한정되어 있음
클라이언트에 보낼 액터 중, 우선권이 높은 액터의 데이터를 우선 전달하도록 설계되어 있음
액터에 설정된 NetPriority 우선권 값을 활용해 전송 순서를 결정함
제한된 대역폭을 가진 네트워크 상태에서 Fountain에서 전송할 데이터가 커서 대역폭에 담기지 못하는 상황

Fountain의 우선권이 가장 낮으면 현재 틱에선 다른 액터들이 먼저 전송됨
다음 틱에 Fountain을 전송하기 위해 우선권을 높여줄 필요가 있음

마지막으로 패킷을 보낸 후의 경과 시간과 최초 우선권 값을 곱해 최종 우선권 값을 생성
최종 우선권 값을 사용해 클라이언트에 보낼 액터 목록을 정렬
네트워크가 포화(Saturation)될 때까지 정렬된 순서대로 리플리케이션을 수행
네트워크가 포화되면 해당 액터는 다음 서버 틱으로 넘김

- BaseGame.ini
TotalNetBandwidth의 디폴트 값은 32000
- DefaultEngine.ini
LogNetTraffic 로그를 활성화 한 후 TotalNetBandwidth를 낮춤
틱에서 보내줄 트래픽이 TotalNetBandwidth에 설정한 바이트를 넘기면 LogNetTraffic Saturated 로그 발생
최종 우선권을 계산하여 반환
Time은 마지막 전송 이후로 지난 시간
// ActorReplication.cpp
float AActor::GetNetPriority(const FVector& ViewPos, const FVector& ViewDir, AActor* Viewer, AActor* ViewTarget, UActorChannel* InChannel, float Time, bool bLowBandwidth)
{
if (bNetUseOwnerRelevancy && Owner)
{
// If we should use our owner's priority, pass it through
return Owner->GetNetPriority(ViewPos, ViewDir, Viewer, ViewTarget, InChannel, Time, bLowBandwidth);
}
if (ViewTarget && (this == ViewTarget || GetInstigator() == ViewTarget))
{
// If we're the view target or owned by the view target, use a high priority
Time *= 4.f;
}
else if (!IsHidden() && GetRootComponent() != NULL)
{
// If this actor has a location, adjust priority based on location
FVector Dir = GetActorLocation() - ViewPos;
float DistSq = Dir.SizeSquared();
// Adjust priority based on distance and whether actor is in front of viewer
if ((ViewDir | Dir) < 0.f)
{
if (DistSq > NEARSIGHTTHRESHOLDSQUARED)
{
Time *= 0.2f;
}
else if (DistSq > CLOSEPROXIMITYSQUARED)
{
Time *= 0.4f;
}
}
else if ((DistSq < FARSIGHTTHRESHOLDSQUARED) && (FMath::Square(ViewDir | Dir) > 0.5f * DistSq))
{
// Compute the amount of distance along the ViewDir vector. Dir is not normalized
// Increase priority if we're being looked directly at
Time *= 2.f;
}
else if (DistSq > MEDSIGHTTHRESHOLDSQUARED)
{
Time *= 0.4f;
}
}
return NetPriority * Time;
}
액터의 전송을 최소화하기 위해 연관성과 더불어 제공하는 속성
액터가 휴면 상태라면 연관성이 있더라도 액터 리플리케이션(RPC)을 수행하지 않음
속성 리플리케이션에 사용시에는 DORM_Initial만 고려하는 것이 좋음
- 조건식 프로퍼티 리플리케이션
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/conditional-property-replication-in-unreal-engine?application_version=5.1
프로퍼티 리플리케이션을 등록하고 해제하는 데에는 많은 비용이 들어 최적화에 좋지 않음
기본적으로 값이 변경되지 않으면 리플리케이트하지 않음
DOREPLIFETIME_CONDITION 매크로 사용/** Secondary condition to check before considering the replication of a lifetime property. */ UENUM(BlueprintType) enum ELifetimeCondition : int { COND_None = 0 UMETA(DisplayName = "None"), // This property has no condition, and will send anytime it changes COND_InitialOnly = 1 UMETA(DisplayName = "Initial Only"), // This property will only attempt to send on the initial bunch COND_OwnerOnly = 2 UMETA(DisplayName = "Owner Only"), // This property will only send to the actor's owner COND_SkipOwner = 3 UMETA(DisplayName = "Skip Owner"), // This property send to every connection EXCEPT the owner COND_SimulatedOnly = 4 UMETA(DisplayName = "Simulated Only"), // This property will only send to simulated actors COND_AutonomousOnly = 5 UMETA(DisplayName = "Autonomous Only"), // This property will only send to autonomous actors COND_SimulatedOrPhysics = 6 UMETA(DisplayName = "Simulated Or Physics"), // This property will send to simulated OR bRepPhysics actors COND_InitialOrOwner = 7 UMETA(DisplayName = "Initial Or Owner"), // This property will send on the initial packet, or to the actors owner COND_Custom = 8 UMETA(DisplayName = "Custom"), // This property has no particular condition, but wants the ability to toggle on/off via SetCustomIsActiveOverride COND_ReplayOrOwner = 9 UMETA(DisplayName = "Replay Or Owner"), // This property will only send to the replay connection, or to the actors owner COND_ReplayOnly = 10 UMETA(DisplayName = "Replay Only"), // This property will only send to the replay connection COND_SimulatedOnlyNoReplay = 11 UMETA(DisplayName = "Simulated Only No Replay"), // This property will send to actors only, but not to replay connections COND_SimulatedOrPhysicsNoReplay = 12 UMETA(DisplayName = "Simulated Or Physics No Replay"), // This property will send to simulated Or bRepPhysics actors, but not to replay connections COND_SkipReplay = 13 UMETA(DisplayName = "Skip Replay"), // This property will not send to the replay connection COND_Dynamic = 14 UMETA(Hidden), // This property wants to override the condition at runtime. Defaults to always replicate until you override it to a new condition. COND_Never = 15 UMETA(Hidden), // This property will never be replicated COND_NetGroup = 16 UMETA(Hidden), // This subobject will replicate to connections that are part of the same group the subobject is registered to. Not usable on properties. COND_Max = 17 UMETA(Hidden) };
NetworkObjects: 네트워크로 업데이트할 액터의 목록
서버는 매 틱마다 클라이언트로 전달
각 액터의 정보는 FNetworkObjectInfo 구조체에 정리
클라이언트에 보낼 수 있는 액터만 모아 ConsiderList에 수집
PreReplication() 호출하여 전송할 준비가 되었다고 알림
서버에 접속한 클라이언트마다 별도의 목록을 만들어줌
클라이언트의 Viewer 정보를 참고하여 ConsiderList에서 별도의 액터 묶음을 만듦
FActorPriority 구조체 사용하여 우선권에 따라 액터를 정렬하여 PriorityList에 대기열이 만들어짐
정렬된 PriorityList에 따라 액터의 정보를 하나씩 클라이언트에 보내다가, 네트워크가 포화되면 FNetworkObjectInfo의 bPendingNetUpdate 플래그를 활성화하여 다음 서버틱에서 이를 참조
다른 클라이언트에 대해서도 PriorityList를 마찬가지로 처리하고, 서버에선 이를 매틱마다 진행

// -------------------------------------------------------------------------------------------------------------------------
// ServerReplicateActors: this is main function to replicate actors to client connections. It can be "outsourced" to a Replication Driver.
// -------------------------------------------------------------------------------------------------------------------------
int32 UNetDriver::ServerReplicateActors(float DeltaSeconds)
{
SCOPE_CYCLE_COUNTER(STAT_NetServerRepActorsTime);
#if WITH_SERVER_CODE
if ( ClientConnections.Num() == 0 )
{
return 0;
}
GetMetrics()->SetInt(UE::Net::Metric::NumReplicatedActors,0 );
GetMetrics()->SetInt(UE::Net::Metric::NumReplicatedActorBytes, 0);
#if CSV_PROFILER_STATS
FScopedNetDriverStats NetDriverStats(this);
GNumClientConnections = ClientConnections.Num(); // 커넥션 파악
#endif
...
// ConsiderList 생성
TArray<FNetworkObjectInfo*> ConsiderList;
ConsiderList.Reserve( GetNetworkObjectList().GetActiveObjects().Num() );
// Build the consider list (actors that are ready to replicate)
ServerReplicateActors_BuildConsiderList( ConsiderList, ServerTickTime ); // CallPreReplication() 호출
...
// 각 커넥션에 대해서 순회
for ( int32 i=0; i < ClientConnections.Num(); i++ )
{
...
FActorPriority* PriorityList = NULL;
FActorPriority** PriorityActors = NULL;
// Get a sorted list of actors for this connection
const int32 FinalSortedCount = ServerReplicateActors_PrioritizeActors(Connection, ConnectionViewers, ConsiderList, bCPUSaturated, PriorityList, PriorityActors);
// 네트워크의 한계에 다다를 때까지 처리하고 Saturated 되면 처리한 액터의 개수를 반환
// Process the sorted list of actors for this connection
TInterval<int32> ActorsIndexRange(0, FinalSortedCount);
const int32 LastProcessedActor = ServerReplicateActors_ProcessPrioritizedActorsRange(Connection, ConnectionViewers, PriorityActors, ActorsIndexRange, Updated);
// LastProcessedActor 이후로 bPendingNetUpdate 플래그 세팅하여 다음 틱에 처리
ServerReplicateActors_MarkRelevantActors(Connection, ConnectionViewers, LastProcessedActor, FinalSortedCount, PriorityActors);
}
...
}
}
// ConsiderList 구성
void UNetDriver::ServerReplicateActors_BuildConsiderList( TArray<FNetworkObjectInfo*>& OutConsiderList, const float ServerTickTime )
{
...
for ( const TSharedPtr<FNetworkObjectInfo>& ObjectInfo : GetNetworkObjectList().GetActiveObjects() )
{
FNetworkObjectInfo* ActorInfo = ObjectInfo.Get();
...
// add it to the list to consider below
// For performance reasons, make sure we don't resize the array. It should already be appropriately sized above!
ensure( OutConsiderList.Num() < OutConsiderList.Max() );
OutConsiderList.Add( ActorInfo );
// Call PreReplication on all actors that will be considered
Actor->CallPreReplication( this );
}
...
}
// PriorityList 구성
int32 UNetDriver::ServerReplicateActors_PrioritizeActors( UNetConnection* Connection, const TArray<FNetViewer>& ConnectionViewers, const TArray<FNetworkObjectInfo*>& ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors )
{
...
const int32 MaxSortedActors = ConsiderList.Num() + DestroyedStartupOrDormantActors.Num();
if ( MaxSortedActors > 0 )
{
OutPriorityList = new ( FMemStack::Get(), MaxSortedActors ) FActorPriority;
OutPriorityActors = new ( FMemStack::Get(), MaxSortedActors ) FActorPriority*;
...
// Sort by priority
Algo::SortBy(MakeArrayView(OutPriorityActors, FinalSortedCount), &FActorPriority::Priority, TGreater<>());
}
...
}
https://github.com/dnjfs/ArenaBattle_Network/commit/eeea1a21f00adfee1ad348c9cd6eda14fbe9d55a
네트워크 대역폭
TotalNetBandwidth로 세팅
Dormancy
DORM_Initial로 액터를 휴면 상태로 시작하게 처리
FlushNetDormancy()로 깨움
PreReplication()
리플리케이션이 처리되기 전에 서버에서 호출
이후 클라이언트에 전송된 것을 로그로 확인