osmgl 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/osmgl.d.cts CHANGED
@@ -4350,6 +4350,14 @@ interface QualityProfile {
4350
4350
  simpleGeometry: boolean;
4351
4351
  }
4352
4352
  declare const qualityProfile: (name: QualityName) => QualityProfile;
4353
+ /**
4354
+ * Класс GPU по строке рендерера.
4355
+ *
4356
+ * `unknown` — не признак слабости, а честное «не знаем»: Safari отдаёт всем одну и ту же строку,
4357
+ * Firefox её огрубляет, а Tor и Brave в строгом режиме не отдают вовсе. Поэтому неизвестность НЕ
4358
+ * должна ничего решать — она просто оставляет вывод за остальными признаками.
4359
+ */
4360
+ type GpuClass = 'software' | 'weak' | 'strong' | 'unknown';
4353
4361
  /** То, из чего делается вывод. Вынесено в аргумент, чтобы это можно было проверить тестом. */
4354
4362
  interface DeviceHints {
4355
4363
  /** Число логических ядер (`navigator.hardwareConcurrency`). */
@@ -4359,7 +4367,22 @@ interface DeviceHints {
4359
4367
  /** Мобильное устройство: грубый указатель или явный признак от браузера. */
4360
4368
  mobile: boolean;
4361
4369
  devicePixelRatio: number;
4370
+ /** Класс видеокарты по строке рендерера, если её удалось прочитать. */
4371
+ gpu?: GpuClass;
4362
4372
  }
4373
+ /**
4374
+ * Класс GPU по строке `UNMASKED_RENDERER_WEBGL`.
4375
+ *
4376
+ * Ровно тот же приём, на котором стоит `detect-gpu`: строка рендерера — единственное, что браузер
4377
+ * говорит о видеокарте, не заставляя ничего рисовать. Полноценная база моделей с замерами (как у
4378
+ * `detect-gpu`) сюда не поместится и устареет, поэтому распознаём только то, что решает исход:
4379
+ * программный растеризатор, заведомо слабое встроенное видео и заведомо сильные дискретные чипы.
4380
+ * Всё остальное — `unknown`, и решают ядра с памятью.
4381
+ *
4382
+ * Мерить бенчмарком на старте по-прежнему нельзя: это сотни миллисекунд ровно там, где важна первая
4383
+ * отрисовка. Строка бесплатна.
4384
+ */
4385
+ declare function classifyRenderer(renderer: string): GpuClass;
4363
4386
  /**
4364
4387
  * Профиль по признакам устройства.
4365
4388
  *
@@ -4381,6 +4404,101 @@ declare function deviceHints(): DeviceHints;
4381
4404
  declare function clampPixelRatio(cssWidth: number, cssHeight: number, devicePixelRatio: number, profile: QualityProfile): number;
4382
4405
  /** Следующий профиль вниз или null, если ниже некуда. */
4383
4406
  declare function nextQualityDown(name: QualityName): QualityName | null;
4407
+ /** Решение контроллера: на какую ступень перейти и в какую сторону. */
4408
+ interface QualityChange {
4409
+ to: QualityName;
4410
+ direction: 'up' | 'down';
4411
+ /** Доля тяжёлых кадров, на которой решение принято, — для сообщения в консоль. */
4412
+ share: number;
4413
+ }
4414
+ /**
4415
+ * Кто решает, менять ли ступень качества.
4416
+ *
4417
+ * ЗАЧЕМ ОТДЕЛЬНЫМ КЛАССОМ. Логика тут вся в состоянии — окно кадров, счётчики попыток, прогрев, —
4418
+ * и ошибка в ней не падает, а проявляется миганием теней у пользователя. Отдельно её видно и можно
4419
+ * проверить тестом, подавая кадры прямо (см. `tests/quality.test.mjs`).
4420
+ *
4421
+ * УСТРОЙСТВО. Вниз — быстро и по доле тяжёлых кадров: тормоза видно сразу. Вверх — медленно, с
4422
+ * ГИСТЕРЕЗИСОМ (порог подъёма на порядок ниже порога спуска) и с ХРАПОВИКОМ: каждая неудачная
4423
+ * попытка удваивает требуемое время спокойствия, а после второй ступень закрывается насовсем.
4424
+ * Именно храповик даёт главное свойство — качели ГАРАНТИРОВАННО затухают, а не ходят вечно.
4425
+ *
4426
+ * Так же устроены адаптивные масштабы разрешения в игровых движках: гистерезис против дребезга и
4427
+ * ограниченное число проб вверх, чтобы «подкачка» качества не стала заметной сама по себе.
4428
+ */
4429
+ declare class QualityGovernor {
4430
+ private current;
4431
+ private ceiling;
4432
+ /** Кадр дольше этого — тяжёлый. */
4433
+ private readonly slowMs;
4434
+ /** Кадр дольше этого весит втрое: сотня миллисекунд — это не «немного не успели». */
4435
+ private readonly verySlowMs;
4436
+ private ring;
4437
+ private at;
4438
+ private sum;
4439
+ private filled;
4440
+ /** Кадров подряд на текущей ступени (после прогрева). */
4441
+ private steady;
4442
+ private warmup;
4443
+ /** Неудачные попытки подъёма на ступень: имя → сколько раз сорвались. */
4444
+ private attempts;
4445
+ /** Пришли ли мы на текущую ступень подъёмом — только такой срыв считается неудачей. */
4446
+ private arrivedByUp;
4447
+ /** Последние измерения — запасная оценка шага развёртки. См. [[threshold]]. */
4448
+ private recent;
4449
+ private recentAt;
4450
+ private recentFilled;
4451
+ /** Измеренный шаг развёртки экрана, мс. Ноль — не измерен. */
4452
+ private refreshMs;
4453
+ constructor(current: QualityName, ceiling: QualityName,
4454
+ /** Кадр дольше этого — тяжёлый. */
4455
+ slowMs: number,
4456
+ /** Кадр дольше этого весит втрое: сотня миллисекунд — это не «немного не успели». */
4457
+ verySlowMs: number);
4458
+ /** Потолок, до которого разрешено подниматься сейчас. Опускается сам, когда ступень не даётся. */
4459
+ get limit(): QualityName;
4460
+ /** Сменили ступень снаружи (`setQuality`) — начать наблюдение заново. */
4461
+ reset(current: QualityName, ceiling: QualityName): void;
4462
+ /**
4463
+ * Очередной кадр. `busy` — едут ли тайлы: пока едут, кадры не считаются вовсе.
4464
+ *
4465
+ * Зум и панорама всегда дают всплеск — приезжают и разбираются новые тайлы, — и на нём порог
4466
+ * набирался за пару секунд у любой карты. Мерить надо УСТОЯВШУЮСЯ стоимость, а не стоимость
4467
+ * загрузки.
4468
+ */
4469
+ frame(frameMs: number, busy: boolean): QualityChange | null;
4470
+ /**
4471
+ * Порог «тяжёлого кадра» с поправкой на ЭКРАН.
4472
+ *
4473
+ * Мерой служит интервал между кадрами, а порог был жёсткий — 32 мс, «медленнее тридцати в
4474
+ * секунду». Но тридцать в секунду бывает и потолком САМОГО экрана: развёртка 30 Гц, режим
4475
+ * энергосбережения, композитор, ограничивший вкладку. Там интервал ровно 33 мс не потому, что
4476
+ * карта не успевает, а потому, что чаще показывать некуда, — и здоровая машина уезжала до
4477
+ * `minimal`, а на границе порога ещё и раскачивалась. Проверено: на широком окне шаг оказался
4478
+ * ровно 33 мс, и профиль сходил на три ступени за шесть секунд.
4479
+ *
4480
+ * Шаг развёртки берём ИЗМЕРЕННЫЙ (`setRefresh`), а не выведенный из тех же кадров. Вывод по
4481
+ * минимуму здесь принципиально неоднозначен: машина, ровно пригвождённая к 40 мс, неотличима от
4482
+ * экрана на 25 Гц — в обоих случаях все интервалы одинаковы. Разводит их только замер на ПУСТЫХ
4483
+ * кадрах в начале жизни карты, когда рисовать ещё нечего и частота кадров равна развёртке.
4484
+ *
4485
+ * Минимум по недавним кадрам остаётся запасным вариантом — на случай, если замер не удался.
4486
+ * Порог ниже исходного не опускается: на экране 120 Гц требовать от карты 120 кадров незачем.
4487
+ */
4488
+ private threshold;
4489
+ /**
4490
+ * Сообщить измеренный шаг развёртки экрана (мс между кадрами при пустой сцене).
4491
+ *
4492
+ * Заведомо неправдоподобное игнорируем: быстрее 240 Гц экранов у нас нет, а всё, что медленнее
4493
+ * 20 кадров в секунду, — это уже не развёртка, а неудавшийся замер.
4494
+ */
4495
+ setRefresh(ms: number): void;
4496
+ private down;
4497
+ private up;
4498
+ private moveTo;
4499
+ /** Забыть накопленное: после смены ступени прежние кадры относятся к другому профилю. */
4500
+ private restart;
4501
+ }
4384
4502
 
4385
4503
  /**
4386
4504
  * Кусок предка, приходящийся на этот тайл: сдвиг и масштаб текстурной
@@ -5534,16 +5652,8 @@ declare class Map$1 extends Evented<MapEvents> {
5534
5652
  * собой заменяли, — см. `setObjects3DEnabled`.
5535
5653
  */
5536
5654
  private objects3dEnabled;
5537
- /** Кадры подряд, не уложившиеся в бюджет, — счётчик для понижения профиля. */
5538
- /** Кольцо весов кадров окна: 0 — уложился, 1 — тяжёлый, 3 — очень тяжёлый. */
5539
- private readonly frameRing;
5540
- private ringAt;
5541
- private ringSum;
5542
- private ringFilled;
5543
- /** Кадров на текущей ступени — чтобы не проситься выше сразу после смены. */
5544
- private steadyFrames;
5545
- /** Профиль, выбранный по железу: выше него не поднимаемся никогда. */
5546
- private qualityCeiling;
5655
+ /** Кто решает, менять ли ступень качества. Вся логика окна и храповика — в нём. */
5656
+ private governor;
5547
5657
  /** Время прошлого кадра и сколько кадров идёт подряд — для честного замера. */
5548
5658
  private lastFrameAt;
5549
5659
  private continuousFrames;
@@ -5554,6 +5664,19 @@ declare class Map$1 extends Evented<MapEvents> {
5554
5664
  /** Слепок прошлого набора этажей — чтобы не слать событие каждый кадр. */
5555
5665
  private lastIndoorKey;
5556
5666
  constructor(options: MapOptions);
5667
+ /**
5668
+ * Замерить шаг развёртки экрана — по пустым кадрам в самом начале жизни карты.
5669
+ *
5670
+ * Автоснижение судит о тормозах по интервалу между кадрами, и без этого числа сравнивать его не
5671
+ * с чем. Отдельный замер нужен потому, что по рабочим кадрам развёртку не вычислить: экран на
5672
+ * 30 Гц и машина, ровно вдвое не успевающая на 60 Гц, дают одинаковые 33 мс. А вот пока рисовать
5673
+ * нечего — тайлы ещё едут, сцена пуста, — частота кадров равна развёртке, и это тот единственный
5674
+ * момент, когда её видно.
5675
+ *
5676
+ * Обработчик НИЧЕГО не рисует: он только запоминает отметки времени. Своих кадров он не заказывает
5677
+ * сверх этих двенадцати и заканчивается за пятую долю секунды.
5678
+ */
5679
+ private measureRefresh;
5557
5680
  private initGL;
5558
5681
  private initSource;
5559
5682
  /** Имя источника размещений 3D-объектов (если они вообще включены). */
@@ -5826,10 +5949,9 @@ declare class Map$1 extends Evented<MapEvents> {
5826
5949
  *
5827
5950
  * Определение по признакам устройства ошибается в обе стороны: телефон с
5828
5951
  * восемью ядрами может нести слабый GPU, а браузер — врать про плотность.
5829
- * Поэтому профиль ещё и проверяется делом. Понижаем только ПОДРЯД идущие
5830
- * тяжёлые кадры: одиночные всплески даёт и сборка мусора, и подъезд тайлов.
5831
- * Обратно вверх не поднимаемся — качели «плохо → хорошо → плохо» заметнее,
5832
- * чем стабильно работающая карта.
5952
+ * Поэтому профиль ещё и проверяется делом. Решение принимает `QualityGovernor` — там же и
5953
+ * защита от качелей: гистерезис, прогрев после смены и храповик на попытки подъёма. Здесь
5954
+ * остаётся только применить решение.
5833
5955
  *
5834
5956
  * ДВА ОГРАНИЧЕНИЯ, без которых от этого механизма больше вреда, чем пользы.
5835
5957
  *
@@ -5837,17 +5959,15 @@ declare class Map$1 extends Evented<MapEvents> {
5837
5959
  * геометрии. Разрешение пользователь видит сразу и целиком: подписи и значки после понижения
5838
5960
  * рисуются в буфер меньше экрана, браузер растягивает его обратно, и карта становится мыльной.
5839
5961
  * Упрощение геометрии отнимает у поверхностей фаску, подъём и скругление — приподнятый газон
5840
- * становится плоским пятном. Обратно вверх мы не поднимаемся, значит и то и другое осталось бы
5841
- * навсегда. Режем РАБОТУ (детализацию, тени, число тайлов и воркеров) — её потерю видно куда
5842
- * меньше. Так же поступают MapLibre и Mapbox: `pixelRatio` у них не адаптивный.
5962
+ * становится плоским пятном. Обратно эти две вещи не поднимаются, значит и то и другое осталось
5963
+ * бы навсегда. Режем РАБОТУ (детализацию, тени, число тайлов и воркеров) — её потерю видно куда
5964
+ * меньше, и она обратима. Так же поступают MapLibre и Mapbox: `pixelRatio` у них не адаптивный.
5843
5965
  *
5844
5966
  * Второе: пока едут тайлы, кадры не считаются вовсе. Зум и панорама всегда
5845
5967
  * дают всплеск — приезжают и разбираются новые тайлы, — и на нём порог
5846
5968
  * набирался за пару секунд у любой карты. Мерить надо УСТОЯВШУЮСЯ стоимость,
5847
5969
  * а не стоимость загрузки.
5848
5970
  */
5849
- /** Забыть накопленное: после смены ступени прежние кадры относятся к другому профилю. */
5850
- private resetFrameWindow;
5851
5971
  private checkFrameBudget;
5852
5972
  getShadows(): ShadowSettings;
5853
5973
  /**
@@ -5985,4 +6105,4 @@ declare class Map$1 extends Evented<MapEvents> {
5985
6105
  destroy(): void;
5986
6106
  }
5987
6107
 
5988
- export { type AnimationOptions, AssetLoader, type AssetLoaderOptions, BASE_SOURCE, BUILDING_HEIGHT_SCALE, BUILDING_POS_SCALE, BUILDING_VERTEX_STRIDE, type BuildingHit, type BuildingsMesh, CITY_STYLE, type CameraOptions, Circle, type CircleOptions, type CoverOptions, DEFAULT_ICONS, DEFAULT_LIGHT, DEFAULT_SHADOWS, DEFAULT_SKY, DEFAULT_STYLE, DepthTarget, type DeviceHints, EARTH_CIRCUMFERENCE, FILL_VERTEX_STRIDE, type Filter, type FitBoundsOptions, type FitViewport, type FogPlanes, GLOBE_MAX_PITCH, GLOBE_ZOOM_MAX, GLOBE_ZOOM_MIN, GLYPH_BORDER, GLYPH_SIZE, GROUND_SCALE, GeomType, type GestureOptions, type Glyph, type GlyphMetric, GlyphSource, GroundArrows, IconSource, IndoorMask, type Interpolated, LEVEL_LIMIT, LINE_VERTEX_STRIDE, type LayerPlan, LayerRenderer, type LightSpec, type LinePlacement, type LngLat, LngLatBounds, type LngLatLike, MAX_LATITUDE, MODEL_VERTEX_STRIDE, Map$1 as Map, type MapEvents, MapObject, type MapObjectOptions, type MapOptions, Marker, type MarkerOptions, Model, ModelLayer, type ModelMesh, type ModelOptions, type ModelPrimitive, ObjectManager, type ObjectPaint, type ObjectPart, type Padding, type PlacedGlyph, type PlacedSymbol, Polygon, type PolygonOptions, Polyline, type PolylineOptions, type PolylineSegment, Popup, type PopupOptions, type Projection, type QualityName, type QualityProfile, RELIEF_STYLE, RUNTIME_FILL_STRIDE, RUNTIME_LINE_STRIDE, RasterSource, type RasterSourceOptions, RasterTile, type RoundOptions, type RuntimeMesh, SDF_EDGE, SDF_PX, SOFT_STYLE, SPLIT_SEP, SURFACE_CUT_BLEED, SURFACE_LEVEL_SCALE, SURFACE_VERTEX_STRIDE, type ScreenPoint$1 as ScreenPoint, SdfAtlas, type ShadowSettings, type SkySpec, Style, type StyleLayerSpec, type StyleSpec, type SymbolHit, SymbolRenderer, THEMES, TILE_SIZE, type ThemeSpec, TileID, Transform, VectorTile, VectorTileFeature, VectorTileLayer, abbreviateStreet, addGround, altitudeFromMercatorZ, applyTheme, arrowLabelOffset, arrowVertices, buildRuntimeFill, buildRuntimeLine, cameraForBounds, circumferenceAtLatitude, clampLat, clampPixelRatio, clipLineToRect, coordinateDigits, coveringTiles, createGround, Map$1 as default, deviceHints, distToSegment, evaluateColor, evaluateNumber, extractFeature, featureLevel, fillFootprint, fogPlanes, footprintOf, formatHash, generateBuildings, generateFill, generateLine, generateSurface, generateSymbols, globeBasis, globeFlatMix, globeRadius, globeToLocal, groundAt, iconRotation, insideFootprint, latFromMercatorY, lngFromMercatorX, lngLatToUnit, matchesFilter, mercatorMetersPerTile, mercatorX, mercatorY, mercatorZFromAltitude, metersPerPixel, metersPerTile, nextQualityDown, parseGlb, parseGlbAsync, parseGlyphs, parseHash, patchUV, pickBuilding, pickFill, pitchIntent, pixelsPerMeter, placeAlongLine, pointInRing, qualityFor, qualityProfile, raySphere, resolveTemplate, roundRing, sdfFromAlpha, shapeText, splitBucketKey, splitByGround, splitPolygons, styleWithTheme, tileOriginMeters, tileUrl, toLngLat, unitToLngLat, worldSize };
6108
+ export { type AnimationOptions, AssetLoader, type AssetLoaderOptions, BASE_SOURCE, BUILDING_HEIGHT_SCALE, BUILDING_POS_SCALE, BUILDING_VERTEX_STRIDE, type BuildingHit, type BuildingsMesh, CITY_STYLE, type CameraOptions, Circle, type CircleOptions, type CoverOptions, DEFAULT_ICONS, DEFAULT_LIGHT, DEFAULT_SHADOWS, DEFAULT_SKY, DEFAULT_STYLE, DepthTarget, type DeviceHints, EARTH_CIRCUMFERENCE, FILL_VERTEX_STRIDE, type Filter, type FitBoundsOptions, type FitViewport, type FogPlanes, GLOBE_MAX_PITCH, GLOBE_ZOOM_MAX, GLOBE_ZOOM_MIN, GLYPH_BORDER, GLYPH_SIZE, GROUND_SCALE, GeomType, type GestureOptions, type Glyph, type GlyphMetric, GlyphSource, type GpuClass, GroundArrows, IconSource, IndoorMask, type Interpolated, LEVEL_LIMIT, LINE_VERTEX_STRIDE, type LayerPlan, LayerRenderer, type LightSpec, type LinePlacement, type LngLat, LngLatBounds, type LngLatLike, MAX_LATITUDE, MODEL_VERTEX_STRIDE, Map$1 as Map, type MapEvents, MapObject, type MapObjectOptions, type MapOptions, Marker, type MarkerOptions, Model, ModelLayer, type ModelMesh, type ModelOptions, type ModelPrimitive, ObjectManager, type ObjectPaint, type ObjectPart, type Padding, type PlacedGlyph, type PlacedSymbol, Polygon, type PolygonOptions, Polyline, type PolylineOptions, type PolylineSegment, Popup, type PopupOptions, type Projection, type QualityChange, QualityGovernor, type QualityName, type QualityProfile, RELIEF_STYLE, RUNTIME_FILL_STRIDE, RUNTIME_LINE_STRIDE, RasterSource, type RasterSourceOptions, RasterTile, type RoundOptions, type RuntimeMesh, SDF_EDGE, SDF_PX, SOFT_STYLE, SPLIT_SEP, SURFACE_CUT_BLEED, SURFACE_LEVEL_SCALE, SURFACE_VERTEX_STRIDE, type ScreenPoint$1 as ScreenPoint, SdfAtlas, type ShadowSettings, type SkySpec, Style, type StyleLayerSpec, type StyleSpec, type SymbolHit, SymbolRenderer, THEMES, TILE_SIZE, type ThemeSpec, TileID, Transform, VectorTile, VectorTileFeature, VectorTileLayer, abbreviateStreet, addGround, altitudeFromMercatorZ, applyTheme, arrowLabelOffset, arrowVertices, buildRuntimeFill, buildRuntimeLine, cameraForBounds, circumferenceAtLatitude, clampLat, clampPixelRatio, classifyRenderer, clipLineToRect, coordinateDigits, coveringTiles, createGround, Map$1 as default, deviceHints, distToSegment, evaluateColor, evaluateNumber, extractFeature, featureLevel, fillFootprint, fogPlanes, footprintOf, formatHash, generateBuildings, generateFill, generateLine, generateSurface, generateSymbols, globeBasis, globeFlatMix, globeRadius, globeToLocal, groundAt, iconRotation, insideFootprint, latFromMercatorY, lngFromMercatorX, lngLatToUnit, matchesFilter, mercatorMetersPerTile, mercatorX, mercatorY, mercatorZFromAltitude, metersPerPixel, metersPerTile, nextQualityDown, parseGlb, parseGlbAsync, parseGlyphs, parseHash, patchUV, pickBuilding, pickFill, pitchIntent, pixelsPerMeter, placeAlongLine, pointInRing, qualityFor, qualityProfile, raySphere, resolveTemplate, roundRing, sdfFromAlpha, shapeText, splitBucketKey, splitByGround, splitPolygons, styleWithTheme, tileOriginMeters, tileUrl, toLngLat, unitToLngLat, worldSize };
package/dist/osmgl.d.ts CHANGED
@@ -4350,6 +4350,14 @@ interface QualityProfile {
4350
4350
  simpleGeometry: boolean;
4351
4351
  }
4352
4352
  declare const qualityProfile: (name: QualityName) => QualityProfile;
4353
+ /**
4354
+ * Класс GPU по строке рендерера.
4355
+ *
4356
+ * `unknown` — не признак слабости, а честное «не знаем»: Safari отдаёт всем одну и ту же строку,
4357
+ * Firefox её огрубляет, а Tor и Brave в строгом режиме не отдают вовсе. Поэтому неизвестность НЕ
4358
+ * должна ничего решать — она просто оставляет вывод за остальными признаками.
4359
+ */
4360
+ type GpuClass = 'software' | 'weak' | 'strong' | 'unknown';
4353
4361
  /** То, из чего делается вывод. Вынесено в аргумент, чтобы это можно было проверить тестом. */
4354
4362
  interface DeviceHints {
4355
4363
  /** Число логических ядер (`navigator.hardwareConcurrency`). */
@@ -4359,7 +4367,22 @@ interface DeviceHints {
4359
4367
  /** Мобильное устройство: грубый указатель или явный признак от браузера. */
4360
4368
  mobile: boolean;
4361
4369
  devicePixelRatio: number;
4370
+ /** Класс видеокарты по строке рендерера, если её удалось прочитать. */
4371
+ gpu?: GpuClass;
4362
4372
  }
4373
+ /**
4374
+ * Класс GPU по строке `UNMASKED_RENDERER_WEBGL`.
4375
+ *
4376
+ * Ровно тот же приём, на котором стоит `detect-gpu`: строка рендерера — единственное, что браузер
4377
+ * говорит о видеокарте, не заставляя ничего рисовать. Полноценная база моделей с замерами (как у
4378
+ * `detect-gpu`) сюда не поместится и устареет, поэтому распознаём только то, что решает исход:
4379
+ * программный растеризатор, заведомо слабое встроенное видео и заведомо сильные дискретные чипы.
4380
+ * Всё остальное — `unknown`, и решают ядра с памятью.
4381
+ *
4382
+ * Мерить бенчмарком на старте по-прежнему нельзя: это сотни миллисекунд ровно там, где важна первая
4383
+ * отрисовка. Строка бесплатна.
4384
+ */
4385
+ declare function classifyRenderer(renderer: string): GpuClass;
4363
4386
  /**
4364
4387
  * Профиль по признакам устройства.
4365
4388
  *
@@ -4381,6 +4404,101 @@ declare function deviceHints(): DeviceHints;
4381
4404
  declare function clampPixelRatio(cssWidth: number, cssHeight: number, devicePixelRatio: number, profile: QualityProfile): number;
4382
4405
  /** Следующий профиль вниз или null, если ниже некуда. */
4383
4406
  declare function nextQualityDown(name: QualityName): QualityName | null;
4407
+ /** Решение контроллера: на какую ступень перейти и в какую сторону. */
4408
+ interface QualityChange {
4409
+ to: QualityName;
4410
+ direction: 'up' | 'down';
4411
+ /** Доля тяжёлых кадров, на которой решение принято, — для сообщения в консоль. */
4412
+ share: number;
4413
+ }
4414
+ /**
4415
+ * Кто решает, менять ли ступень качества.
4416
+ *
4417
+ * ЗАЧЕМ ОТДЕЛЬНЫМ КЛАССОМ. Логика тут вся в состоянии — окно кадров, счётчики попыток, прогрев, —
4418
+ * и ошибка в ней не падает, а проявляется миганием теней у пользователя. Отдельно её видно и можно
4419
+ * проверить тестом, подавая кадры прямо (см. `tests/quality.test.mjs`).
4420
+ *
4421
+ * УСТРОЙСТВО. Вниз — быстро и по доле тяжёлых кадров: тормоза видно сразу. Вверх — медленно, с
4422
+ * ГИСТЕРЕЗИСОМ (порог подъёма на порядок ниже порога спуска) и с ХРАПОВИКОМ: каждая неудачная
4423
+ * попытка удваивает требуемое время спокойствия, а после второй ступень закрывается насовсем.
4424
+ * Именно храповик даёт главное свойство — качели ГАРАНТИРОВАННО затухают, а не ходят вечно.
4425
+ *
4426
+ * Так же устроены адаптивные масштабы разрешения в игровых движках: гистерезис против дребезга и
4427
+ * ограниченное число проб вверх, чтобы «подкачка» качества не стала заметной сама по себе.
4428
+ */
4429
+ declare class QualityGovernor {
4430
+ private current;
4431
+ private ceiling;
4432
+ /** Кадр дольше этого — тяжёлый. */
4433
+ private readonly slowMs;
4434
+ /** Кадр дольше этого весит втрое: сотня миллисекунд — это не «немного не успели». */
4435
+ private readonly verySlowMs;
4436
+ private ring;
4437
+ private at;
4438
+ private sum;
4439
+ private filled;
4440
+ /** Кадров подряд на текущей ступени (после прогрева). */
4441
+ private steady;
4442
+ private warmup;
4443
+ /** Неудачные попытки подъёма на ступень: имя → сколько раз сорвались. */
4444
+ private attempts;
4445
+ /** Пришли ли мы на текущую ступень подъёмом — только такой срыв считается неудачей. */
4446
+ private arrivedByUp;
4447
+ /** Последние измерения — запасная оценка шага развёртки. См. [[threshold]]. */
4448
+ private recent;
4449
+ private recentAt;
4450
+ private recentFilled;
4451
+ /** Измеренный шаг развёртки экрана, мс. Ноль — не измерен. */
4452
+ private refreshMs;
4453
+ constructor(current: QualityName, ceiling: QualityName,
4454
+ /** Кадр дольше этого — тяжёлый. */
4455
+ slowMs: number,
4456
+ /** Кадр дольше этого весит втрое: сотня миллисекунд — это не «немного не успели». */
4457
+ verySlowMs: number);
4458
+ /** Потолок, до которого разрешено подниматься сейчас. Опускается сам, когда ступень не даётся. */
4459
+ get limit(): QualityName;
4460
+ /** Сменили ступень снаружи (`setQuality`) — начать наблюдение заново. */
4461
+ reset(current: QualityName, ceiling: QualityName): void;
4462
+ /**
4463
+ * Очередной кадр. `busy` — едут ли тайлы: пока едут, кадры не считаются вовсе.
4464
+ *
4465
+ * Зум и панорама всегда дают всплеск — приезжают и разбираются новые тайлы, — и на нём порог
4466
+ * набирался за пару секунд у любой карты. Мерить надо УСТОЯВШУЮСЯ стоимость, а не стоимость
4467
+ * загрузки.
4468
+ */
4469
+ frame(frameMs: number, busy: boolean): QualityChange | null;
4470
+ /**
4471
+ * Порог «тяжёлого кадра» с поправкой на ЭКРАН.
4472
+ *
4473
+ * Мерой служит интервал между кадрами, а порог был жёсткий — 32 мс, «медленнее тридцати в
4474
+ * секунду». Но тридцать в секунду бывает и потолком САМОГО экрана: развёртка 30 Гц, режим
4475
+ * энергосбережения, композитор, ограничивший вкладку. Там интервал ровно 33 мс не потому, что
4476
+ * карта не успевает, а потому, что чаще показывать некуда, — и здоровая машина уезжала до
4477
+ * `minimal`, а на границе порога ещё и раскачивалась. Проверено: на широком окне шаг оказался
4478
+ * ровно 33 мс, и профиль сходил на три ступени за шесть секунд.
4479
+ *
4480
+ * Шаг развёртки берём ИЗМЕРЕННЫЙ (`setRefresh`), а не выведенный из тех же кадров. Вывод по
4481
+ * минимуму здесь принципиально неоднозначен: машина, ровно пригвождённая к 40 мс, неотличима от
4482
+ * экрана на 25 Гц — в обоих случаях все интервалы одинаковы. Разводит их только замер на ПУСТЫХ
4483
+ * кадрах в начале жизни карты, когда рисовать ещё нечего и частота кадров равна развёртке.
4484
+ *
4485
+ * Минимум по недавним кадрам остаётся запасным вариантом — на случай, если замер не удался.
4486
+ * Порог ниже исходного не опускается: на экране 120 Гц требовать от карты 120 кадров незачем.
4487
+ */
4488
+ private threshold;
4489
+ /**
4490
+ * Сообщить измеренный шаг развёртки экрана (мс между кадрами при пустой сцене).
4491
+ *
4492
+ * Заведомо неправдоподобное игнорируем: быстрее 240 Гц экранов у нас нет, а всё, что медленнее
4493
+ * 20 кадров в секунду, — это уже не развёртка, а неудавшийся замер.
4494
+ */
4495
+ setRefresh(ms: number): void;
4496
+ private down;
4497
+ private up;
4498
+ private moveTo;
4499
+ /** Забыть накопленное: после смены ступени прежние кадры относятся к другому профилю. */
4500
+ private restart;
4501
+ }
4384
4502
 
4385
4503
  /**
4386
4504
  * Кусок предка, приходящийся на этот тайл: сдвиг и масштаб текстурной
@@ -5534,16 +5652,8 @@ declare class Map$1 extends Evented<MapEvents> {
5534
5652
  * собой заменяли, — см. `setObjects3DEnabled`.
5535
5653
  */
5536
5654
  private objects3dEnabled;
5537
- /** Кадры подряд, не уложившиеся в бюджет, — счётчик для понижения профиля. */
5538
- /** Кольцо весов кадров окна: 0 — уложился, 1 — тяжёлый, 3 — очень тяжёлый. */
5539
- private readonly frameRing;
5540
- private ringAt;
5541
- private ringSum;
5542
- private ringFilled;
5543
- /** Кадров на текущей ступени — чтобы не проситься выше сразу после смены. */
5544
- private steadyFrames;
5545
- /** Профиль, выбранный по железу: выше него не поднимаемся никогда. */
5546
- private qualityCeiling;
5655
+ /** Кто решает, менять ли ступень качества. Вся логика окна и храповика — в нём. */
5656
+ private governor;
5547
5657
  /** Время прошлого кадра и сколько кадров идёт подряд — для честного замера. */
5548
5658
  private lastFrameAt;
5549
5659
  private continuousFrames;
@@ -5554,6 +5664,19 @@ declare class Map$1 extends Evented<MapEvents> {
5554
5664
  /** Слепок прошлого набора этажей — чтобы не слать событие каждый кадр. */
5555
5665
  private lastIndoorKey;
5556
5666
  constructor(options: MapOptions);
5667
+ /**
5668
+ * Замерить шаг развёртки экрана — по пустым кадрам в самом начале жизни карты.
5669
+ *
5670
+ * Автоснижение судит о тормозах по интервалу между кадрами, и без этого числа сравнивать его не
5671
+ * с чем. Отдельный замер нужен потому, что по рабочим кадрам развёртку не вычислить: экран на
5672
+ * 30 Гц и машина, ровно вдвое не успевающая на 60 Гц, дают одинаковые 33 мс. А вот пока рисовать
5673
+ * нечего — тайлы ещё едут, сцена пуста, — частота кадров равна развёртке, и это тот единственный
5674
+ * момент, когда её видно.
5675
+ *
5676
+ * Обработчик НИЧЕГО не рисует: он только запоминает отметки времени. Своих кадров он не заказывает
5677
+ * сверх этих двенадцати и заканчивается за пятую долю секунды.
5678
+ */
5679
+ private measureRefresh;
5557
5680
  private initGL;
5558
5681
  private initSource;
5559
5682
  /** Имя источника размещений 3D-объектов (если они вообще включены). */
@@ -5826,10 +5949,9 @@ declare class Map$1 extends Evented<MapEvents> {
5826
5949
  *
5827
5950
  * Определение по признакам устройства ошибается в обе стороны: телефон с
5828
5951
  * восемью ядрами может нести слабый GPU, а браузер — врать про плотность.
5829
- * Поэтому профиль ещё и проверяется делом. Понижаем только ПОДРЯД идущие
5830
- * тяжёлые кадры: одиночные всплески даёт и сборка мусора, и подъезд тайлов.
5831
- * Обратно вверх не поднимаемся — качели «плохо → хорошо → плохо» заметнее,
5832
- * чем стабильно работающая карта.
5952
+ * Поэтому профиль ещё и проверяется делом. Решение принимает `QualityGovernor` — там же и
5953
+ * защита от качелей: гистерезис, прогрев после смены и храповик на попытки подъёма. Здесь
5954
+ * остаётся только применить решение.
5833
5955
  *
5834
5956
  * ДВА ОГРАНИЧЕНИЯ, без которых от этого механизма больше вреда, чем пользы.
5835
5957
  *
@@ -5837,17 +5959,15 @@ declare class Map$1 extends Evented<MapEvents> {
5837
5959
  * геометрии. Разрешение пользователь видит сразу и целиком: подписи и значки после понижения
5838
5960
  * рисуются в буфер меньше экрана, браузер растягивает его обратно, и карта становится мыльной.
5839
5961
  * Упрощение геометрии отнимает у поверхностей фаску, подъём и скругление — приподнятый газон
5840
- * становится плоским пятном. Обратно вверх мы не поднимаемся, значит и то и другое осталось бы
5841
- * навсегда. Режем РАБОТУ (детализацию, тени, число тайлов и воркеров) — её потерю видно куда
5842
- * меньше. Так же поступают MapLibre и Mapbox: `pixelRatio` у них не адаптивный.
5962
+ * становится плоским пятном. Обратно эти две вещи не поднимаются, значит и то и другое осталось
5963
+ * бы навсегда. Режем РАБОТУ (детализацию, тени, число тайлов и воркеров) — её потерю видно куда
5964
+ * меньше, и она обратима. Так же поступают MapLibre и Mapbox: `pixelRatio` у них не адаптивный.
5843
5965
  *
5844
5966
  * Второе: пока едут тайлы, кадры не считаются вовсе. Зум и панорама всегда
5845
5967
  * дают всплеск — приезжают и разбираются новые тайлы, — и на нём порог
5846
5968
  * набирался за пару секунд у любой карты. Мерить надо УСТОЯВШУЮСЯ стоимость,
5847
5969
  * а не стоимость загрузки.
5848
5970
  */
5849
- /** Забыть накопленное: после смены ступени прежние кадры относятся к другому профилю. */
5850
- private resetFrameWindow;
5851
5971
  private checkFrameBudget;
5852
5972
  getShadows(): ShadowSettings;
5853
5973
  /**
@@ -5985,4 +6105,4 @@ declare class Map$1 extends Evented<MapEvents> {
5985
6105
  destroy(): void;
5986
6106
  }
5987
6107
 
5988
- export { type AnimationOptions, AssetLoader, type AssetLoaderOptions, BASE_SOURCE, BUILDING_HEIGHT_SCALE, BUILDING_POS_SCALE, BUILDING_VERTEX_STRIDE, type BuildingHit, type BuildingsMesh, CITY_STYLE, type CameraOptions, Circle, type CircleOptions, type CoverOptions, DEFAULT_ICONS, DEFAULT_LIGHT, DEFAULT_SHADOWS, DEFAULT_SKY, DEFAULT_STYLE, DepthTarget, type DeviceHints, EARTH_CIRCUMFERENCE, FILL_VERTEX_STRIDE, type Filter, type FitBoundsOptions, type FitViewport, type FogPlanes, GLOBE_MAX_PITCH, GLOBE_ZOOM_MAX, GLOBE_ZOOM_MIN, GLYPH_BORDER, GLYPH_SIZE, GROUND_SCALE, GeomType, type GestureOptions, type Glyph, type GlyphMetric, GlyphSource, GroundArrows, IconSource, IndoorMask, type Interpolated, LEVEL_LIMIT, LINE_VERTEX_STRIDE, type LayerPlan, LayerRenderer, type LightSpec, type LinePlacement, type LngLat, LngLatBounds, type LngLatLike, MAX_LATITUDE, MODEL_VERTEX_STRIDE, Map$1 as Map, type MapEvents, MapObject, type MapObjectOptions, type MapOptions, Marker, type MarkerOptions, Model, ModelLayer, type ModelMesh, type ModelOptions, type ModelPrimitive, ObjectManager, type ObjectPaint, type ObjectPart, type Padding, type PlacedGlyph, type PlacedSymbol, Polygon, type PolygonOptions, Polyline, type PolylineOptions, type PolylineSegment, Popup, type PopupOptions, type Projection, type QualityName, type QualityProfile, RELIEF_STYLE, RUNTIME_FILL_STRIDE, RUNTIME_LINE_STRIDE, RasterSource, type RasterSourceOptions, RasterTile, type RoundOptions, type RuntimeMesh, SDF_EDGE, SDF_PX, SOFT_STYLE, SPLIT_SEP, SURFACE_CUT_BLEED, SURFACE_LEVEL_SCALE, SURFACE_VERTEX_STRIDE, type ScreenPoint$1 as ScreenPoint, SdfAtlas, type ShadowSettings, type SkySpec, Style, type StyleLayerSpec, type StyleSpec, type SymbolHit, SymbolRenderer, THEMES, TILE_SIZE, type ThemeSpec, TileID, Transform, VectorTile, VectorTileFeature, VectorTileLayer, abbreviateStreet, addGround, altitudeFromMercatorZ, applyTheme, arrowLabelOffset, arrowVertices, buildRuntimeFill, buildRuntimeLine, cameraForBounds, circumferenceAtLatitude, clampLat, clampPixelRatio, clipLineToRect, coordinateDigits, coveringTiles, createGround, Map$1 as default, deviceHints, distToSegment, evaluateColor, evaluateNumber, extractFeature, featureLevel, fillFootprint, fogPlanes, footprintOf, formatHash, generateBuildings, generateFill, generateLine, generateSurface, generateSymbols, globeBasis, globeFlatMix, globeRadius, globeToLocal, groundAt, iconRotation, insideFootprint, latFromMercatorY, lngFromMercatorX, lngLatToUnit, matchesFilter, mercatorMetersPerTile, mercatorX, mercatorY, mercatorZFromAltitude, metersPerPixel, metersPerTile, nextQualityDown, parseGlb, parseGlbAsync, parseGlyphs, parseHash, patchUV, pickBuilding, pickFill, pitchIntent, pixelsPerMeter, placeAlongLine, pointInRing, qualityFor, qualityProfile, raySphere, resolveTemplate, roundRing, sdfFromAlpha, shapeText, splitBucketKey, splitByGround, splitPolygons, styleWithTheme, tileOriginMeters, tileUrl, toLngLat, unitToLngLat, worldSize };
6108
+ export { type AnimationOptions, AssetLoader, type AssetLoaderOptions, BASE_SOURCE, BUILDING_HEIGHT_SCALE, BUILDING_POS_SCALE, BUILDING_VERTEX_STRIDE, type BuildingHit, type BuildingsMesh, CITY_STYLE, type CameraOptions, Circle, type CircleOptions, type CoverOptions, DEFAULT_ICONS, DEFAULT_LIGHT, DEFAULT_SHADOWS, DEFAULT_SKY, DEFAULT_STYLE, DepthTarget, type DeviceHints, EARTH_CIRCUMFERENCE, FILL_VERTEX_STRIDE, type Filter, type FitBoundsOptions, type FitViewport, type FogPlanes, GLOBE_MAX_PITCH, GLOBE_ZOOM_MAX, GLOBE_ZOOM_MIN, GLYPH_BORDER, GLYPH_SIZE, GROUND_SCALE, GeomType, type GestureOptions, type Glyph, type GlyphMetric, GlyphSource, type GpuClass, GroundArrows, IconSource, IndoorMask, type Interpolated, LEVEL_LIMIT, LINE_VERTEX_STRIDE, type LayerPlan, LayerRenderer, type LightSpec, type LinePlacement, type LngLat, LngLatBounds, type LngLatLike, MAX_LATITUDE, MODEL_VERTEX_STRIDE, Map$1 as Map, type MapEvents, MapObject, type MapObjectOptions, type MapOptions, Marker, type MarkerOptions, Model, ModelLayer, type ModelMesh, type ModelOptions, type ModelPrimitive, ObjectManager, type ObjectPaint, type ObjectPart, type Padding, type PlacedGlyph, type PlacedSymbol, Polygon, type PolygonOptions, Polyline, type PolylineOptions, type PolylineSegment, Popup, type PopupOptions, type Projection, type QualityChange, QualityGovernor, type QualityName, type QualityProfile, RELIEF_STYLE, RUNTIME_FILL_STRIDE, RUNTIME_LINE_STRIDE, RasterSource, type RasterSourceOptions, RasterTile, type RoundOptions, type RuntimeMesh, SDF_EDGE, SDF_PX, SOFT_STYLE, SPLIT_SEP, SURFACE_CUT_BLEED, SURFACE_LEVEL_SCALE, SURFACE_VERTEX_STRIDE, type ScreenPoint$1 as ScreenPoint, SdfAtlas, type ShadowSettings, type SkySpec, Style, type StyleLayerSpec, type StyleSpec, type SymbolHit, SymbolRenderer, THEMES, TILE_SIZE, type ThemeSpec, TileID, Transform, VectorTile, VectorTileFeature, VectorTileLayer, abbreviateStreet, addGround, altitudeFromMercatorZ, applyTheme, arrowLabelOffset, arrowVertices, buildRuntimeFill, buildRuntimeLine, cameraForBounds, circumferenceAtLatitude, clampLat, clampPixelRatio, classifyRenderer, clipLineToRect, coordinateDigits, coveringTiles, createGround, Map$1 as default, deviceHints, distToSegment, evaluateColor, evaluateNumber, extractFeature, featureLevel, fillFootprint, fogPlanes, footprintOf, formatHash, generateBuildings, generateFill, generateLine, generateSurface, generateSymbols, globeBasis, globeFlatMix, globeRadius, globeToLocal, groundAt, iconRotation, insideFootprint, latFromMercatorY, lngFromMercatorX, lngLatToUnit, matchesFilter, mercatorMetersPerTile, mercatorX, mercatorY, mercatorZFromAltitude, metersPerPixel, metersPerTile, nextQualityDown, parseGlb, parseGlbAsync, parseGlyphs, parseHash, patchUV, pickBuilding, pickFill, pitchIntent, pixelsPerMeter, placeAlongLine, pointInRing, qualityFor, qualityProfile, raySphere, resolveTemplate, roundRing, sdfFromAlpha, shapeText, splitBucketKey, splitByGround, splitPolygons, styleWithTheme, tileOriginMeters, tileUrl, toLngLat, unitToLngLat, worldSize };