델리게이트

딜리게이트(delegate)라는 단어를 사전에서 검색해보면 집단의 의사를 대표하는 대표자라고 나옵니다. 
하지만 컴퓨터 프로그래밍에서 딜리게이트는 함수를 안전하게 호출할 수 있는 기능을 의미합니다. 

얼핏 들어보면 둘 간의 개념이 잘 매칭이 안되는데요, 잠깐 딜리게이트에 대해 알아보겠습니다. 

사실 C++ 언어에서는 딜리게이트라는 개념이 존재하지 않습니다. 
딜리게이트는 C++ 의 다음 세대 언어인 C#에서 ( 정확히는 CLI 플랫폼 기반 ) 선보인 개념인데,

콜백 함수를 등록하기 위해 C 혹은 C++에서 사용한 함수 포인터가 문법이 복잡하고, 위험한 방식이었다면, 
딜리게이트는 간편한 문법과 안전성을 갖춰 콜백 함수를 호출하기 위해 고안되었습니다. 

딜리게이트의 간편함과 안정성 외에도 이전 방식과 다른 큰 특징은 사전적 의미대로 집단의 의사를 대표합니다. 
딜리게이트가 하나의 함수 뿐만 아니라 동일한 리턴값과 인자 타입을 가지는 여러 개를 관리할 수 있다는 것 입니다. 

딜리게이트의 이러한 특징은 C# 언어의 주요 패턴 중 하나인 발행/구독(Publish/Subscribe) 패턴의 구현으로 이어집니다. 

C#의 발행/구독 패턴은 우리가 일상 생활에서 흔히 접하는 신문을 구독하는 절차와 유사한 방식입니다. 
구독자가 신문사에게 신문을 받아보겠다는 구독 의사를 밝히고 등록하면, 신문이 발행될 때마다 
동일한 시간에 구독한 모든 구독자의 집으로 신문을 배달해주듯이, 특정 이벤트가 발생하면 
딜리게이트에 등록된 모든 함수를 한꺼번에 호출할 수 있습니다.  

예를 들어 어떤 게임에서 보스와 보스가 스폰한 미니언(Minion)이 있어서 보스가 죽으면, 
이 미니언(Minion)도 함께 죽게 만들고 싶다고 가정합시다. 

보스가 죽는 이벤트를 딜리게이트로 정의하고 미니언을 스폰할 때마다 각 미니언들이 이를 구독하게 설정해두면, 
보스가 죽을 때, 명령 하나로 모든 미니언들에게 보스가 죽었다고 알려줄 수 있습니다. 

※ 딜리게이트 기능은 C++언어에서는 제공하지 않습니다만, 언리얼 C++은 자체적으로 프레임웍을 제작해 이 기능을 지원하고 있습니다.


딜리게이트 기능의 특징은 다음과 같이 요약할 수 있습니다.

- 함수 포인터 직접 접근이 아닌 대리자를 통한 함수 호출 방식 
- 호출할 함수나 이를 포함하는 객체가 없어져도, 대리자가 체크해 안전하게 처리할 수 있음. 
- 동일한 형을 가진 함수 여러 개를 대리자가 묶어서 관리하고, 필요할 때 동시에 모두 호출하는 것이 가능함.

언리얼에서 딜리게이트 기능을 사용하려면 먼저 매크로를 사용해 딜리게이트를 선언해야 합니다.
딜리게이트는 모든 함수 유형을 대변할 수 없고, 
우리가 지정한 함수의 리턴값과 인자 타입 가지는 함수 대표할 수 있습니다. 

 

언리얼 딜리게이트 시스템에 등록 가능한 함수는 다음과 같습니다.(바인딩)

  • 전역 C++ 함수 : BindStatic API를 사용해 등록
  • 전역 C++ 람다 함수 : BindLambda API를 사용해 등록
  • C++클래스 멤버 함수 : BindRaw  API를 사용해 등록
  • 공유포인터 클래스의 멤버 함수 (쓰레드 미지원) : BindSP API를 사용해 등록 
  • 공유포인터 클래스의 멤버 함수 (쓰레드 지원) : BindThreadSafeSP API를 사용해 등록
  • UFUNCTION 멤버 함수 : BindUFunction API를 사용해 등록
  • 언리얼 오브젝트의 멤버함수 : BindUObject API를 사용해 등록

