게임플레이 아키텍처

UObject와 UCLASS 매크로
UObject에서 파생되는 클래스에 UCLASS 매크로를 사용하여 태그를 해주면 UObject 처리 시스템에서 인식하게 된다.
각 UClass는 Class Default Object라는 오브젝트를 하나 유지한다.
UClass에는 클래스를 정의하는 프로퍼티와 함수 세트가 들어있다. 이것들은 네이티브 코드에서 사용할 수 있는 일반 C++ 함수와 변수
+ 오브젝트 시스템 안에서의 동작을 제어하는 언리얼 전용 메타데이터까지 포함한다.

UObject에 제공되는 함수성
가비지 컬렉션
직렬화
네트워크 리플리케이션
자동 에디터 통합
레퍼런스 업데이트

비동기 애셋 로딩
ㄴ UObject* 프로퍼티 대신 FSoftObjectPath를 사용하여 특정 애셋에 대한 레퍼런스를 만들되 항상 로드되지는 않도록 한다.
ㄴ FSoftObjectPath를 약하게 참조하는 TSoftObjectPtr을 사용하여 애셋이 메모리에 존재하는지 값을 반환 받을 수 있고, 없다면 메모리에 로드한 다음에 다시 참조한다.

명령줄 인수
엔진 실행 방식을 조절하는 옵션 설정을 위해 엔진 실행파일에 전해줄 수 있는 인수
ㄴ 모드 : 메인 에디터 실행파일을 게임 혹은 서버로 실행시킬 수 있다.
ㄴ URL 파라미터 : 게임 시동시 특정 맵을 강제로 로드시키기 위해 맵의 주소를 전달
ㄴ 스위치 : 특정 키워드를 값 또는 키-값 쌍으로 게임이나 에디터에 전달할 수 있다.
ㄴ INI / 환경설정 : 게임이나 에디터가 로드하는 INI 파일을 임시로 덮어쓸 수 있다.

데이터 유효성 검사
ㄴ UObject에 정의된 IsDataValid 가상함수를 오버라이드하여 사용자 정의 조건으로 유효성을 검사할 수 있다.
ㄴ Data Validation 플러그인을 사용
ㄴ 애셋이 명명 규칙을 충족하는지 확인
ㄴ 공간 및 퍼포먼스 예산 집행
ㄴ 비순환 종속성 발견
ㄴ 단일 애셋 ~ 프로젝트 전체 범위로 데이터 유효성 검사를 할 수 있다.

애셋 레지스트리 
ㄴ 애셋 레지스트리는 에디터가 로드되면서 로드되지 않은 애셋에 대한 정보를 비동기적으로 모으는 서브시스템이다.
ㄴ 애셋에 대한 데이터를 검색 가능하게 만드려면, 프로퍼티에 AssetRegistrySearchable 태그를 추가해 줘야 한다.
ㄴ 애셋을 로드하지 않고 목록을 만들 수 있도록 메모리에 저장된다.

클래스별 애셋 목록 만들기
ㄴ 애셋 레지스트리 모듈을 로드한 다음 Module.Get().GetAssetsByClass를 호출하여 얻는다.
ㄴ 목록은 FAssetData 오브젝트 배열에 반환된다.
ㄴ FAssetData는 GetAsset함수를 사용하여 UObject*로 변환할 수 있다.

FAssetData에는 애셋의 프로퍼티 값의 목록인 TagsAndValues라는 이름과 값의 매핑이 들어있다.
이 정보는 애셋이 저장되어 UAsset 파일의 헤더에 기록될때 수집된다.
UPROPERTY 매크로의 AssetRegistrySearchable 플래그로 마킹된 프로퍼티만 TagsAndValues 맵을 채운다.
<변수이름 - 값>의 형태로

