Система событий в Deck.gl

Система событий в Deck.gl построена вокруг абстракции взаимодействия между низкоуровневым WebGL-контекстом и высокоуровневыми декларативными слоями. В основе лежит механизм picking, диспетчеризация pointer-событий и единая модель обработки пользовательского ввода, интегрированная с рендер-циклом.

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

Ключевые компоненты:

  • слой контроллеров (controllers), отвечающих за преобразование пользовательского ввода в изменения камеры;
  • система picking, выполняющая идентификацию объектов под курсором;
  • диспетчер событий Deck, агрегирующий и нормализующий события;
  • слой взаимодействия, передающий события в props слоёв.

В рамках Deck.gl события формируются как результат синхронизации этих компонентов.

Механизм picking как основа событий

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

Алгоритм включает этапы:

  1. Рендер сцены в picking framebuffer.
  2. Кодирование objectIndex и layerId в цветовые каналы.
  3. Чтение пикселя под координатами курсора.
  4. Декодирование идентификатора объекта.
  5. Формирование события.

Результат операции — объект события, содержащий ссылки на слой, индекс экземпляра и геометрию.

Структура события

Событие в системе Deck.gl представляет собой расширенный объект, включающий как данные DOM-события, так и метаданные сцены.

Типичная структура:

  • type — тип взаимодействия (click, hover, dragstart и др.);
  • coordinate — координаты в мировом пространстве;
  • pixel — экранные координаты;
  • object — объект данных слоя;
  • layer — ссылка на слой-источник;
  • index — индекс экземпляра;
  • lngLat — географические координаты (если используется геопространственный контекст);
  • picked — флаг успешного picking.

Дополнительно могут присутствовать вычисленные поля камеры и матрицы преобразования.

Типы событий

Событийная модель включает несколько категорий взаимодействий.

Pointer-события

Pointer-события являются базовым уровнем:

  • pointermove — движение курсора;
  • pointerdown — нажатие;
  • pointerup — отпускание кнопки;
  • pointerleave — выход за пределы canvas.

Они формируются из DOM-событий, но проходят через нормализацию координат и проверку picking.

Семантические события

На основе pointer-событий формируются более высокоуровневые:

  • click — комбинация pointerdown + pointerup на одном объекте;
  • hover — активное наведение на объект;
  • dragstart, drag, dragend — последовательность перемещения объекта.

Эти события зависят от состояния слоя и конфигурации контроллера.

Интеграция с системой слоёв

Каждый слой в Deck.gl может объявлять обработчики событий в props. При этом события не закреплены за DOM-узлами, а маршрутизируются через систему диспетчеризации.

Пример логики:

  • событие проходит picking;
  • определяется слой-источник;
  • вызывается соответствующий обработчик слоя;
  • формируется контекст с данными экземпляра.

Важно, что один пиксель может соответствовать множеству логических сущностей (например, при instanced rendering), поэтому система учитывает индекс инстанса.

Контроллеры и управление камерой

Контроллеры (controller API) перехватывают часть событий до их попадания в слои. Их задача — преобразование ввода в изменения состояния камеры:

  • панорамирование (pan);
  • масштабирование (zoom);
  • вращение (rotate).

Контроллер может «поглощать» событие, предотвращая его передачу в слои. Это создаёт двухуровневую модель:

  • уровень навигации;
  • уровень интерактивных объектов.

Такое разделение критично для сцен с картографической или 3D-навигацией.

Приоритеты и маршрутизация событий

Система использует порядок обработки:

  1. DOM-событие поступает в canvas.
  2. Controller решает, перехватывать ли событие.
  3. Выполняется picking.
  4. Генерируется событие сцены.
  5. Событие передаётся слоям.

При наличии нескольких слоёв под курсором применяется правило глубины и порядка рендеринга (z-index в логике WebGL).

Drag-логика и состояние взаимодействия

Drag-события реализуются через внутреннее состояние:

  • фиксируется начальный object;
  • отслеживается смещение курсора;
  • вычисляется delta в экранных и мировых координатах;
  • обновляется состояние слоя.

Система учитывает, что объект может исчезнуть из-под курсора во время drag (например, при фильтрации данных), поэтому используется сохранённый reference на object index.

Производительность событийной системы

Основная нагрузка связана с picking-pass. Оптимизации включают:

  • выполнение picking только при изменении pointer;
  • кеширование framebuffer;
  • ограничение частоты hover через throttle;
  • пропуск picking при отсутствии интерактивных слоёв.

В сложных сценах стоимость чтения пикселя и пересборки буфера становится критическим фактором, поэтому события не вычисляются непрерывно без необходимости.

Координатные преобразования

Каждое событие сопровождается трансформацией координат:

  • экранные координаты (x, y);
  • нормализованные координаты WebGL;
  • мировые координаты;
  • географические координаты (при использовании проекций).

Deck.gl выполняет преобразование через матрицы view/projection, синхронизированные с состоянием камеры.

Пользовательские сценарии взаимодействия

Событийная модель допускает расширение через кастомные слои. Такие слои могут:

  • переопределять обработку pointer-событий;
  • вводить собственные жесты;
  • комбинировать события нескольких объектов;
  • формировать агрегированные взаимодействия (например, выделение кластеров).

В этом случае слой становится самостоятельной единицей логики взаимодействия, сохраняя совместимость с глобальной системой picking.

Синхронизация с рендер-циклом

События тесно связаны с кадрами рендеринга. Это означает:

  • событие отражает состояние сцены на конкретный frame;
  • изменения данных между кадрами могут изменить результат picking;
  • обработчики должны учитывать возможную асинхронность состояния.

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