[언리얼엔진] 슬레이트 아키텍처
- ⭐ Game Programming/Unreal Document
- 2023. 5. 3.
동기
- 위젯에서 UI를 만들 때 어려운 부분
- UI 디자인과 반복 작업
- 데이터 흐름 제어 : 위젯과 내재된 데이터끼리 묶어주는것
- UI 기술(description)을 위한 외국어 습득
- IMGUI : Immediate Mode Graphics User Interface
- 장점
- 프로그래머는 UI 기술이 데이터에서 구하기 쉽다는 점에서 코드와 비슷하다는 점을 선호한다.
- 실효(Invalidation)은 문제가 되지 않고, 그냥 데이터를 직접 폴링(poll)한다.
- 인터페이스의 절차적 생성이 쉽다.
- 단점
- 애니메이션과 스타일링 추가가 어려움
- UI기술이 명령형 코드라서, 데이터 주도형으로 만들기 어려움
- 장점
- 목표
- 모델의 코드와 데이터에 쉽게 접근
- UI 절차적 생성 지원
- UI 설명이 꼬이지 않도록
- 애니메이션과 스타일을 반드시 지원
핵심 원리
- 불투명 캐시와 중복 스테이트를 피함. UI는 스테이트를 캐시에 담고 실효(Invalidation)는 명시적으로 해야한다.
- 폴링
- 투명 캐시
- 불투명 캐시에 드물게(low-grain) 실효(invalidation)
- UI 구조가 변할 때, 알림(notification)보다 폴링(Polling)을 선호 (알림을 사용하고자 한다면 빈도가 낮은곳에서 사용하자)
- 모든 레이아웃은 프로그래머 세팅에서 계산되도록 하여 피드백 루프를 회피하자. 이전 레이아웃 스테이트에 의존하지 않도록.
- 스크롤바가 UI 스테이트를 시각화시키는 경우처럼 UI 스테이트가 모델이 되는 경우는 예외다.
- 퍼포먼스 떄문이라기 보다 프로그래머의 분별을 위해서이다.
- 많은 양의 난잡한 즉성 UI에 대한 계획을 세우고, 용도를 이해하고 난 후 깔끔한 시스템으로 일반화 하자.
델리게이트와 데이터 흐름 폴링
UI는 Model을 시각화시키고 조작한다. Slate는 Model의 데이터를 읽고 쓸 필요가 있는 위젯의 통로로써 델리게이트(delegate)를 사용한다.
Slate 위젯은 Model의 데이터를 표시할 필요가 있을 때 읽어들인다. 사용자가 동작을 하면, Slate 위젯은 데이터 수정을 위해 write 델리게이트를 부른다(invoke)
특성(Attribute)과 인수(Argument)
델리게이트를 사용하는 것이 항상 바람직한 것은 아니다. 용도에 따라 Slate 위젯에 붙일 인수는 상수값이나 함수일 수도 있다. 이러한 개념은 TAttribute 클래스를 통해 캡슐화시킨다. 이러한 특성은 상수나 델리게이트로 설정 가능하다.
퍼포먼스 고려사항
- UI 복잡도는 활성화된 위젯의 갯수에 귀속된다.
- 콘텐츠의 스크롤 작업은 가능하면 가상화 시킨다.
- 화면 밖에 있는 위젯 수가 많으면 Slate 퍼포먼스를 망칠 수 있다.
- 큰 화면 사용자는 우월한 사양의 하드웨어를 사용하므로, 다수의 위젯을 처리해 낼 수 있다 가정 (왜?)
실효(invalidation) vs 폴링(polling)
실효의 경우 Model의 구조가 급격히 변하는 상황에서는 보통 기존 UI를 파기하고 재생성 한다. 그러나 스테이트는 잃게 될테니 꼭 필요할 때만 사용하자. 실효는 빈도가 낮은 이벤트에 쓰는것을 규칙으로 삼자
자손 슬롯
모든 Slate 위젯에서 자손은 자손 슬롯에 저장한다. 자손 슬롯은 항상 유효한 위젯이며, 기본적으로 시각화나 상호작용이 없는 위젯인 SNullWidget을 저장한다. 각 위젯 유형은 자체적인 요구를 충족시키는 자손 슬롯 유형을 별도로 선언 할 수 있다. 각 패널 유형은 슬롯을 통해 자손의 배치에 영향을 끼치는 자손별 세팅 집합을 요청할 수 있다.
위젯 규칙
다음 3가지 변종이 있다.
- 잎 위젯(Leaf Widgets) - 자손 슬롯이 없는 위젯. STextBlock은 텍스트를 표시하기에 더이상 자손슬롯이 필요 없다.
- 패널 (Panels) - 자손 슬롯의 수가 가변적인 위젯. SVerticlaBox는 몇개의 자손도 수직 배치가 가능하다.
- 복합 위젯(Compound Widgets) - 명시적으로 이름붙은 자손 슬롯의 갯수가 고정된 위젯. SButton에는 Content라는 슬롯이 있어 버튼 안에 몇 개의 위젯이든 포함 가능하다.
레이아웃
Slate 레이아웃은 두 패스로 이루어 진다.
- Pass 1 : Cache Desired Size - 연관된 함수는 SWidget::CacheDesiredSize and SWidget::ComputeDesiredSize
- Pass 2 : ArrangeChildren - 연관된 함수는 SWidget::ArrangeChildren
Pass 1 : Cache Desired Size
이 패스의 목표는 각 위젯이 얼마만큼 공간을 차지하려는지 알아내는 것이다. 자손이 없는(잎)위젯같은 경우 내재된(instrinsic) 프로퍼티에 따라 크기를 계산하여 캐시하도록 요청받는다. 다른 위젝과 합쳐지는 위젯(복합 위젯과 패널)은 자손 크기 결정 함수로서 크기를 결정하는데 특수한 로직을 사용한다. ComputeDesiredSize()를 구현할 때만 각 위젯 유형이 필요하다는 점에 유의하고, 캐시나 탐색 로직은 Slate가 구현한다. 위젯에서 ComputeDesiredSize()가 호출될 때, 자손에는 이미 원하는 크기가 계산 및 캐시되어있음을 Slate가 보장해준다. 이건 상향식(bottom up) 패스이다.
Pass 2 : ArrangeChildren
Arrange children은 하향식(Top down) 패스다. Slate는 최상위 창 부터 시작해서 프로그래머가 제공한 제약(Constraint)에 따라 자식을 배치할 것인지 각 창에 묻는다. 각 자손에 할당된 공간이 알려지면 Slate는 재귀를 통해 자손의 자손을 배치할 수 있다.
Slate 그리기 : OnPaint
패인트 패스 도중 Slate는 보이는 모든 위젯에 대해 반복하면서 렌더링 시스템이 소비하게 될 그리기 요소 목록을 만든다. 이 목록은 매프레임 새로 만든다.
최상위 창부터 시작하여 계층구조를 따라 내려오면서, 모든 위젯의 그리기 요소를 그리기 목록에 덧붙인다. 위젯은 페인트 도중 두 가지 작업을 한다. 실제 그리기 요소를 출력하거나, 자손 위젯이 있을 위치를 알아낸 다음 자손 위젯더러 스스로 그리라고 요청한다.
SWidget 해부도
Slate에서 SWidget의 주요 함수
- ComputeDesiredSize() - 사이즈 크기를 계산한다.
- ArrangeChildren() - 부모 할당 영역 내 자손의 배치를 담당
- OnPaint() - 출력을 담당
- 이벤트 핸들러 - OnSomething 형태의 것으로, 이들은 다양한 시점에서 Slate 가 위젯에서 부를 수 있는 함수들
Composition
Composition 이란 어느 슬롯도 임의의 위젯 내용을 담을 수 있어야 한다는 개념이다. 이로인해 Slate 사용자들은 엄청난 유연성을 확보할 수 있다. Composition 은 가능하면 언제든지, 핵심 Slate 위젯에서 사용된다.
어떤 경우엔 위젯에 특정 유형의 자손을 포함하기도 하는데, 그러한 경우 Composition 조건은 더이상 적용되지 않는다. 이들은 절대 Slate 코어 속 위젯이라기 보다는, 일정 영역 밖에서 재사용되도록 고안되지는 않은 도메인 전용 위젯이다.
선언형 문법
코드에서 Slate를 직접 접근할 필요가 있어서, UI 기술에 선언형(declarative) 언어가 필요했고, C++ 함수 바인딩에 컴파일 시간 검사도 가능하도록 하였다.
그래서 C++ 부분집합으로써 선언형 UI 기술 언어를 만들게 되었다.
문서
https://docs.unrealengine.com/5.1/ko/understanding-the-slate-ui-architecture-in-unreal-engine/