@react-x11/components 0.7.1 → 0.9.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 (354) hide show
  1. package/README.md +295 -51
  2. package/dist/charts/node.d.ts +1 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +6 -2
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/color-picker/ColorPicker.d.ts.map +1 -1
  7. package/dist/color-picker/ColorPicker.js +43 -33
  8. package/dist/color-picker/ColorPicker.js.map +1 -1
  9. package/dist/embed/client.d.ts +48 -3
  10. package/dist/embed/client.d.ts.map +1 -1
  11. package/dist/embed/client.js +79 -5
  12. package/dist/embed/client.js.map +1 -1
  13. package/dist/embed/index.d.ts +1 -1
  14. package/dist/embed/index.d.ts.map +1 -1
  15. package/dist/embed/index.js +1 -1
  16. package/dist/embed/index.js.map +1 -1
  17. package/dist/html/node.d.ts +3 -1
  18. package/dist/html/node.d.ts.map +1 -1
  19. package/dist/html/node.js +7 -5
  20. package/dist/html/node.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
  26. package/dist/internal/markdown/ast.d.ts.map +1 -0
  27. package/dist/internal/markdown/ast.js +22 -0
  28. package/dist/internal/markdown/ast.js.map +1 -0
  29. package/dist/internal/markdown/parse.d.ts.map +1 -0
  30. package/dist/{markdown → internal/markdown}/parse.js +112 -22
  31. package/dist/internal/markdown/parse.js.map +1 -0
  32. package/dist/internal/markdown/stringify.d.ts +4 -0
  33. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  34. package/dist/internal/markdown/stringify.js +684 -0
  35. package/dist/internal/markdown/stringify.js.map +1 -0
  36. package/dist/internal/markdown/tags.d.ts.map +1 -0
  37. package/dist/internal/markdown/tags.js.map +1 -0
  38. package/dist/internal/scroll.d.ts +3 -2
  39. package/dist/internal/scroll.d.ts.map +1 -1
  40. package/dist/internal/scroll.js +33 -15
  41. package/dist/internal/scroll.js.map +1 -1
  42. package/dist/internal/units.d.ts +2 -0
  43. package/dist/internal/units.d.ts.map +1 -0
  44. package/dist/internal/units.js +35 -0
  45. package/dist/internal/units.js.map +1 -0
  46. package/dist/internal/window.d.ts.map +1 -1
  47. package/dist/internal/window.js +4 -2
  48. package/dist/internal/window.js.map +1 -1
  49. package/dist/maps/anchors.d.ts +98 -0
  50. package/dist/maps/anchors.d.ts.map +1 -0
  51. package/dist/maps/anchors.js +531 -0
  52. package/dist/maps/anchors.js.map +1 -0
  53. package/dist/maps/controller.d.ts +283 -0
  54. package/dist/maps/controller.d.ts.map +1 -0
  55. package/dist/maps/controller.js +743 -0
  56. package/dist/maps/controller.js.map +1 -0
  57. package/dist/maps/gl/buckets.d.ts +95 -0
  58. package/dist/maps/gl/buckets.d.ts.map +1 -0
  59. package/dist/maps/gl/buckets.js +362 -0
  60. package/dist/maps/gl/buckets.js.map +1 -0
  61. package/dist/maps/gl/build-worker.d.ts +13 -0
  62. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  63. package/dist/maps/gl/build-worker.js +44 -0
  64. package/dist/maps/gl/build-worker.js.map +1 -0
  65. package/dist/maps/gl/color.d.ts +8 -0
  66. package/dist/maps/gl/color.d.ts.map +1 -0
  67. package/dist/maps/gl/color.js +57 -0
  68. package/dist/maps/gl/color.js.map +1 -0
  69. package/dist/maps/gl/cover.d.ts +51 -0
  70. package/dist/maps/gl/cover.d.ts.map +1 -0
  71. package/dist/maps/gl/cover.js +124 -0
  72. package/dist/maps/gl/cover.js.map +1 -0
  73. package/dist/maps/gl/index.d.ts +3 -0
  74. package/dist/maps/gl/index.d.ts.map +1 -0
  75. package/dist/maps/gl/index.js +20 -0
  76. package/dist/maps/gl/index.js.map +1 -0
  77. package/dist/maps/gl/markers.d.ts +28 -0
  78. package/dist/maps/gl/markers.d.ts.map +1 -0
  79. package/dist/maps/gl/markers.js +60 -0
  80. package/dist/maps/gl/markers.js.map +1 -0
  81. package/dist/maps/gl/overlays.d.ts +68 -0
  82. package/dist/maps/gl/overlays.d.ts.map +1 -0
  83. package/dist/maps/gl/overlays.js +300 -0
  84. package/dist/maps/gl/overlays.js.map +1 -0
  85. package/dist/maps/gl/pane.d.ts +12 -0
  86. package/dist/maps/gl/pane.d.ts.map +1 -0
  87. package/dist/maps/gl/pane.js +37 -0
  88. package/dist/maps/gl/pane.js.map +1 -0
  89. package/dist/maps/gl/placement.d.ts +111 -0
  90. package/dist/maps/gl/placement.d.ts.map +1 -0
  91. package/dist/maps/gl/placement.js +567 -0
  92. package/dist/maps/gl/placement.js.map +1 -0
  93. package/dist/maps/gl/renderer.d.ts +352 -0
  94. package/dist/maps/gl/renderer.d.ts.map +1 -0
  95. package/dist/maps/gl/renderer.js +1127 -0
  96. package/dist/maps/gl/renderer.js.map +1 -0
  97. package/dist/maps/gl/shaders.d.ts +120 -0
  98. package/dist/maps/gl/shaders.d.ts.map +1 -0
  99. package/dist/maps/gl/shaders.js +451 -0
  100. package/dist/maps/gl/shaders.js.map +1 -0
  101. package/dist/maps/gl/store.d.ts +115 -0
  102. package/dist/maps/gl/store.d.ts.map +1 -0
  103. package/dist/maps/gl/store.js +469 -0
  104. package/dist/maps/gl/store.js.map +1 -0
  105. package/dist/maps/gl/text.d.ts +154 -0
  106. package/dist/maps/gl/text.d.ts.map +1 -0
  107. package/dist/maps/gl/text.js +382 -0
  108. package/dist/maps/gl/text.js.map +1 -0
  109. package/dist/maps/gl/view.d.ts +54 -0
  110. package/dist/maps/gl/view.d.ts.map +1 -0
  111. package/dist/maps/gl/view.js +974 -0
  112. package/dist/maps/gl/view.js.map +1 -0
  113. package/dist/maps/index.d.ts +1 -1
  114. package/dist/maps/index.d.ts.map +1 -1
  115. package/dist/maps/index.js +216 -52
  116. package/dist/maps/index.js.map +1 -1
  117. package/dist/maps/labels.d.ts +37 -16
  118. package/dist/maps/labels.d.ts.map +1 -1
  119. package/dist/maps/labels.js +188 -119
  120. package/dist/maps/labels.js.map +1 -1
  121. package/dist/maps/node.d.ts +162 -56
  122. package/dist/maps/node.d.ts.map +1 -1
  123. package/dist/maps/node.js +607 -515
  124. package/dist/maps/node.js.map +1 -1
  125. package/dist/maps/overlay.d.ts +72 -9
  126. package/dist/maps/overlay.d.ts.map +1 -1
  127. package/dist/maps/overlay.js +108 -28
  128. package/dist/maps/overlay.js.map +1 -1
  129. package/dist/maps/paint.d.ts +3 -0
  130. package/dist/maps/paint.d.ts.map +1 -1
  131. package/dist/maps/paint.js.map +1 -1
  132. package/dist/maps/proj.d.ts +14 -0
  133. package/dist/maps/proj.d.ts.map +1 -1
  134. package/dist/maps/proj.js +28 -0
  135. package/dist/maps/proj.js.map +1 -1
  136. package/dist/maps/renderer.d.ts +69 -0
  137. package/dist/maps/renderer.d.ts.map +1 -0
  138. package/dist/maps/renderer.js +66 -0
  139. package/dist/maps/renderer.js.map +1 -0
  140. package/dist/maps/sources.d.ts +25 -3
  141. package/dist/maps/sources.d.ts.map +1 -1
  142. package/dist/maps/sources.js +17 -0
  143. package/dist/maps/sources.js.map +1 -1
  144. package/dist/maps/theme.d.ts +19 -0
  145. package/dist/maps/theme.d.ts.map +1 -0
  146. package/dist/maps/theme.js +44 -0
  147. package/dist/maps/theme.js.map +1 -0
  148. package/dist/maps/tiles.d.ts +117 -17
  149. package/dist/maps/tiles.d.ts.map +1 -1
  150. package/dist/maps/tiles.js +163 -30
  151. package/dist/maps/tiles.js.map +1 -1
  152. package/dist/maps/types.d.ts +188 -20
  153. package/dist/maps/types.d.ts.map +1 -1
  154. package/dist/markdown/expressions.d.ts +1 -1
  155. package/dist/markdown/expressions.d.ts.map +1 -1
  156. package/dist/markdown/expressions.js.map +1 -1
  157. package/dist/markdown/index.d.ts +7 -6
  158. package/dist/markdown/index.d.ts.map +1 -1
  159. package/dist/markdown/index.js +9 -7
  160. package/dist/markdown/index.js.map +1 -1
  161. package/dist/markdown/spans.d.ts +1 -1
  162. package/dist/markdown/spans.d.ts.map +1 -1
  163. package/dist/media-player/index.d.ts +11 -3
  164. package/dist/media-player/index.d.ts.map +1 -1
  165. package/dist/media-player/index.js +13 -2
  166. package/dist/media-player/index.js.map +1 -1
  167. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  168. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  169. package/dist/rich-text-editor/clipboard.js +307 -0
  170. package/dist/rich-text-editor/clipboard.js.map +1 -0
  171. package/dist/rich-text-editor/collab.d.ts +32 -0
  172. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  173. package/dist/rich-text-editor/collab.js +55 -0
  174. package/dist/rich-text-editor/collab.js.map +1 -0
  175. package/dist/rich-text-editor/commands.d.ts +50 -0
  176. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  177. package/dist/rich-text-editor/commands.js +376 -0
  178. package/dist/rich-text-editor/commands.js.map +1 -0
  179. package/dist/rich-text-editor/drag.d.ts +68 -0
  180. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  181. package/dist/rich-text-editor/drag.js +148 -0
  182. package/dist/rich-text-editor/drag.js.map +1 -0
  183. package/dist/rich-text-editor/html.d.ts +145 -0
  184. package/dist/rich-text-editor/html.d.ts.map +1 -0
  185. package/dist/rich-text-editor/html.js +516 -0
  186. package/dist/rich-text-editor/html.js.map +1 -0
  187. package/dist/rich-text-editor/index.d.ts +263 -0
  188. package/dist/rich-text-editor/index.d.ts.map +1 -0
  189. package/dist/rich-text-editor/index.js +822 -0
  190. package/dist/rich-text-editor/index.js.map +1 -0
  191. package/dist/rich-text-editor/inline.d.ts +94 -0
  192. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  193. package/dist/rich-text-editor/inline.js +252 -0
  194. package/dist/rich-text-editor/inline.js.map +1 -0
  195. package/dist/rich-text-editor/keymap.d.ts +71 -0
  196. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  197. package/dist/rich-text-editor/keymap.js +393 -0
  198. package/dist/rich-text-editor/keymap.js.map +1 -0
  199. package/dist/rich-text-editor/keys.d.ts +29 -0
  200. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  201. package/dist/rich-text-editor/keys.js +93 -0
  202. package/dist/rich-text-editor/keys.js.map +1 -0
  203. package/dist/rich-text-editor/look.d.ts +71 -0
  204. package/dist/rich-text-editor/look.d.ts.map +1 -0
  205. package/dist/rich-text-editor/look.js +342 -0
  206. package/dist/rich-text-editor/look.js.map +1 -0
  207. package/dist/rich-text-editor/markdown.d.ts +18 -0
  208. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  209. package/dist/rich-text-editor/markdown.js +496 -0
  210. package/dist/rich-text-editor/markdown.js.map +1 -0
  211. package/dist/rich-text-editor/nodes.d.ts +135 -0
  212. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  213. package/dist/rich-text-editor/nodes.js +397 -0
  214. package/dist/rich-text-editor/nodes.js.map +1 -0
  215. package/dist/rich-text-editor/render.d.ts +68 -0
  216. package/dist/rich-text-editor/render.d.ts.map +1 -0
  217. package/dist/rich-text-editor/render.js +500 -0
  218. package/dist/rich-text-editor/render.js.map +1 -0
  219. package/dist/rich-text-editor/schema.d.ts +16 -0
  220. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  221. package/dist/rich-text-editor/schema.js +386 -0
  222. package/dist/rich-text-editor/schema.js.map +1 -0
  223. package/dist/rich-text-editor/store.d.ts +11 -0
  224. package/dist/rich-text-editor/store.d.ts.map +1 -0
  225. package/dist/rich-text-editor/store.js +43 -0
  226. package/dist/rich-text-editor/store.js.map +1 -0
  227. package/dist/rich-text-editor/suggest.d.ts +127 -0
  228. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  229. package/dist/rich-text-editor/suggest.js +0 -0
  230. package/dist/rich-text-editor/suggest.js.map +1 -0
  231. package/dist/rich-text-editor/tables.d.ts +41 -0
  232. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  233. package/dist/rich-text-editor/tables.js +204 -0
  234. package/dist/rich-text-editor/tables.js.map +1 -0
  235. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  236. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  237. package/dist/rich-text-editor/toolbar.js +548 -0
  238. package/dist/rich-text-editor/toolbar.js.map +1 -0
  239. package/dist/rich-text-editor/view.d.ts +368 -0
  240. package/dist/rich-text-editor/view.d.ts.map +1 -0
  241. package/dist/rich-text-editor/view.js +1968 -0
  242. package/dist/rich-text-editor/view.js.map +1 -0
  243. package/dist/rich-text-editor/virtual.d.ts +54 -0
  244. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  245. package/dist/rich-text-editor/virtual.js +262 -0
  246. package/dist/rich-text-editor/virtual.js.map +1 -0
  247. package/dist/richtext/node.d.ts +25 -2
  248. package/dist/richtext/node.d.ts.map +1 -1
  249. package/dist/richtext/node.js +46 -24
  250. package/dist/richtext/node.js.map +1 -1
  251. package/dist/table/index.d.ts.map +1 -1
  252. package/dist/table/index.js +14 -5
  253. package/dist/table/index.js.map +1 -1
  254. package/dist/terminal/backends.d.ts +4 -1
  255. package/dist/terminal/backends.d.ts.map +1 -1
  256. package/dist/terminal/backends.js.map +1 -1
  257. package/dist/terminal/index.d.ts +18 -7
  258. package/dist/terminal/index.d.ts.map +1 -1
  259. package/dist/terminal/index.js +28 -11
  260. package/dist/terminal/index.js.map +1 -1
  261. package/dist/terminal/vt/node.d.ts +56 -3
  262. package/dist/terminal/vt/node.d.ts.map +1 -1
  263. package/dist/terminal/vt/node.js +106 -26
  264. package/dist/terminal/vt/node.js.map +1 -1
  265. package/dist/terminal/vt/renderer.d.ts +4 -0
  266. package/dist/terminal/vt/renderer.d.ts.map +1 -1
  267. package/dist/terminal/vt/renderer.js +14 -5
  268. package/dist/terminal/vt/renderer.js.map +1 -1
  269. package/dist/tree/index.d.ts.map +1 -1
  270. package/dist/tree/index.js +6 -4
  271. package/dist/tree/index.js.map +1 -1
  272. package/package.json +23 -4
  273. package/src/charts/node.ts +6 -2
  274. package/src/color-picker/ColorPicker.ts +107 -81
  275. package/src/embed/client.ts +102 -8
  276. package/src/embed/index.ts +2 -0
  277. package/src/html/node.ts +7 -5
  278. package/src/index.ts +2 -0
  279. package/src/{markdown → internal/markdown}/ast.ts +24 -7
  280. package/src/{markdown → internal/markdown}/parse.ts +134 -24
  281. package/src/internal/markdown/stringify.ts +772 -0
  282. package/src/internal/scroll.ts +39 -18
  283. package/src/internal/units.ts +34 -0
  284. package/src/internal/window.ts +4 -2
  285. package/src/maps/anchors.ts +607 -0
  286. package/src/maps/controller.ts +999 -0
  287. package/src/maps/gl/buckets.ts +457 -0
  288. package/src/maps/gl/build-worker.ts +55 -0
  289. package/src/maps/gl/color.ts +62 -0
  290. package/src/maps/gl/cover.ts +170 -0
  291. package/src/maps/gl/index.ts +23 -0
  292. package/src/maps/gl/markers.ts +83 -0
  293. package/src/maps/gl/overlays.ts +379 -0
  294. package/src/maps/gl/pane.ts +42 -0
  295. package/src/maps/gl/placement.ts +816 -0
  296. package/src/maps/gl/renderer.ts +1576 -0
  297. package/src/maps/gl/shaders.ts +486 -0
  298. package/src/maps/gl/store.ts +566 -0
  299. package/src/maps/gl/text.ts +495 -0
  300. package/src/maps/gl/view.ts +1224 -0
  301. package/src/maps/index.ts +270 -81
  302. package/src/maps/labels.ts +214 -121
  303. package/src/maps/node.ts +763 -629
  304. package/src/maps/overlay.ts +148 -43
  305. package/src/maps/paint.ts +3 -0
  306. package/src/maps/proj.ts +31 -0
  307. package/src/maps/renderer.ts +155 -0
  308. package/src/maps/sources.ts +38 -3
  309. package/src/maps/theme.ts +51 -0
  310. package/src/maps/tiles.ts +191 -33
  311. package/src/maps/types.ts +188 -20
  312. package/src/markdown/expressions.ts +5 -1
  313. package/src/markdown/index.ts +14 -11
  314. package/src/markdown/spans.ts +1 -1
  315. package/src/media-player/index.ts +18 -4
  316. package/src/rich-text-editor/clipboard.ts +439 -0
  317. package/src/rich-text-editor/collab.ts +91 -0
  318. package/src/rich-text-editor/commands.ts +448 -0
  319. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  320. package/src/rich-text-editor/drag.ts +211 -0
  321. package/src/rich-text-editor/html.ts +619 -0
  322. package/src/rich-text-editor/index.ts +1331 -0
  323. package/src/rich-text-editor/inline.ts +345 -0
  324. package/src/rich-text-editor/keymap.ts +510 -0
  325. package/src/rich-text-editor/keys.ts +119 -0
  326. package/src/rich-text-editor/look.ts +427 -0
  327. package/src/rich-text-editor/markdown.ts +653 -0
  328. package/src/rich-text-editor/nodes.ts +497 -0
  329. package/src/rich-text-editor/render.ts +800 -0
  330. package/src/rich-text-editor/schema.ts +431 -0
  331. package/src/rich-text-editor/store.ts +39 -0
  332. package/src/rich-text-editor/suggest.ts +597 -0
  333. package/src/rich-text-editor/tables.ts +272 -0
  334. package/src/rich-text-editor/toolbar.ts +724 -0
  335. package/src/rich-text-editor/view.ts +2287 -0
  336. package/src/rich-text-editor/virtual.ts +347 -0
  337. package/src/richtext/node.ts +59 -30
  338. package/src/table/index.ts +14 -5
  339. package/src/terminal/backends.ts +4 -1
  340. package/src/terminal/index.ts +43 -15
  341. package/src/terminal/vt/node.ts +128 -29
  342. package/src/terminal/vt/renderer.ts +27 -5
  343. package/src/tree/index.ts +6 -4
  344. package/dist/markdown/ast.d.ts.map +0 -1
  345. package/dist/markdown/ast.js +0 -17
  346. package/dist/markdown/ast.js.map +0 -1
  347. package/dist/markdown/parse.d.ts.map +0 -1
  348. package/dist/markdown/parse.js.map +0 -1
  349. package/dist/markdown/tags.d.ts.map +0 -1
  350. package/dist/markdown/tags.js.map +0 -1
  351. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  352. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  353. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  354. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -0,0 +1,566 @@
