Управление состоянием

Архитектура состояния в Deck.gl

Система управления состоянием в Deck.gl строится вокруг идеи однонаправленного потока данных: состояние описывается как набор входных параметров, а визуализация является функцией этого состояния. Основной источник истины — объект Deck и массив слоёв (layers), передаваемый при каждом обновлении.

Ключевые элементы состояния:

  • состояние экземпляра Deck
  • параметры представления (viewState)
  • состояние слоёв (Layer state)
  • интерактивное состояние (hover, picking)
  • внешнее состояние приложения

В отличие от классических UI-фреймворков, Deck.gl минимизирует внутреннее изменяемое состояние слоёв, предпочитая пересборку вычисляемых параметров при изменении входных данных.


Иммутабельность как основа обновлений

Обновление сцены в Deck.gl основано на сравнении ссылок, а не глубоких структурных проверках. Это означает:

  • массив layers должен создаваться заново при изменениях
  • объекты viewState рассматриваются как неизменяемые
  • любые изменения приводят к триггеру повторного рендера

Пример базовой модели:

const deck = new Deck({
  initialViewState: {
    longitude: 0,
    latitude: 0,
    zoom: 3
  },
  controller: true,
  layers: []
});

Обновление состояния:

deck.setProps({
  viewState: {
    longitude: 10,
    latitude: 50,
    zoom: 5
  },
  layers: newLayers
});

Ключевая особенность: Deck.gl не модифицирует существующие объекты, а заменяет их целиком.


Управление viewState и камерой

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

Основные свойства:

  • longitude, latitude
  • zoom
  • pitch, bearing
  • дополнительные параметры проекции

Контролируемое состояние камеры

const onViewStateCha nge = ({ viewState }) => {
  deck.setProps({ viewState });
};

В этом режиме внешний код полностью управляет камерой.

Неконтролируемый режим

При использовании встроенного контроллера:

new Deck({
  controller: true,
  initialViewState: {...}
});

Deck.gl самостоятельно обновляет viewState, но может уведомлять приложение через onViewStateChange.


Состояние слоёв и механизм diffing

Массив layers — основной источник визуального состояния. Deck.gl применяет диффинг:

  • сравнение по id слоя
  • переиспользование WebGL ресурсов
  • минимизация пересоздания буферов

Пример:

const layers = [
  new ScatterplotLayer({
    id: 'points',
    data,
    getPosition: d => d.position,
    getRadius: 100
  })
];

Изменение данных:

const updatedLayers = [
  new ScatterplotLayer({
    id: 'points',
    data: newData,
    getPosition: d => d.position,
    getRadius: 120
  })
];

Даже при создании нового объекта слоя, Deck.gl может переиспользовать GPU-атрибуты при совпадении id.


updateTriggers и частичное обновление атрибутов

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

new ScatterplotLayer({
  id: 'points',
  data,
  getPosition: d => d.position,
  getRadius: d => d.size,
  updateTriggers: {
    getRadius: dataVersion
  }
});

Поведение:

  • если dataVersion не изменился — атрибуты не пересчитываются
  • если изменился — происходит пересборка буферов

Это критически важно для больших наборов данных.


Внутреннее состояние слоя

Каждый слой в Deck.gl имеет собственное внутреннее состояние:

  • кэш атрибутов (AttributeManager)
  • WebGL буферы
  • вычисленные индексы
  • состояние загрузки данных

Это состояние не хранится в пользовательских объектах и управляется жизненным циклом слоя:

  • initializeState
  • updateState
  • finalizeState

Пример логики обновления:

updateState({ props, oldProps, changeFlags }) {
  if (changeFlags.dataChanged) {
    this._updateAttributes();
  }
}

Контроль состояния через props

Deck.gl использует модель, близкую к React:

  • все параметры слоя — это props
  • изменение props инициирует пересчёт
  • внутреннее состояние синхронизируется с props

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

new LineLayer({
  id: 'lines',
  data,
  getSourcePosition,
  getTargetPosition,
  opacity: 0.8
});

Изменение opacity не требует пересоздания буферов, но изменение функций доступа (get*) может их триггерить.


Интерактивное состояние и picking

Состояние взаимодействия формируется через систему picking:

  • hover
  • click
  • selection
  • highlighted objects

