액터 리플리케이션 우선권
- 클라리언트에 보내는 대역폭(NetBandwidth)는 물리적으로 한정되어있다.
- 클라이언트에 보낼 Actor 중, 우선권이 높은 Actor의 데이터를 우선 전달하도록 설계가 되어있다.
- Actor에 설정된 NetPriority 우선권 값을 활용해 전송 순서를 결정한다. (값이 높으면 우선권이 높다)
- 우선권은 네트워크로 전송하는 비율을 높이는 것이 아니라 우선권을 높히거나 낮춰서 네트워크 성능이 향상되진 않는다.
Actor 종류 | 우선권(NetPriority) |
일반적인 Actor | 1.0 |
Pawn | 3.0 |
PlayerController | 3.0 |
Actor의 현재 우선권은 GetNetPriority()함수를 사용해서 계산한다.
이 GetNetPriority함수도 연관성과 동일하 Actor와 관찰자 사이의 거리와 상대적 위치를 고려한다.
우선권 설정 로직
A, B, C, D의 순서로 우선권이 높은 패킷이 있다고 가정해보자.
D의 패킷의 용량이 커서 하나의 Tick으로 보낼 수 없을 때 A, B, C의 패킷을 보낸 이후에 D의 패킷을 보내줘야한다.
하지만 이때 A, B, C, D의 우선권이 그대로라면 D는 다음 Tick에서도 보내지지 못하고 A, B, C, D가 보내질 것이다.
이러한 현상을 막기위해 우선권 설정 로직이 존재한다.
우선권 로직 순서
- 마지막으로 패킷을 보낸 후의 경과 시간과 최초 우선권 값을 곱해 최종 우선권 값을 생성한다.
또는 중요도(Actor Type(뷰어, 뷰타겟 등), 잘 보이는 Actor)에 따라 가중치를 설정해서 곱한 후 최종 우선권 값을 생성한다. - 최종 우선권 값을 사용해 클라이언트에 보낼 Actor 목록을 정렬한다.
- 네트워크가 포화(Saturation)될 때까지 정렬된 순서대로 리플리케이션을 수행
- 네트워크가 포화되면 해당 Actor는 다음 서버 Tick으로 넘긴다.
(물론 해당 우선권이 상대적으로 많이 낮다면 다음 서버 Tick에서 반드시 보내준다는 보장은 없다)
우선필요 이유 Test
일단 우선권이 필요한 이유를 알아보기 위해 의도적으로 Tarffic을 늘려보자.
DefaultEngine.ini에서 다음과 같은 코드를 추가해 NetTrafficLog가 찍히도록 하고 Bandwidth를 의도적으로 줄인다.
h파일에 다음과 같이 float array를 추가하고
BeginPlay에서 계속 돌려주면 다음과 같은 Log가 나온다.
현재 네트워크가 포화되었기에 BP_Fountain은 NetUpdateTime에 도달했지만 다음 Tick에 처리된다.
액터의 휴면(Dormancy)
- 액터의 전송을 최소화하기 위해 연관성과 더불어 제공하는 속성
- 액터가 휴면 상태라면 연관성이 있어도 액터 리플리케이션을 수행하지 않는다.
- Unreal Engine에서 지정한 휴면 상태
Unreal Engine에서 지정한 휴면 상태
- DORM_Never : 액터는 휴면이 없음
- DORM_Awake : 액터는 깨어나 있음
- DORM_DormantAll : 액터는 언제나 휴면상태이지만 필요시 깨울 수 있음
- DORM_DormantPartial : 특정 조건을 만족하는 경우에만 리플리케이션 수행
- DORM_Inital : 액터를 휴면 상태로 시작하고 필요할 때 깨울 수 있음
(프로퍼티(속성) 리플리케이션 사용시에는 해당 DORM_Initial만 고려하는 것이 좋다.)
이제 한번 프로퍼티를 휴면상태로 놓고 풀어보자.
이렇게 Color를 바꾸기 위해 OnRep_ServerLightColor 함수와 변수를 h파일에 작성한다.
OnRep_ServerLightColor()는 간단하게 호출이 되면 ServerLightColor로 변경하는 로직이다.
생성자에 다음과 같이 작성을 하고
BeginPlay에 10초뒤에 FlushNetDormancy()를 하면 휴면상태가 해제된다.
즉, 시작한지 10초뒤에 서버로부터 프로퍼티 값을 받아와서 회전 및 색상 변화를 적용한다.
조건식 프로퍼티 리플리케이션
위 링크는 Unreal에서 제공하는 조건식 프로퍼티 리플리케이션 공식 문서이다. 위의 글을 간단하게 설명하면
최적화의 이유로 프로퍼티가 리플리케이션에 등록되면 해제할 수 없다.
즉, 휴면에서 해제되면 다시 휴면으로 바꿀 수 없다는 의미이다.
그러면 프로퍼티 리플리케이션의 세밀한 조정을 위해서는 등록 ,해제를 못하니 조건식 프로퍼티 리플리케이션을 사용하면 된다.
기본적으론 프로퍼티가 변경되지 않으면 리플리케이션을 하지 않는 것은 동일하지만 여기서 더 세밀하게 조정이 가능하다.
이 조건식은 게임을 제작하면서 Insight를 사용했을 때 네트워크 Tick에서 프로퍼티 때문에 Saturated가 많이 발생하면 조건식을 사용해서 네트워크의 부하를 줄여줄 때 사용한다.
다음과 같이 DOREPLIFETIME을 DOREPLIFETIME_CONDITION으로 바꿔주고 세번째 인자를 추가해주면 된다.
세번째 인자는 너무 많아서 공식문서 들어가거나 엔진 소스에 설명이 나와있어 그것을 참고하면 된다.
지금 예제에서 사용할 COND_InitalOnly는 최초에 한번만 보내는 설정을 한다.
다음과 같이 사용하면 ServerLightColor의 값이 서버에서 변화하면 서버에선 계속 값이 변하지만
클라이언트에서는 최초가 1번만 받고 그 이후엔 변하지 않는다.
액터 리플리케이션 로우레벨 플로우
Unreal에서 제공하는 액터 리플리케이션 플로우의 공식 문서이다.
순서를 간단하게 정리하자면
- 네트워크로 업데이트할 Actor의 목록(Network Objects)을 만든다.
- 서버는 매 틱마다 Actor의 목록을 클라이언트로 전송한다.
각 Actor의 정보는 FNetworkObjectInfo라는 구조체에 정리가 되어있다.
여기에는 다음에 전송할 시간을 지정한 NextUpdateTime, 전송되어있는지 파악하는 bPendingNetUpdate가 있다.
NextUpdateTime은 이전에 설명했던 NetUpdateFrequency와 관련이 있다.
bPendingNetUpdate는 포화상태가 되어 패킷을 보내지 못했을 때 true가 되어 이는 우선권과 관련이 있다.
이 Actor 목록에서 각 Actor의 상태에 따라서 정보를 보낼 Actor와 보내지 않을 Actor로 나눈다.
보내지 않을 Actor : 서버Tick 기준으로 초기화가 안되어있거나 보낼 타이밍이 안됐거나 휴면상태. - Actor 목록에서 보낼 수 있는 조건을 만족하는 Actor만 따로 모아서 목록(ConsiderList)을 관리하게된다.
여기에 들어있는 Actor들은 각각 자신의 PreReplication()이란 함수를 호출하여
전송할 준비가 되었다고 알려준다. - ConsiderList를 기반해서 서버에 접속한 클라이언트마다 별도의 목록을 만들어준다. (클라이언트 마다 다르다)
클라이언트의 뷰어의 따라서 네트워크를 통해 받아야할 정보가 다를 수 있기 때문에
클라이언트 뷰어 정보를 참고해서 ConsiderList에서 각각 별도의 Actor 묶음을 다시 클라이언트마다 만들어준다.
여기서는 연관성 정보가 사용된다. - 이제 클라이언트마다 보낼 Actor의 목록이 만들어지면 이 목록에서 우선권을 계산해 Actor를 정렬(우선권 리스트)한다.
우선권으로 정렬하기 위해 내부적으론 FActorPriority라는 구조체를 사용한다.
이 구조체는 Priority변수와 위에서 설명한 FNetworkObjectInfo 두가지 정보를 담는다. - 정렬된 우선권 리스트에 따라 Actor의 정보를 보내고 네트워크 상태가 포화되면 FNetworkObjectInfo의 bPendingNetUpdate 플래그를 활성화해서 다음 서버의 Tick에서 이전에 보내지지 않음을 알려줘서 조정한다.