언리얼 엔진(UE) 네트워크 리플리케이션은 신뢰성 및 비신뢰성 커뮤니케이션 메서드를 복합적으로 사용하여 서버 및 연결된 클라이언트 간에 정보를 전송합니다. 신뢰성 커뮤니케이션은 지속적으로 전송되며 수신 시스템에서 이를 확인할 때까지 다른 모든 네트워크 통신이 중단됩니다. 비신뢰성 커뮤니케이션은 전송된 다음 수신 머신에서 수신 확인을 하지 않으면 현재 네트워크 틱 동안 재전송되지 않습니다.
이 커뮤니케이션의 상대적 순서와 관련하여 액터 프로퍼티 및 원격 프로시저 호출(RPC) 리플리케이션 측면에서 무엇이 보장되는지, 그리고 게임 코드에서 이를 고려하기 위해 무엇을 할 수 있는지를 이해하는 것이 중요합니다. 이 페이지에서는 UE의 리플리케이션 시스템이 무엇을 보장하는지, 그리고 마찬가지로 중요한 내용인 무엇을 보장하지 않는지에 대해 설명합니다.
액터 프로퍼티
액터 프로퍼티 업데이트는 비신뢰성 커뮤니케이션이며 단일 번치로 전송됩니다. 이 업데이트는 다른 모든 RPC 다음에, 하지만 큐에 등록된 RPC보다는 먼저 전송되는 하나의 비신뢰성 RPC로 생각할 수 있습니다. 큐에 등록된 RPC에 대한 자세한 내용은 이 페이지의 강제 큐 섹션을 참조하세요.
리플리케이트된 사용 순서
서로 다른 리플리케이트된 변수의 OnRep(RepNotify) 콜백 간에는 결정론적 순서가 없습니다. 클라이언트의 호출 순서는 더티로 표시된 변수 또는 메모리의 선언 위치와 관련이 없습니다. 여러 변수 간에 신뢰할 수 있는 순서가 필요한 경우, 구조체에 함께 저장하는 것이 좋습니다.
만약 액터의 프로퍼티 리플리케이션 순서가 게임에 중요하다면 프레임별 프로퍼티 업데이트를 트래킹하도록 OnReps를 구현하는 것이 좋습니다. 리플리케이트된 값이 수신되고 OnReps가 호출되면 변경 내용을 UObject::PostRepNotifies 함수로 처리할 수 있습니다. 또한 특정 수신 값을 사용 준비가 될 때까지 해당하는 OnReps에 저장해야 할 수도 있습니다.
원격 프로시저 호출
언리얼 엔진의 리플리케이션 시스템은 최대한 신뢰할 수 있게 RPC를 실행하므로 네트워킹 부작용에 대한 걱정 없이 게임플레이 시스템을 빌드할 수 있습니다.
액터 전반의 순서
여러 액터 전반에 걸친 RPC의 원래 호출 순서가 유지되고 원격 머신에 다시 적용되는 메커니즘은 없습니다. 다음은 송신 머신에서 RPC 호출 순서의 예시입니다.
AActor* MyActor;
AActor* OtherActor;
// 유효한 MyActor 포인터
MyActor->ClientRPC1();
OtherActor->ClientRPC2();
MyActor->ClientRPC3();
이 예시에서 수신 머신의 RPC 실행 순서는 결정론적이 아니며 RPC는 수신 머신에서 어떤 순서로든 실행될 수 있습니다.
RPC1 --> RPC2 --> RPC3
RPC1 --> RPC3 --> RPC2
RPC2 --> RPC1 --> RPC3
RPC2 --> RPC3 --> RPC1
RPC3 --> RPC1 --> RPC2
RPC3 --> RPC2 --> RPC1
액터 내부의 순서
리플리케이션 시스템은 같은 액터에서 신뢰성 RPC 호출 순서를 보장합니다. 수신 머신에서 호출이 실행되는 순서는 송신 머신에서 호출되는 순서와 같습니다. 송신 머신에서 호출 순서가 다음과 같은 경우,
AActor* MyActor;
// 유효한 MyActor 포인터
MyActor->ClientReliableRPC1();
MyActor->ClientReliableRPC2();
MyActor->ClientReliableRPC3();
수신 머신에서는 항상 다음 순서로 RPC를 실행합니다.
RPC1 --> RPC2 --> RPC3
액터와 서브오브젝트 간 순서
수신 머신에서의 RPC 실행 순서는 액터 및 액터의 서브오브젝트에서 호출되는 모든 RPC에 대해 존중됩니다. 예를 들어, 송신 머신에서 다음과 같이 전송하는 경우,
AActor* MyActor;
// 유효한 MyActor 포인터
MyActor->RPC1();
MyActor->SubObject1->RPC2();
MyActor->SubObject2->RPC3();
MyActor->RPC4();
수신 머신에서의 실행 순서는 다음과 같습니다.
RPC1 --> RPC2 --> RPC3 --> RPC4
비신뢰성 순서와 신뢰성 순서 비교
비신뢰성 RPC와 신뢰성 RPC 간의 RPC 실행 순서는 보존되는 것으로 보이지만, 보장되지는 않습니다. 패킷 손실이나 패킷 순서 변경이 발생하지 않으면, 비신뢰성 RPC와 신뢰성 RPC 간의 실행 순서는 수신 머신과 송신 머신에서 동일합니다. 다음은 송신 머신에서 RPC 호출 순서의 예시입니다.
AActor* MyActor;
// 유효한 MyActor 포인터
MyActor->ClientReliableRPC1();
MyActor->ClientUnicastUnreliableRPC2();
MyActor->ClientReliableRPC3();
패킷 손실이나 순서 변경이 발생하지 않으면 수신 머신은 다음 순서로 RPC를 실행할 수 있습니다.
RPC1 --> RPC2 --> RPC3
드롭되거나 순서가 변경된 개별 패킷에 RPC1 이 있으면 수신 머신에서의 실행 순서는 다음과 같습니다.
RPC2 --> RPC1 --> RPC3
드롭된 개별 패킷에 RPC2 가 있으면 수신 머신에서의 실행 순서는 다음과 같습니다.
RPC1 --> RPC3
후자의 경우, RPC2 는 신뢰할 수 없으므로 수신 머신에서 드롭되고 실행되지 않습니다.
비신뢰성 RPC2 가 RPC3 다음에 실행되는 시나리오는 없어야 합니다. RPC2 가 포함된 패킷의 순서가 변경되어 RPC3 보다 늦게 도착하면, 수신 시 무시됩니다.
멀티캐스트 순서와 유니캐스트 순서 비교
멀티캐스트 RPC 순서는 좀 더 복잡한데, UE의 리플리케이션 시스템이 멀티캐스트 RPC와 유니캐스트 RPC 간의 호출 순서를 항상 보존하는 것은 아니기 때문입니다.
멀티캐스트는 신뢰성 RPC
신뢰성 멀티캐스트 및 다른 신뢰성 유니캐스트 RPC 간의 호출 순서는 보존됩니다. 예를 들어, 송신 머신에서 다음과 같은 순서로 함수가 호출된 경우,
MyActor->MulticastReliableRPC1();
MyActor->UnicastReliableRPC2();
MyActor->UnicastReliableRPC3();
MyActor->MulticastReliableRPC4();
수신 머신에서는 다음 순서로 RPC를 실행합니다.
RPC1 --> RPC2 --> RPC3 --> RPC4
비신뢰성 RPC3 의 순서는 결정론적이 아니며 더 먼저 실행될 수도 있고 아예 실행되지 않을 수도 있습니다.
멀티캐스트는 비신뢰성 RPC
비신뢰성 멀티캐스트는 다른 유니캐스트와 신뢰성 멀티캐스트 간의 호출 순서를 보존하지 않습니다. 예를 들어, 송신 머신에서 다음과 같은 순서로 RPC가 호출된 경우,
MyActor->MulticastUnreliableRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastUnreliableRPC3();
MyActor->UnicastUnreliableRPC4();
수신 머신에서는 다음 순서로 RPC를 실행합니다.
RPC2 --> RPC4 --> RPC1 --> RPC3
RPC1 및 RPC3 는 비신뢰성 멀티캐스트 RPC이므로 마지막으로 큐에 등록되고 시리얼라이즈됩니다. 즉, 유니캐스트가 먼저 실행되고 비신뢰성 멀티캐스트는 마지막에 실행됩니다. 드롭된 비신뢰성 유니캐스트 RPC에 적용되는 규칙이 여기에도 적용됩니다.
드롭되거나 순서가 변경된 개별 패킷에 RPC2 가 있으면 수신 머신에서의 실행 순서는 다음과 같습니다.
RPC1 --> RPC3 --> RPC2 --> RPC4
RPC 전송 정책
RPC 순서에 영향을 주는 명시적 전송 정책을 RPC에 할당할 수 있습니다. ERemoteFunctionSendPolicy 를 지정하면 됩니다. RPC 전송 정책과 관련된 자세한 내용은 원격 프로시저 호출 문서를 참조하세요.
강제 전송
ERemoteFunctionSendPolicy::ForceSend 정책이 지정된 RPC는 비신뢰성 멀티캐스트 RPC의 순서를 변경하고 이 RPC가 큐에 등록되지 않게 합니다. 다음은 그 예시입니다.
MyActor->ForceSendMulticastUnreliableRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastUnreliableRPC3();
MyActor->UnicastUnreliableRPC4();
클라이언트는 다음 순서로 이러한 RPC를 실행합니다.
RPC1 --> RPC2 --> RPC4 --> RPC3
강제 큐
ERemoteFunctionSendPolicy::ForceQueue 정책이 지정된 RPC는 다른 ForceQueue` RPC 및 비신뢰성 멀티캐스트를 제외하고는 호출 순서를 존중하지 않습니다. 다음은 그 예시입니다.
MyActor->ForceQueueRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastUnreliableRPC3();
MyActor->UnicastUnreliableRPC4();
클라이언트는 다음 순서로 이러한 RPC를 실행합니다.
RPC2 --> RPC4 --> RPC1 --> RPC3
RPC 및 액터 프로퍼티 간 순서
RPC 실행 간의 순서와 리플리케이트된 프로퍼티 업데이트가 적용되는 시점을 이해하는 것도 중요합니다. 이러한 경우 다음 규칙이 적용됩니다.
- RPC가 먼저 실행됩니다.
- 프로퍼티가 두 번째로 업데이트됩니다.
- 프로퍼티 업데이트는 하나의 비신뢰성 데이터 블록으로 전송됩니다.
이 번치 페이로드는 다음과 같이 구성됩니다.
- 큐에 등록되지 않은 RPC를 시리얼라이즈합니다.
- 리플리케이트된 프로퍼티 데이터를 시리얼라이즈합니다.
- 큐에 등록된 RPC를 시리얼라이즈합니다.
RPC 내부에서 작성된 리플리케이트된 변수는 처리되지 않은 프로퍼티 업데이트에 의해 손실되고 즉시 덮어써질 수 있습니다.
이 규칙이 적용되지 않는 예외 항목은 비신뢰성 멀티캐스트 RPC인데, 왜냐하면 이 RPC는 호출 사이트에서 큐에 등록되고 항상 마지막에 시리얼라이즈되기 때문입니다. 즉, 이 비신뢰성 멀티캐스트 RPC는 프로퍼티 업데이트가 적용된 뒤에 실행됩니다.
다음은 그 예시입니다.
MyActor->ReliableRPC1();
MyActor->bReplicatedVar1 = true
MyActor->MulticastUnreliableRPC2();
MyActor->bReplicatedVar2 = true;
MyActor->ReliableRPC3();
원격 머신에서는 이를 다음 순서로 실행합니다.
RPC1 --> RPC3 --> Var1 && Var2 --> RPC2
다음은 PPC와 혼합된 프로퍼티 업데이트의 또 다른 예시입니다.
MyActor->ReliableRPC1();
MyActor->bReplicatedVar1 = true
MyActor->MulticastUnreliableRPC2();
MyActor->bReplicatedVar2 = true;
MyActor->ReliableRPC3();
프로퍼티 업데이트가 드롭되었다고 가정하면, 수신 머신에서는 다음 순서로 RPC와 프로퍼티 업데이트를 실행합니다.
RPC1 --> RPC3 --> RPC2
// 다음 업데이트 이후
Var1 && Var2
신뢰성 RPC1만 드롭된 또 다른 시나리오에서는 수신 머신에서 다음 순서로 실행됩니다.
Var1 && Var2 --> RPC2 --> RPC1 --> RPC3
비신뢰성 RPC로 게임플레이 코드 테스트하기
비신뢰성 RPC를 사용하여 리플리케이트되는 코드를 생성하거나 그런 코드에 의존하는 경우, 비신뢰성 RPC를 강제 드롭하여 시스템이 어떻게 반응하는지 확인하는 것이 좋습니다. 열악한 네트워크 상태를 에뮬레이션하여 테스트하는 방법에 대한 자세한 내용은 네트워크 에뮬레이션 사용 문서를 참조하세요.