Обработчик:

onHover: info => {
  const { object, x, y } = info;
}

info содержит:

  • object — выбранный элемент
  • coordinate — географические координаты
  • pixel — экранные координаты
  • layer — источник события

Это состояние обычно не хранится внутри Deck.gl, а выносится в внешнее приложение.


Управляемое интерактивное состояние

Типичная схема:

const [hoverInfo, setHoverInfo] = useState(null);

const layers = [
  new ScatterplotLayer({
    id: 'points',
    data,
    pickable: true,
    onHover: setHoverInfo
  })
];

Такое разделение позволяет:

  • сохранять чистоту слоя
  • централизовать UI-логику
  • избегать дублирования состояния

React-интеграция и синхронизация состояния

При использовании React состояние становится частью props-композиции:

<DeckGL
  viewState={viewState}
  layers={layers}
  onViewStateCha nge={setViewState}
  controller={true}
/>

Особенности:

  • viewState полностью контролируемый
  • layers пересоздаются при каждом изменении данных
  • React-рендер синхронизирован с WebGL сценой

Критический момент — стабильность ссылок:

const layers = useMemo(() => [
  new ScatterplotLayer({ ... })
], [data]);

Без мемоизации создаётся избыточная пересборка слоёв.


Разделение внешнего и внутреннего состояния

Архитектурно состояние делится на два слоя:

Внешнее состояние приложения:

  • данные (GeoJSON, массивы)
  • UI-флаги
  • фильтры
  • параметры визуализации

Внутреннее состояние Deck.gl:

  • GPU-буферы
  • кэш атрибутов
  • оптимизации рендера
  • состояние камерных переходов

Разделение важно для масштабируемости: внешнее состояние управляется приложением, внутреннее — самим движком.


Управление переходами состояния (transitions)

Deck.gl поддерживает анимированные изменения состояния:

new ScatterplotLayer({
  id: 'points',
  data,
  getRadius: 100,
  transitions: {
    getRadius: 500
  }
});

Особенности:

  • интерполяция выполняется на GPU
  • состояние перехода хранится внутри слоя
  • изменения не блокируют основной поток рендеринга

Состояние контроллера

Если включён controller: true, появляется дополнительный слой состояния:

  • инерция движения
  • ограничение zoom/pitch
  • обработка drag/scroll
  • кинетика камеры
new Deck({
  controller: {
    inertia: true,
    scrollZoom: { speed: 0.01 },
    dragRotate: true
  }
});

Это состояние не связано с данными слоёв, но влияет на viewState.


Конфликт и приоритеты состояния

При одновременном управлении камерой возможны конфликты:

  • initialViewState используется только один раз
  • viewState (если задан) имеет приоритет
  • controller может перезаписывать состояние

Приоритетная схема:

  1. внешний viewState
  2. onViewStateChange
  3. внутренний controller
  4. initialViewState

Кэширование и производительность состояния

Deck.gl активно использует кэш:

  • кэш атрибутов слоёв
  • кэш геометрии
  • кэш проекций
  • memoization вычисляемых значений

Ключевая идея: минимизация пересчёта при неизменном состоянии.

Пример влияния:

  • изменение цвета не затрагивает геометрию
  • изменение данных инвалидирует атрибуты
  • изменение камеры не пересчитывает данные слоя

Состояние в многослойных сценах

При наличии большого количества слоёв управление состоянием становится иерархическим:

  • глобальное состояние сцены
  • состояние групп слоёв
  • состояние отдельных слоёв

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


Типичные ошибки управления состоянием

  • повторное создание слоёв без необходимости мемоизации
  • мутация viewState вместо замены объекта
  • отсутствие id у слоёв
  • слишком частые вызовы setProps
  • перенос интерактивного состояния внутрь слоя

Эти ошибки приводят к деградации производительности и потере кэширования.


Синхронизация состояния с внешними хранилищами

Deck.gl часто интегрируется с внешними системами состояния:

  • Redux
  • Zustand
  • MobX

Типовая схема:

  • store хранит viewState и фильтры
  • Deck.gl получает props из store
  • события Deck.gl диспатчат обновления обратно

Это обеспечивает строгую синхронизацию UI и визуализации без расхождений состояния.