dioxus-compose 가이드

아키텍처

두 쪽과 좁은 경계 하나

Rust 는 Compose 를 부르지 않습니다. 그릴 것을 평범한 값으로 기술하고, 반대편의 Kotlin 인터프리터가 그것을 Compose 로 바꿉니다. 둘 사이에는 작은 C ABI 가 있습니다. 동기이고, 한 스레드에서 돌고, 아무것도 복사하지 않습니다. Redwood 와 Jetpack Glance 도 같은 방식입니다.

Host 와 렌더러

Host
Rust 쪽입니다. 컴포넌트를 Dioxus VirtualDom 에서 실행하고, 상태를 갖고, HTML 화면이면 스타일과 레이아웃을 계산합니다.
렌더러
미리 네이티브 라이브러리로 컴파일한 Compose Multiplatform 이고, 정해진 그리기 원소와 위젯을 해석하는 인터프리터가 들어 있습니다. 픽셀, 텍스트 스택, 입력기, 스크롤, 포커스를 갖습니다.
저장소담는 것
dioxus-compose(이 저장소)Dioxus 렌더러: Dioxus 에서 compose-rust 로 가는 어댑터와 HTML 경로(dioxus_compose::html)
compose-rustRust 에서 Compose 쓰기: 위젯 스키마, 렌더러와 그 빌드, 경계, 디자인 시스템

나눈 기준은 각 부분이 무엇에 관한 일이냐입니다. CSS 를 그리기 원소로 바꾸는 일은 Dioxus 와 HTML 에 관한 일이므로 여기에 있고, compose-rust 는 그것을 알 필요가 없습니다. 그 원소를 그리는 일은 Compose 에 관한 일이므로 그쪽에 있고, compose-rust 의 사용자도 같은 원소를 씁니다.

HTML 경로

HTML 경로
your component (rsx! with div, span, CSS)
  -> Dioxus VirtualDom                 runs it, produces mutations
  -> blitz-dom document                Stylo resolves CSS, Taffy lays out
       text sizes from a TextMeasurer
  -> DisplayList                       boxes in paint order, absolute rectangles
  -> Plan, and diff against the last   drawing elements nested in their containers
  -> renderer (Compose)                planned: the bridge is not built yet

단계마다 들여다볼 수 있는 값이고, 예제는 그렇게 테스트됩니다. DisplayList 는 레이아웃이 정한 것입니다. 사각형, 색, 테두리, 반지름, 그림자, 클립, 텍스트 조각, 입력란, 이미지, 스크롤 컨테이너입니다. Plan 은 렌더러가 그릴 것입니다. 부모 안의 오프셋에 놓인 상자, 내용을 담은 스크롤 컨테이너, 텍스트, 입력란, 이미지이고, 색과 브러시는 한 번만 등록합니다. 그 diff 는 노드와 모디파이어 단위로 바뀐 것을 알려 주므로, 상자 하나의 배경을 바꾸면 변경 하나만 갑니다.

CSS 이름과 선택자는 렌더러로 넘어가지 않습니다. 그래서 렌더러는 작고 모든 앱에 똑같으며, CSS 기능은 Rust 의 한 곳에서 더해집니다.

아직 없는 것이 둘입니다. 계획을 compose-rust 의 AbsoluteBox 와 그리기 모디파이어로 넘기는 다리가 부분 이고, Host 가 레이아웃 도중 텍스트 조각의 크기를 Compose 에 묻는 동기 호출도 그렇습니다 (#43).

위젯 경로

위젯 이름으로 쓴 화면에서는 Host 가 Dioxus 의 변경을 고정 배치 레코드(생성, 속성 설정, 모디파이어 설정, 삽입, 이동, 삭제, 글자 설정, 글자 덧붙이기)로 바꿔 호출마다 버퍼 하나에 담습니다. 렌더러는 버퍼 전체를 Compose 스냅숏 하나 안에서 적용하므로 반쯤 적용된 트리가 그려지는 일이 없습니다.

경계

경계는 C 함수 몇 개이고, 하나를 더하는 것은 편의가 아니라 결정입니다. 본보기는 예전 React Native 브리지가 아니라 JSI 입니다. 같은 스레드, 직접 호출, 동기 반환값입니다.

프레임 모델

  1. 렌더러가 입력을 받아 UI 스레드에서 Host 를 부릅니다.
  2. Host 가 핸들러를 실행하고, diff 를 계산하고, 변경을 인코딩해 핸들러의 결과(예: 키를 소비했는지)와 함께 돌려줍니다.
  3. 렌더러가 변경을 스냅숏 하나에서 적용하고 결과를 Compose 에 알립니다.
  4. 워커 스레드가 시그널을 바꾸면 프레임 요청이 생깁니다. 요청은 Compose 프레임 시계의 다음 틱에서 프레임 하나로 합쳐집니다.

VirtualDom 은 자기 스레드가 아니라 렌더러의 UI 스레드에서 돌고, 그래서 락도 큐도 필요 없습니다. 이 설계에서 스레드를 넘는 신호는 프레임 요청 하나뿐입니다. 핸들러가 도는 동안 요청된 프레임은 핸들러가 돌아올 때까지 기다리므로, 호출 안에서 렌더러로 다시 들어가는 일이 없습니다.

무엇이 어디서 도는가

렌더러는 compose-rust 가 플랫폼마다 빌드합니다. Compose 를 그 플랫폼용으로 바로 컴파일할 수 있으면 결과물에 가상 머신이 없습니다.

플랫폼렌더러 빌드 방식Java 런타임 포함
macOSKotlin/Native아니요
LinuxKotlin/Native아니요
iOSKotlin/Native아니요
WebKotlin/Wasm아니요
Android플랫폼 자체 런타임 위의 Kotlin플랫폼의 것
WindowsGraalVM 네이티브 이미지예, 컴파일되어 들어감

Windows 가 예외인 이유는 Compose 에 Skia 와 링크할 수 있는 그곳의 네이티브 타깃이 없기 때문입니다. 앱은 어느 쪽인지 모릅니다. 경계, 레코드, 인터프리터가 모든 플랫폼에서 같습니다.