내일배움캠프 34일차 - 언리얼 엔진 C++ 심화 1 : 리플렉션/UHT/UBT/CDO

UPROPERTY() / UFUNCTION() / UCLASS() 매크로

리플렉션을 위한 매크로.

해당 매크로를 등록함으로써 언리얼 리플렉션 시스템에 해당 변수/함수/클래스를 등록하여 블루프린트와 C++의 다리 역할을 해준다.

리플렉션이란?
 : 실행 중에 프로그램이 자기 자신의 구조를 인지하고 조작할 수 있는 능력
→ C++로 작성된 코드를 언리얼 엔진이 인지하고 조작할 수 있는 기능

리플렉션 핵심 매크로
 - UPROPERTY() : 변수를 에디터에 노출
 - UFUNCTION() : 함수를 블루프린트에 연결
 - UCLASS() : 클래스를 언리얼에 등록

메타데이터 목록 (매크로 안에 넣는 매개변수)
 - 공통: Category=""
 - 함수: BlueprintCallable
 - 변수: BlueprintReadWrite, BlueprintReadOnly,
       EditAnywhere, VisibleAnywhere

메타데이터: 사진의 촬영정보처럼 숨겨진 세부정보를 보관

리플렉션의 주요 역할

 - 런타임 타입 정보 확인: IsA() 함수로 객체 타입 실시간 확인

 - 에디터 연동: UPROPERTY 변수가 디테일 창에 자동 표시

 - 가비지 컬렉션 지원: 객체 참조 관계 추적으로 메모리 자동 관리

※ 리플렉션이 없다면?
 : 언리얼 내에서 C++로 작성된 변수/함수/클래스에 접근할 수 없다.


UPROPERTY를 쓰지 않아도 되는 경우

1) UObject가 아닌 타입
   → GC는 UObject계열만 관리함, 이런 경우 주로 C++에서 서로 주고받음

2) 월드나 액터차원에서 해당 실제 오브젝트를 소유할 경우
   → 배치되었거나 스폰되었으면 GC에 회수되지 않음

포인터로 가리킨 UObject제거방법

1) 포인터주소를 nullptr로 변경 → 기존 C++과 달리 잔류된 힙메모리는 GC가 회수
2) GC에게 해당 오브젝트를 삭제요청을 하는 함수 사용

가비지 컬렉션(GC)의 동작 원리

표준 C++ vs 언리얼 엔진

표준 C++: new/delete 직접 관리 필요
 - 메모리 누수 위험
 - dangling 포인터 문제

언리얼 엔진: GC가 자동 관리
 - 안전한 메모리 해제
 - 개발자 실수 방지

GC의 작동 매커니즘.
UPROPERTY, UCLASS 로 연결되어있지 않은 고립객체는 삭제한다.

UPROPERTY()를 붙이지 않은 변수는 원치 않아도 GC 작동주기에 삭제된다.

GC 작동 시 주의점

리플렉션 매크로붙이지 않은 객체는 GC에 의해 삭제된다.
이때, 삭제된 객체를 참조하는 포인터에서 문제가 발생한다.

Danger객체가 GC에 의해 삭제되었음에도,
IsValid()로 존재유무를 확인 시 True로 뜬다.

위 사진 처럼 IsValid()로 걸러지지 않은 삭제객체에 접근 시 프로그램 크래시가 발생한다.
좀더 정밀하게 존재유무를 체크하려면 (객체명)->IsValidLowLevel()를 통해 검사해야 한다.

IsValidLowLevel()를 통한 체크.
GC에 삭제된 객체는 False를 반환한다.

그럼에도 불구하고 포인터는 주소를 참조하여(댕글링 포인터) 개발자가 실수할 여지가 존재한다.
이를 위해 언리얼에서는 해당 오브젝트 포인터를 제공하고 있다.

UObject용 스마트 포인터
 - TObjectPtr: UObject에 강한참조를 하는 스마트 포인터

 - TWeakObjectPtr: 약한참조를 하는 스마트 포인터 ← 댕글링 포인터 방지
    : 적 유닛(Target)을 저장할 때, 적 유닛이 소멸될 때 null 초기화를 위해 사용한다.

 - TSubclassOf: <지정한 클래스>의 파생클래스만을 참조

