이미 정한 구조
- 모바일의 일반 UI는 태그마다 네이티브 뷰를 만들지 않고 GPU로 그립니다.
- 웹 빌드는 브라우저 DOM·CSS를 사용합니다. 동일 소스 목표가 렌더러까지 같다는 뜻은 아닙니다.
- Rust 코어가 UI 트리·스타일·레이아웃과 플랫폼 중립 렌더 데이터를 담당합니다.
- 각 프레임워크 어댑터가 자기 반응성·갱신 알고리즘을 소유합니다. React Fiber를 Rust에서 다시 만들지 않습니다.
SPINON / 모바일 렌더링 설계
프레임워크가 만든 UI 변경을 Rust의 공통 트리와 배치 결과에 연결하고, Android·iOS의 GPU 표면에 일관된 화면으로 표시하는 순서입니다. 픽셀을 그리는 렌더러와 React·Vue·Svelte의 갱신 어댑터를 분리해 책임과 검증 기준을 정리합니다.
전체 통과 조건을 이 페이지에 정리했습니다. 공식 구현 상태와 API 명세는 별도 문서가 기준입니다.
| 계층 | 입력 → 출력 | 소유 책임 | 소유하지 않는 것 |
|---|---|---|---|
| 프레임워크 어댑터 | React·Vue·Svelte 변경 → 공통 호스트 작업 | 훅·상태·반응성, 해당 프레임워크의 갱신 및 이벤트 핸들러 수명 | GPU 자원, Rust 포인터, 화면 합성 |
| Rust 문서·UI 코어 | 호스트 작업 → 검증된 트리 revision | 안정 노드 ID, 자식 순서, 변경 커밋, 스타일 입력, 오류 의미 | React Fiber나 V8 객체의 소유권 |
| 스타일·레이아웃 | 트리·스타일 값 → 계산된 스타일·박스 | 지원 CSS 계산, 측정 제약, 좌표·크기와 무효화 원인 | GPU 프레임 제출 |
spinon-render | 일관된 트리·배치 snapshot → 플랫폼 중립 표시 데이터 | 페인트 속성, 그리기 순서, 클립·변환, hit-test 데이터, 자원 참조 | JS 객체와 플랫폼 UI 객체 |
| 플랫폼 렌더 호스트 | 표시 데이터 → GPU 제출·화면 프레임 | GPU 장치·표면 수명, 자원 업로드·해제, 표시 결과와 오류 보고 | 컴포넌트 상태와 프레임워크 reconciliation |
| OS 기능 연결 | 터치·IME·접근성 요청 ↔ 노드 ID | 입력 좌표 변환, 의미 노드, 텍스트 편집 세션, 플랫폼 생명주기 | 일반 UI를 네이티브 뷰로 다시 그리기 |
핵심 구분: React 쪽 “renderer/host adapter”는 React가 만든 변경을 스피논 노드 작업으로 바꾸는 연결기입니다. GPU 렌더러는 커밋된 공통 트리와 레이아웃 결과를 픽셀로 바꿉니다. React용 연결기를 추가해도 GPU 렌더러가 React 전용이 되는 것은 아닙니다.
R06·R08·R10·R13 실험을 Android와 iOS 실기기에서 진행합니다. GPU 표면, 네이티브 프레임 시계에 따른 commit-to-present, 최소 화면, 텍스트 측정, 입력, 회전·백그라운드 복귀를 재현하고 후보 backend의 빌드 크기·지원 기기·복구 비용을 기록합니다.
통과: 두 플랫폼에서 네이티브 프레임 예약으로 commit-to-present를 확인하고, 재현 가능한 로그·화면 증거와 backend 선택 근거를 남깁니다. 성능 우위는 별도 비교 전까지 주장하지 않습니다.혼합 요소·텍스트 순서가 필요한 렌더 경로의 트리 모델, 앱 루트, commit 실패·취소·오래된 revision 복구를 정합니다. 공개 단계는 중간에 끊지 않습니다. 취소가 공개 전에 이기면 이전 트리·revision을 유지하고, 공개가 먼저면 취소는 늦은 요청으로 처리합니다. JS 어댑터의 최종 결과도 실제 공개 상태와 일치해야 합니다.
통과: 공개 직전과 직후에 취소를 멈춰 둔 두 fixture에서 트리 상태·렌더 대상 revision·어댑터의 최종 결과가 일치하고, 부분 변경이 snapshot에 나타나지 않습니다.spinon-render가 공개된 revision만 읽어 표시 명령·자원 표·hit-test 데이터를 만듭니다. 폰트 측정기와 이미지 제공자는 결정적 Rust 테스트에서 주입할 수 있게 둡니다.
CSS 좌표와 OS 논리 좌표·물리 픽셀, 배율·안전 영역·반올림 규칙을 고정합니다. 스타일 결과는 입력 트리 revision을, 레이아웃 결과는 입력 트리·스타일 revision과 결과 revision을 보유합니다. tuple이 맞지 않는 늦은 결과는 버립니다.
통과: 네이티브 프레임 driver로 commit-to-present를 확인하고, 중첩·겹침과 stale 계산 fixture의 계산 상자·화면 순서·클립·터치 대상이 Android·iOS에서 맞습니다. 이 단계는 공개 JSrequestAnimationFrame을 요구하지 않습니다.측정과 GPU 표시가 같은 shaping·fallback 결과를 사용합니다. 이미지 본래 크기 변화는 관련 레이아웃 조상과 paint·hit-test·접근성 bounds를 무효화합니다. 제거·교체·OTA 이후의 늦은 응답은 세대가 다르면 버립니다.
통과: 한글·라틴 측정·표시, 이미지 크기 변경·실패·늦은 응답, 표면 재생성 뒤 자원 업로드를 확인합니다.입력과 OS 의미 노드는 같은 마지막 표시 frame revision과 변환을 사용합니다. OS 제스처 취소와 JS 이벤트 의미를 분리하고, 이벤트 순서·캡처·버블링·stopPropagation()·preventDefault()·포인터 취소·키보드 및 접근성 활성화·삭제 노드 대상·콜백 수명을 fixture로 고정합니다. 플랫폼 취소만으로 pointercancel을 가정하지 않습니다.
U07·E06 범위에서 scroll offset, 중첩 제스처와 관성을 구현하고 공개 JS requestAnimationFrame·cancelAnimationFrame의 의미를 정합니다. OS 프레임 시계와 commit 때의 단발 렌더 예약은 00·03단계에서 먼저 제공합니다.
동시 프레임 제출 수와 역압력, 표면 획득 실패·대기·취소, 자원 참조 퇴역 시점을 정합니다. 글리프·이미지 캐시의 메모리 예산·축출·재업로드를 표면 세대와 연결합니다.
통과: 빠른 추가·삭제, 회전, 표면 손실, 장시간 실행·메모리 압박 뒤 stale handle·누수·오래된 성공 보고가 없습니다.R05·S07·S09 측정은 공식 benchmark 규칙과 공통 fixture를 사용합니다. 웹·Android·iOS의 실제 기능 차이를 먼저 기록하고 동등한 시나리오만 비교합니다.
통과: 같은 실기기 조건의 원본 측정 자료가 남고, 기능 차이가 있으면 성능 순위를 매기지 않습니다.R15·D02·D04 단계는 정식 OTA 설계가 검증한 불변 릴리스 snapshot을 소비합니다. release ID·manifest digest·binary runtime ID와 JS/CSS/폰트/이미지 해시를 프레임·자원 세대에 묶습니다. 렌더러는 digest를 불투명한 snapshot 식별자로만 쓰고, 생성·표준화·서명 검증 규칙은 OTA 계약에 둡니다.
통과: digest 계약을 R15/D02에서 정의한 뒤, 다른 snapshot이나 비호환 조합을 거부하고 정식 OTA 계약에 따라 이전 정상 릴리스로 복구합니다.| 시나리오 | 확인할 불변 조건 | 연결 ID |
|---|---|---|
| 변환된 부모 안에서 겹치고 잘리는 버튼 | 화면상 맨 위의 보이는 버튼과 hit-test·접근성 활성화 대상이 일치합니다. | S04, S07, E03, R10 |
| 중첩 스크롤 중 OS 제스처 취소·관성 종료 | native gesture와 JS 이벤트 결과를 분리합니다. JS 의미는 이벤트 순서·캡처·버블링·대상·취소·기본 동작·콜백 수명 fixture로 고정된 경우에만 판정합니다. | U07, E03, E06 |
| 작업 묶음 중 오류·취소·오래된 commit | 취소와 공개 중 먼저 선형화된 결과만 적용하며, 이전 공개 revision 또는 전체 새 revision만 표시합니다. JS 어댑터 결과도 공개 revision과 일치합니다. | R03, R06, S01, S05 |
| commit 취소와 공개 경합의 두 순서 | 공개 직전 취소는 이전 트리·revision과 취소 결과를 유지하고, 공개 직후 취소는 새 revision과 성공 결과를 유지합니다. 어댑터 결과와 트리 상태가 같으며 부분 트리가 없습니다. | R03, R06, S01 |
| 새 트리·스타일 뒤 이전 스타일·레이아웃 계산이 늦게 완료 | 입력 revision 중 하나라도 frame tuple과 다르면 결과를 폐기하고, 화면·hit-test·접근성이 같은 수락 tuple을 사용합니다. | R03, R10, E01, E03 |
| 이미지·글리프 업로드 중 노드 삭제와 표면 재생성 | 제출 중 자원은 안전하게 유지하고 오래된 응답·handle은 버리며 새 표면 세대에서 다시 만듭니다. | R08, R13, S11, E02, E07 |
| OTA 뒤 이전 릴리스의 이미지·폰트 응답 도착 | 활성 immutable snapshot의 release ID·manifest digest·runtime ID·JS/CSS/폰트/이미지 해시가 모두 맞는 자원만 새 프레임에 포함하고, 나머지는 거부·롤백합니다. | R15, D02, D04 |
세부 계측은 공식 benchmark 계획을 따릅니다. 이 페이지는 별도의 측정 계약을 만들지 않습니다.
| 공식 항목 | 이 계획에서 다루는 부분 | 완료 증거의 중심 |
|---|---|---|
| R03 · 공통 호스트 계약 | 01–02 | 루트·혼합 텍스트·commit 실패 복구·revision |
| R05 · 계측 계약 | 08 | 공식 benchmark 조건과 화면 표시 계측 자료 |
| R06 · 스레드·소유권 | 00–02, 05–07 | 취소·경합·프레임과 자원 수명 |
| R08 · GPU 출력 위험 실험 | 00 | Android·iOS GPU 최소 화면, 텍스트·터치·IME·접근성 가능성 및 제약 |
| R10·R13 · 레이아웃·복구 위험 | 00–03, 07 | 좌표·측정, 표면 재생성·자원 복구 근거 |
| S02 · Taffy 레이아웃 | 01–04 | 공통 좌표 fixture와 텍스트 측정 경계 |
| S04 · GPU 적용기 | 01–03, 05 | 변경 배치 → GPU 장면 → 플랫폼 입력의 첫 동작 |
| S07 · 텍스트·터치·화면 | 04–05 | 폰트 측정·표시·hit test·접근성 이름의 기기 증거 |
| S09 · 네 구현 비교 | 08 | 동등 시나리오의 실기기 측정 |
| S11 · 표면·콜백 수명 복구 | 00, 07 | 회전·복귀·표면 재생성 뒤 ID·콜백·GPU 자원 복구 |
| U07·E01–E07 | 05–07 | 스크롤·부분 갱신·GPU 자원·이벤트·제스처·프레임 예약 |
| E01 · 부분 레이아웃 계산 | 02, 06 | 계산된 박스의 정확성과 변경된 범위만 다시 계산하는 근거 |
| E02 · GPU 텍스트·이미지 품질 | 04, 06 | 글리프·이미지 자원, 클립·합성 품질과 메모리 |
| E03 · 입력·접근성 | 05 | hit test, IME, OS 의미 노드와 활성화 결과 |
| E04–E05 · 프레임 배치·스케줄링 | 06 | 무효화 정확성, 제출 수, 지연, 프레임 누락, 자원 사용량 |
| E06 · 제스처·애니메이션 | 05–06 | 입력 취소·동시 제스처와 프레임 진행 규칙 |
| E07 · 장시간·오류 회귀 | 00, 07 | 반복 복구, 취소, 누수, 비정상 종료와 오류 진단 |
| E08 · 시작 시간 최적화 | 렌더러 범위 밖 | V8·번들 초기화 측정으로 별도 관리 |
| R15·D02·D04 · 청크 OTA | 09 | 릴리스 자원 일치·원자 활성화·롤백 |
공식 완료 여부는 공식 구현 상태 대장만 기준으로 삼습니다. 이 페이지와 원문 초안은 상태 체크나 “지원 완료” 선언을 대신하지 않습니다.
네이티브 뷰 PoC: 공식 아키텍처에서 기존 PoC는 비교 기준으로 보존합니다. GPU 주 렌더링 경로의 대안으로 구현하지 않습니다.
공용 라이브러리 또는 플랫폼별 경로를 지원 기기, 앱 용량, 빌드·디버그 난이도, GPU 손실 복구, 실제 프레임 자료로 비교합니다. 현재 문서는 특정 API를 선택하지 않습니다.
첫 구현은 안정된 노드 ID를 가진 revision snapshot을 기준으로 삼고, 표시 명령은 결정적으로 만들 것을 제안합니다. dirty-region 최적화는 정확한 전체 프레임이 먼저 나온 뒤 측정합니다.
플랫폼 폰트 서비스와 공통 래스터 backend를 비교합니다. 측정값과 그리는 글리프가 다르지 않아야 하며, 한글 조합·fallback·이모지·RTL의 지원 차이를 공개합니다.
UI 입력 처리와 JS 실행의 동기 경계, GPU queue 소유자, 취소·역압력·오류 전달을 실험합니다. 스레드/고루틴 선택을 렌더러 계약보다 먼저 고정하지 않습니다.
시각 요소를 네이티브 뷰로 바꾸지 않고 OS 접근성 인터페이스에 의미 트리를 노출하는 방향을 검증합니다. IME·선택·커서는 GPU 표시와 플랫폼 입력 세션 사이의 책임을 분리합니다.
표시 snapshot·GPU handle은 Rust 내부와 플랫폼 host 경계에 둡니다. 앱 작성자용 imperative drawing/WebGPU API는 별도 요구가 명세되기 전까지 이 계획에서 제공한다고 약속하지 않습니다.
| 영역 | 기록할 값 | 잘못된 완료 판정을 막는 규칙 |
|---|---|---|
| 결과 정확성 | revision, 계산 박스, 표시 명령, 이벤트 대상, 실패 이유 | 트리 commit만으로 화면 표시 완료라 하지 않고 present 결과를 별도로 확인합니다. |
| 시각 회귀 | Android·iOS별 캡처, OS·기기·배율, 글꼴·색 차이 | 서로 다른 GPU·OS의 픽셀 일치를 전제하지 않습니다. 박스/명령 기대값과 플랫폼별 허용 오차를 함께 둡니다. |
| 입력·접근성 | 좌표·화면 revision·대상 노드·콜백 수명, 접근성 탐색 결과 | 자동화된 Rust 테스트와 실제 기기의 IME·VoiceOver·TalkBack 결과를 분리해 보고합니다. |
| 성능 | commit-to-present p50/p95, CPU layout/paint, GPU 시간, 누락 프레임, 메모리 | 실기기·릴리스 조건·같은 UI 시나리오끼리 비교하고 시뮬레이터 수치를 섞지 않습니다. |
| 수명·오류 | surface generation, 재생성 횟수, 자원 생성·해제, 복구 결과 | GPU 오류 뒤 오래된 프레임을 새 성공 프레임처럼 표시하지 않습니다. |
renderer-plan.md입니다. 공식 저장소에 아직 반영되지 않았으며, 구현 계약은 별도 버전 있는 내부 명세와 공식 상태 ID에 연결합니다.