Change detection

Система визуализации карты в Mapbox GL JS построена вокруг реактивного механизма отслеживания изменений состояния стиля, источников данных и параметров рендеринга. Любое изменение входных данных не приводит к полному пересозданию карты, вместо этого применяется дифференциальное обновление, затрагивающее только те части сцены, которые действительно изменились.

Принцип изменения состояния и модель неизменяемого стиля

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

Изменение стиля инициируется через API:

map.setStyle('mapbox://styles/mapbox/streets-v12');

При вызове происходит:

  • создание нового графа стиля
  • сравнение (diff) с предыдущим состоянием
  • определение добавленных, удалённых и изменённых слоёв
  • пересборка только затронутых GPU-ресурсов

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

Дифференциальное обновление слоёв

Каждый слой в стиле имеет уникальный идентификатор и набор свойств:

  • layout properties (геометрическое размещение)
  • paint properties (визуальное оформление)
  • filter expressions (условия отображения)

При изменении слоя:

map.setPaintProperty('water-layer', 'fill-color', '#00ffff');

движок выполняет:

  • поиск слоя по идентификатору
  • сравнение старого и нового значения свойства
  • пометку слоя как “dirty”
  • пересчёт только связанных шейдерных параметров

Рендеринг всего кадра не блокируется; обновляется только часть GPU-пайплайна, связанная с изменённым свойством.

Change detection в источниках данных

Источники (sources) являются ключевым триггером изменения сцены. Особенно это касается GeoJSON источников:

map.getSource('cities').setData(geojson);

При вызове setData происходит:

  • полная или частичная переразметка геометрии
  • пересборка буферов вершин
  • обновление spatial index (если используется clustering или tiling)
  • маркировка зависимых слоёв как требующих перерисовки

Векторные тайловые источники ведут себя иначе: изменение данных на уровне клиента отсутствует, а change detection сводится к обновлению тайлового кеша и запросу новых тайлов.

Влияние выражений (expressions) на систему отслеживания изменений

Expressions являются реактивным слоем между данными и визуализацией. Они позволяют динамически вычислять значения свойств на основе данных.

map.setPaintProperty('population', 'circle-radius', [
  'interpolate',
  ['linear'],
  ['get', 'pop'],
  0, 2,
  1000, 10
]);

При изменении входных данных:

  • expression tree пересчитывается только для затронутых атрибутов
  • константные части кэшируются
  • GPU uniforms обновляются частично

Это снижает стоимость recalculation при большом количестве объектов.

Механизм флага “dirty” и маркировка обновлений

Внутренняя система change detection опирается на флаги состояния:

  • style dirty
  • layer dirty
  • source dirty
  • tile dirty
  • render required

При любом изменении API вызывается цепочка маркировки:

  1. изменённый объект помечается как dirty
  2. зависимые сущности рекурсивно получают обновление флага
  3. планируется новый render cycle

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

Рендер-цикл и requestRender

Mapbox GL JS использует событийно-ориентированный render loop. Полный непрерывный цикл отсутствует; рендеринг происходит только при наличии изменений.

Основной триггер:

map.triggerRepaint();

После вызова:

  • сцена помечается как требующая перерисовки
  • планируется следующий frame через requestAnimationFrame
  • выполняется pipeline обновления

Если изменений нет, карта переходит в состояние idle и прекращает активный рендеринг.

Idle-состояние и стабилизация сцены

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

map.on('idle', () => {});

срабатывает при выполнении условий:

  • нет pending tile requests
  • нет dirty layers
  • завершены animations
  • GPU pipeline стабилен

Idle является важной частью change detection, так как сигнализирует о полной синхронизации состояния карты.

Анимации как источник постоянных изменений

Анимации нарушают статичность сцены и поддерживают непрерывный change detection.

Пример:

map.setPaintProperty('roads', 'line-opacity', 0.5);
map.setLayoutProperty('roads', 'visibility', 'visible');

Также используются:

  • camera animations (zoom, pitch, bearing)
  • property transitions
  • time-based expressions

Каждый frame анимации инициирует частичный diff состояния.

Камера как отдельный контур изменений

Камера рассматривается как независимый объект состояния:

  • center
  • zoom
  • bearing
  • pitch

Изменение камеры не затрагивает стиль напрямую, но влияет на:

  • видимые тайлы
  • уровень детализации
  • выбор слоёв по zoom range

Каждое изменение камеры инициирует пересчёт видимой области и частичный reload данных.

Tile-based change detection

Векторные и растровые тайлы обрабатываются отдельно:

  • загрузка новых тайлов при изменении viewport
  • инвалидирование устаревших тайлов
  • кеширование по координатам и zoom

Изменение одного источника может затронуть десятки тайлов, но система ограничивает обновление только пересекающимися регионами.

Батчинг изменений

Для оптимизации производительности изменения группируются:

map.batch(function () {
  map.setPaintProperty('water', 'fill-color', 'blue');
  map.setPaintProperty('land', 'fill-color', 'green');
});

Внутри батча:

  • отменяется промежуточный render
  • изменения накапливаются
  • выполняется единый diff pass
  • запускается один render cycle

Это критически снижает нагрузку при массовых обновлениях.

Приоритизация изменений

Система change detection включает приоритизацию:

  1. camera updates (высший приоритет)
  2. sources updates
  3. style updates
  4. paint/layout changes
  5. metadata changes

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

Типичные источники лишних перерисовок

Частые причины избыточного change detection:

  • частый вызов setData с полным пересозданием GeoJSON
  • обновление paint properties в цикле без батчинга
  • неограниченные camera animations
  • частые изменения visibility слоёв
  • отсутствие стабилизации входных данных

Система реагирует на каждое такое изменение отдельным diff pass, что увеличивает нагрузку на CPU и GPU.

Оптимизационные стратегии через управление изменениями

Контроль change detection достигается через:

  • минимизацию изменений style tree
  • использование immutable data patterns
  • группировку обновлений источников
  • ограничение частоты camera updates
  • использование expressions вместо постоянных setPaintProperty

Такая модель позволяет удерживать стабильный frame rate даже при большом количестве объектов и сложных стилях.