TWeakObjectPtr로 참조한 논 프로퍼티 객체.
GC에 의해 소멸 시 주소가 초기화된다.

※ 언리얼 내에서 GC가 관리하는 클래스는 UObject 파생클래스 한정이다.
 → TSharedPtr: UObject 미상속 외부 라이브러리 등으로 포인터를 사용할 때 사용.

UBT (언리얼 빌더 툴)의 기능

빌드 프로세스의 첫 번째 관문.

UBT의 핵심 역할
 - 가장 먼저 실행되는 빌드 도구
 - 전체 프로젝트 구조 파악
 - 플랫폼별 환경 설정

주요 기능들
 - 플러그인 및 모듈 검색
 - .Target.cs 파일 분석
 - .Build.cs 파일 처리
 - 플랫폼 호환성 확인
 - 개발 환경 자동 구성

UBT는 실행 시 플랫폼에 맞는 환경설정을 적용한다.


UHT(언리얼 헤더 툴)와 메타데이터

컴파일 전 마법사 - 코드를 에디터가 이해할 수 있게 번역한다.

UHT메타데이터에디터에 적용시킨다.
UHT가 하는 일

 - UCLASS, UPROPERTYUFUNTION 메타데이터 수집
  : 에디터 디테일 창 연동 정보 생성 (리플렉션)
  : 블루프린트 노드 연결 정보 준비 (리플렉션)

 - MyObject.generated.h 파일 자동 생성
→ 결과물: MyObject.generated.h


전체 빌드 타임라인과 라이브코딩

클래스 내의 GENERATED_BODY()를 통해 일련의 과정을 거친다.

이때, 빌드 시에만 UHT(언리얼 헤더 툴)이 작용하여 메타데이터를 수집하므로,
변경된 헤더메타데이터에디터에 적용하려면 에디터를 재시작하는 것이 좋다.

빌드 시에 UHT를 처리하므로,
헤더파일의 변경으로 메타데이터가 수정되었다면 리빌드가 필요하다.


CDO (Class Default Object)의 개념

UObject 클래스마다 하나씩 존재하는 기본값 보관소CDO라 한다.

한 UObject 클래스모든 인스턴스의 기본값해당 클래스의 CDO로부터 비롯된다.

붕어빵 틀(CDO)내에서 정해진 값의 붕어빵(인스턴스 오브젝트)이 생산된다.

생성 시점
 : 모듈 로딩 단계에서 생성
 : UBT → UHT → 컴파일 → 엔진 실행(CDO 실행)
 : 생성자 딱 한번만 호출

CDO 초기화 및 복제(Serialization)

생성자에서 초기화하는 이유
 : 헤더 수정 = 전체 리빌드 필요 (충돌우려)
 : CDO는 모듈 로딩 시 한번만 생성
 : 기본값 설정의 중요성

생성자에서 변수값들을 초기화한다.

CDO는 객체를 직접 생성하는 대신,
메모리째로 복사하여 최적화하는 방법을 택한다.

CDO없는 객체생성: 생성자 매번 실행 → CPU 부담
CDO를 사용한 생성: 클래스째로 메모리 복사 → 빠른 생성


CDO를 활용한 최적화 원리

성능과 효율성을 극대화하는 세 가지 핵심 원리

1) 메모리 절약


 : 공통 기본 값 CDO 하나에만 저장
 → 여러 인스턴스가 같은 CDO 참조

2) 빠른 초기화


 : 런타임 타임 체크 즉각 가능
 → 생성자 호출 없이 메모리 복사

3) 델타 직렬화

  : 차이점만 기록 전송
 → 파일 용량, 네트워크 효율 극대화

※ CDO가 없다면?
 : 생성자 매번 실행
 : 전체 데이터 저장/전송
 → 성능 저하

댓글

이 블로그의 인기 게시물

내일배움캠프 사전캠프 - 사전캠프설 연휴 커피 파밍 이벤트 작품 [ EXTREMITY ]

내일배움캠프 29일차 - 커리어데이 2일차 : 클라이언트 프로그래머로서 포트폴리오, 입사준비팁