ex) 위 API 목록 중에서 우리가 바인딩하려는 RequestTokenComplete 함수는 
언리얼 오브젝트 ABGameInstance의 멤버 함수이므로, 
CreateUObject를 사용해 바인딩하여야 함을 알 수 있습니다. 


다이내믹 델리게이트

 

함수의 이름을 기반으로 등록해 호출하는 방
C++ 함수 뿐만 아니라 블루프린트 함수도 연결

언리얼 에서 델리게이트는 C++ 객체에만 사용할 수 있는 델리게이트와 C++, 블루프린트 객체가 모두 사용할 수 있는 델리게이트로 나뉜다. 블루프린트 오브젝트는 멤버 함수에 대한 정보를 저장하고 로딩하는 직렬화 매커니즘이 들어있기 때문에 일반 C++ 언어가 관리하는 방법으로 멤버 함수를 관리 할수 없다. 

그래서 블루프린트와 관련된 C++함수는 모두 UFUNCTION 매크로를 사용해야 한다.
이렇게 블루프린트 객체와도 연동하는 델리게이트를 언리얼 엔진에서는 다이내믹 델리게이트라고 한다.

 

다이나믹 딜리게이트 함수포인터가 아닌, 함수의 이름을 기반으로 등록해 호출하는 방식입니다.

이름 기반이다보니 저장할 수 있다는 특징이 있습니다만, 반면에 동작이 느리다는 단점이 있습니다. 
다이나믹 방식으로 딜리게이트를 선언하려면 인자의 이름까지 정확히 일치해야 합니다. 
그래서 딜리게이트의 선언도 함수 인자 하나당 타입과 이름 정보가 들어가야 합니다.  

이 다이나믹 딜리게이트 시스템이 필요한 이유는 딜리게이트 시스템에
C++ 함수 뿐만 아니라 블루프린트 함수도 연결할 수 있게 하기 위해서입니다.


다이나믹 델리게이트 바인딩

BindDynamic( UserObject, FuncName )

다이내믹 델리게이트에서 BindDynamic() 호출을 위한 헬퍼 매크로입니다. 함수 이름 문자열을 자동 생성합니다.

AddDynamic( UserObject, FuncName )

다이내믹 멀티-캐스트 델리게이트에서 AddDynamic() 호출을 위한 헬퍼 매크로입니다. 함수 이름 문자열을 자동 생성합니다.

RemoveDynamic( UserObject, FuncName )

다이내믹 멀티-캐스트 델리게이트에서 RemoveDynamic() 호출을 위한 헬퍼 매크로입니다. 함수 이름 문자열을 자동 생성합니다.


다이내믹 델리게이트 실행

델리게이트에 바인딩된 함수는 델리게이트의 Execute() 함수를 호출하여 실행됩니다.

델리게이트를 실행하기 전 "바인딩" 되었는지 반드시 확인해야 합니다.
이는 코드 안전성을 도모하기 위함인데, 초기화되지 않은 상태로 접근이 가능한 반환값과 출력 파라미터가 델리게이트에 있을 수 있기 때문입니다. 바인딩되지 않은 델리게이트를 실행시키면 일부 인스턴스에서 메모리에 낙서를 해버릴 수가 있습니다.

델리게이트가 실행해도 안전한 지는 IsBound() 를 호출하여 검사해 볼 수 있습니다.
또한 반환값이 없는 델리게이트에 대해서는 ExecuteIfBound() 를 호출할 수 있으나,
출력 파라미터는 초기화되지 않을 수 있다는 점 주의하시기 바랍니다.

 


멀티캐스트 델리게이트

여러개 함수가능
블루프린트에서 함수를 사용할려면 멀티캐스트 기능이 필요함


여러 개의 함수를 받을 수 있어서 행동이 끝나면 등록된 모든 함수들에게 모두 알려주는 기능도 제공한다.
이러한 델리게이트를 
멀티캐스트 델리게이트 라고한다


하나의 딜리게이트에 여러 개의 함수를 연결하는 기능은 
DECLARE_DELEGATE 매크로가
아닌 MULTICAST를 붙인 DECLARE_MULTICAST_DELEGATE 매크로를 사용해야 합니다.

MULTICAST 딜리게이트는 Execute API 대신에 Broadcast API를 사용해야 딜리게이트에 연결된 모든 함수가 실행됩니다. (Broadcast API에는 BroadcastIfBound라는 함수는 없습니다. 아무 연결이 없으면 그냥 아무일도 안 일어납니다. ) 

 

 다이나믹 딜리게이트 시스템이 필요한 이유는 