태스크 시스템
ㄴ 게임 플레이 코드를 비동기 실행할 수 있는 기능을 제공하는 작업 매니저, 종속 작업에 대해 방향성 비사이클 그래프를 만들고 실행할 수 있다.
ㄴ 태스크를 실행하려면 태스크의 디버그 이름과 호출 가능한 태스크 바디 오브젝트를 제공해야 한다.
ㄴ 태스크 실행(Launch)은 결과 FTask(태스크 핸들)를 반환할 수 있다. 태스크 바디에서 반환한 결과의 타입과 일치하는 TTask<타입> 으로 받는다.
ㄴ 태스크 우선순위는 태스크의 실행 순서에 영향을 준다. Launch할 때 인자로 지정할 수 있다.

busy wait 
ㄴ 대기중인 태스크가 완료될 때까지 다른 작업을 실행하려는 시도
ㄴ 스케줄러가 태스크를 선택할 수 없다..
ㄴ 데드락의 위험도 있다.

전제조건
ㄴ 선행 태스크 핸들을 후속 태스크 Launch할 때 인자로 넣어주면 태스크 간 종속성을 표현할 수 있다.

중첩된 태스크
ㄴ 전제 조건과 다른 점은 두 태스크가 동시에 실행되어도 괜찮지만, 완료는 순서가 있다는 점
ㄴ 태스크 A의 바디에서 태스크 B를 Launch하고, AddNested(TaskB)를 호출하면 태스크B는 A의 자식 태스크가 된다
ㄴ TaskA.Wait을 하게 되면 TaskB와 TaskA가 완료될 때까지 기다린다.
AddNested 함수는 태스크 내부에서 호출하지 않는 경우 Assert를 호출한다.

파이프
ㄴ 동시 실행이 아닌 연속적으로 실행되는 태스크로 이루어진 체인
ㄴ 여러 스레드에서 공유 자원에 접근할 때 뮤텍스를 잠그는 방법을 사용하지 않음
ㄴ 공유된 리소스에 대한 비동기 작업을 위해 존재한다. (비동기 인터페이스를 직접 구현할 필요가 없다)
리소스마다 파이프를 두고, 공유된 리소스에 대한 모든 액세스는 파이프에서 실행한 태스크 내부에서 수행한다.

태스크 이벤트
ㄴ 태스크 바디가 없고 실행할 수 없는 태스크 타입
ㄴ 명시적으로 Trigger함수를 호출해야 실행된다.
ㄴ 다른 태스크의 전제 조건으로 활용하여 다양한 작업을 할 수 있다.

언리얼 오브젝트 (UObject) 처리
ㄴ 자동 프로퍼티 초기화 : 생성자 호출 전 초기화시 자동으로 0으로 채워진다. (클래스, UProperty, 네이티브 멤버 모두)
ㄴ 레퍼런스 자동 업데이트 : AActor 또는 UActorComponent가 소멸되거나 다른 식으로 플레이에서 제거되면 리플렉션 시스템에서 보이고 있는 그에 대한 모든 레퍼런스는 자동으로 null이 된다.
UProperty가 아닌 오브젝트 포인터가 필요한 경우, TWeakObjectPtr을 대신 사용
에디터에서 애셋을 Force Delete한 경우에도 참조된 UObject UProperty가 자동으로 null이 된다.

Serialization

런타임 유형 정보 및 형변환
ㄴ 자식 클래스에서 Super로 부모 클래스에 접근할 수 있다.
ㄴ Cast 함수 템플릿을 사용해서 UObject를 형변환할 수 있다.

가비지 컬렉션
ㄴ 엔진에서는 레퍼런스 그래프를 만들어 어느 오브젝트가 아직 사용중이고 어느 것이 필요 없는지
ㄴ 주기적으로 소멸 예약시킨 UObject를 정리할 때, UObject 레퍼런스 트리를 검색하여 참조되지 않은 오브젝트,
즉 트리 검색에서 찾지 못한 것들은 더 이상 필요하지 않은 오브젝트라고 가정하고 제거한다.
ㄴ 살려두고자 하는 오브젝트가 있다면 UPROPERTY 레퍼런스를 유지하거나 그에 대한 포인터를 언리얼 엔진 컨테이너 클래스에 저장해야 한다.
ㄴ 액터는 자신의 Destroy 함수를 호출하여 명시적으로 소멸 예약시킨다. (컴포넌트는 소유 액터가 제거될 때 소멸)
ㄴ 가비지 컬렉션 튜닝을 위한 세팅은 프로젝트 세팅 -> 가비지 컬렉션에서 설정할 수 있다.

