Memory management

Особенности модели памяти в WebGL-приложениях

CesiumJS работает поверх WebGL, где управление памятью разделено между двумя уровнями: JavaScript-heap и GPU-memory. Сборщик мусора V8 управляет только объектами JavaScript, тогда как текстуры, буферы вершин, индексные буферы и шейдерные программы находятся в видеорежиме и требуют явного освобождения.

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


Жизненный цикл объектов Cesium

Большинство объектов CesiumJS реализуют контракт уничтожения через метод destroy() и состояние isDestroyed.

Типичный жизненный цикл:

  • создание объекта (например, тайлсет, примитив, слой)
  • регистрация в сцене или коллекции
  • использование в рендер-цикле
  • явное уничтожение через destroy()

Общий паттерн:

if (!resource.isDestroyed()) {
    resource.destroy();
}

Многие объекты также поддерживают утилиту:

Cesium.destroyObject(resource);

Она безопасно вызывает destroy() и обнуляет поля.


Viewer и каскадное освобождение ресурсов

Viewer агрегирует множество подсистем: сцену, глобус, imagery layers, primitives, entities, post-processing цепочки.

При вызове:

viewer.destroy();

происходит каскадное освобождение:

  • WebGL контекст
  • сцена (Scene)
  • коллекции примитивов
  • imagery layers
  • обработчики событий
  • кредит-тайлы и кэш ресурсов

Однако не все внешние ресурсы автоматически очищаются, особенно если они были добавлены вручную вне стандартных коллекций.


Cesium3DTileset и управление тайловыми данными

Cesium3DTileset является одним из наиболее тяжёлых объектов по потреблению памяти.

Основные источники нагрузки:

  • геометрия тайлов (vertex buffers)
  • текстуры материалов
  • кэшированные узлы дерева
  • декодированные glTF-фрагменты

Освобождение выполняется явно:

scene.primitives.remove(tileset);
tileset.destroy();

Критически важно удалять ссылку из сцены до вызова destroy(), чтобы исключить повторные обращения рендер-цикла.

Дополнительный контроль кэша:

tileset.maximumScreenSpaceError = 16;
tileset.maximumCacheOverflowBytes = 536870912;

Primitive API и ручное управление ресурсами

Примитивы (Primitive, GroundPrimitive, Model) не входят в Entity-систему и требуют ручного контроля.

Добавление:

const primitive = scene.primitives.add(new Cesium.Primitive({
    geometryInstances: instance,
    appearance: appearance
}));

Удаление:

scene.primitives.remove(primitive);
primitive.destroy();

Если объект не удалён из коллекции, он продолжает участвовать в рендере и удерживает GPU-ресурсы.


Entity API и EntityCollection

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

Удаление:

viewer.entities.remove(entity);

Полная очистка:

viewer.entities.removeAll();

Проблемные зоны:

  • кастомные callbackProperties
  • внешние текстуры и изображения
  • подписки на события внутри entity

При использовании слушателей требуется ручная очистка:

handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);

Imagery layers и кэш текстур

ImageryLayer активно использует кэширование тайлов и текстур.

Удаление слоя:

viewer.imageryLayers.remove(layer, true);

Второй параметр (destroy) критичен: он определяет, будет ли вызвано освобождение WebGL-ресурсов.

Без явного уничтожения слой может оставлять текстуры в GPU-кэше.

Дополнительные источники памяти:

  • TileDiscardPolicy
  • ImageryProvider cache
  • HTTP texture cache

Terrain и глобус

Глобус (Globe) удерживает:

  • кэш высотных данных
  • геометрические батчи
  • текстуры поверхности

Очистка:

viewer.scene.globe = undefined;

или более корректно:

viewer.scene.globe.destroy();

Удаление imagery на глобусе:

viewer.scene.imageryLayers.removeAll(true);

События и утечки через замыкания

Частая причина утечек памяти — сохранённые ссылки в обработчиках событий.

Пример проблемного сценария:

viewer.clock.onTick.addEventListener(update);

Если update замыкает объекты сцены, они не будут освобождены.

Корректное удаление:

viewer.clock.onTick.removeEventListener(update);

Аналогично:

  • Scene.postRender
  • Camera.changed
  • ScreenSpaceEventHandler

WebGL контекст и потеря ресурсов

При потере WebGL-контекста (например, смена вкладки или GPU reset) Cesium автоматически инвалидирует GPU-ресурсы.

Событие:

viewer.scene.contextLost.addEventListener(() => {});
viewer.scene.contextRestored.addEventListener(() => {});

После восстановления требуется повторная инициализация некоторых ресурсов, особенно кастомных шейдеров и FBO.


Кэширование и лимиты памяти

Cesium использует внутренние кэши:

  • texture cache
  • geometry cache
  • request scheduler
  • tile cache

Контроль осуществляется через настройки сцены:

viewer.scene.maximumAliasedLineWidth = 1;
viewer.scene.globe.tileCacheSize = 1000;

Также важен контроль запроса данных:

Cesium.RequestScheduler.maximumRequestsPerServer = 6;

Чрезмерные значения приводят к росту RAM и VRAM.


Геометрия и TypedArray

Низкоуровневые объекты (Geometry, VertexArray) используют TypedArray, которые могут удерживаться в памяти даже после удаления JS-объекта, если остаются ссылки в WebGL-буферах.

Типичная ошибка:

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

Рекомендуемый подход:

  • минимизация динамического создания геометрии
  • переиспользование буферов
  • явное уничтожение примитивов

Потоки загрузки и Resource

Resource управляет сетевыми загрузками и кэшем.

Очистка:

resource.destroy();

Неконтролируемый кэш приводит к накоплению:

  • изображений
  • glTF
  • terrain tiles
  • metadata JSON

Общая модель предотвращения утечек

Устойчивость памяти в CesiumJS достигается через сочетание факторов:

  • явное уничтожение объектов (destroy)
  • удаление из коллекций
  • разрыв event listeners
  • контроль imagery layers
  • ограничение кэшей сцены и запроса данных
  • минимизация создания динамических WebGL ресурсов