딜리게이트 시스템에 C++ 함수 뿐만 아니라 블루프린트 함수도 연결할 수 있게 하기 위해서입니다.

하지만 
연결 된걸 사용할 수 있게 하려면 기본적으로 MULTICAST 기능도 지원해주어야 합니다.
(1개가 아닌 여러 함수를 실행해야 해서?)


멀티캐스트 델리게이트 바인딩

Add()

이 멀티캐스트 델리게이트의 실행 목록에 함수 델리게이트를 추가합니다.

AddStatic()

raw C++ 포인터 글로벌 함수 델리게이트를 추가합니다.

AddRaw()

raw C++ 포인터 델리게이트를 추가합니다. raw 포인터는 어떠한 레퍼런스도 사용하지 않기에,
오브젝트가 자신의 델리게이트 하에서 삭제된 경우 호출시 안전하지 않을 수 있습니다.
Execute() 호출시 주의하세요!

AddSP()

공유 포인터 기반 (빠르지만 스레드 안전성은 떨어지는) 멤버 함수 델리게이트를 추가합니다.
공유 포인터 델리게이트는 자신의 오브젝트에 대한 약 레퍼런스를 유지합니다.

AddUObject()

UObject 기반 멤버 함수 델리게이트를 추가합니다.
UObject 델리게이트는 자신의 오브젝트에 대한 약 레퍼런스를 유지합니다.

Remove()

이 멀티캐스트 델리게이트의 실행 목록에서 함수를 제거합니다 (퍼포먼스는 O(N) 입니다).
참고로 델리게이트 순서는 유지되지 않을 수 있습니다!

RemoveAll()

지정된 UserObject 에 바인딩된 이 멀티캐스트 델리게이트의 실행 목록에서
모든 함수를 제거합니다. 참고로 델리게이트 순서는 유지되지 않을 수 있습니다!

 

※ RemoveAll() 은 제공된 포인터에 바인딩된 모든 등록 델리게이트를 제거합니다.
  염두에 둘 것은, 오브젝트 포인터에 바인딩되지 않은 raw 델리게이트는 이 함수로 제거되지 않습니다!


멀티캐스트 실행

멀티캐스트 델리게이트를 통해 여러 함수 델리게이트를 붙인(attach) 다음,
멀티캐스트 델리게이트의 Broadcast() 함수를 호출하여 그 모두를 동시에 실행시킬 수 있습니다.
멀티캐스트 델리게이트 시그너처에는 반환값을 사용할 수 없습니다.

멀티캐스트 델리게이트에서의 Broadcast() 호출은 아무것도 바인딩되어 있지 않더라도 항상 안전합니다.

딱 한 가지 경우 조심해야 할 때가 있는데,
델리게이트를 사용하여 출력 변수를 초기화시킬 때로 보통은 매우 좋지 않은 일입니다.

Broadcast() 호출시 바인딩된 함수의 실행 순서는 정의되지 않습니다.
함수가 추가된 순서대로 실행되지 않을 수가 있습니다.

Broadcast()

이 델리게이트를 만료되었을 수도 있는 것을 제외하고, 바인딩된 모든 오브젝트에 뿌립니다.

 


다이나믹 멀티캐스트 델리게이트

 

C++ 함수 뿐만 아니라 블루프린트 함수도 연결 +  블루프린트에서 사용할 수 있게 

따라서 딜리게이트를 블루프린트의 함수와도 연동하고 싶은 경우에는

DYNAMIC과 MULTICAST가 합쳐진 

DECLARE_DYNAMIC_MULTICAST_DELEGATE 매크로를 사용


시그니처

애님 인스턴스 헤더에 선언된 OnMontageEnded가 사용하는 델리게이트를 저으의한 코드는 다음과 같다.
언리얼 엔진에서 델리게이트의 선언은 언리얼이 제공하는 매크로를 통해 정의되며,
이렇게 정의된 델리게이트의 형식을 
시그니처라고 한다.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnMontageEndedMCDelegate, UAnimMontage*,
Montage,bool,bInterrupted);

 

'Unreal > Concept' 카테고리의 다른 글

언리얼 스마트포인터  (0) 2019.06.03
FArchive 아카이브, << 연산자  (1) 2019.05.31
UWorld  (0) 2019.05.30
World - 작성 중  (0) 2019.05.29
Actor, Pawn  (0) 2019.05.29

+ Recent posts