@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.0

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.
Files changed (321) hide show
  1. package/README.md +59 -2
  2. package/dist/core.js +36923 -0
  3. package/dist/core.js.map +6 -0
  4. package/dist/index.js +32 -0
  5. package/dist/loaders-gl-worker.js +82 -0
  6. package/dist/loaders-gl-worker.js.map +6 -0
  7. package/dist/map-logic.js +2 -0
  8. package/dist/map-logic.js.map +6 -0
  9. package/dist/projected-basemaps-worker.js +3 -0
  10. package/dist/projected-basemaps-worker.js.map +6 -0
  11. package/dist/shader-modules.js +141 -0
  12. package/dist/shader-modules.js.map +6 -0
  13. package/dist/style-schema.js +126 -0
  14. package/dist/style-schema.js.map +6 -0
  15. package/dist/tangram-style.schema.json +882 -0
  16. package/dist/tangram.debug.mjs +37139 -0
  17. package/dist/tangram.debug.mjs.map +6 -0
  18. package/dist/tangram.min.mjs +4950 -0
  19. package/dist/types/builders/earcut.d.ts +15 -0
  20. package/dist/types/core.d.ts +29 -0
  21. package/dist/types/gl/constants.d.ts +2 -0
  22. package/dist/types/gl/globe_mesh.d.ts +48 -0
  23. package/dist/types/gl/glsl.d.ts +17 -0
  24. package/dist/types/gl/vertex-constants.d.ts +4 -0
  25. package/dist/types/gl/vertex-types.d.ts +34 -0
  26. package/dist/types/gl/vertex_data.d.ts +32 -0
  27. package/dist/types/gl/vertex_elements.d.ts +8 -0
  28. package/dist/types/gl/vertex_layout.d.ts +32 -0
  29. package/dist/types/glsl.d.ts +14 -0
  30. package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
  31. package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
  32. package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
  33. package/dist/types/index.d.ts +155 -0
  34. package/dist/types/lights/light-definitions.d.ts +121 -0
  35. package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
  36. package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
  37. package/dist/types/map-logic/globe-terrain.d.ts +54 -0
  38. package/dist/types/map-logic/index.d.ts +33 -0
  39. package/dist/types/map-logic/label-identity.d.ts +22 -0
  40. package/dist/types/map-logic/label-placement.d.ts +32 -0
  41. package/dist/types/map-logic/screen-bounds.d.ts +15 -0
  42. package/dist/types/map-logic/terrain-data.d.ts +55 -0
  43. package/dist/types/map-logic/terrain-error.d.ts +51 -0
  44. package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
  45. package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
  46. package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
  47. package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
  48. package/dist/types/map-logic/terrain-query.d.ts +33 -0
  49. package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
  50. package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
  51. package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
  52. package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
  53. package/dist/types/map-logic/tile-residency.d.ts +17 -0
  54. package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
  55. package/dist/types/procedures/js-yaml.d.ts +15 -0
  56. package/dist/types/procedures/mesh-projector.d.ts +107 -0
  57. package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
  58. package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
  59. package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
  60. package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
  61. package/dist/types/procedures/terrain-draping.d.ts +165 -0
  62. package/dist/types/procedures/terrain-globe.d.ts +6 -0
  63. package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
  64. package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
  65. package/dist/types/procedures/web-mercator-math.d.ts +4 -0
  66. package/dist/types/scene/globals.d.ts +11 -0
  67. package/dist/types/scene/ground_footprint.d.ts +16 -0
  68. package/dist/types/scene/host_frame.d.ts +54 -0
  69. package/dist/types/scene/projection_math.d.ts +52 -0
  70. package/dist/types/scene/scene-resource-types.d.ts +177 -0
  71. package/dist/types/scene/scene_bundle.d.ts +69 -0
  72. package/dist/types/scene/visibility_adapter.d.ts +106 -0
  73. package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
  74. package/dist/types/selection/terrain_surface.d.ts +149 -0
  75. package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
  76. package/dist/types/shader-modules/height-decode.d.ts +13 -0
  77. package/dist/types/shader-modules/hillshade.d.ts +13 -0
  78. package/dist/types/shader-modules/index.d.ts +6 -0
  79. package/dist/types/shader-modules/planar.d.ts +12 -0
  80. package/dist/types/shader-modules/sphere-map.d.ts +13 -0
  81. package/dist/types/shader-modules/triplanar.d.ts +15 -0
  82. package/dist/types/sources/decoded_tile_store.d.ts +95 -0
  83. package/dist/types/sources/tile_source_metadata.d.ts +32 -0
  84. package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
  85. package/dist/types/tile/tile_id.d.ts +37 -0
  86. package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
  87. package/dist/types/types.d.ts +293 -0
  88. package/dist/types/utils/geo.d.ts +57 -0
  89. package/dist/types/utils/hash.d.ts +1 -0
  90. package/dist/types/utils/log.d.ts +27 -0
  91. package/dist/types/utils/props.d.ts +6 -0
  92. package/dist/types/utils/subscribe.d.ts +10 -0
  93. package/dist/types/utils/thread.d.ts +6 -0
  94. package/dist/types/utils/urls.d.ts +26 -0
  95. package/dist/types/utils/utils.d.ts +26 -0
  96. package/dist/types/utils/version.d.ts +2 -0
  97. package/dist/types/utils/worker-types.d.ts +68 -0
  98. package/dist/types/utils/worker_broker.d.ts +3 -0
  99. package/package.json +103 -3
  100. package/src/builders/common.ts +41 -0
  101. package/src/builders/earcut.d.ts +15 -0
  102. package/src/builders/geometry-types.ts +60 -0
  103. package/src/builders/points.ts +115 -0
  104. package/src/builders/polygons.ts +225 -0
  105. package/src/builders/polylines.ts +682 -0
  106. package/src/builders/wireframe.ts +31 -0
  107. package/src/core.d.ts +29 -0
  108. package/src/core.ts +33 -0
  109. package/src/experimental/loaders-gl-worker.ts +19 -0
  110. package/src/experimental/projected-basemaps-worker.ts +13 -0
  111. package/src/experimental/projected-mesh-cache.ts +137 -0
  112. package/src/experimental/projected-mesh-domain.ts +110 -0
  113. package/src/experimental/projected-mesh-refinement.ts +193 -0
  114. package/src/experimental/projected-mesh.ts +399 -0
  115. package/src/experimental/projected-normal.ts +26 -0
  116. package/src/experimental/projected-symbol-mesh.ts +74 -0
  117. package/src/gl/constants.ts +18 -0
  118. package/src/gl/context.ts +103 -0
  119. package/src/gl/extensions.ts +30 -0
  120. package/src/gl/globe_mesh.ts +285 -0
  121. package/src/gl/glsl.ts +257 -0
  122. package/src/gl/mesh-types.ts +56 -0
  123. package/src/gl/render_state.ts +111 -0
  124. package/src/gl/shader_program.ts +1310 -0
  125. package/src/gl/texture.ts +814 -0
  126. package/src/gl/uniform_buffer.ts +340 -0
  127. package/src/gl/vao.ts +154 -0
  128. package/src/gl/vbo_mesh.ts +349 -0
  129. package/src/gl/vertex-constants.ts +19 -0
  130. package/src/gl/vertex-types.ts +35 -0
  131. package/src/gl/vertex_data.ts +126 -0
  132. package/src/gl/vertex_elements.ts +54 -0
  133. package/src/gl/vertex_layout.ts +241 -0
  134. package/src/glsl.d.ts +14 -0
  135. package/src/gpu/luma_device_renderer.ts +579 -0
  136. package/src/gpu/pixel_readback.ts +51 -0
  137. package/src/gpu/resource_lifecycle.ts +32 -0
  138. package/src/gpu/tangram_gpu_backend.ts +195 -0
  139. package/src/index.d.ts +155 -0
  140. package/src/index.ts +105 -0
  141. package/src/labels/collision-types.ts +94 -0
  142. package/src/labels/collision.ts +273 -0
  143. package/src/labels/collision_grid.ts +61 -0
  144. package/src/labels/intersect.ts +30 -0
  145. package/src/labels/label-types.ts +91 -0
  146. package/src/labels/label.ts +207 -0
  147. package/src/labels/label_line.ts +801 -0
  148. package/src/labels/label_point.ts +131 -0
  149. package/src/labels/main-pass-types.ts +73 -0
  150. package/src/labels/main_pass.ts +221 -0
  151. package/src/labels/point_anchor.ts +100 -0
  152. package/src/labels/point_placement.ts +150 -0
  153. package/src/labels/projected-pass.ts +259 -0
  154. package/src/labels/repeat_group.ts +72 -0
  155. package/src/lights/ambient_light.glsl +18 -0
  156. package/src/lights/directional_light.glsl +39 -0
  157. package/src/lights/geographic-lights.ts +21 -0
  158. package/src/lights/light-definitions.ts +285 -0
  159. package/src/lights/light.ts +590 -0
  160. package/src/lights/lighting-uniforms.ts +80 -0
  161. package/src/lights/lighting-wgsl.ts +116 -0
  162. package/src/lights/material.glsl +237 -0
  163. package/src/lights/material.ts +145 -0
  164. package/src/lights/native-falloff.ts +37 -0
  165. package/src/lights/point_light.glsl +96 -0
  166. package/src/lights/spot_light.glsl +111 -0
  167. package/src/map-logic/draped-terrain-bounds.ts +71 -0
  168. package/src/map-logic/globe-terrain-preparation.ts +217 -0
  169. package/src/map-logic/globe-terrain.ts +296 -0
  170. package/src/map-logic/index.ts +37 -0
  171. package/src/map-logic/label-identity.ts +36 -0
  172. package/src/map-logic/label-placement.ts +117 -0
  173. package/src/map-logic/screen-bounds.ts +34 -0
  174. package/src/map-logic/terrain-data.ts +107 -0
  175. package/src/map-logic/terrain-error.ts +134 -0
  176. package/src/map-logic/terrain-ground-topology.ts +164 -0
  177. package/src/map-logic/terrain-height-field.ts +254 -0
  178. package/src/map-logic/terrain-navigation.ts +50 -0
  179. package/src/map-logic/terrain-neighborhood.ts +43 -0
  180. package/src/map-logic/terrain-query.ts +51 -0
  181. package/src/map-logic/terrain-resource-budget.ts +124 -0
  182. package/src/map-logic/terrain-transition-topology.ts +164 -0
  183. package/src/map-logic/tile-build-queue.ts +110 -0
  184. package/src/map-logic/tile-cache-policy.ts +89 -0
  185. package/src/map-logic/tile-residency.ts +50 -0
  186. package/src/map-logic/triangle-query-index.ts +265 -0
  187. package/src/procedures/js-yaml.d.ts +15 -0
  188. package/src/procedures/mesh-projector.ts +191 -0
  189. package/src/procedures/mlt-loaders.ts +55 -0
  190. package/src/procedures/mvt-dependencies.d.ts +20 -0
  191. package/src/procedures/mvt-legacy.ts +150 -0
  192. package/src/procedures/mvt-loaders.ts +53 -0
  193. package/src/procedures/mvt-parser.ts +48 -0
  194. package/src/procedures/mvt-properties.ts +36 -0
  195. package/src/procedures/mvt-tile-provider.ts +72 -0
  196. package/src/procedures/pmtiles-loader.ts +75 -0
  197. package/src/procedures/projected-coordinate-transform.ts +79 -0
  198. package/src/procedures/projection-batch-executor.ts +140 -0
  199. package/src/procedures/scene-yaml-legacy.ts +16 -0
  200. package/src/procedures/scene-yaml-loaders.ts +10 -0
  201. package/src/procedures/terrain-draping.ts +713 -0
  202. package/src/procedures/terrain-globe.ts +32 -0
  203. package/src/procedures/terrain-styled-topology.ts +184 -0
  204. package/src/procedures/web-mercator-legacy.ts +24 -0
  205. package/src/procedures/web-mercator-math.ts +50 -0
  206. package/src/scene/camera.ts +298 -0
  207. package/src/scene/camera_base.ts +116 -0
  208. package/src/scene/classic_scene.ts +20 -0
  209. package/src/scene/external_camera.ts +101 -0
  210. package/src/scene/globals.ts +120 -0
  211. package/src/scene/ground_footprint.ts +139 -0
  212. package/src/scene/host_frame.ts +326 -0
  213. package/src/scene/projected_tile_lod.ts +125 -0
  214. package/src/scene/projection_math.ts +142 -0
  215. package/src/scene/projection_shaders.ts +183 -0
  216. package/src/scene/renderer.ts +516 -0
  217. package/src/scene/scene-resource-types.ts +174 -0
  218. package/src/scene/scene.ts +2378 -0
  219. package/src/scene/scene_bundle.ts +304 -0
  220. package/src/scene/scene_debug.ts +129 -0
  221. package/src/scene/scene_loader.ts +402 -0
  222. package/src/scene/scene_worker.ts +528 -0
  223. package/src/scene/view.ts +683 -0
  224. package/src/scene/visibility_adapter.ts +319 -0
  225. package/src/scene/worker_url.ts +15 -0
  226. package/src/selection/draped-terrain-surface.ts +377 -0
  227. package/src/selection/selection-types.ts +79 -0
  228. package/src/selection/selection.ts +542 -0
  229. package/src/selection/selection_fragment.glsl +18 -0
  230. package/src/selection/selection_globals.glsl +9 -0
  231. package/src/selection/selection_pixels.ts +59 -0
  232. package/src/selection/selection_vertex.glsl +15 -0
  233. package/src/selection/terrain_surface.ts +298 -0
  234. package/src/shader-modules/globe-horizon.ts +52 -0
  235. package/src/shader-modules/height-decode.ts +41 -0
  236. package/src/shader-modules/hillshade.ts +70 -0
  237. package/src/shader-modules/index.ts +10 -0
  238. package/src/shader-modules/planar.ts +24 -0
  239. package/src/shader-modules/sphere-map.ts +38 -0
  240. package/src/shader-modules/triplanar.ts +45 -0
  241. package/src/sources/aligned_tile_source.ts +122 -0
  242. package/src/sources/data_source.ts +655 -0
  243. package/src/sources/decoded_tile_store.ts +245 -0
  244. package/src/sources/geojson.ts +300 -0
  245. package/src/sources/mvt.ts +247 -0
  246. package/src/sources/raster.ts +341 -0
  247. package/src/sources/shared_tile_source_adapter.ts +117 -0
  248. package/src/sources/sources.ts +10 -0
  249. package/src/sources/tile_source_adapter.ts +84 -0
  250. package/src/sources/tile_source_metadata.ts +65 -0
  251. package/src/sources/tile_source_state.ts +112 -0
  252. package/src/sources/topojson.ts +90 -0
  253. package/src/styles/feature_annotations.ts +16 -0
  254. package/src/styles/filter.ts +190 -0
  255. package/src/styles/geometry-style-types.ts +209 -0
  256. package/src/styles/globe_visibility_wgsl.ts +22 -0
  257. package/src/styles/layer-types.ts +45 -0
  258. package/src/styles/layer.ts +586 -0
  259. package/src/styles/lines/dasharray.ts +49 -0
  260. package/src/styles/lines/lines.ts +707 -0
  261. package/src/styles/lines/lines_wgsl.ts +224 -0
  262. package/src/styles/points/point-runtime-types.ts +125 -0
  263. package/src/styles/points/points.ts +1079 -0
  264. package/src/styles/points/points_fragment.glsl +126 -0
  265. package/src/styles/points/points_vertex.glsl +230 -0
  266. package/src/styles/points/points_wgsl.ts +189 -0
  267. package/src/styles/polygons/polygons.ts +268 -0
  268. package/src/styles/polygons/polygons_fragment.glsl +173 -0
  269. package/src/styles/polygons/polygons_vertex.glsl +213 -0
  270. package/src/styles/polygons/polygons_wgsl.ts +127 -0
  271. package/src/styles/property-types.ts +125 -0
  272. package/src/styles/raster/raster.ts +33 -0
  273. package/src/styles/raster/raster_globals.glsl +46 -0
  274. package/src/styles/style-mixing-types.ts +101 -0
  275. package/src/styles/style-runtime-types.ts +220 -0
  276. package/src/styles/style-schema.ts +133 -0
  277. package/src/styles/style.ts +1033 -0
  278. package/src/styles/style_globals.glsl +69 -0
  279. package/src/styles/style_manager.ts +464 -0
  280. package/src/styles/style_parser.ts +620 -0
  281. package/src/styles/text/font_manager.ts +180 -0
  282. package/src/styles/text/fontfaceobserver.d.ts +10 -0
  283. package/src/styles/text/text-types.ts +220 -0
  284. package/src/styles/text/text.ts +378 -0
  285. package/src/styles/text/text_canvas.ts +773 -0
  286. package/src/styles/text/text_labels.ts +436 -0
  287. package/src/styles/text/text_segments.ts +142 -0
  288. package/src/styles/text/text_settings.ts +228 -0
  289. package/src/styles/text/text_wgsl.ts +159 -0
  290. package/src/styles/text/text_wrap.ts +194 -0
  291. package/src/tile/globe_tile_preload.ts +40 -0
  292. package/src/tile/tangram_tileset_2d.ts +108 -0
  293. package/src/tile/tile-build-types.ts +155 -0
  294. package/src/tile/tile.ts +807 -0
  295. package/src/tile/tile_id.ts +172 -0
  296. package/src/tile/tile_manager.ts +889 -0
  297. package/src/tile/tile_pyramid.ts +119 -0
  298. package/src/tile/tile_resource_cache.ts +91 -0
  299. package/src/tile/tile_traversal_adapter.ts +132 -0
  300. package/src/types.ts +332 -0
  301. package/src/utils/debounce.ts +17 -0
  302. package/src/utils/debug_settings.ts +43 -0
  303. package/src/utils/errors.ts +12 -0
  304. package/src/utils/functions.ts +95 -0
  305. package/src/utils/geo.ts +364 -0
  306. package/src/utils/hash.ts +14 -0
  307. package/src/utils/log.ts +108 -0
  308. package/src/utils/media_capture.ts +193 -0
  309. package/src/utils/merge.ts +44 -0
  310. package/src/utils/obb.ts +134 -0
  311. package/src/utils/props.ts +43 -0
  312. package/src/utils/slice.ts +14 -0
  313. package/src/utils/subscribe.ts +58 -0
  314. package/src/utils/task.ts +154 -0
  315. package/src/utils/thread.ts +26 -0
  316. package/src/utils/urls.ts +223 -0
  317. package/src/utils/utils.ts +310 -0
  318. package/src/utils/vector.ts +197 -0
  319. package/src/utils/version.ts +8 -0
  320. package/src/utils/worker-types.ts +79 -0
  321. package/src/utils/worker_broker.ts +523 -0