네트워크 리플리케이션
ㄴ 흔히 쓰는 모델 : 서버에서 변수가 변경되면 엔진에서 그 변화를 감지하여 모든 클라이언트에 신뢰성 있게 전송하는 것
ㄴ UProperty에 태그를 설정하면 네트워크 플레이 도중 데이터 복제 여부를 엔진에게 알릴 수 있다.
ㄴ UFuncion에 태그를 설정하면 server 태그가 붙은 함수는 클라이언트 머신에서 호출할 때 서버 머신에서 해당 액터의
서버 버전이 실행되도록 한다. client 함수는 서버에서 호출될 수는 있지만 해당 액터의 소유중인 클라이언트 버전이 실행된다.

언리얼 스마트 포인터 라이브러리
ㄴ TSharedPtr : 참조하는 오브젝트를 소유하며, 참조하는 쉐어드 포인터 또는 쉐어드 레퍼런스가 없을 경우에 오브젝트를 소멸시킨다. 
ㄴ TSharedRef : sharedPtr과 비슷하지만 항상 Null이 불가능한 오브젝트를 참조해야 한다는 점이 다르다.
ㄴ TWeakPtr : 오브젝트를 소유하지 않기 때문에 생명 주기에 영향을 주지 않는다.
ㄴ TUniquePtr : 다른 유니크 포인터에 넘겨줄 때는(소유권 이전) MoveTemp 함수로 넘겨준다.

스마트 포인터 구현 세부사항
ㄴ 일부 스마트 포인터 타입은 C++ 기본 포인터보다 느리기 때문에 렌더링과 같은 로우 레벨 엔진 코드에는 덜 유용하다.

스마트 포인터의 이점
ㄴ 메모리 누수 방지 : 스마트 포인터(위크 포인터 제외) 들은 공유된 레퍼런스가 없으면 오브젝트가 자동 소멸된다.
ㄴ 위크 레퍼런싱 : 위크 포인터는 참조 주기에 영향을 주지 않고 댕글링 포인터를 방지한다.
ㄴ 선택적인 스레드 안전 : 스레드 세이프 스마트 포인터와 그냥 스마트 포인터가 구분되어 있어서 스레드 안정성이 필요없다면 향상된 퍼포먼스를 구현할 수 있다.
ㄴ 런타임 안정성 : 쉐어드 레퍼런스는 절대 null일 수 없고, 언제든지 참조 해제할 수 있다.
ㄴ 메모리(?) : 스마트 포인터는 64비트 C++ 포인터 크기의 두 배(128비트)이다. 유니크 포인터는 예외로 64비트 크기
ㄴ 명확한 의도 : 관찰자 중에서 오브젝트의 소유자를 쉽게 분별할 수 있다.

스마트 포인터의 퍼포먼스 이점 
ㄴ 빌드를 출시할 때, 대부분의 스마트 포인터들을 참조 해제하는 속도가 C++ 기본 포인터만큼 빠르다.
ㄴ 스마트 포인터를 복사해도 절대 메모리가 할당되지 않는다.
ㄴ 스레드 세이프 스마트 포인터는 잠금 없는 구조이다.

스마트 포인터의 퍼포먼스 문제점
ㄴ 스마트 포인터의 생성 및 복사는 C++ 기본 포인터의 생성 및 복사보다 더 많은 오버헤드가 발생한다.
ㄴ 참조 카운트를 많이 유지하면 기본 사이클(?)이 복잡해진다. 

스레드 안정성
기본적으로 스마트 포인터는 싱글 스레드가 접근하는 것에 안전하기 때문에 멀티 스레드가 접근해야한다면 
스마트 포인터 클래스의 스레드 세이프 버전을 사용해야 한다.

+ Recent posts