Maps: canvas renderer for heavy-vector maps (SVG blew Chromium tile memory, tiles vanished)

Viewing a big track (10865-pt, 60km) and zooming/panning threw
'cc/tiles/tile_manager tile memory limits exceeded, some content may not
draw' dozens of times - Chromium's compositor tile-memory cap. The track
is drawn by Leaflet's default SVG renderer, multi-layered (casing/core +
per-1%-slope segments), so the giant SVG layer exceeds the raster budget
and the map tiles stop drawing until a force-stop resets the compositor.

Set preferCanvas:true on the heavy-vector view maps - showSessionDetail,
showRoadsMap (many road polylines, also reported laggy), showComparisonView,
showRouteDetail. Canvas draws into one viewport-bounded canvas instead of
thousands of SVG paths. Left the recording map on SVG (rotate:true;
leaflet-rotate barely supports Canvas) and the route editor (line clicks).
This commit is contained in:
4aiman 2026-09-29 13:20:53 +03:00
commit 834204a58a
3 changed files with 35 additions and 5 deletions

View file

@ -142,7 +142,7 @@
### Экраны
- **Сегодня** — баланс ккал, еда (продукты + блюда), GPS тренировки, активность, вес, сон, добавки, прогресс-бар жидкости, нутриенты (приход + запасы)
- **Продукты** — подтабы [Продукты | Рецепты], поиск, карточки с КБЖУ, добавление в дневник
- **Трек** — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate). Навигационный «пак» позиции (SVG divIcon): круг цвета трека (на 20% темнее) с белой + 1px чёрной обводкой + треугольник, указывающий ВВЕРХ (карта и так доворачивается активным источником — курс/компас — вверх, поэтому без покадрового поворота: не трясёт). Мини-компас только в режиме компаса. Z-order панелями: тайлы < дороги(`roadsPane` 240) < маршрут(`routePane` 250) < трек(overlay) < пак(marker). course-up по GPS-курсу (карта и панель доворачиваются в ландшафт вместе, `bearing = h`), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, цвета по вело/пешие/бег), сохранённые маршруты (оверлей-навигация, кнопка-тоггл «М», предпросмотр с профилем высоты, топо/уклон-обводка, тоггл «∡/⛰», высоты по цепочке GPXZ→open-elevation→open-meteo→OpenTopoData), де-джиттер трека (Савицкий-Голей, кнопка «≈» на детали), тоггл следования карты за позицией (`followMode`, кнопка `⊕` в `rec-map-btn` стеке; guard вокруг `panTo`/`setView` в `onPoint`/`onBatchUpdate`/`onStateChange`; выкл = свободный просмотр карты на ходу, повторный тап центрирует; не запоминается)
- **Трек** — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate). Навигационный «пак» позиции (SVG divIcon): круг цвета трека (на 20% темнее) с белой + 1px чёрной обводкой + треугольник, указывающий ВВЕРХ (карта и так доворачивается активным источником — курс/компас — вверх, поэтому без покадрового поворота: не трясёт). Мини-компас только в режиме компаса. Z-order панелями: тайлы < дороги(`roadsPane` 240) < маршрут(`routePane` 250) < трек(overlay) < пак(marker). course-up по GPS-курсу (карта и панель доворачиваются в ландшафт вместе, `bearing = h`), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, цвета по вело/пешие/бег), сохранённые маршруты (оверлей-навигация, кнопка-тоггл «М», предпросмотр с профилем высоты, топо/уклон-обводка, тоггл «∡/⛰», высоты по цепочке GPXZ→open-elevation→open-meteo→OpenTopoData), де-джиттер трека (Савицкий-Голей, кнопка «≈» на детали), тоггл следования карты за позицией (`followMode`, кнопка `⊕` в `rec-map-btn` стеке; guard вокруг `panTo`/`setView` в `onPoint`/`onBatchUpdate`/`onStateChange`; выкл = свободный просмотр карты на ходу, повторный тап центрирует; не запоминается). Карты с тяжёлыми векторами (`showSessionDetail`, `showRoadsMap`, `showComparisonView`, `showRouteDetail`) создаются с `preferCanvas: true` — большой трек/дороги в дефолтном SVG превышают лимит памяти тайлов компоновщика Chromium на зуме (`tile_manager tile memory limits exceeded`) и тайлы гаснут до перезапуска; canvas рисует в один холст. НЕ на карте записи (leaflet-rotate плохо дружит с Canvas) и не в редакторе маршрута (клик по линии)
- **Добавки** — 41 нутриент (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10)
- **Настройки** — профиль, образ жизни, нормы ЧСС/давления, BLE пульсометр, велосипеды, GPS конфиг (в т.ч. цвет трека `trackColor`, цвета дорог, GPXZ-ключ), OTA обновления, импорт тайлов, карта дорог

View file

