아키텍처 개요
양쪽의 소유권
TSX / hooks → Mutation Stream → Scene / Layout / Paint
(TypeScript Shell) 바이너리·배치 (Rust Core, wasm)
↓
Canvas2D 리플레이어 ← DisplayList ← PictureShell은 컴포넌트 트리를, Core는 Scene을 소유합니다. 둘은 가변 객체를 공유하지 않습니다. 경계를 넘는 모든 통신은 버전이 부여된 바이너리 스트림입니다. 리틀 엔디언, 4바이트 정렬, 명령 단위이며, 수신 측은 메모리에 접근하기 전에 opcode, 길이, 정렬, ID, 산술을 검증합니다. 잘못된 입력은 부분 적용 없이 원자적으로 거부됩니다.
이 경계는 성능 최적화가 아니라 정확성의 경계입니다. 바이트가 보통 이 프로젝트 자신의 인코더에서 오더라도 디코더는 신뢰할 수 없는 입력으로 취급하며 퍼징으로 보호됩니다.
이중 클록
UI 클록(메인 스레드)과 렌더링 클록(Worker)은 서로 독립적입니다.
- 메인 스레드는 입력을 수집하고 컴포넌트 트리를 실행하며 Mutation 프레임을 커밋합니다.
- Worker는 스크롤 물리, 애니메이션, 레이아웃, 합성을 구동합니다.
스크롤 중에는 Shell을 호출하지 않습니다. 없는 데이터는 자리표시자로 그리고 이후 프레임에서 보완합니다. 그래서 애플리케이션 코드가 메인 스레드를 200ms 막아도 스크롤과 애니메이션은 끊기지 않습니다. 이 상황은 자동 장애 주입 테스트가 지킵니다.
폴백 체인
능력 탐지가 순서대로 전송 경로를 고릅니다. 세 단계는 기능적으로 동등합니다.
- SharedArrayBuffer —— 교차 출처 격리(COOP/COEP)가 필요
- postMessage —— SAB를 쓸 수 없을 때
- 메인 스레드 Canvas2D —— Worker / OffscreenCanvas를 쓸 수 없을 때
const root = await createHostedCanvasRoot(canvas, {
transport: { preference: "sab" }, // 선호일 뿐이며 충족되지 않으면 폴백합니다
});
console.log(root.mode); // "sab" | "post-message" | "main-thread"이 사이트의 Playground가 살아 있는 예입니다. GitHub Pages는 COOP/COEP 헤더를 내려줄 수 없으므로 배포본은 postMessage 경로로 동작하고, 페이지 상단의 transport 배지가 이를 그대로 보여 줍니다.
무효화 모델
prop의 의미가 무효화 도메인을 결정합니다. 호출자가 직접 더티를 표시하지 않으며 forceUpdate 같은 탈출구도 없습니다.
각 속성은 단일 스키마에서 레이아웃, 페인트, 히트, 시맨틱 중 무엇에 영향을 주는지 선언합니다. opacity를 바꿔도 재레이아웃은 일어나지 않지만 width를 바꾸면 일어납니다. 더티 비트맵은 도메인별로 유지되고 onFrame이 도메인별 더티 노드 수를 노출합니다.
이 선택은 "공격적으로 가장 좁은 무효화 + 속성 테스트라는 안전망"입니다. 증분 렌더링 결과는 전체 렌더링과 픽셀 단위로 일치해야 하며, 차분 테스트가 반례를 최소 실패 사례로 축소합니다.
Scene 표현
Core 안의 Scene은 SoA(구조체 배열이 아니라 배열의 구조체)입니다.
- 노드 ID는 세대를 포함하므로 슬롯을 재사용해도 오래된 ID가 다시 유효해지지 않습니다.
- 커밋 후에는 토폴로지 순서를 유지합니다. 부모는 항상 자식보다 앞에 옵니다.
- 구조 변경의 압축은 mutation 단위가 아니라 커밋 단위로 한 번만 수행합니다.
- 레이아웃 결과는 이중 버퍼 SoA로 일괄 비교하며, 핫 패스에서 노드마다 클로저나 리스너를 할당하지 않습니다.
교체 가능한 백엔드
Core는 평탄한 바이너리 DisplayList를 출력하고 백엔드는 리플레이어일 뿐입니다. Canvas2D 백엔드는 할당을 아끼는 typed-array 루프입니다. 그리기마다 wasm→JS를 호출하는 방식은 허용 가능한 렌더링 경로가 아닙니다.
같은 DisplayList를 격리된 wgpu 프로토타입에도 넣고 두 출력을 픽셀 차분합니다. WebGPU 채택 여부는 데이터로 결정하며 ADR-0006에 있습니다.
결정성
시간, 난수원, 입력 스트림은 모두 주입하거나 재생할 수 있고 Core 출력은 스레드 스케줄링 순서에 의존하지 않습니다. DOPR 아카이브는 Mutation과 Input 스트림을 원래 순서대로 기록하며 브라우저 없이 헤드리스 환경에서 결정적으로 재생할 수 있습니다. 덕분에 운영 문제를 로컬에서 재현할 수 있고, 민감한 편집 스트림은 명시적으로 기록에서 제외됩니다.
더 깊이
알고리즘, 자료구조, 수용 기준 전체는 기술 설계 문서에 있습니다.