OffscreenCanvas Web API позволяет нам передавать право собственности на элементы Canvas DOM рабочим.

Поскольку рабочие процессы выполняются в отдельном потоке (если возможно, используя ваш собственный ЦП), это означает, что дорогая логика JS, связанная с приложением, не может замедлить производительность рендеринга Canvas, и наоборот: дорогостоящая логика для вашего Canvas не повлияет на остальную часть вашего приложения.

Хорошие варианты использования - это манипуляции с изображениями, а также рендеринг интерактивных диаграмм или карт. Поскольку Canvas также поддерживает WebGL, создание игрового движка также имеет смысл с использованием концепции закадрового экрана.

Содержание

  1. Вступление
  2. Поддержка браузера
  3. Улучшения, связанные с фреймворком
  4. Престижность
  5. Создание демонстрационного приложения на основе neo.mjs
  6. Создание настраиваемого компонента WebGlComponent
  7. Взгляд в холст. Класс помощника
  8. Использование d3 прямо в вашем браузере
  9. Демо-видео
  10. Онлайн-демонстрации
  11. Последние мысли

1. Введение

Рабочие не имеют доступа к DOM → window, а также window.document не определены.

Достичь многопоточности без этого API довольно сложно. Проект neo.mjs неплохо решил эту проблему:

Использование рабочего приложения в качестве основного действующего лица оставляет основные потоки как можно более простаивающими. Виртуальная модель DOM обязательна для этого, поскольку ваши приложения (включая ваши компоненты) живут внутри App worker.

Чтобы получить максимальную отдачу от веб-API OffscreenCanvas, нам необходимо улучшить настройку воркеров следующим образом:

«Объединение закадрового холста и работника приложения» было бы подходящим названием для этой статьи, но мы также собираемся рассказать о создании хорошего демонстрационного приложения на основе WebGL в рамках этой настройки:

2. Поддержка браузера

Chromium (Chrome, Edge) уже хорошо поддерживает новый API.

Хотя таблица совместимости поначалу выглядит пугающей,

Важно знать, что команды Mozilla (Firefox) и Webkit (Safari) активно продвигают эту тему.



Оценка: «Идеально в этом году»



183720 - Полная реализация OffscreenCanvas
Ошибка 183720: Полная реализация OffscreenCanvas bugs.webkit.org



Теперь, когда появилась« ошибка 224178 , я считаю, что все основные функции OffscreenCanvas реализованы и включены на платформах Linux. Предстоит еще немало работы по укреплению и оптимизации этой реализации и, конечно же, по включению ее на всех платформах, но основы есть. Ура!"

Нам понадобится время, чтобы создать потрясающие реализации, так что начать сейчас имеет смысл.

3. Улучшения, связанные с платформой

Первое, что нам понадобится, это новый рабочий холст:
src / worker / Canvas.mjs

Ключевой частью здесь является использование метода afterConnect() для создания нового MessageChannel, который устанавливает прямую связь между приложением и работниками Canvas (сообщения postMessage не должны проходить через основные потоки).

Мы храним узлы Canvas внутри конфигурации map, используя
DOM (=== Component id) в качестве ключа.

Нам также необходимо создать новый класс Component:
src / component / Canvas.mjs

Мы используем конфигурацию offscreen, которая по умолчанию равна true.