@ -47,3 +47,29 @@
делал: область products/dedup имела баги (FK), трогать без нужды рискованно. Вынес пользователю
на решение.
- Тайминг-обвязку (JS [BOOT], Rust [SEED-TIMING]) убрал; lib.rs/main.js вернулись к HEAD.
## Пропажа тайлов карты на большом треке (отдельный баг)
Симптом: смотришь трек 60 км (сессия 1790501377655, 10865 точек), зумишь/таскаешь - тайлы карты
пропадают, помогает только force-stop+старт.
Диагностика по живому logcat (пользователь воспроизвёл: открыл трек -> достроил -> сохранил ->
reopen -> убрал достройку -> зум/таскание; буфер предварительно увеличил до 16МБ и очистил):
- `E chromium: [ERROR:cc/tiles/tile_manager.cc:1046] tile memory limits exceeded, some content
may not draw` - 40+ раз ровно в окно зум/таскания. Это лимит памяти тайлов КОМПОНОВЩИКА
Chromium (не карты, не TileCache). Никаких OOM-килла, крахов рендера, CSP по тайлам.
- Причина: трек рисуется дефолтным SVG-рендерером Leaflet (`preferCanvas` нигде не задан), да
ещё многослойно (casing/core + раскраска по уклону мелкими сегментами). Гигантский SVG-слой на
весь трек при зуме превышает бюджет растровой памяти -> компоновщик перестаёт рисовать часть,
тайлы гаснут. Force-stop сбрасывает компоновщик. Проявляется на любом крупном треке/карте.
Фикс (frontend/js/main.js): `preferCanvas: true` на картах с тяжёлыми векторами (canvas рисует
в один вьюпортный холст вместо тысяч SVG-путей):
- `showSessionDetail` (деталь трека, исходный баг), `showRoadsMap` (карта дорог - много
полилиний, пользователь отметил подтормаживания), `showComparisonView` (два трека),
`showRouteDetail` (превью маршрута с раскраской).
- НЕ трогал: `renderRecordingInline` (там `rotate:true`, а leaflet-rotate почти не поддерживает
Canvas - 1 ссылка на L.Canvas против 5 на SVG; canvas+rotate рискован, отдельная проверка),
редактор/рисование маршрута (клик/тап по линии, малые полилинии), цель-маркер (без треков).
Проверка: воспроизвести зум/таскание на треке 60км и на карте дорог, снять logcat, убедиться,
что `tile memory limits exceeded` больше НЕ появляется, тайлы держатся.

View file

@ -3807,7 +3807,7 @@ async function showRouteDetail(container, routeId) {
if (mapEl) {
void mapEl.offsetHeight; // force layout so Leaflet reads the real size
L.Icon.Default.imagePath = "lib/";
let map = L.map(mapEl, { attributionControl: false, zoomControl: false });
let map = L.map(mapEl, { preferCanvas: true, attributionControl: false, zoomControl: false }); // canvas: slope-coloured route vectors vs SVG compositor tile-memory limit
map.setView([pts[0].lat, pts[0].lon], 13); // give the map a view NOW so vector layers render (full-page timing)
new (TileCache.createLayer())({ maxZoom: 19 }).addTo(map);
let routeLayer = drawRoutePolyline(map, pts, route.color, mode);
@ -5402,7 +5402,11 @@ async function showSessionDetail(sessionId) {
if (mapEl && points.length) {
void mapEl.offsetHeight; // force layout so Leaflet reads the real container size
L.Icon.Default.imagePath = "lib/";
let map = L.map(mapEl, { attributionControl: false, zoomControl: false, scrollWheelZoom: false, dragging: false, tap: false, touchZoom: true });
// preferCanvas: big tracks (10k+ points, multi-layer slope colouring) as SVG blow the
// Chromium compositor's tile-memory limit on zoom/pan → map tiles stop drawing until a
// restart ("tile memory limits exceeded, some content may not draw"). Canvas renders the
// vectors into one viewport-bounded canvas instead of thousands of SVG paths.
let map = L.map(mapEl, { preferCanvas: true, attributionControl: false, zoomControl: false, scrollWheelZoom: false, dragging: false, tap: false, touchZoom: true });
mapEl._leafletMap = map; // save ref for winStack collapse
// Ctrl+wheel zoom on desktop
mapEl.addEventListener("wheel", (e) => {
@ -6436,7 +6440,7 @@ async function showComparisonView(currentId, compareId, currentSession, currentP
if (btns) btns.style.display = "none";
L.Icon.Default.imagePath = "lib/";
let map = L.map(mapEl, { attributionControl: false, zoomControl: false, touchZoom: true, dragging: true });
let map = L.map(mapEl, { preferCanvas: true, attributionControl: false, zoomControl: false, touchZoom: true, dragging: true }); // canvas: two big tracks vs SVG compositor tile-memory limit
mapEl._leafletMap = map;
addZoomDisplay(map);
let CachedLayer = TileCache.createLayer();
@ -8386,7 +8390,7 @@ async function showRoadsMap() {
let mapEl = document.querySelector("#roads-map");
L.Icon.Default.imagePath = "lib/";
let map = L.map(mapEl, { attributionControl: false }).setView([47.42, 40.08], 13);
let map = L.map(mapEl, { preferCanvas: true, attributionControl: false }).setView([47.42, 40.08], 13); // canvas: many road polylines vs SVG compositor tile-memory limit
addZoomDisplay(map);
let CachedLayer = TileCache.createLayer();
new CachedLayer({ maxZoom: 19 }).addTo(map);