Push Model이란 뭘까?
언리얼의 Property Replication System은 기본적으로 Polling System이다.
라이엇게임즈의 발로란트 최적화 과정에서 Replication의 Polling System에 관한 이야기가 나와있다.
Unfortunately, it’s also pretty slow. It requires scanning through every variable marked as replicated every frame, then comparing it to each of the 10 clients’ last known states, and then packaging any deltas to send to the client. This is effectively random access across memory and is really cache-intensive, slow work. Regardless of state changes, the variables are still checked. I consider “polling” systems like this a performance anti-pattern.
요컨데, Server 측에서 매 프레임마다 복제할 값이 변경되었는지 확인(dirty check) 후 복제시키는데, 이 과정은 random memory access이고 cache-intensive하다는 것이다.
cache-intensive 하다는 것은 결국 random memory access로 인해 cache miss가 많이 발생하므로 bottle neck이 발생할 수 있다는 것.
엔진내부 기능인 APlayerState에서 Push Model을 사용한 예제를 찾을 수 있다.
APlayerState가 Push Model을 사용한 이유를 추측하기 위해선 먼저 APlayerState에 대해 알아야한다.
APlayerState는 복제될 수 있으며, AGameStateBase 내부에 존재하는 PlayerArray를 통해 모든 Client들 간의 복제된 값을 얻을 수 있다.
예를 들어 광활한 필드에서 어떤 Client가 게임 상황을 파악하기 위해 탭을 눌러 현재 1위 Score를 본다고 가정하자.
Score는 멀리있다고 해서 볼 수 없어야 하는 것은 아니므로, Relevancy에 영향을 받지 말아야할 것이다. 반면에 100명중 어떤 Player의 Score가 변경된다면, 다른 99개의 Client들도 그 값을 복제받아야 하므로 Player가 증가함에 따라 잠재적인 복제 빈도와 복제 대상이 모두 증가하여 최종적인 복제 비용이 기하급수적으로 증가할 것이다.
그래서 PlayerState는 Always Relevant하며, Net Priority는 가장 낮은 1.0이고 기본 NetUpdateFrequency도 1.0 밖에 가지지 않는다.
[ Push Model Code Example ]
우선, 엔진내에서 Push Model이 사용될 수 있게 설정을 켜줘야 한다.
DefaultEngine.ini에서 net.IsPushModelEnabled=1 을 입력하자.
이걸 입력하지 않으면 MARK DIRTY를 하지 않아도 기본 복제가 이루어진다.
코드 상에선 Replication을 사용하기 위한 헤더파일과 더불어 Push Model을 사용하기 위한 헤더파일이 필요하다.
그리고 Push Model의 헤더 파일을 포함하기 위해서, Build.cs에서 "NetCore"를 추가해줘야한다.
엔진내 기본적으로 포함되어 있는 APlayerState의 Property Replication 코드를 보자.
FDoRepLifetimeParams를 통해 복제되는 조건을 추가할 수 있는데,
Owner라면 핑이 0일 것이므로, CompressedPing은 SkipOwner 조건이 걸려있다.
그리고 PlayerId, UniqueId...도 최초로 설정된 후, 변경될 일이 없으니 InitialOnly로 설정했을 것이다.
[ Mark Dirty ]
서버측에서 값이 변경되었을 때, 변경되었다고 수동으로 알려줘야 할 것이다.
번거로운 작업이 될 수 있는데, 여기서 사용할 수 있는 테크닉은 setter 내부에서 값을 변경하는 코드와 MARK DIRTY를 묶어두고, setter로만 값을 설정한다면 프로그래머가 매번 수동으로 Mark Dirty를 하지 않아도 될 것이다.
[ Code Example Snippet ]
[ 성능 개선 ]
에픽게임즈 개발자와의 FAQ에 따르면, 프로젝트마다 다르지만 에픽 게임즈 내부에선 성능 향상이 크진 않다고 한다.
그래도 최적화는 프로젝트마다 상황이 다를 수 있으므로 Property Dirty Check 관련해서 bottleneck이 발생한다면 고려해볼 수 있을 것이다.
[ Reference ]
Chart Image : https://wizardcell.com/unreal/multiplayer-tips-and-tricks/
Epic Games FAQ : https://dev.epicgames.com/community/learning/knowledge-base/ZGZk/unreal-engine-faq-multiplayer-networking
Riot games : https://technology.riotgames.com/news/valorants-128-tick-servers
System Settings : https://forums.unrealengine.com/t/push-model-networking/510684/6
'UE5 > Network' 카테고리의 다른 글
Multiplayer in UE : How to Understand Network Replication (0) | 2024.08.29 |
---|---|
[UE5] About HUD After Seamless Travel (1) | 2024.08.22 |
Listen Server에서 DestroySession()에 관한 궁금증 (0) | 2024.07.05 |
[UE5] Controller in BeginPlay on Server (0) | 2024.07.03 |
Multiplayer Best Practices in UE #NotGDC (1) | 2024.05.07 |