После того, как новый компонент холста смонтирован, сработает метод afterSetMounted(). Если для offscreen установлено значение true, он запросит право собственности на узел холста и передаст его работнику холста (который сохранит его внутри только что упомянутой карты.

После этого конфигурация offscreenRegistered получит значение true, чтобы мы могли использовать afterSetOffscreenRegistered() как точку входа для наших собственных реализаций компонентов.

Миссия выполнена.

4. Престижность

Поскольку я не хотел описывать всю логику работы с WebGL (не по теме), я искал красивую и хорошо документированную демонстрацию.

Мне повезло!

Большое спасибо Крису Пирсу за эту прекрасную работу!



Хотя статье уже исполнился год, она по-прежнему актуальна по многим аспектам.

Один момент, с которым я категорически не согласен, - это использование нескольких MessageChannels для охвата разных категорий сообщений. Использование собственного API поверх postMessages - это способ пойти и реализовать внутри проекта neo.mjs.

Вы можете найти демо-репозиторий Криса здесь:



5. Создание демонстрационного приложения на основе neo.mjs.

Я начал с использования интерфейса командной строки и создал новое рабочее пространство, используя:
npx neo-app

Здесь вы можете найти окончательный результат:



Поскольку новый рабочий холст необязателен, первое, что нам нужно сделать, это активировать его внутри нашего neo-config.json файла:

Фреймворк использует app.mjs файл в качестве точки входа для работника приложения:

Поэтому имело смысл использовать canvas.mjs именованный файл в качестве отправной точки для нового рабочего холста:

В этом также используется метод onStart(), который позволяет запускать логику, когда рабочий будет готов.

Начнем с вида MainContainer.mjs:

Мы используем Viewport с вертикальной рамкой (vbox).

Поскольку мы используем d3, мы отображаем наш пользовательский WebGlComponent в контейнер (пользовательский тег d3-fc), который отвечает за изменение размера узла холста по мере необходимости.

Ставим внизу панель инструментов с несколькими кнопками.

Вы заметите, что срабатывают некоторые MyApp.canvas.Helper.* методы. Это основано на доступе к удаленному методу (удаленный API), и мы рассмотрим его более подробно позже.

onStopMainButtonClick() вызовет Neo.Main.alert(), поскольку рабочие не могут вызывать alert() самостоятельно. Это тоже удаленный метод. Отображение предупреждения остановит выполнение JS, связанного с основным потоком. Однако это не останавливает поток рендеринга, связанный с пользовательским интерфейсом, поэтому наш рабочий холста может продолжить анимацию нашего узла холста.

6. Создание настраиваемого компонента WebGlComponent

Требуется всего пара строк кода:

И снова d3 требует от нас создания настраиваемого узла-оболочки
(тег d3fc-canvas).

Мы используем этот узел для применения настраиваемого d3 measure domListener, поэтому мы можем держать наш корень vdom на верхнем уровне.

Однако нам по-прежнему нужен уникальный идентификатор для нашего внутреннего узла холста, поскольку мы используем его для запроса передачи права собственности. Я реализовал метод getCanvasId() на уровне component.Canvas, поэтому мы можем просто указать здесь на наш первый дочерний узел.

7. Взгляд в холст. Класс помощника

Как вы, надеюсь, помните, наша canvas.mjs точка входа импортирует canvas.Helper. Итак, этот файл (синглтон) будет работать внутри холста-воркера.

Самым важным моментом здесь является строка 55: мы добавляем некоторые имена наших методов класса в remote config.

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

Если вам интересно, что:

MyApp.canvas.Helper.enableAnimation(enableAnimation);

внутри файла MainContainer.mjs был ответ на него.

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

Поскольку наш метод является Promise, мы можем использовать then() в качестве обратного вызова или работать с async и await.

После импорта файлов d3 и d3fc мы будем следовать логике демонстрационного приложения Криса Скотта. Сгенерировав данные, серию и как только появится право собственности на узел холста, мы можем визуализировать серию.

Одно отличие от исходной демонстрации состоит в том, что изменение количества элементов (точек) не требует перезагрузки всего приложения (страницы), а динамически генерирует новую серию холста. Я также добавил функцию остановки / повторного включения анимации.

Очень удобно, что у воркеров появилась своя requestAnimationFrame() реализация. Я думаю, что OffscreenCanvas - это первый и единственный вариант его использования.

8. Использование d3 прямо в браузере.

Вероятно, это тот предмет, с которым мне приходилось проводить больше всего времени.

Глядя на демо Криса:

[Боковое примечание] d3-collection устарела на данный момент → больше не требуется.

Использование importScripts() невозможно / запрещено при использовании воркера, основанного на модуле JS.

new Worker('App.mjs', {type: 'module'})

Первое, что я попробовал:

Это прекрасно работало в режиме разработки (то есть напрямую в вашем браузере без каких-либо сборок или транспиляций).

Однако я также хотел запустить его в средах dist/development и dist/production, которые основаны на веб-пакетах.

Использование статического импорта создает гигантский разделенный кусок, и для меня это не сработало (ошибки времени выполнения JS).

Чтобы решить проблему с фрагментированием, нам нужно переключиться на динамический импорт.

d3 довольно скуп, поскольку каждый файл в основном представляет собой вложенную функцию, которая сразу запускает внутреннюю функцию. Каждая внутренняя функция требует, чтобы предыдущий импорт уже присутствовал.

В качестве решения нам нужен динамический импорт, который обрабатывается как последовательность. Вот почему я добавил в наш файл canvas.Helper следующую логику:

[Боковое примечание] Я в основном придерживаюсь «let» и запятых вместо «const», чтобы добиться меньшего размера файла.

Очень важно обернуть каждый импорт в функцию, например () => import(), чтобы гарантировать, что импорт не будет выполнен сразу. Затем мы можем использовать await для каждого из них. Определенно немного медленнее, но у нас нет выбора.

Хотя d3 может добавлять свои функции в глобальное пространство имен d3, на самом деле это не то же самое для библиотеки d3fc. Внутри dist envs он просто добавляет функции в модуль, поэтому нам нужно исправить это вручную. Вскоре я напишу отчет об ошибке.

9. Демо-видео

Вот и конец:

Мы регистрируем вспомогательный класс работника холста в консоли.

Мы можем просто изменить конфигурации здесь напрямую, и пользовательский интерфейс настроится автоматически.

10. Онлайн-демонстрации

Имейте в виду, что на данный момент демонстрации могут работать только в браузерах на основе Chromium.

Режим разработки (запуск кода как есть):
https://neomjs.github.io/pages2/workspace/neo-offscreen-canvas-demo/apps/myapp/index.html

dist / production (на основе webpack):
https://neomjs.github.io/pages2/workspace/neo-offscreen-canvas-demo/dist/production/apps/myapp/index.html

Я заметил, что анимация холста работает НАМНОГО более плавно в режиме разработки, особенно при увеличении до 1 миллиона точек.

Однако я не уверен на 100%, почему. Я предполагаю, что webpack создает функции-обертки для доступа к каждому модулю → общее количество вызовов функций, которые складываются для такого количества вызовов.

Нажатие кнопки «остановить основную» создает предупреждение (), которое приостанавливает таймер, в то время как наш холст продолжает анимацию.

11. Заключительные мысли

Если вы не нашли времени погрузиться в проект neo.mjs, вам действительно стоит:



Новый рабочий холст знаменует собой выпуск v2.3 (я скорректирую номер версии).

Завершение календаря будет перенесено в v2.4 (это довольно близко).

Теперь у нас есть базовая настройка для создания библиотеки изображений, графиков или игр внутри проекта neo.

Если кто-то захочет внести свой вклад в эту часть, мы будем очень признательны!

Я с нетерпением жду демонстрации или реальных приложений, созданных с помощью нового рабочего OffscreenCanvas!

С уважением и удачного кодирования,
Тобиас