signalk-underkeel 0.2.1 → 0.4.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.
- package/CHANGELOG.md +108 -0
- package/README.md +50 -14
- package/lib/api/routes.js +35 -7
- package/lib/api-wiring.js +4 -3
- package/lib/box-sizing.js +94 -0
- package/lib/box-status.js +61 -0
- package/lib/build-process.js +174 -0
- package/lib/config-charts.js +34 -0
- package/lib/config-schema.js +23 -15
- package/lib/config.js +13 -0
- package/lib/engine/chart-box.js +83 -0
- package/lib/engine/fan.js +32 -22
- package/lib/engine/lookahead.js +3 -2
- package/lib/engine/schedule.js +32 -29
- package/lib/engine/ukc-reach.js +2 -2
- package/lib/engine/ukc-tile-cache.js +216 -0
- package/lib/engine/ukc-tile-geometry.js +70 -115
- package/lib/engine/verdict.js +5 -2
- package/lib/foundation/area-cover.js +212 -0
- package/lib/foundation/child-process.js +82 -0
- package/lib/foundation/geometry.js +61 -250
- package/lib/foundation/rect-distance.js +227 -0
- package/lib/index-loader.js +49 -22
- package/lib/index-source.js +65 -0
- package/lib/index.js +24 -2
- package/lib/ingest/cell-cache.js +352 -0
- package/lib/ingest/cell-source.js +23 -0
- package/lib/ingest/enc-read-process.js +69 -0
- package/lib/ingest/enc-wasm.js +38 -6
- package/lib/livechart/csb-thin.js +39 -0
- package/lib/livechart-service.js +14 -2
- package/lib/monitor.js +17 -11
- package/lib/query-service.js +26 -12
- package/lib/route-service.js +3 -1
- package/lib/setup-checks.js +69 -5
- package/lib/shared-enc.js +214 -0
- package/lib/spatial/build-box.js +6 -3
- package/lib/spatial/build-items.js +70 -7
- package/lib/spatial/build-progress.js +41 -0
- package/lib/spatial/build-reuse.js +58 -7
- package/lib/spatial/build-source.js +26 -0
- package/lib/spatial/build-survey.js +112 -0
- package/lib/spatial/build-worker.js +156 -135
- package/lib/spatial/chart-index.js +48 -21
- package/lib/spatial/hazard-columns.js +213 -0
- package/lib/spatial/hazard-file.js +104 -0
- package/lib/spatial/hazards.js +170 -58
- package/lib/spatial/index-parts.js +25 -0
- package/lib/spatial/parsed-cells.js +101 -0
- package/lib/spatial/polygon-index.js +49 -0
- package/lib/spatial/pyramid-build.js +16 -46
- package/lib/spatial/pyramid-regions.js +2 -2
- package/lib/spatial/pyramid.js +48 -50
- package/lib/spatial/region-jobs.js +46 -0
- package/lib/spatial/survey-shadow.js +44 -12
- package/lib/spatial/tile-reuse.js +345 -0
- package/lib/status-report.js +13 -3
- package/lib/tick-publisher.js +60 -17
- package/lib/wfs-service.js +129 -152
- package/lib/wfs-wiring.js +50 -1
- package/lib/wfs-worker.js +11 -5
- package/package.json +1 -1
- package/public/panel.js +9 -0
- package/public/runtime.js +2 -3
- package/public/ukc-shared.js +37 -1
- package/public/widget.js +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,114 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to signalk-underkeel are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-11-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **ENC from Charts Provider** reads the ENC cells signalk-charts-provider-simple (2.16.0 or
|
|
10
|
+
later) shares in its ENC store: one chart set's or converted chart's cells by name, or all
|
|
11
|
+
of them with `*`, in place of **ENC cells directory**. The plugin restarts itself and
|
|
12
|
+
rebuilds the index when the charts provider writes new editions or updates of those cells,
|
|
13
|
+
and the admin form names the collections shared at the moment.
|
|
14
|
+
- When the Charts Provider collection in use has no cells, the reason says why, as
|
|
15
|
+
signalk-charts-provider-simple tells it: a chart set not downloaded yet or downloading, its
|
|
16
|
+
cells being written, or no such collection.
|
|
17
|
+
- **Ask Charts Provider for the ENC around the vessel** (expert, off by default): while all
|
|
18
|
+
of the charts provider's shared ENC is read, each new index box asks it to download the free
|
|
19
|
+
ENC cells of that area, when the charts provider allows such downloads. The cells arrive in
|
|
20
|
+
a chart set of their own and the index is rebuilt with them. An answer that may change
|
|
21
|
+
without the box changing is asked again later.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The index records the ENC source it was built from: the cells directory, or the Charts
|
|
26
|
+
Provider collection by its id. A cells directory that is gone or empty keeps the saved index
|
|
27
|
+
only when that index was built from it, as an unmounted drive leaves it; switching to a
|
|
28
|
+
source with no cells yet no longer keeps using the previous source's index, and the reason
|
|
29
|
+
says why. Clearing the ENC source leaves no index in use rather than the previous source's,
|
|
30
|
+
again with the reason. An index saved by an earlier version, which records no source, is
|
|
31
|
+
kept as before.
|
|
32
|
+
|
|
33
|
+
## [0.3.0] - 2026-10-28
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Index builds run in a child process (Node `fork`, `MALLOC_ARENA_MAX=2`) instead of a worker
|
|
38
|
+
thread, and S-57 cells are read with GDAL in a short-lived reader process of their own, so
|
|
39
|
+
the memory a build used is returned when it ends. After the whole 20-cell Waddenzee build the
|
|
40
|
+
server held 1.07–1.42 GB; it now holds about 100 MB (x86). A build that crashes or passes its
|
|
41
|
+
heap limit fails with the reason and keeps the index in use. Region threads default to 1 on
|
|
42
|
+
machines with up to 4 GB of RAM.
|
|
43
|
+
- Named hazards are saved in `hazards.bin` (CRC-checked columns, depths exact in centimetres)
|
|
44
|
+
instead of `hazards.json`: 11 MB instead of 89 MB for the Waddenzee set. Loading the saved
|
|
45
|
+
index takes 0.31–0.35 s instead of 1.5–1.7 s, blocks the event loop for 5–7 ms instead of
|
|
46
|
+
1.1–1.3 s, and leaves 97–101 MB resident instead of 282–292 MB. A damaged file rebuilds the
|
|
47
|
+
index; an index saved with `hazards.json` is rebuilt once.
|
|
48
|
+
- Index builds test soundings against survey zones through a zone grid, and coverage through
|
|
49
|
+
the edge grids, with results identical to before: the 10 km Waddenzee box built in 31.6 s
|
|
50
|
+
instead of 57.2 s, the whole set in 147 s instead of 216 s (x86, one thread).
|
|
51
|
+
- The look-ahead's chart walk measures footprint-to-tile distances with exact pre-checks and
|
|
52
|
+
allocates less: ticks on the Waddenzee index take 13–18 ms instead of 22–28 ms (median, x86),
|
|
53
|
+
with every result byte-identical.
|
|
54
|
+
- UKC overlay tiles are kept encoded (4 MiB) beside their geometry (24 MiB, about 41 % of its
|
|
55
|
+
former size, source edges stored as float32 rounded outward), and gzipped off the event
|
|
56
|
+
loop. A 40-tile viewport redrawn unchanged takes about 0 ms instead of 1.4–2.1 s (x86).
|
|
57
|
+
- While the chart box is rebuilding, or the vessel is outside it, the no-coverage reason says
|
|
58
|
+
so ("outside the chart box, the box is rebuilding around the vessel (last build took 5
|
|
59
|
+
min)"); the state stays `warn`. `GET /status` `chartBox` reports the rebuild's phase,
|
|
60
|
+
progress and estimate, and the panel shows it. The box grows with speed and the measured
|
|
61
|
+
build time up to `boxMaxRadiusKm` (by default from the build heap: 10 km on 1 GB, about 19 km on a
|
|
62
|
+
4 GB Raspberry Pi 4) and says when no size keeps up. Route legs left unverified past the box say why.
|
|
63
|
+
- `navigation.underKeel.state` and `.envelopeRev` are sent when they change and at least every
|
|
64
|
+
5 s, no longer every second: at the dock a history logger stored two points a second. Over
|
|
65
|
+
240 stationary ticks the plugin sent 626 values in 242 deltas, now 242 in 74. A display should
|
|
66
|
+
read a `state` not received for 15 s as unknown; the status widget does, and only a fresh
|
|
67
|
+
`state` keeps it current, and the chart runtime withdraws the envelope 15 s after the last
|
|
68
|
+
`envelopeRev` (it was 10 s). Notifications are unchanged.
|
|
69
|
+
- The plugin status no longer carries the seconds to shoal (`GET /status` still has them), and a
|
|
70
|
+
status that differs only in its numbers is set at most every 30 s: through the warn zone of a
|
|
71
|
+
240 s approach the status was set 67 times, now 5.
|
|
72
|
+
- Below 1 kn, or without a fresh SOG, the living chart's CSB export log (`soundings.ndjson`)
|
|
73
|
+
keeps one sounding per cell per minute. A 10 Hz sounder on a moored boat rewrote the
|
|
74
|
+
40 000-line file every 33 minutes. Every accepted sounding still updates its cell, so
|
|
75
|
+
discrepancies are found as before. `GET /status` counts the left-out soundings as
|
|
76
|
+
`livechart.csbThinned`.
|
|
77
|
+
|
|
78
|
+
- An index build keeps each S-57 cell's read layers and extent in `cell-cache` in the plugin
|
|
79
|
+
data directory (at most 512 MB) and reads with GDAL only the cells it holds no valid entry
|
|
80
|
+
for: changed cells, and cells whose entry is missing or was evicted. The
|
|
81
|
+
10 km Waddenzee chart box rebuilt in 12–15 s instead of 30–34 s, with no 300–370 MB reader
|
|
82
|
+
process; the whole 20-cell set in 78–90 s instead of 141 s on one thread (x86). A torn or
|
|
83
|
+
damaged entry is read from the cell again. The built index is byte for byte the same.
|
|
84
|
+
- A chart box refresh copies from the saved box index each tile down to z15 that lies inside
|
|
85
|
+
both boxes, or in both boxes' build-area band outside them, and is evaluated from the same
|
|
86
|
+
cell inputs in both builds; `tiles.json` beside the index holds
|
|
87
|
+
the digests. On the 10 km Waddenzee box (x86, warm cell cache) a 5 km move copied 66 % of
|
|
88
|
+
the nodes and built in 6.9–7.2 s instead of 11.6–11.7 s, an 8 km move 54 % in 6.4–6.9 s
|
|
89
|
+
instead of 10.4–11.6 s. The index is byte for byte the one a fresh build of the new box
|
|
90
|
+
makes; a changed cell's tiles, another leaf zoom or plugin version, or a missing or foreign
|
|
91
|
+
`tiles.json` are built afresh. `cells.json` is written in the field order it is read back.
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
|
|
95
|
+
- A hardware line in the setup check: CPUs, RAM and free RAM, with advice on 1 GB machines
|
|
96
|
+
(keep the index box, consider leaf zoom 19), on little free RAM, and on large ones (box 0
|
|
97
|
+
avoids rebuilds for a small chart set). It changes no setting.
|
|
98
|
+
- Descriptions on **Index leaf zoom**, **Rebuild index at start**, **Look-ahead distance** and
|
|
99
|
+
**Minimum horizon** saying what each costs in CPU or memory.
|
|
100
|
+
- README: what to check before installing on Victron Venus OS, whose Node.js version the
|
|
101
|
+
repository does not record.
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
|
|
105
|
+
- A larger least position buffer could lower the look-ahead's P: the buffer also set how far
|
|
106
|
+
apart the footprints were scored, so changing it moved the samples even where the footprints
|
|
107
|
+
kept the same buffer, and one set of samples could read a shoal up to 1e-4 higher (safety
|
|
108
|
+
invariant 2). Each footprint now sweeps the hull since the step before, at hull positions that
|
|
109
|
+
touch with no buffer, so no footprint time or shape depends on the buffer or the position's
|
|
110
|
+
uncertainty. The step stays 3 s at any speed; a fast boat scores fewer, longer footprints.
|
|
111
|
+
Beyond the warn window a footprint sweeps at most three hull lengths.
|
|
112
|
+
|
|
5
113
|
## [0.2.1] - 2026-10-27
|
|
6
114
|
|
|
7
115
|
### Fixed
|
package/README.md
CHANGED
|
@@ -89,6 +89,16 @@ Restart the server, then enable **Under-Keel Awareness** under _Server → Plugi
|
|
|
89
89
|
Signal K admin UI. Its one runtime dependency is gdal3.js (about 40 MB), which reads the S-57
|
|
90
90
|
cells.
|
|
91
91
|
|
|
92
|
+
**Victron Venus OS (Cerbo GX).** `package.json` asks for Node.js 22 or newer (`engines`), and CI
|
|
93
|
+
tests Node 22 and 24 only. Venus OS runs Signal K on the Node.js it ships, and this repository
|
|
94
|
+
does not record which version that is: check with `node --version` over SSH on the device before
|
|
95
|
+
installing. On an older Node, npm prints an `EBADENGINE` warning and installs anyway unless
|
|
96
|
+
`engine-strict` is set, and the plugin runs untested.
|
|
97
|
+
|
|
98
|
+
On a 1 GB machine such as a Raspberry Pi 3 or a Cerbo GX, keep the index box (the default
|
|
99
|
+
10 km) rather than indexing every cell, and consider **Index leaf zoom** 19. The setup check
|
|
100
|
+
shows the RAM and CPUs it finds and says the same; it never changes a setting.
|
|
101
|
+
|
|
92
102
|
## Prepare the charts
|
|
93
103
|
|
|
94
104
|
The plugin reads unencrypted S-57 ENC cells as they are delivered: each `.000` base cell with
|
|
@@ -96,7 +106,20 @@ its `.001`, `.002`, … updates beside it. Copy the exchange set, for example it
|
|
|
96
106
|
folder, under one directory and set **ENC cells directory** to it; `.000` files are found down
|
|
97
107
|
to three folders deep, and down to six below a folder holding the exchange set's `CATALOG.031`,
|
|
98
108
|
so an unzipped Inland ENC download (`<set>/ENC_ROOT/1R/7/1R7WAD07/1R7WAD07.000`) can be pointed
|
|
99
|
-
at as it is.
|
|
109
|
+
at as it is.
|
|
110
|
+
|
|
111
|
+
Cells downloaded with signalk-charts-provider-simple (2.16.0 or later) are read where that
|
|
112
|
+
plugin shares them, in its ENC store: set **ENC from Charts Provider** to one of the chart sets
|
|
113
|
+
or converted charts the admin form lists as shared, or to `*` for all of them. It is used
|
|
114
|
+
instead of **ENC cells directory**. When the charts provider writes new editions or updates of
|
|
115
|
+
those cells, the plugin restarts itself and rebuilds the index from them. When the collection
|
|
116
|
+
has no cells yet, the reason says why, as the charts provider tells it: a chart set not
|
|
117
|
+
downloaded or still downloading, cells being written, or no such collection. With `*` and
|
|
118
|
+
**Ask Charts Provider for the ENC around the vessel** on (an expert setting, off by default),
|
|
119
|
+
each new index box asks the charts provider to download the free ENC cells of that area, when
|
|
120
|
+
its **Download the ENC cells around the vessel** is on too; they arrive in its chart set
|
|
121
|
+
"Around the vessel" and the index is rebuilt with them. The cells are
|
|
122
|
+
read in place with GDAL compiled to WebAssembly
|
|
100
123
|
(gdal3.js), which the plugin installs with it, and the updates are applied as they are read.
|
|
101
124
|
Nothing is converted or written next to the cells. A cell without an `M_COVR` coverage layer
|
|
102
125
|
is rejected. A cell's usage band comes from the third character of its name, else from its
|
|
@@ -114,18 +137,27 @@ The index covers a box around the vessel, **Index box around the vessel (km)** (
|
|
|
114
137
|
each way), and is rebuilt off the event loop as the vessel moves, so a large exchange set such
|
|
115
138
|
as NOAA's 698 East and Gulf coast cells is never indexed whole: only the cells reaching the box
|
|
116
139
|
are read (21 of them around Miami, 22 s at leaf zoom 20 on x86). Outside the box the plugin
|
|
117
|
-
reports no coverage, never clear water
|
|
140
|
+
reports no coverage, never clear water, and the reason says the vessel is outside the chart box
|
|
141
|
+
and whether it is being rebuilt; the plugin status and the details panel show the rebuild's
|
|
142
|
+
phase, start and the last build's duration. At speed the box grows past its radius, up to
|
|
143
|
+
**Largest index box at speed** (0 sizes it from the RAM), so the vessel cannot reach the edge
|
|
144
|
+
before the next box is built (docs/HOW-IT-WORKS.md §12). A radius under 2 km is raised to 2.
|
|
145
|
+
Until the first position arrives the engine is `off`
|
|
118
146
|
with the reason `waiting for the vessel position to build the chart box`. Set the box to 0 to
|
|
119
147
|
index every cell.
|
|
120
148
|
|
|
121
|
-
At the first start the plugin builds a chart index from the cells in a
|
|
149
|
+
At the first start the plugin builds a chart index from the cells in a child process and saves
|
|
122
150
|
it under the plugin data directory, `~/.signalk/plugin-config-data/signalk-underkeel/index`
|
|
123
151
|
unless **Chart index directory** says otherwise. Until the index is ready the engine is `off`
|
|
124
152
|
with the reason `building chart index`. The 12 NOAA cells around Miami (6.8 MB of `.000` and
|
|
125
153
|
updates) build in 19 s at leaf zoom 18 on x86. Later starts load the saved index, 0.1 s for
|
|
126
154
|
those cells, and rebuild it when a cell was added or removed, or a cell's `.000` or update files
|
|
127
155
|
changed; `GET /status` names the cells that caused the rebuild. **Rebuild index at start**
|
|
128
|
-
forces a rebuild on every start. A cell folder may be a symbolic link.
|
|
156
|
+
forces a rebuild on every start. A cell folder may be a symbolic link. What the build read
|
|
157
|
+
from each cell is kept, compressed, in `cell-cache` in the plugin data directory (at most 512 MB), so a
|
|
158
|
+
chart box refresh reads again only the cells whose files changed: the 10 km Waddenzee box
|
|
159
|
+
rebuilds in 12–15 s instead of 30–34 s on x86. Deleting the folder is safe; the cells are then
|
|
160
|
+
read again.
|
|
129
161
|
|
|
130
162
|
Folders of GeoJSON layers exported by `scripts/enc-to-geojson.sh` for earlier versions are no
|
|
131
163
|
longer read: point **ENC cells directory** at the original `.000` cells instead. Such a folder
|
|
@@ -140,20 +172,22 @@ There are two places to set the plugin up, and both write the same configuration
|
|
|
140
172
|
level and warnings, in the same panel with a link back to the details. Drag the keel, the margin
|
|
141
173
|
line and the sounder on a side view of the hull, or type the numbers. The page shows the draft
|
|
142
174
|
Signal K already states, the vertical datum of your charts with the water-level source that fits
|
|
143
|
-
it, and a setup check
|
|
175
|
+
it, and a setup check, which also lists the machine's RAM and CPUs with advice on the index
|
|
176
|
+
settings. **Save and restart** needs a Signal K admin login.
|
|
144
177
|
- The plugin's page in the admin UI. It asks for the same essentials first; **Show expert
|
|
145
178
|
settings** opens the rest, and an optional feature's fields appear once it is enabled. Admin
|
|
146
179
|
UIs older than Signal K 2.32 show every field at once.
|
|
147
180
|
|
|
148
181
|