@@ -0,0 +1,43 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {sampleTerrainHeight, type TerrainHeightGrid} from './terrain-data';
6
+ import {TerrainHeightField} from './terrain-height-field';
7
+
8
+ /** Sample touching footprints with shared-edge averaging; callers validate datum and topology. */
9
+ export function sampleTerrainNeighborhood(grids: readonly TerrainHeightGrid[], position: readonly [number, number]): number | null {
10
+ let sum = 0, count = 0;
11
+ for (const grid of grids) {
12
+ const [west, south, east, north] = grid.bounds;
13
+ const tolerance = Math.max(east - west, north - south) * 1e-10;
14
+ if (position[0] < west - tolerance || position[0] > east + tolerance || position[1] < south - tolerance || position[1] > north + tolerance) continue;
15
+ const height = sampleTerrainHeight(grid, [Math.max(west, Math.min(east, position[0])), Math.max(south, Math.min(north, position[1]))]);
16
+ // A touching invalid sample is a hole, not permission to bridge it with its neighbor.
17
+ if (height === null) return null;
18
+ sum += height; count++;
19
+ }
20
+ return count ? sum / count : null;
21
+ }
22
+
23
+ /** Reconstruct explicit boundary nodes from pixel-center data and adjacent tiles. */
24
+ export function resampleTerrainNodes(grid: TerrainHeightGrid, neighbors: readonly TerrainHeightGrid[] = []): TerrainHeightGrid {
25
+ const grids = [grid, ...neighbors.filter(neighbor => neighbor !== grid)];
26
+ const [west, south, east, north] = grid.bounds;
27
+ for (const neighbor of grids) {
28
+ if (neighbor.verticalDatum !== grid.verticalDatum || neighbor.registration !== grid.registration || neighbor.width !== grid.width || neighbor.height !== grid.height ||
29
+ Math.abs((neighbor.bounds[2] - neighbor.bounds[0]) / (east - west) - 1) > 1e-8 ||
30
+ Math.abs((neighbor.bounds[3] - neighbor.bounds[1]) / (north - south) - 1) > 1e-8) throw new Error('Terrain neighbors require matching resolution and datum');
31
+ }
32
+ const columns = grid.registration === 'pixel-center' ? grid.width : grid.width - 1;
33
+ const rows = grid.registration === 'pixel-center' ? grid.height : grid.height - 1;
34
+ const width = columns + 1, height = rows + 1;
35
+ const field = new TerrainHeightField(grids);
36
+ const heights = new Float32Array(width * height), valid = new Uint8Array(width * height);
37
+ for (let row = 0; row < height; row++) for (let column = 0; column < width; column++) {
38
+ const value = field.sampleHeight([west + column / columns * (east - west), north - row / rows * (north - south)]);
39
+ const index = row * width + column;
40
+ heights[index] = value ?? NaN; valid[index] = Number(value !== null);
41
+ }
42
+ return {width, height, heights, valid, bounds: [...grid.bounds], registration: 'grid-node', verticalDatum: grid.verticalDatum};
43
+ }
@@ -0,0 +1,51 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {TerrainSurface, TerrainSurfaceHit, TerrainSurfaceBounds, TerrainRay} from '../selection/terrain_surface';
6
+
7
+ /** Read-only structural hit accepted from loaders.gl-compatible terrain providers. */
8
+ export type TerrainQueryHit = Omit<TerrainSurfaceHit, 'position' | 'coordinate' | 'normal' | 'barycentric'> & {
9
+ /** Displayed XYZ in the provider's content units. */
10
+ readonly position: readonly [number, number, number];
11
+ /** Longitude/latitude degrees and unexaggerated physical source meters. */
12
+ readonly coordinate: readonly [number, number, number];
13
+ /** Unit normal following displayed winding. */
14
+ readonly normal: readonly [number, number, number];
15
+ /** Original triangle vertex order. */
16
+ readonly barycentric: readonly [number, number, number];
17
+ };
18
+
19
+ /**
20
+ * Structural loaders.gl terrain surface contract. No package-version, GPU, deck or
21
+ * HostFrame dependency is introduced. The provider owns its current displayed generation.
22
+ */
23
+ export interface TerrainQuerySource {
24
+ /** Mercator XYZ meters or host-defined center-relative globe common units. */
25
+ readonly projection: TerrainSurface['projection'];
26
+ /** Nearest loaded hit, with distance independent of direction length; misses do not certify coverage. */
27
+ intersectRay(ray: TerrainRay, maxDistance?: number): TerrainQueryHit | null;
28
+ /** Optional conservative displayed envelope; null means empty, absence means unknown. */
29
+ getBounds?(): {readonly minimum: readonly [number, number, number]; readonly maximum: readonly [number, number, number]} | null;
30
+ }
31
+
32
+ /**
33
+ * Borrow a shared terrain query provider without taking disposal or index ownership.
34
+ * Preserve Tangram's mutable result API using detached tuples. In particular, physical
35
+ * coordinate altitude is not reconstructed from exaggerated displayed positions.
36
+ */
37
+ export function adaptTerrainQuerySource(source: TerrainQuerySource): TerrainSurface {
38
+ if (!['web-mercator', 'globe'].includes(source.projection)) throw new Error('Invalid terrain query projection');
39
+ const adapter: TerrainSurface = {
40
+ projection: source.projection,
41
+ intersectRay(ray, maxDistance) {
42
+ const hit = source.intersectRay(ray, maxDistance);
43
+ return hit ? {...hit, position: [...hit.position], coordinate: [...hit.coordinate], normal: [...hit.normal], barycentric: [...hit.barycentric]} : null;
44
+ }
45
+ };
46
+ if (source.getBounds) adapter.getBounds = (): TerrainSurfaceBounds | null => {
47
+ const bounds = source.getBounds?.();
48
+ return bounds ? {minimum: [...bounds.minimum], maximum: [...bounds.maximum]} : null;
49
+ };
50
+ return adapter;
51
+ }
@@ -0,0 +1,124 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /** Independently reported storage domains; GPU bytes are estimates, not driver allocation measurements. */
6
+ export type TerrainResourceDomain = 'retained' | 'transient' | 'gpu';
7
+
8
+ /** One entry in an atomically admitted allocation batch. */
9
+ export interface TerrainResourceRequest {
10
+ /** Independently reported storage category. */
11
+ readonly domain: TerrainResourceDomain;
12
+ /** Exact represented allocation bytes, a non-negative safe integer. */
13
+ readonly bytes: number;
14
+ /** Optional immutable allocation identity; matching owners count once. */
15
+ readonly identity?: object;
16
+ }
17
+
18
+ /** An owned reservation. Releasing twice is safe, including cancellation racing completion. */
19
+ export interface TerrainResourceLease {
20
+ /** Atomically resize a private reservation; shared immutable reservations permit only the same size. */
21
+ resize(bytes: number): boolean;
22
+ /** Release this owner's reservation. */
23
+ release(): void;
24
+ }
25
+
26
+ /** Shared admission across source, preparation and renderer owners; never takes resource disposal ownership. */
27
+ export class TerrainResourceBudget {
28
+ /** Validated total reservation limit, in bytes. */
29
+ readonly byteLimit: number;
30
+ /** Current allocations, keyed by opaque lease identity rather than reusable tile names. */
31
+ private readonly allocations = new Map<object, {domain: TerrainResourceDomain; bytes: number; references: number}>();
32
+ /** Weak allocation identities permit independent owners to retain one immutable allocation once. */
33
+ private readonly sharedIdentities = new WeakMap<object, object>();
34
+ /** Largest simultaneously reserved total since construction. */
35
+ private peakBytes = 0;
36
+ /** Current total, updated without rescanning every owner. */
37
+ private bytes = 0;
38
+
39
+ /** Construct a finite hard admission limit; reservations must precede the represented allocation. */
40
+ constructor(byteLimit: number) {
41
+ validateBytes(byteLimit);
42
+ this.byteLimit = byteLimit;
43
+ }
44
+
45
+ /** Reserve before allocating; rejection leaves every incumbent owner unchanged. */
46
+ reserve(domain: TerrainResourceDomain, bytes: number): TerrainResourceLease | null {
47
+ return this.reserveAll([{domain, bytes}])?.[0] ?? null;
48
+ }
49
+
50
+ /** Retain an immutable allocation by identity; shared leases permit only no-op resizing. */
51
+ reserveShared(identity: object, domain: TerrainResourceDomain, bytes: number): TerrainResourceLease | null {
52
+ return this.reserveAll([{identity, domain, bytes}])?.[0] ?? null;
53
+ }
54
+
55
+ /** Create a lease only after its complete admission has been validated. */
56
+ private createLease(domain: TerrainResourceDomain, bytes: number, shared?: object): TerrainResourceLease {
57
+ const identity = (shared ? this.sharedIdentities.get(shared) : undefined) ?? {};
58
+ let allocation = this.allocations.get(identity);
59
+ if (allocation) allocation.references++;
60
+ else {
61
+ allocation = {domain, bytes, references: 1};
62
+ this.allocations.set(identity, allocation);
63
+ if (shared) this.sharedIdentities.set(shared, identity);
64
+ this.bytes += bytes; this.peakBytes = Math.max(this.peakBytes, this.bytes);
65
+ }
66
+ const allocationIdentity = identity;
67
+ const owned = allocation;
68
+ let active = true;
69
+ return {
70
+ resize: next => {
71
+ validateBytes(next);
72
+ if (!active || shared && next !== owned.bytes || next - owned.bytes > this.byteLimit - this.bytes) return false;
73
+ this.bytes += next - owned.bytes;
74
+ owned.bytes = next; this.peakBytes = Math.max(this.peakBytes, this.bytes);
75
+ return true;
76
+ },
77
+ release: () => {
78
+ if (!active) return;
79
+ active = false; owned.references--;
80
+ if (!owned.references) {this.bytes -= owned.bytes; this.allocations.delete(allocationIdentity);}
81
+ }
82
+ };
83
+ }
84
+
85
+ /** Admit a complete set atomically; invalid or over-budget batches allocate no leases. */
86
+ reserveAll(requests: readonly TerrainResourceRequest[]): TerrainResourceLease[] | null {
87
+ const entries = requests.map(({domain, bytes, identity}) => ({domain, bytes, identity}));
88
+ let total = 0;
89
+ const planned = new Map<object, {domain: TerrainResourceDomain; bytes: number}>();
90
+ for (const request of entries) {
91
+ validateBytes(request.bytes);
92
+ if (!['retained', 'transient', 'gpu'].includes(request.domain)) throw new Error('Invalid terrain resource domain');
93
+ if (request.identity !== undefined) {
94
+ if (request.identity === null || typeof request.identity !== 'object') throw new Error('Invalid shared terrain resource identity');
95
+ const identity = this.sharedIdentities.get(request.identity);
96
+ const allocation = identity ? this.allocations.get(identity) : undefined;
97
+ const previous = allocation ?? planned.get(request.identity);
98
+ if (previous) {
99
+ if (previous.domain !== request.domain || previous.bytes !== request.bytes) throw new Error('Shared terrain resource metadata must match');
100
+ continue;
101
+ }
102
+ planned.set(request.identity, request);
103
+ }
104
+ if (request.bytes > Number.MAX_SAFE_INTEGER - total) throw new Error('Invalid terrain resource byte reservation');
105
+ total += request.bytes;
106
+ }
107
+ if (total > this.byteLimit - this.bytes) return null;
108
+ return entries.map(request => this.createLease(request.domain, request.bytes, request.identity));
109
+ }
110
+
111
+ /** Detached unique allocation bytes and live owner-lease count; excludes unregistered resources and driver overhead. */
112
+ getStatistics(): {bytes: number; peakBytes: number; byteLimit: number; retainedBytes: number; transientBytes: number; gpuBytes: number; leases: number} {
113
+ const totals = {retained: 0, transient: 0, gpu: 0};
114
+ let leases = 0;
115
+ for (const allocation of this.allocations.values()) {totals[allocation.domain] += allocation.bytes; leases += allocation.references;}
116
+ return {bytes: this.bytes, peakBytes: this.peakBytes, byteLimit: this.byteLimit,
117
+ retainedBytes: totals.retained, transientBytes: totals.transient, gpuBytes: totals.gpu, leases};
118
+ }
119
+ }
120
+
121
+ /** Reject overflow and fractional reservations before changing an owner. */
122
+ function validateBytes(bytes: number): void {
123
+ if (!Number.isSafeInteger(bytes) || bytes < 0) throw new Error('Invalid terrain resource byte reservation');
124
+ }
@@ -0,0 +1,164 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {TerrainGroundTopology} from './terrain-ground-topology';
6
+
7
+ /** Barycentric reference to one unchanged endpoint face. Negative indices mean absent previous coverage. */
8
+ export interface TerrainFaceReference {
9
+ /** Canonical vertex indices in original winding. */
10
+ readonly indices: readonly [number, number, number];
11
+ /** Weights at the final geometric position. */
12
+ readonly weights: readonly [number, number, number];
13
+ }
14
+
15
+ /** Common planar refinement of two field generations; neither endpoint is bilinearly resampled at new cuts. */
16
+ export interface TerrainTransitionTopology {
17
+ /** Complete current footprint partition, including newly covered regions. */
18
+ readonly topology: TerrainGroundTopology;
19
+ /** Current endpoint references, one per output vertex. */
20
+ readonly current: readonly TerrainFaceReference[];
21
+ /** Previous endpoint references, null outside the previous admitted footprint. */
22
+ readonly previous: readonly (TerrainFaceReference | null)[];
23
+ }
24
+
25
+ /** Closed face envelope used by the temporary sweep broad phase. */
26
+ interface Face {
27
+ /** Canonical face identity. */
28
+ indices: [number, number, number];
29
+ /** Exact sample-domain corners. */
30
+ points: [number, number][];
31
+ /** Closed bounds. */
32
+ bounds: [number, number, number, number];
33
+ }
34
+
35
+ /** Temporary binary broad phase; no retained index escapes preparation. */
36
+ interface FaceNode {
37
+ /** Closed descendant envelope. */
38
+ bounds: [number, number, number, number];
39
+ /** Small original-order leaf. */
40
+ faces?: Face[];
41
+ /** Median split children. */
42
+ children?: [FaceNode, FaceNode];
43
+ }
44
+
45
+ /** Bound edge work to overlapping old faces rather than the product of both meshes. */
46
+ function buildNode(faces: Face[]): FaceNode | null {
47
+ if (!faces.length) return null;
48
+ const bounds: FaceNode['bounds'] = [Infinity, Infinity, -Infinity, -Infinity];
49
+ for (const face of faces) for (let axis = 0; axis < 2; axis++) {
50
+ bounds[axis] = Math.min(bounds[axis], face.bounds[axis]); bounds[axis + 2] = Math.max(bounds[axis + 2], face.bounds[axis + 2]);
51
+ }
52
+ if (faces.length <= 8) return {bounds, faces};
53
+ const axis = bounds[2] - bounds[0] > bounds[3] - bounds[1] ? 0 : 1;
54
+ faces.sort((first, second) => first.bounds[axis] + first.bounds[axis + 2] - second.bounds[axis] - second.bounds[axis + 2]);
55
+ const middle = Math.floor(faces.length / 2);
56
+ return {bounds, children: [buildNode(faces.slice(0, middle))!, buildNode(faces.slice(middle))!]};
57
+ }
58
+
59
+ /** Signed twice-area, preserving the canonical world-XY winding. */
60
+ function cross(first: readonly number[], second: readonly number[], point: readonly number[]): number {
61
+ return (second[0] - first[0]) * (point[1] - first[1]) - (second[1] - first[1]) * (point[0] - first[0]);
62
+ }
63
+
64
+ /** Classify collinearity within world-coordinate representation error, not a fixed geometric epsilon. */
65
+ function signedDistance(first: readonly number[], second: readonly number[], point: readonly number[]): number {
66
+ const distance = cross(first, second, point);
67
+ const tolerance = Number.EPSILON * Math.max(1, ...first.map(Math.abs), ...second.map(Math.abs), ...point.map(Math.abs)) *
68
+ (Math.abs(second[0] - first[0]) + Math.abs(second[1] - first[1])) * 16;
69
+ return Math.abs(distance) <= tolerance ? 0 : distance;
70
+ }
71
+
72
+ /** Reference coordinates without changing the existing face's diagonal or endpoint samples. */
73
+ function reference(face: Face, point: readonly number[]): TerrainFaceReference {
74
+ const denominator = cross(face.points[0], face.points[1], face.points[2]);
75
+ const first = cross(face.points[1], face.points[2], point) / denominator;
76
+ const second = cross(face.points[2], face.points[0], point) / denominator;
77
+ return {indices: face.indices, weights: [first, second, 1 - first - second]};
78
+ }
79
+
80
+ /** Snapshot an immutable topology's faces; temporary arrays never survive the worker preparation. */
81
+ function faces(topology: TerrainGroundTopology): Face[] {
82
+ const result: Face[] = [];
83
+ for (let offset = 0; offset < topology.indices.length; offset += 3) {
84
+ const indices: [number, number, number] = [topology.indices[offset], topology.indices[offset + 1], topology.indices[offset + 2]];
85
+ const points = indices.map((index): [number, number] => [topology.samplePositions[index * 2], topology.samplePositions[index * 2 + 1]]);
86
+ result.push({indices, points, bounds: [Math.min(...points.map(point => point[0])), Math.min(...points.map(point => point[1])),
87
+ Math.max(...points.map(point => point[0])), Math.max(...points.map(point => point[1]))]});
88
+ }
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * Split current faces along previous edges, retaining both exact planar endpoint
94
+ * surfaces and newly covered ground. Bounded work/vertices reject atomically.
95
+ * This does not claim spherical coplanarity after nonlinear globe projection.
96
+ */
97
+ export function refineTerrainTransitionTopology(current: TerrainGroundTopology, previous: TerrainGroundTopology, maxVertices: number,
98
+ maxEdgeTests = 1048576): TerrainTransitionTopology | null {
99
+ if (!Number.isSafeInteger(maxVertices) || maxVertices < 0 || !Number.isSafeInteger(maxEdgeTests) || maxEdgeTests < 0 || current.sampleOffset !== previous.sampleOffset) throw new Error('Invalid terrain transition budget or sampling world');
100
+ if (current.samplePositions.length === previous.samplePositions.length && current.indices.length === previous.indices.length &&
101
+ current.samplePositions.every((value, index) => value === previous.samplePositions[index]) && current.indices.every((value, index) => value === previous.indices[index])) return null;
102
+ const root = buildNode(faces(previous));
103
+ const positions: number[] = [], samplePositions: number[] = [], normalizedPositions: number[] = [], indices: number[] = [];
104
+ const currentReferences: TerrainFaceReference[] = [], previousReferences: (TerrainFaceReference | null)[] = [];
105
+ let tests = 0;
106
+ for (const face of faces(current)) {
107
+ const candidates: Face[] = [];
108
+ const pending = root ? [root] : [];
109
+ while (pending.length) {
110
+ const node = pending.pop()!;
111
+ if (++tests > maxEdgeTests) throw new Error('Terrain transition edge-work budget exceeded');
112
+ if (node.bounds[0] >= face.bounds[2] || node.bounds[2] <= face.bounds[0] || node.bounds[1] >= face.bounds[3] || node.bounds[3] <= face.bounds[1]) continue;
113
+ if (node.faces) for (const candidate of node.faces) {
114
+ if (candidate.bounds[0] < face.bounds[2] && candidate.bounds[2] > face.bounds[0] && candidate.bounds[1] < face.bounds[3] && candidate.bounds[3] > face.bounds[1]) candidates.push(candidate);
115
+ }
116
+ else pending.push(...node.children!);
117
+ }
118
+ let polygons = [face.points];
119
+ const edges = new Set<string>();
120
+ for (const candidate of candidates) for (let edge = 0; edge < 3; edge++) {
121
+ let start = candidate.points[edge], end = candidate.points[(edge + 1) % 3];
122
+ if (start[0] > end[0] || start[0] === end[0] && start[1] > end[1]) [start, end] = [end, start];
123
+ const key = `${start.join(',')}/${end.join(',')}`;
124
+ if (edges.has(key)) continue;
125
+ edges.add(key);
126
+ const next: [number, number][][] = [];
127
+ for (const polygon of polygons) {
128
+ if (++tests > maxEdgeTests) throw new Error('Terrain transition edge-work budget exceeded');
129
+ const distances = polygon.map(point => signedDistance(start, end, point));
130
+ if (!distances.some(value => value < 0) || !distances.some(value => value > 0)) {next.push(polygon); continue;}
131
+ const positive: [number, number][] = [], negative: [number, number][] = [];
132
+ polygon.forEach((point, index) => {
133
+ const distance = distances[index], following = polygon[(index + 1) % polygon.length], followingDistance = distances[(index + 1) % polygon.length];
134
+ if (distance >= 0) positive.push(point);
135
+ if (distance <= 0) negative.push(point);
136
+ if (distance * followingDistance < 0) {
137
+ const fraction = distance / (distance - followingDistance);
138
+ const intersection: [number, number] = [point[0] + (following[0] - point[0]) * fraction, point[1] + (following[1] - point[1]) * fraction];
139
+ positive.push(intersection); negative.push(intersection);
140
+ }
141
+ });
142
+ next.push(positive, negative);
143
+ if (next.length * 3 > maxVertices) throw new Error('Terrain transition vertex budget exceeded');
144
+ }
145
+ polygons = next;
146
+ }
147
+ for (const polygon of polygons) {
148
+ if (samplePositions.length / 2 + polygon.length > maxVertices) throw new Error('Terrain transition vertex budget exceeded');
149
+ const center = polygon.reduce(([x, y], point) => [x + point[0] / polygon.length, y + point[1] / polygon.length], [0, 0]);
150
+ const oldFace = candidates.find(candidate => candidate.points.every((point, index) => signedDistance(point, candidate.points[(index + 1) % 3], center) >= 0));
151
+ const base = samplePositions.length / 2;
152
+ for (const point of polygon) {
153
+ const currentReference = reference(face, point);
154
+ currentReferences.push(currentReference); previousReferences.push(oldFace ? reference(oldFace, point) : null);
155
+ samplePositions.push(...point); positions.push(point[0] + current.sampleOffset, point[1]);
156
+ for (let axis = 0; axis < 2; axis++) normalizedPositions.push(currentReference.indices.reduce((sum, index, corner) =>
157
+ sum + current.normalizedPositions[index * 2 + axis] * currentReference.weights[corner], 0));
158
+ }
159
+ for (let index = 1; index + 1 < polygon.length; index++) if (cross(polygon[0], polygon[index], polygon[index + 1]) !== 0) indices.push(base, base + index, base + index + 1);
160
+ }
161
+ }
162
+ return {topology: {positions: Float64Array.from(positions), samplePositions: Float64Array.from(samplePositions), normalizedPositions: Float64Array.from(normalizedPositions),
163
+ indices: Uint32Array.from(indices), sampleOffset: current.sampleOffset}, current: currentReferences, previous: previousReferences};
164
+ }
@@ -0,0 +1,110 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /** A single source/style generation's tile build. */
6
+ export interface TileBuildTask {
7
+ /** Source-normalized tile key. */
8
+ readonly key: string;
9
+ /** Tile identity and scene generation, used to reject stale completions. */
10
+ readonly token: string;
11
+ /** Visible detail precedes pinned preload, which precedes off-screen work. */
12
+ priority: number;
13
+ /** Start worker processing; completion is reported separately. */
14
+ start(): void;
15
+ /** Release ownership when starting the task fails synchronously. */
16
+ fail(error: unknown): void;
17
+ }
18
+
19
+ /** CPU-only, callback-based stable-priority scheduling shared by all eyes and sources. */
20
+ export default class TileBuildQueue {
21
+ /** Builds that have been submitted to workers. */
22
+ private readonly active = new Map<string, TileBuildTask>();
23
+ /** Latest unsent generation for each tile key, in stable insertion order. */
24
+ private readonly pending = new Map<string, TileBuildTask>();
25
+ /** Unlimited unless the host opts into a build limit. */
26
+ private limit = Infinity;
27
+ /** Prevent synchronous completion/failure callbacks from recursively pumping. */
28
+ private pumping = false;
29
+ /** Nested visibility updates must install all priorities before any submission. */
30
+ private suspensionDepth = 0;
31
+
32
+ /** Set a validated concurrency limit without cancelling active work. */
33
+ setLimit(limit: number | undefined): void {
34
+ this.limit = limit ?? Infinity;
35
+ }
36
+
37
+ /** Begin an atomic source/eye-union update without submitting stale queued work. */
38
+ suspend(): void { this.suspensionDepth++; }
39
+
40
+ /** Commit a batch and resume priority-based submission after the outermost update. */
41
+ resume(): void {
42
+ this.suspensionDepth = Math.max(0, this.suspensionDepth - 1);
43
+ this.pump();
44
+ }
45
+
46
+ /** Queue the latest generation; a duplicate active task is not submitted twice. */
47
+ enqueue(task: TileBuildTask): void {
48
+ if (this.active.get(task.key)?.token === task.token) return;
49
+ this.pending.set(task.key, task);
50
+ this.pump();
51
+ }
52
+
53
+ /** Update unsent work when the shared eye-union visibility changes. */
54
+ setPriority(key: string, priority: number): void {
55
+ const task = this.pending.get(key);
56
+ if (task) task.priority = priority;
57
+ }
58
+
59
+ /** Release only the matching generation; late worker replies cannot free new work. */
60
+ finish(key: string, token: string): boolean {
61
+ if (this.active.get(key)?.token !== token) return false;
62
+ this.active.delete(key);
63
+ this.pump();
64
+ return true;
65
+ }
66
+
67
+ /** Remove a tile's queued and active ownership after the caller cancels its worker. */
68
+ cancel(key: string): void {
69
+ this.pending.delete(key);
70
+ this.active.delete(key);
71
+ }
72
+
73
+ /** Whether any generation of a tile still owns queue state. */
74
+ has(key: string): boolean {
75
+ return this.active.has(key) || this.pending.has(key);
76
+ }
77
+
78
+ /** Active and unsent counts, detached from the queue's internal maps. */
79
+ getCounts(): {activeBuilds: number; queuedBuilds: number} {
80
+ return {activeBuilds: this.active.size, queuedBuilds: this.pending.size};
81
+ }
82
+
83
+ /** Submit available slots, retaining stable ordering among equal priorities. */
84
+ pump(): void {
85
+ if (this.pumping || this.suspensionDepth > 0) return;
86
+ this.pumping = true;
87
+ try {
88
+ while (this.active.size < this.limit) {
89
+ let next: TileBuildTask | undefined;
90
+ for (const task of this.pending.values()) {
91
+ if (!this.active.has(task.key) && (!next || task.priority < next.priority)) next = task;
92
+ }
93
+ if (!next) break;
94
+ this.pending.delete(next.key);
95
+ this.active.set(next.key, next);
96
+ try { next.start(); }
97
+ catch (error) {
98
+ this.active.delete(next.key);
99
+ next.fail(error);
100
+ }
101
+ }
102
+ } finally { this.pumping = false; }
103
+ }
104
+
105
+ /** Release all queue metadata during scene teardown. */
106
+ clear(): void {
107
+ this.pending.clear();
108
+ this.active.clear();
109
+ }
110
+ }
@@ -0,0 +1,89 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /** Immutable resource metadata supplied by the host, with unique keys per snapshot. */
6
+ export interface TileCacheRecord {
7
+ /** Stable content identity, including source/style generation when necessary. */
8
+ readonly key: string;
9
+ /** Non-negative finite resource bytes; the host defines which resources are counted. */
10
+ readonly bytes: number;
11
+ /** Whether visibility, loading, proxies, pins or consumers currently prevent eviction. */
12
+ readonly protected: boolean;
13
+ }
14
+
15
+ /** Already validated non-negative cache limits; protected records do not consume these budgets. */
16
+ export interface TileCacheOptions {
17
+ /** Maximum evictable entries; omit for unlimited. */
18
+ readonly maxCachedTiles?: number;
19
+ /** Maximum evictable resource bytes; omit for unlimited. */
20
+ readonly maxCachedBytes?: number;
21
+ }
22
+
23
+ /** Detached accounting for a host-provided resident snapshot, not a resource allocator. */
24
+ export interface TileCacheStatistics {
25
+ /** All retained records, including protected entries. */
26
+ residentTiles: number;
27
+ /** Entries eligible for eviction. */
28
+ cachedTiles: number;
29
+ /** Bytes in evictable entries. */
30
+ cachedBytes: number;
31
+ /** Entries excluded from eviction. */
32
+ protectedTiles: number;
33
+ /** Bytes in protected entries. */
34
+ protectedBytes: number;
35
+ }
36
+
37
+ /** Stable LRU policy owning recency metadata only; selection never disposes or removes content. */
38
+ export class TileCachePolicy {
39
+ /** Logical access order, independent of clocks and resource ownership. */
40
+ private readonly lastUsed = new Map<string, number>();
41
+ /** Monotonic access sequence retained across metadata resets. */
42
+ private tick = 0;
43
+
44
+ /** Mark a key as recently used; callers decide when visibility counts as reuse. */
45
+ touch(key: string): void {
46
+ this.lastUsed.set(key, ++this.tick);
47
+ }
48
+
49
+ /** Release recency metadata after the host removes a record. */
50
+ forget(key: string): void {
51
+ this.lastUsed.delete(key);
52
+ }
53
+
54
+ /** Select oldest evictable keys until both budgets fit, without changing input or recency. */
55
+ selectEvictions(records: readonly TileCacheRecord[], options?: TileCacheOptions): string[] {
56
+ if (options?.maxCachedTiles === undefined && options?.maxCachedBytes === undefined) return [];
57
+ const cached = records.filter(record => !record.protected);
58
+ cached.sort((first, second) => (this.lastUsed.get(first.key) ?? 0) - (this.lastUsed.get(second.key) ?? 0));
59
+ let count = cached.length;
60
+ let bytes = cached.reduce((total, record) => total + record.bytes, 0);
61
+ const evictions: string[] = [];
62
+ for (const record of cached) {
63
+ if (count <= (options?.maxCachedTiles ?? Infinity) && bytes <= (options?.maxCachedBytes ?? Infinity)) break;
64
+ evictions.push(record.key);
65
+ count--;
66
+ bytes -= record.bytes;
67
+ }
68
+ return evictions;
69
+ }
70
+
71
+ /** Count the current snapshot without changing recency or retaining record objects. */
72
+ getStatistics(records: readonly TileCacheRecord[]): TileCacheStatistics {
73
+ let cachedTiles = 0, cachedBytes = 0, protectedBytes = 0;
74
+ for (const record of records) {
75
+ if (record.protected) protectedBytes += record.bytes;
76
+ else {
77
+ cachedTiles++;
78
+ cachedBytes += record.bytes;
79
+ }
80
+ }
81
+ return {residentTiles: records.length, cachedTiles, cachedBytes,
82
+ protectedTiles: records.length - cachedTiles, protectedBytes};
83
+ }
84
+
85
+ /** Clear recency metadata without touching host content or resetting the logical clock. */
86
+ clear(): void {
87
+ this.lastUsed.clear();
88
+ }
89
+ }
@@ -0,0 +1,50 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /** Private, copied selection metadata for one logical view or resource consumer. */
6
+ interface TileConsumerState {
7
+ /** Requested keys, including not-yet-loaded content. */
8
+ selected: Set<string>;
9
+ /** Drawable fallback and refinement keys. */
10
+ visible: Set<string>;
11
+ }
12
+
13
+ /** Shared-consumer residency metadata; does not own tiles, loading or disposal. */
14
+ export class TileResidency<ConsumerId = symbol> {
15
+ /** Consumers retain independent snapshots of their selected and visible keys. */
16
+ private readonly consumers = new Map<ConsumerId, TileConsumerState>();
17
+
18
+ /** Attach a consumer with empty state, resetting it if already attached. */
19
+ attachConsumer(id: ConsumerId): void {
20
+ this.updateConsumer(id, [], []);
21
+ }
22
+
23
+ /** Replace a consumer's state with copied keys; unknown consumers are attached. */
24
+ updateConsumer(id: ConsumerId, selected: readonly string[], visible: readonly string[]): void {
25
+ this.consumers.set(id, {selected: new Set(selected), visible: new Set(visible)});
26
+ }
27
+
28
+ /** Release only this consumer's protection, leaving other consumers intact. */
29
+ detachConsumer(id: ConsumerId): void {
30
+ this.consumers.delete(id);
31
+ }
32
+
33
+ /** Return a detached, deduplicated union in consumer and key insertion order. */
34
+ getSelectedTileKeys(): string[] {
35
+ return [...new Set([...this.consumers.values()].flatMap(consumer => [...consumer.selected]))];
36
+ }
37
+
38
+ /** Whether any consumer selects or displays a key, even if its content is not loaded. */
39
+ isProtected(key: string): boolean {
40
+ for (const consumer of this.consumers.values()) {
41
+ if (consumer.selected.has(key) || consumer.visible.has(key)) return true;
42
+ }
43
+ return false;
44
+ }
45
+
46
+ /** Release all consumer metadata; no tile resources are unloaded. */
47
+ clear(): void {
48
+ this.consumers.clear();
49
+ }
50
+ }