Skip to content

Архитектура

Кто чем владеет

TSX / хуки              →  Mutation Stream  →   Scene / Layout / Paint
(оболочка TypeScript)     бинарный, пакетами    (ядро на Rust, wasm)

Проигрыватель Canvas2D  ←   DisplayList      ←      Picture

Оболочка владеет деревом компонентов, ядро владеет Scene. Они не разделяют изменяемые объекты. Всё общение через эту границу — версионированные бинарные потоки: little-endian, выравнивание по четыре байта, в виде инструкций. Получатель проверяет опкод, длину, выравнивание, идентификаторы и арифметику до обращения к памяти, а некорректный ввод отклоняется атомарно, а не применяется частично.

Эта граница — не оптимизация производительности, а граница корректности: даже когда байты обычно приходят от собственного кодировщика проекта, декодер считает их недоверенным вводом и покрыт фаззингом.

Две тактовые оси

Часы интерфейса (главный поток) и часы рендеринга (Worker) независимы:

  • Главный поток собирает ввод, выполняет дерево компонентов и коммитит кадры Mutation.
  • Worker ведёт физику прокрутки, анимацию, раскладку и композицию.

Установившаяся прокрутка не вызывает оболочку. Отсутствующие данные рисуются заглушками и дополняются на следующих кадрах. Поэтому, если прикладной код блокирует главный поток на 200 мс, прокрутка и анимация остаются непрерывными; этот сценарий защищён автоматическими тестами с внедрением отказов.

Цепочка отката

Определение возможностей выбирает транспорт по порядку; все три ступени функционально эквивалентны:

  1. SharedArrayBuffer — требует изоляции между источниками (COOP/COEP)
  2. postMessage — когда SAB недоступен
  3. Canvas2D в главном потоке — когда недоступны Worker или OffscreenCanvas
ts
const root = await createHostedCanvasRoot(canvas, {
  transport: { preference: "sab" }, // это лишь предпочтение; если оно недостижимо, произойдёт откат
});
console.log(root.mode); // "sab" | "post-message" | "main-thread"

Playground этого сайта — живой пример: GitHub Pages не может отдавать заголовки COOP/COEP, поэтому опубликованная версия работает по postMessage, и значок транспорта наверху страницы честно это показывает.

Модель инвалидации

Семантика свойства определяет домен инвалидации. Вызывающий код ничего не помечает грязным вручную, и лазейки вроде forceUpdate нет.

Каждое свойство в схеме с единственным источником объявляет, на что оно влияет: раскладку, отрисовку, попадания или семантику. Смена opacity не вызывает переразметку, а смена width вызывает. Битовые карты «грязного» ведутся по доменам, а onFrame показывает число грязных узлов в каждом.

Выбор такой: «инвалидировать максимально узко, а страховкой служат property-тесты». Результат инкрементального рендеринга обязан совпадать с полным попиксельно, а дифференциальные тесты сжимают любой контрпример до минимального падающего случая.

Представление Scene

Внутри ядра Scene хранится как SoA (структура массивов вместо массива структур):

  • Идентификаторы узлов содержат поколение: повторное использование слота никогда не оживляет устаревший идентификатор.
  • После коммита сохраняется топологический порядок: родитель всегда идёт раньше детей.
  • Уплотнение структурных правок выполняется один раз за коммит, а не на каждую мутацию.
  • Результаты раскладки сравниваются пакетно по SoA с двойной буферизацией, без выделения замыканий и слушателей на узел в горячем пути.

Сменный бэкенд

Ядро выдаёт плоский бинарный DisplayList, а бэкенд — всего лишь проигрыватель. Бэкенд Canvas2D — это экономный к аллокациям цикл по типизированным массивам: вызов wasm→JS на каждую операцию рисования не является приемлемым путём рендеринга.

Тот же DisplayList подаётся в изолированный прототип на wgpu, и оба вывода сравниваются попиксельно. Принимать ли WebGPU — решение по данным, см. ADR-0006.

Детерминизм

Время, источник случайности и потоки ввода внедряемы или воспроизводимы, а вывод ядра не зависит от порядка планирования потоков. Архив DOPR записывает потоки Mutation и Input в исходном порядке и детерминированно воспроизводится без браузера, в headless-среде: благодаря этому проблему из продакшена можно повторить локально, а чувствительные потоки редактирования явно исключены из записи.

Углубиться

Полные алгоритмы, структуры данных и критерии приёмки собраны в документе технического дизайна.

Опубликовано под лицензией MIT