|
|
149
182
|
|
|
150
|
-
| Setting
|
|
151
|
-
|
|
|
152
|
-
| ENC cells directory
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
183
|
+
| Setting | What it does |
|
|
184
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| ENC cells directory | where the `.000` cells are |
|
|
186
|
+
| ENC from Charts Provider | or a chart set or converted chart whose cells signalk-charts-provider-simple shares, `*` for all of them; used instead of the directory |
|
|
187
|
+
| Boat | draft (empty uses Signal K `design.draft.maximum`) and safety margin under the keel (0 m); only the keel's lowest point counts, and squat and waves are added on top (below) |
|
|
188
|
+
| Water level | source (`none`, `manual`, `signalk-tide`, `fmi`, see below); only the chosen source's fields are shown |
|
|
189
|
+
| Warnings | Early, Balanced or Late, in the table below |
|
|
190
|
+
| Optional features | living chart (own soundings), Traficom WFS depths, grounding detector; each off until enabled |
|
|
157
191
|
|
|
158
192
|
| Warning style | Looks ahead | Alert at | Warn at, within | Alarm at, within | Alarm on negative mean UKC |
|
|
159
193
|
| ------------------ | ----------- | -------- | --------------- | ---------------- | -------------------------- |
|
|
@@ -180,7 +214,7 @@ Expert settings, with their defaults:
|
|
|
180
214
|
|
|
181
215
|
| Group | Settings |
|
|
182
216
|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
183
|
-
| Chart index | index directory, rebuild at start, index leaf zoom (20), box around the vessel (10 km)
|
|
217
|
+
| Chart index | index directory, rebuild at start, index leaf zoom (20), box around the vessel (10 km), ask Charts Provider for the ENC around the vessel |
|
|
184
218
|
| Boat | length, beam (fallback: Signal K `design.*`); draft σ (0.05 m); block coefficient and heel effect (none, fin keel or a table); double squat in narrow channels; wave allowance factor (0.4 × Hs) |
|
|
185
219
|
| Look-ahead | minimum horizon (60 s); position buffer (10 m, 2σ); fan spread (COG 5°, rate of turn 0.1°/s); escape-heading rate of turn (3°/s); and the distance, probabilities and windows of the warning style, each overriding the style when set |
|
|
186
220
|
| Water level | manual height and σ; FMI refresh (600 s); chart-datum mappings for FMI |
|
|
@@ -257,7 +291,9 @@ screenshots, including the plugin layers in the chart list and the setup steps i
|
|
|
257
291
|
| `notifications.grounding` | only from the optional grounding detector, when the boat has gone aground |
|
|
258
292
|
|
|
259
293
|
Any Signal K app can show these; the status line on the plugin's admin page reads, for
|
|
260
|
-
example, `Monitoring: unknown — position stale`.
|
|
294
|
+
example, `Monitoring: unknown — position stale`. Each value is sent when it changes; `state` and
|
|
295
|
+
`envelopeRev` at least every 5 s, the others every 10 s. A display that shows them should read a
|
|
296
|
+
`state` older than 15 s as unknown, as the plugin's own widget does.
|
|
261
297
|
|
|
262
298
|
## Optional features
|
|
263
299
|
|
package/lib/api/routes.js
CHANGED
|
@@ -144,13 +144,7 @@ function registerRoutes(router, api, log = console.error) {
|
|
|
144
144
|
res.setHeader("Cache-Control", "no-cache");
|
|
145
145
|
if (reply.body instanceof Uint8Array) {
|
|
146
146
|
res.setHeader("Vary", "Accept-Encoding");
|
|
147
|
-
|
|
148
|
-
const gzip = reply.body.length >= GZIP_MIN_BYTES && acceptsGzip(req.headers?.["accept-encoding"]);
|
|
149
|
-
if (gzip) {
|
|
150
|
-
res.setHeader("Content-Encoding", "gzip");
|
|
151
|
-
}
|
|
152
|
-
const body = gzip ? (0, zlib_1.gzipSync)(reply.body) : Buffer.from(reply.body);
|
|
153
|
-
res.status(reply.status).type(reply.contentType).send(body);
|
|
147
|
+
sendTile(res, reply, reply.body, acceptsGzip(req.headers?.["accept-encoding"]));
|
|
154
148
|
}
|
|
155
149
|
else {
|
|
156
150
|
res.status(reply.status).json(publicBody(reply.status, reply.body));
|
|
@@ -191,6 +185,40 @@ function registerSettings(router, api, log) {
|
|
|
191
185
|
.catch((err) => fail(res, err, log));
|
|
192
186
|
});
|
|
193
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Sends a tile body, gzipped when the client takes it and the body is worth it.
|
|
190
|
+
* Signal K's compression() skips the tile content type and leaves an encoded body alone.
|
|
191
|
+
*/
|
|
192
|
+
function sendTile(res, reply, raw, gzip) {
|
|
193
|
+
const sendBody = (body, encoded) => {
|
|
194
|
+
if (encoded) {
|
|
195
|
+
res.setHeader("Content-Encoding", "gzip");
|
|
196
|
+
}
|
|
197
|
+
res
|
|
198
|
+
.status(reply.status)
|
|
199
|
+
.type(reply.contentType)
|
|
200
|
+
.send(Buffer.from(body.buffer, body.byteOffset, body.byteLength));
|
|
201
|
+
};
|
|
202
|
+
if (raw.length < GZIP_MIN_BYTES || !gzip) {
|
|
203
|
+
sendBody(raw, false);
|
|
204
|
+
}
|
|
205
|
+
else if (reply.memo?.gzip) {
|
|
206
|
+
sendBody(reply.memo.gzip, true);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Off the event loop: a tile of some kilobytes takes a millisecond or more on a Pi.
|
|
210
|
+
(0, zlib_1.gzip)(raw, (err, packed) => {
|
|
211
|
+
if (err) {
|
|
212
|
+
sendBody(raw, false);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (reply.memo) {
|
|
216
|
+
reply.memo.gzip = packed;
|
|
217
|
+
}
|
|
218
|
+
sendBody(packed, true);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
194
222
|
/** Smaller bodies gain less from gzip than its header costs; compression() uses the same. */
|
|
195
223
|
const GZIP_MIN_BYTES = 1024;
|
|
196
224
|
/** Whether an Accept-Encoding header accepts gzip, by name or `*`, with a q above 0. */
|
package/lib/api-wiring.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.apiFor = apiFor;
|
|
|
4
4
|
const ack_service_1 = require("./ack-service");
|
|
5
5
|
const manifest_1 = require("./api/manifest");
|
|
6
6
|
const ukc_tile_changes_1 = require("./api/ukc-tile-changes");
|
|
7
|
-
const
|
|
7
|
+
const ukc_tile_cache_1 = require("./engine/ukc-tile-cache");
|
|
8
8
|
const config_1 = require("./config");
|
|
9
9
|
const water_level_1 = require("./model/water-level");
|
|
10
10
|
const query_service_1 = require("./query-service");
|
|
@@ -19,7 +19,7 @@ function livechartReply(s, body) {
|
|
|
19
19
|
}
|
|
20
20
|
function apiFor(app, clock, s) {
|
|
21
21
|
const tileChanges = new ukc_tile_changes_1.UkcTileChanges();
|
|
22
|
-
const tileCache = new
|
|
22
|
+
const tileCache = new ukc_tile_cache_1.UkcTileCache();
|
|
23
23
|
const queryDeps = () => ({
|
|
24
24
|
index: s.load?.index,
|
|
25
25
|
indexReason: s.load?.reason ?? "plugin not started",
|
|
@@ -39,7 +39,8 @@ function apiFor(app, clock, s) {
|
|
|
39
39
|
waveHeight: () => s.monitor?.nav.waveHeight?.value,
|
|
40
40
|
getRoute: (id) => app.resourcesApi.getResource("routes", id),
|
|
41
41
|
now: () => clock.now(),
|
|
42
|
-
queue: s.requests
|
|
42
|
+
queue: s.requests,
|
|
43
|
+
chartBox: () => s.wfs?.view().bbox
|
|
43
44
|
});
|
|
44
45
|
const withAcks = (fn) => {
|
|
45
46
|
const m = s.monitor;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_BOX_KM = exports.SHRINK_BELOW = exports.SPEED_HEADROOM = exports.STEP_KM = void 0;
|
|
4
|
+
exports.autoCapKm = autoCapKm;
|
|
5
|
+
exports.buildMsAt = buildMsAt;
|
|
6
|
+
exports.fits = fits;
|
|
7
|
+
exports.nextSize = nextSize;
|
|
8
|
+
const build_items_1 = require("./spatial/build-items");
|
|
9
|
+
/**
|
|
10
|
+
* The radius of the chart box around the vessel (plan §4.2). A box must let the vessel run,
|
|
11
|
+
* from its centre, the distance it covers while the next box is built (with the refresh
|
|
12
|
+
* margin) plus the look-ahead's reach and the no-coverage band inside the edge; otherwise
|
|
13
|
+
* rebuilds run back to back and a slow machine at speed lets the vessel reach the edge. The
|
|
14
|
+
* box grows for that from the configured radius up to a cap, and shrinks back only once the
|
|
15
|
+
* speed needs much less, so the radius does not change with every gust.
|
|
16
|
+
*/
|
|
17
|
+
/** Radii are chosen in steps of this many km. */
|
|
18
|
+
exports.STEP_KM = 0.5;
|
|
19
|
+
/** A new box is sized for this much more than the current speed. */
|
|
20
|
+
exports.SPEED_HEADROOM = 1.25;
|
|
21
|
+
/** A box is kept at its size while the size the speed needs is above this share of it. */
|
|
22
|
+
exports.SHRINK_BELOW = 0.7;
|
|
23
|
+
/** The automatic cap: this radius on a 256 MB build heap, growing with the heap's root. */
|
|
24
|
+
const CAP_AT_256_MB_KM = 10;
|
|
25
|
+
/** The largest box the configuration allows, km. */
|
|
26
|
+
exports.MAX_BOX_KM = 50;
|
|
27
|
+
/**
|
|
28
|
+
* The automatic cap for a build heap of `heapMb`: 10 km at 256 MB (a 1 GB Pi 3 or Cerbo GX),
|
|
29
|
+
* 20 km at 1 GB, 28 km at 2 GB, rounded down to STEP_KM. The heap is a quarter of the RAM the
|
|
30
|
+
* system reports, which on a 4 GB Pi 4 is about 3.8 GiB: 19 km. A build's memory grows with the box's area,
|
|
31
|
+
* and the 10 km box with the ENC pair at leaf zoom 20 builds in under 96 MB.
|
|
32
|
+
*/
|
|
33
|
+
function autoCapKm(heapMb) {
|
|
34
|
+
const km = CAP_AT_256_MB_KM * Math.sqrt(heapMb / 256);
|
|
35
|
+
return Math.min(exports.MAX_BOX_KM, Math.floor(km / exports.STEP_KM) * exports.STEP_KM);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The build time of a box of `km`: the measured one, grown with the build area past the
|
|
39
|
+
* radius it was measured at. Tiles are built to BOX_REACH past the box, so the area grows
|
|
40
|
+
* with (radius + reach)². Never shorter than measured, so a smaller box is not assumed to
|
|
41
|
+
* build faster until it has.
|
|
42
|
+
*/
|
|
43
|
+
function buildMsAt(km, i) {
|
|
44
|
+
const reach = build_items_1.BOX_REACH / 1000;
|
|
45
|
+
return i.buildMs * Math.max(1, ((km + reach) / (i.builtKm + reach)) ** 2);
|
|
46
|
+
}
|
|
47
|
+
/** Whether a box of `km` holds a refresh's run at `sog` plus the reach between its centre and edge. */
|
|
48
|
+
function fits(km, i, sog = i.sog) {
|
|
49
|
+
return km * 1000 >= (i.margin * sog * buildMsAt(km, i)) / 1000 + i.reachM;
|
|
50
|
+
}
|
|
51
|
+
/** The smallest radius on the STEP_KM grid in [from, to] that fits at `sog`. */
|
|
52
|
+
function smallestFit(from, to, i, sog) {
|
|
53
|
+
for (let km = from; km <= to + 1e-9; km = nextStep(km)) {
|
|
54
|
+
if (fits(km, i, sog)) {
|
|
55
|
+
return km;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const nextStep = (km) => (Math.floor(km / exports.STEP_KM + 1e-9) + 1) * exports.STEP_KM;
|
|
61
|
+
/** The radius at which the box's spare reach is greatest, when none fits. */
|
|
62
|
+
function roomiest(i) {
|
|
63
|
+
let best = i.minKm;
|
|
64
|
+
const spare = (km) => km * 1000 - (i.margin * i.sog * buildMsAt(km, i)) / 1000;
|
|
65
|
+
for (let km = i.minKm; km <= i.capKm + 1e-9; km = nextStep(km)) {
|
|
66
|
+
if (spare(km) > spare(best)) {
|
|
67
|
+
best = km;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return best;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The radius for the next box, given the radius of the box in use. Stopped or without a
|
|
74
|
+
* speed the box in use keeps its size, within the configured radius and the cap.
|
|
75
|
+
*/
|
|
76
|
+
function nextSize(i, currentKm) {
|
|
77
|
+
const minKm = Math.min(i.minKm, i.capKm);
|
|
78
|
+
const within = currentKm !== undefined && currentKm >= minKm && currentKm <= i.capKm;
|
|
79
|
+
if (!(i.sog > 0)) {
|
|
80
|
+
return { radiusKm: within ? currentKm : minKm, enough: true };
|
|
81
|
+
}
|
|
82
|
+
const want = smallestFit(minKm, i.capKm, i, i.sog * exports.SPEED_HEADROOM);
|
|
83
|
+
if (want === undefined) {
|
|
84
|
+
const radiusKm = smallestFit(minKm, i.capKm, i, i.sog) ?? roomiest(i);
|
|
85
|
+
const enough = fits(radiusKm, i);
|
|
86
|
+
const neededKm = enough ? undefined : smallestFit(i.capKm, exports.MAX_BOX_KM, i, i.sog);
|
|
87
|
+
return { radiusKm, enough, ...(neededKm === undefined ? {} : { neededKm }) };
|
|
88
|
+
}
|
|
89
|
+
// Kept while it holds the speed and the speed does not need much less.
|
|
90
|
+
if (within && fits(currentKm, i) && want <= currentKm && want > exports.SHRINK_BELOW * currentKm) {
|
|
91
|
+
return { radiusKm: currentKm, enough: true };
|
|
92
|
+
}
|
|
93
|
+
return { radiusKm: want, enough: true };
|
|
94
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rebuildStatus = rebuildStatus;
|
|
4
|
+
exports.boxSummary = boxSummary;
|
|
5
|
+
const box_sizing_1 = require("./box-sizing");
|
|
6
|
+
const chart_box_1 = require("./engine/chart-box");
|
|
7
|
+
function rebuildStatus(r, now) {
|
|
8
|
+
return {
|
|
9
|
+
startedAt: new Date(r.startedAt).toISOString(),
|
|
10
|
+
elapsedMs: now - r.startedAt,
|
|
11
|
+
radiusKm: r.radiusKm,
|
|
12
|
+
...r.progress,
|
|
13
|
+
...(r.estimateMs === undefined ? {} : { estimateMs: r.estimateMs })
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function phaseText(r) {
|
|
17
|
+
const of = r.done !== undefined && r.total !== undefined ? ` ${r.done}/${r.total}` : "";
|
|
18
|
+
switch (r.phase) {
|
|
19
|
+
case "download":
|
|
20
|
+
return "downloading WFS depths";
|
|
21
|
+
case "cells":
|
|
22
|
+
return `reading cells${of}`;
|
|
23
|
+
case "roots":
|
|
24
|
+
return `building roots${of}`;
|
|
25
|
+
case "save":
|
|
26
|
+
return "saving the index";
|
|
27
|
+
default:
|
|
28
|
+
return "starting";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function rebuildText(r, lastMs) {
|
|
32
|
+
const expected = r.estimateMs === undefined
|
|
33
|
+
? "first build"
|
|
34
|
+
: r.estimateMs === lastMs
|
|
35
|
+
? `last build took ${(0, chart_box_1.spoken)(r.estimateMs)}`
|
|
36
|
+
: `about ${(0, chart_box_1.spoken)(r.estimateMs)} expected`;
|
|
37
|
+
const ago = (0, chart_box_1.spoken)(r.elapsedMs);
|
|
38
|
+
return `chart box rebuilding (${r.radiusKm} km): ${phaseText(r)}, started ${ago} ago, ${expected}`;
|
|
39
|
+
}
|
|
40
|
+
function tooSmallText(s, i) {
|
|
41
|
+
const kn = ((s.sog * 3600) / 1852).toFixed(1);
|
|
42
|
+
const builds = (0, chart_box_1.spoken)((0, box_sizing_1.buildMsAt)(s.radiusKm, i));
|
|
43
|
+
const need = s.neededKm === undefined
|
|
44
|
+
? "no box size keeps up"
|
|
45
|
+
: `needs ${s.neededKm} km, limit ${s.capKm} km`;
|
|
46
|
+
return `chart box ${s.radiusKm} km is too small for ${kn} kn with ${builds} builds: ${need}`;
|
|
47
|
+
}
|
|
48
|
+
/** One line on the rebuild in progress and the box size; undefined when neither needs saying. */
|
|
49
|
+
function boxSummary(s) {
|
|
50
|
+
const parts = [];
|
|
51
|
+
if (s.rebuild) {
|
|
52
|
+
parts.push(rebuildText(s.rebuild, s.lastMs));
|
|
53
|
+
}
|
|
54
|
+
if (!s.sizing.enough) {
|
|
55
|
+
parts.push(tooSmallText(s.sizing, s.input));
|
|
56
|
+
}
|
|
57
|
+
if (s.ceilingKm !== undefined) {
|
|
58
|
+
parts.push(`chart box limited to ${s.ceilingKm} km: a larger one ran out of memory`);
|
|
59
|
+
}
|
|
60
|
+
return parts.length > 0 ? parts.join("; ") : undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.workerRefresher = exports.workerBuilder = void 0;
|
|
7
|
+
exports.workerHeapLimitMb = workerHeapLimitMb;
|
|
8
|
+
exports.runBuildProcess = runBuildProcess;
|
|
9
|
+
exports.inThreadRefresher = inThreadRefresher;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const child_process_1 = require("./foundation/child-process");
|
|
13
|
+
const memory_1 = require("./foundation/memory");
|
|
14
|
+
const wfs_1 = require("./ingest/wfs");
|
|
15
|
+
const wfs_worker_1 = require("./wfs-worker");
|
|
16
|
+
const build_progress_1 = require("./spatial/build-progress");
|
|
17
|
+
const build_worker_1 = require("./spatial/build-worker");
|
|
18
|
+
const block_reader_1 = require("./spatial/block-reader");
|
|
19
|
+
const chart_index_1 = require("./spatial/chart-index");
|
|
20
|
+
/** Settles once every build process aborted so far has exited. */
|
|
21
|
+
let exits = Promise.resolve();
|
|
22
|
+
/**
|
|
23
|
+
* Old-generation heap for a build process: a quarter of the RAM, 256 MB to 2 GB. The ENC pair
|
|
24
|
+
* plus a 10 km WFS box at leaf zoom 20 builds in under 96 MB; without a limit an oversized
|
|
25
|
+
* build grows until the kernel ends the whole Signal K server on a 1 GB Pi 3 or Cerbo GX.
|
|
26
|
+
*/
|
|
27
|
+
function workerHeapLimitMb(totalBytes = (0, memory_1.usableMemory)()) {
|
|
28
|
+
return Math.min(2048, Math.max(256, Math.floor(totalBytes / 4 / 2 ** 20)));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Runs the compiled `script` in a child process (`forkLimited`) with `data` and resolves with
|
|
32
|
+
* its staged reply. The child only stages; the caller moves the result into place on the main
|
|
33
|
+
* thread, and never for an aborted run, so a build still exiting cannot replace an index
|
|
34
|
+
* saved after it was aborted. An abort kills the child and its S-57 reader (`killGroup`); its
|
|
35
|
+
* run rejects once the child has exited and its staging is removed, and the next run starts
|
|
36
|
+
* only then. stop() itself returns at once. A child that ends without a reply fails the run.
|
|
37
|
+
* Progress messages the child sends before its reply go to `progress`.
|
|
38
|
+
*/
|
|
39
|
+
async function runBuildProcess(script, data, signal, indexDir, heapMb = workerHeapLimitMb(), progress) {
|
|
40
|
+
await exits;
|
|
41
|
+
if (signal.aborted) {
|
|
42
|
+
throw new Error("aborted");
|
|
43
|
+
}
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const { child, stderr, outOfMemory } = (0, child_process_1.forkLimited)(script, heapMb, true);
|
|
46
|
+
const { pid } = child;
|
|
47
|
+
let replied = false;
|
|
48
|
+
// Staging the child left half-written: once it has exited nothing else writes there.
|
|
49
|
+
const cleanup = () => {
|
|
50
|
+
if (indexDir !== undefined && pid !== undefined) {
|
|
51
|
+
(0, chart_index_1.removeStaging)(indexDir, (p) => p === pid);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const closed = new Promise((r) => child.once("close", () => r()));
|
|
55
|
+
const abort = () => {
|
|
56
|
+
(0, child_process_1.killGroup)(child);
|
|
57
|
+
const exit = closed.then(cleanup).catch(() => undefined);
|
|
58
|
+
exits = Promise.all([exits, exit]);
|
|
59
|
+
void exit.then(() => reject(new Error("aborted")));
|
|
60
|
+
};
|
|
61
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
62
|
+
child.on("message", (m) => {
|
|
63
|
+
if ((0, build_progress_1.isProgress)(m)) {
|
|
64
|
+
if (!replied && !signal.aborted) {
|
|
65
|
+
progress?.(m.progress);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
replied = true;
|
|
70
|
+
// It exits once the reply is sent; killed, it cannot linger holding its memory.
|
|
71
|
+
child.kill("SIGKILL");
|
|
72
|
+
if (signal.aborted) {
|
|
73
|
+
if (m.staging) {
|
|
74
|
+
fs_1.default.rmSync(m.staging, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
signal.removeEventListener("abort", abort);
|
|
79
|
+
if (m.ok && m.staging) {
|
|
80
|
+
resolve(m);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
reject(new Error(m.error ?? "index build failed"));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// A child that could not start, or ended without a reply: the index in use stays.
|
|
87
|
+
child.once("error", (err) => {
|
|
88
|
+
if (!signal.aborted && !replied) {
|
|
89
|
+
signal.removeEventListener("abort", abort);
|
|
90
|
+
(0, child_process_1.killGroup)(child);
|
|
91
|
+
reject(err);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
child.once("close", (code, end) => {
|
|
95
|
+
if (signal.aborted || replied) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
signal.removeEventListener("abort", abort);
|
|
99
|
+
try {
|
|
100
|
+
cleanup();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Left for the next start's recovery, which removes staging of exited processes.
|
|
104
|
+
}
|
|
105
|
+
const how = code === null ? `signal ${end ?? "?"}` : `code ${code}`;
|
|
106
|
+
const tail = stderr() === "" ? "" : `: ${stderr()}`;
|
|
107
|
+
reject(new Error(outOfMemory()
|
|
108
|
+
? `index build stopped at the ${heapMb} MB heap limit${tail}`
|
|
109
|
+
: `index build process exited with ${how} without a result${tail}`));
|
|
110
|
+
});
|
|
111
|
+
// A child that cannot take the job exits, and "close" reports it.
|
|
112
|
+
child.send(data, () => undefined);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Moves a staged index into `indexDir` and adopts the one the build posted, unless the run
|
|
117
|
+
* was aborted meanwhile. Reading the saved index back blocked the event loop for 52–61 ms
|
|
118
|
+
* with the Nauvo pair and a 10 km WFS box at leaf zoom 20, 258–292 ms with 32 cells (x86).
|
|
119
|
+
*/
|
|
120
|
+
async function commit(indexDir, m, signal) {
|
|
121
|
+
await (0, block_reader_1.readsSettled)(indexDir);
|
|
122
|
+
if (signal.aborted) {
|
|
123
|
+
fs_1.default.rmSync(m.staging, { recursive: true, force: true });
|
|
124
|
+
throw new Error("aborted");
|
|
125
|
+
}
|
|
126
|
+
(0, chart_index_1.commitChartIndex)(indexDir, m.staging);
|
|
127
|
+
if (m.index) {
|
|
128
|
+
return (0, build_worker_1.adoptIndex)(m.index);
|
|
129
|
+
}
|
|
130
|
+
// A pyramid above HELD_WHOLE_NODES is not posted: the committed file is read block by block,
|
|
131
|
+
// its blocks verified off the event loop.
|
|
132
|
+
const index = await (0, chart_index_1.loadChartIndexAsync)(indexDir);
|
|
133
|
+
if (signal.aborted) {
|
|
134
|
+
throw new Error("aborted");
|
|
135
|
+
}
|
|
136
|
+
return index;
|
|
137
|
+
}
|
|
138
|
+
/** Builds in a child process from the compiled `lib/spatial/build-worker.js`. */
|
|
139
|
+
const workerBuilder = async (job, signal, progress) => {
|
|
140
|
+
const script = path_1.default.join(__dirname, "spatial", "build-worker.js");
|
|
141
|
+
const heapMb = workerHeapLimitMb();
|
|
142
|
+
const data = { ...job, heapMb };
|
|
143
|
+
const m = await runBuildProcess(script, data, signal, job.indexDir, heapMb, progress);
|
|
144
|
+
return await commit(job.indexDir, m, signal);
|
|
145
|
+
};
|
|
146
|
+
exports.workerBuilder = workerBuilder;
|
|
147
|
+
/**
|
|
148
|
+
* The whole refresh in a child process from the compiled `lib/wfs-worker.js`, which fetches
|
|
149
|
+
* with its own global fetch.
|
|
150
|
+
*/
|
|
151
|
+
const workerRefresher = async (job, signal, progress) => {
|
|
152
|
+
const script = path_1.default.join(__dirname, "wfs-worker.js");
|
|
153
|
+
const heapMb = workerHeapLimitMb();
|
|
154
|
+
const data = { ...job, build: { ...job.build, heapMb } };
|
|
155
|
+
const { indexDir } = job.build;
|
|
156
|
+
const m = await runBuildProcess(script, data, signal, indexDir, heapMb, progress);
|
|
157
|
+
const features = m.features ?? wfs_worker_1.NO_FEATURES;
|
|
158
|
+
return { index: await commit(job.build.indexDir, m, signal), features };
|
|
159
|
+
};
|
|
160
|
+
exports.workerRefresher = workerRefresher;
|
|
161
|
+
/** The download on this thread with `fetch`, then `build`: for an injected fetch or builder. */
|
|
162
|
+
function inThreadRefresher(fetch, build) {
|
|
163
|
+
return async (job, signal, progress) => {
|
|
164
|
+
const cells = (0, wfs_worker_1.refreshCells)(job);
|
|
165
|
+
if (job.download) {
|
|
166
|
+
progress?.({ phase: "download" });
|
|
167
|
+
}
|
|
168
|
+
const features = job.download
|
|
169
|
+
? await (0, wfs_1.downloadWfsCell)({ ...job.download, now: new Date(job.download.now), fetch, signal })
|
|
170
|
+
: wfs_worker_1.NO_FEATURES;
|
|
171
|
+
await (0, block_reader_1.readsSettled)(job.build.indexDir);
|
|
172
|
+
return { index: await build((0, wfs_worker_1.refreshBuild)(job, cells), signal, progress), features };
|
|
173
|
+
};
|
|
174
|
+
}
|