1
+ // Tiles for the GL renderer: loaded, parsed, bucketed, and let go.
2
+ //
3
+ // The retained renderer's cache has three layers (data, surfaces, labels);
4
+ // this one has one, because a tile's buckets are valid for every camera and
5
+ // every palette. What it keeps per tile is the bucket arrays — a few hundred
6
+ // kilobytes where the retained cache keeps two 4 MB surfaces — and the only
7
+ // thing that invalidates them is a style whose *filters* changed, since the
8
+ // filters are what decide which features a layer draws. Colours, widths and
9
+ // zoom ranges are uniforms and gates, read every frame; a light-to-dark
10
+ // switch rebuilds nothing.
11
+ //
12
+ // Building is the one cost a tile has, and there are two places to pay it.
13
+ // On this thread, `pump` builds until the frame's deadline — always at
14
+ // least one tile, the retained renderer's budget rule — and a dense city
15
+ // tile is 5–20 ms of it, which is most of a 50 Hz frame. On **worker
16
+ // threads** (`workers > 0`) this thread only dispatches: the bytes go over
17
+ // by copy, the buckets come back by transfer, and the main thread's whole
18
+ // cost for a tile is its upload. The bucket format is what makes the second
19
+ // one free to have — two ArrayBuffers and a small draw list, nothing that
20
+ // needs a serializer.
21
+ import { parseVectorTile } from '../sources.js';
22
+ import type { MapSource, TileData } from '../sources.js';
23
+ import type { TileId } from '../proj.js';
24
+ import type { PreparedStyle } from '../paint.js';
25
+ import type { MapStyleLayer } from '../style.js';
26
+ import { buildTileBuckets, rasterTileData } from './buckets.js';
27
+ import type { GlTileData } from './buckets.js';
28
+ import type { BuildRequest } from './build-worker.js';
29
+ import type { TileLookup } from './cover.js';
30
+
31
+ type State = 'loading' | 'loaded' | 'building' | 'ready' | 'empty' | 'failed';
32
+
33
+ interface Entry {
34
+ tile: TileId;
35
+ state: State;
36
+ /** The tile's bytes, kept so a style change can rebuild without a fetch. */
37
+ bytes: Uint8Array | null;
38
+ data: GlTileData | null;
39
+ /** The frame the cover last asked for this tile — the eviction order. */
40
+ used: number;
41
+ retryAt: number;
42
+ failures: number;
43
+ /** The worker job whose answer this entry is waiting for. */
44
+ job: number;
45
+ /** Cancels the load in flight, while there is one. */
46
+ abort: (() => void) | null;
47
+ }
48
+
49
+ export interface GlTileStoreOptions {
50
+ source: MapSource;
51
+ /** What the source is called in its requests and its errors —
52
+ * `source-<index>` when it has no `id`. */
53
+ sourceId?: string;
54
+ prepared: PreparedStyle;
55
+ /**
56
+ * Called once per failed load, with whatever the source threw — what
57
+ * `<Map onTileError>` hears. A load aborted because the map stopped
58
+ * wanting the tile is not a failure, whatever it answers after that.
59
+ */
60
+ onError?: (error: unknown, tile: TileId & { sourceId: string }) => void;
61
+ /** A load landed or a build finished: a frame is worth drawing. */
62
+ onChange: () => void;
63
+ /** Loads in flight at once. 8 by default. */
64
+ concurrency?: number;
65
+ /** Tiles kept, bytes and buckets both. 256 by default. */
66
+ capacity?: number;
67
+ /**
68
+ * Build buckets on this many worker threads. `0` (the default) builds on
69
+ * this thread under `pump`'s budget. Falls back to that when the runtime
70
+ * has no `worker_threads`.
71
+ */
72
+ workers?: number;
73
+ }
74
+
75
+ const globals = globalThis as {
76
+ performance?: { now(): number };
77
+ AbortController?: new () => { signal: { aborted: boolean }; abort(): void };
78
+ };
79
+ const now = (): number => globals.performance?.now() ?? Date.now();
80
+ const key = (t: TileId): string => `${t.z}/${t.x}/${t.y}`;
81
+
82
+ export class GlTileStore implements TileLookup {
83
+ private readonly _source: MapSource;
84
+ private readonly _sourceId: string;
85
+ private readonly _onError:
86
+ ((error: unknown, tile: TileId & { sourceId: string }) => void) | undefined;
87
+ private _prepared: PreparedStyle;
88
+ private _style: { id: number; layers: MapStyleLayer[] };
89
+ private readonly _onChange: () => void;
90
+ private readonly _concurrency: number;
91
+ private readonly _capacity: number;
92
+ private readonly _entries = new Map<string, Entry>();
93
+ /** Loaded tiles waiting for a build, oldest first. */
94
+ private _queue: Entry[] = [];
95
+ private _waiting: Entry[] = [];
96
+ private _inFlight = 0;
97
+ private _frame = 0;
98
+ private _disposed = false;
99
+ private readonly _pool: BuildPool | null;
100
+ /** Bucket builds and their cost on this thread, for a HUD. */
101
+ built = 0;
102
+ buildMs = 0;
103
+
104
+ constructor(options: GlTileStoreOptions) {
105
+ this._source = options.source;
106
+ this._sourceId = options.sourceId ?? options.source.id ?? 'source-0';
107
+ this._onError = options.onError;
108
+ this._prepared = options.prepared;
109
+ this._style = {
110
+ id: 1,
111
+ layers: options.prepared.layers.map((l) => l.layer),
112
+ };
113
+ this._onChange = options.onChange;
114
+ this._concurrency = options.concurrency ?? 8;
115
+ this._capacity = options.capacity ?? 256;
116
+ const workers = options.workers ?? 0;
117
+ this._pool =
118
+ workers > 0 ? new BuildPool(workers, () => this._onChange()) : null;
119
+ }
120
+
121
+ get source(): MapSource {
122
+ return this._source;
123
+ }
124
+
125
+ /** Tiles loaded and waiting for (or in) a build. */
126
+ get building(): number {
127
+ let n = this._queue.length;
128
+ for (const entry of this._entries.values())
129
+ if (entry.state === 'building') n++;
130
+ return n;
131
+ }
132
+
133
+ /** Tiles asked for and not yet answered. */
134
+ get loading(): number {
135
+ return this._inFlight + this._waiting.length;
136
+ }
137
+
138
+ /** Whether builds happen off this thread. */
139
+ get threaded(): boolean {
140
+ return this._pool?.ready === true;
141
+ }
142
+
143
+ get(tile: TileId): GlTileData | null | undefined {
144
+ const entry = this._entries.get(key(tile));
145
+ if (!entry) return undefined;
146
+ entry.used = this._frame;
147
+ if (entry.state === 'ready') return entry.data;
148
+ if (entry.state === 'empty') return null;
149
+ // A tile being rebuilt for a new style keeps drawing its old buckets
150
+ // until the new ones exist, so a restyle never opens a hole.
151
+ return entry.data ?? undefined;
152
+ }
153
+
154
+ /** A new frame: what the cover asks for from here on is this frame's. */
155
+ tick(): void {
156
+ this._frame++;
157
+ }
158
+
159
+ /** Ask for tiles, centre-out. Already-known tiles cost a lookup. */
160
+ want(tiles: readonly TileId[]): void {
161
+ const at = now();
162
+ for (const tile of tiles) {
163
+ const k = key(tile);
164
+ let entry = this._entries.get(k);
165
+ if (entry && !(entry.state === 'failed' && at >= entry.retryAt)) continue;
166
+ if (!entry) {
167
+ entry = {
168
+ tile,
169
+ state: 'loading',
170
+ bytes: null,
171
+ data: null,
172
+ used: this._frame,
173
+ retryAt: 0,
174
+ failures: 0,
175
+ job: 0,
176
+ abort: null,
177
+ };
178
+ this._entries.set(k, entry);
179
+ }
180
+ entry.state = 'loading';
181
+ this._waiting.push(entry);
182
+ }
183
+ this._drain();
184
+ }
185
+
186
+ private _drain(): void {
187
+ while (this._inFlight < this._concurrency && this._waiting.length > 0) {
188
+ const entry = this._waiting.shift()!;
189
+ // Asked for, then scrolled away from before a slot came free.
190
+ if (entry.used < this._frame - 2) {
191
+ this._entries.delete(key(entry.tile));
192
+ continue;
193
+ }
194
+ this._load(entry);
195
+ }
196
+ }
197
+
198
+ private _load(entry: Entry): void {
199
+ this._inFlight++;
200
+ // A real `AbortController` where the runtime has one: `fetch` rejects a
201
+ // signal that is not an instance of `AbortSignal`, so a look-alike would
202
+ // fail every load a source makes the documented way (`../tiles.ts`).
203
+ const controller = globals.AbortController
204
+ ? new globals.AbortController()
205
+ : null;
206
+ let aborted = false;
207
+ entry.abort = () => {
208
+ aborted = true;
209
+ controller?.abort();
210
+ };
211
+ const { z, x, y } = entry.tile;
212
+ const sourceId = this._sourceId;
213
+ Promise.resolve()
214
+ .then(() =>
215
+ this._source.load({ z, x, y, sourceId, signal: controller?.signal }),
216
+ )
217
+ .then(
218
+ (data: TileData) => {
219
+ if (this._disposed || aborted) return;
220
+ entry.failures = 0;
221
+ if (data && data.kind === 'vector' && data.data.length > 0) {
222
+ entry.bytes = data.data;
223
+ entry.state = 'loaded';
224
+ this._queue.push(entry);
225
+ } else if (data && data.kind === 'raster' && data.width > 0) {
226
+ // An image needs no building — it is its own texture — and no
227
+ // style: nothing to queue, and nothing a restyle rebuilds.
228
+ entry.data = rasterTileData(data.width, data.height, data.data);
229
+ entry.state = 'ready';
230
+ } else {
231
+ entry.state = 'empty';
232
+ }
233
+ },
234
+ (error: unknown) => {
235
+ // Whatever an aborted load answers — its `AbortError` included —
236
+ // is not news: the map stopped wanting the tile.
237
+ if (this._disposed || aborted) return;
238
+ entry.failures++;
239
+ entry.state = 'failed';
240
+ // 0.5 s, 1, 2, 4 … up to 30 s: the retained cache's backoff, so a
241
+ // source that is down costs nothing and a blip repairs itself.
242
+ entry.retryAt =
243
+ now() + Math.min(30_000, 500 * 2 ** (entry.failures - 1));
244
+ // A failed tile draws nothing, so a map whose tiles all fail looks
245
+ // exactly like a map that is still loading: somebody has to be
246
+ // told, once per failure, as the retained cache tells them.
247
+ this._onError?.(error, { z, x, y, sourceId });
248
+ },
249
+ )
250
+ .finally(() => {
251
+ this._inFlight--;
252
+ entry.abort = null;
253
+ if (this._disposed) return;
254
+ this._drain();
255
+ if (!aborted) this._onChange();
256
+ });
257
+ }
258
+
259
+ /**
260
+ * How many of `tiles` failed to load and are waiting on a retry — the
261
+ * frame's `errors`.
262
+ */
263
+ failedAmong(tiles: readonly TileId[]): number {
264
+ let n = 0;
265
+ for (const tile of tiles) {
266
+ if (this._entries.get(key(tile))?.state === 'failed') n++;
267
+ }
268
+ return n;
269
+ }
270
+
271
+ /**
272
+ * Stop loading: every load in flight is aborted and forgotten, and nothing
273
+ * waiting is started. For a source taken off the map, whose tiles that
274
+ * have arrived stay, so switching back to it is free — the retained
275
+ * cache's rule.
276
+ */
277
+ abortLoads(): void {
278
+ for (const [k, entry] of this._entries) {
279
+ if (entry.state !== 'loading') continue;
280
+ entry.abort?.();
281
+ this._entries.delete(k);
282
+ }
283
+ this._waiting = [];
284
+ }
285
+
286
+ /** Build every tile again from its bytes — `refresh()`, after a style
287
+ * the application edited in place. The old buckets draw until then. */
288
+ rebuild(): void {
289
+ this._style = { ...this._style, id: this._style.id + 1 };
290
+ this._requeue();
291
+ }
292
+
293
+ /**
294
+ * Work through the loaded tiles. Answers whether more are waiting on this
295
+ * thread, which is the caller's cue for another frame.
296
+ *
297
+ * With workers, everything waiting is dispatched and nothing is built
298
+ * here: a finished build calls `onChange` like a finished load does. On
299
+ * this thread, tiles are built until `deadline` — always at least one, or
300
+ * a budget smaller than one tile would build nothing forever.
301
+ */
302
+ pump(deadline: number): boolean {
303
+ const pool = this._pool;
304
+ if (pool && !pool.failed) {
305
+ if (!pool.ready) return this._queue.length > 0;
306
+ while (this._queue.length > 0 && pool.hasRoom()) {
307
+ const entry = this._queue.shift()!;
308
+ if (!entry.bytes) continue;
309
+ entry.state = 'building';
310
+ const style = this._style.id;
311
+ entry.job = pool.build(entry.bytes, this._style, (result) => {
312
+ if (this._disposed || entry.job === 0) return;
313
+ entry.job = 0;
314
+ if (!this._entries.has(key(entry.tile))) return;
315
+ if (result.data && style === this._style.id) {
316
+ entry.data = result.data;
317
+ entry.state = 'ready';
318
+ } else if (result.data) {
319
+ // Built for a style that has since been replaced: build again.
320
+ entry.state = 'loaded';
321
+ this._queue.push(entry);
322
+ } else {
323
+ entry.state = 'empty';
324
+ }
325
+ this._onChange();
326
+ });
327
+ }
328
+ return false;
329
+ }
330
+ let first = true;
331
+ while (this._queue.length > 0 && (first || now() < deadline)) {
332
+ first = false;
333
+ const entry = this._queue.shift()!;
334
+ if (!entry.bytes) continue;
335
+ const started = now();
336
+ try {
337
+ entry.data = buildTileBuckets(
338
+ parseVectorTile(entry.bytes),
339
+ this._prepared,
340
+ );
341
+ entry.state = 'ready';
342
+ } catch {
343
+ entry.state = 'empty';
344
+ }
345
+ this.buildMs += now() - started;
346
+ this.built++;
347
+ }
348
+ return this._queue.length > 0;
349
+ }
350
+
351
+ /**
352
+ * A new style. Buckets are rebuilt only if the new one would select
353
+ * different features — a different layer list or different filters —
354
+ * and until a tile is rebuilt its old buckets keep drawing.
355
+ */
356
+ setStyle(prepared: PreparedStyle): void {
357
+ const same = structureOf(prepared) === structureOf(this._prepared);
358
+ this._prepared = prepared;
359
+ if (same) return;
360
+ this._style = {
361
+ id: this._style.id + 1,
362
+ layers: prepared.layers.map((l) => l.layer),
363
+ };
364
+ this._requeue();
365
+ }
366
+
367
+ /** Every tile with bytes back on the build queue, most recently drawn
368
+ * first; each keeps drawing its old buckets until its new ones exist. */
369
+ private _requeue(): void {
370
+ this._queue = [];
371
+ for (const entry of this._entries.values()) {
372
+ if (
373
+ entry.bytes &&
374
+ (entry.state === 'ready' ||
375
+ entry.state === 'loaded' ||
376
+ entry.state === 'building')
377
+ ) {
378
+ entry.state = 'loaded';
379
+ entry.job = 0;
380
+ this._queue.push(entry);
381
+ }
382
+ }
383
+ this._queue.sort((a, b) => b.used - a.used);
384
+ }
385
+
386
+ /**
387
+ * The end of a frame: cancel every load it did not want, and let go of the
388
+ * least recently drawn tiles past capacity.
389
+ *
390
+ * A tile panned or zoomed out of the cover — and the ring around it the
391
+ * cover keeps warm — would otherwise load to the end: a request nobody is
392
+ * waiting for, pointed at somebody else's servers. That is the contract
393
+ * the docs make for `request.signal`, on either renderer. A tile that
394
+ * comes back is asked for again, with a new signal.
395
+ */
396
+ evict(release: (data: GlTileData) => void): void {
397
+ for (const [k, entry] of this._entries) {
398
+ if (entry.abort && entry.used < this._frame) {
399
+ entry.abort();
400
+ this._entries.delete(k);
401
+ }
402
+ }
403
+ if (this._entries.size <= this._capacity) return;
404
+ const candidates = [...this._entries.entries()]
405
+ .filter(
406
+ ([, e]) =>
407
+ e.state !== 'loading' &&
408
+ e.state !== 'building' &&
409
+ e.used < this._frame,
410
+ )
411
+ .sort((a, b) => a[1].used - b[1].used);
412
+ let excess = this._entries.size - this._capacity;
413
+ for (const [k, entry] of candidates) {
414
+ if (excess <= 0) break;
415
+ if (entry.data) release(entry.data);
416
+ this._entries.delete(k);
417
+ excess--;
418
+ }
419
+ this._queue = this._queue.filter((e) => this._entries.has(key(e.tile)));
420
+ }
421
+
422
+ dispose(release: (data: GlTileData) => void): void {
423
+ this._disposed = true;
424
+ for (const entry of this._entries.values()) {
425
+ if (entry.data) release(entry.data);
426
+ }
427
+ this._entries.clear();
428
+ this._queue = [];
429
+ this._waiting = [];
430
+ this._pool?.terminate();
431
+ }
432
+ }
433
+
434
+ // --- the worker pool ---------------------------------------------------------
435
+
436
+ interface WorkerLike {
437
+ on(event: 'message' | 'error', listener: (value: unknown) => void): void;
438
+ postMessage(message: BuildRequest, transfer?: ArrayBuffer[]): void;
439
+ terminate(): unknown;
440
+ unref?(): void;
441
+ }
442
+
443
+ interface BuildResult {
444
+ job: number;
445
+ data?: GlTileData;
446
+ error?: string;
447
+ }
448
+
449
+ /** Jobs a worker may hold at once: enough to never sit idle between two
450
+ * messages, few enough that the store's queue — which is centre-out and
451
+ * reorders every frame — still decides what is built next. */
452
+ const PER_WORKER = 2;
453
+ const WORKER_THREADS = 'node:worker_threads';
454
+
455
+ class BuildPool {
456
+ ready = false;
457
+ failed = false;
458
+ private readonly _slots: {
459
+ worker: WorkerLike;
460
+ busy: number;
461
+ style: number;
462
+ }[] = [];
463
+ private readonly _jobs = new Map<number, (result: BuildResult) => void>();
464
+ private _next = 1;
465
+
466
+ constructor(count: number, onReady: () => void) {
467
+ void this._spawn(count).then(onReady);
468
+ }
469
+
470
+ private async _spawn(count: number): Promise<void> {
471
+ try {
472
+ // `URL` and `import.meta.url` reached structurally, like everything
473
+ // else runtime-only here: `src/` compiles with `types: []` and no DOM
474
+ // lib, so neither is declared. The entry has to be a URL object, not a
475
+ // string — Node reads a string as a path.
476
+ const here = (import.meta as { url?: string }).url;
477
+ const Url = (
478
+ globalThis as { URL?: new (input: string, base?: string) => object }
479
+ ).URL;
480
+ if (!here || !Url) throw new Error('no module URL to find the worker by');
481
+ const { Worker } = (await import(WORKER_THREADS)) as {
482
+ Worker: new (url: object) => WorkerLike;
483
+ };
484
+ for (let i = 0; i < count; i++) {
485
+ const worker = new Worker(new Url('./build-worker.js', here));
486
+ // An idle pool must not keep the process alive.
487
+ worker.unref?.();
488
+ const slot = { worker, busy: 0, style: 0 };
489
+ worker.on('message', (value) => {
490
+ const result = value as BuildResult;
491
+ slot.busy--;
492
+ const done = this._jobs.get(result.job);
493
+ this._jobs.delete(result.job);
494
+ done?.(result);
495
+ });
496
+ worker.on('error', () => {
497
+ this.failed = true;
498
+ });
499
+ this._slots.push(slot);
500
+ }
501
+ this.ready = true;
502
+ } catch {
503
+ this.failed = true;
504
+ }
505
+ }
506
+
507
+ hasRoom(): boolean {
508
+ return this._slots.some((s) => s.busy < PER_WORKER);
509
+ }
510
+
511
+ build(
512
+ bytes: Uint8Array,
513
+ style: { id: number; layers: MapStyleLayer[] },
514
+ done: (result: BuildResult) => void,
515
+ ): number {
516
+ let slot = this._slots[0];
517
+ for (const s of this._slots) if (s.busy < slot.busy) slot = s;
518
+ if (slot.style !== style.id) {
519
+ slot.worker.postMessage({
520
+ type: 'style',
521
+ id: style.id,
522
+ layers: style.layers,
523
+ });
524
+ slot.style = style.id;
525
+ }
526
+ const job = this._next++;
527
+ this._jobs.set(job, done);
528
+ // A copy of just this tile's bytes, transferred: the store keeps its own
529
+ // for a restyle, and a view into a larger buffer would clone all of it.
530
+ const copy = bytes.slice();
531
+ slot.busy++;
532
+ slot.worker.postMessage(
533
+ { type: 'build', job, style: style.id, bytes: copy },
534
+ [copy.buffer as ArrayBuffer],
535
+ );
536
+ return job;
537
+ }
538
+
539
+ terminate(): void {
540
+ for (const slot of this._slots) void slot.worker.terminate();
541
+ this._slots.length = 0;
542
+ this._jobs.clear();
543
+ }
544
+ }
545
+
546
+ const structures = new WeakMap<PreparedStyle, string>();
547
+
548
+ /** What in a style decides a tile's buckets: which layers, over which
549
+ * source layers, with which filters — and for a label layer, which field
550
+ * its text is read from. Everything else is per frame. */
551
+ function structureOf(prepared: PreparedStyle): string {
552
+ let structure = structures.get(prepared);
553
+ if (structure === undefined) {
554
+ structure = JSON.stringify(
555
+ prepared.layers.map(({ layer }) => [
556
+ layer.type,
557
+ layer.sourceLayer,
558
+ layer.visible === false,
559
+ 'filter' in layer ? (layer.filter ?? null) : null,
560
+ layer.type === 'symbol' ? layer.textField : null,
561
+ ]),
562
+ );
563
+ structures.set(prepared, structure);
564
+ }
565
+ return structure;
566
+ }