las-laz-js 0.3.0 → 0.3.1
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 +12 -0
- package/package.json +1 -1
- package/src/index.js +1 -1
- package/src/laz/chunkWorker.js +3 -1
- package/src/laz/pool.js +46 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 (2026-10-21)
|
|
4
|
+
|
|
5
|
+
Worker fixes.
|
|
6
|
+
|
|
7
|
+
- Node: a script that stopped part way through a parallel LAZ read (one `chunks()` window, then
|
|
8
|
+
done) never exited, because an idle pool kept Node running. Workers now hold Node only while
|
|
9
|
+
they have work.
|
|
10
|
+
- Node: `node --input-type=module -e` scripts can decode in parallel. Workers used to inherit
|
|
11
|
+
`--input-type` and fail to start.
|
|
12
|
+
- A worker that cannot start (blocked or failing script) now means one-thread decoding, as
|
|
13
|
+
documented, rather than a failed read. The pool waits for every worker to report ready.
|
|
14
|
+
|
|
3
15
|
## 0.3.0 (2026-10-21)
|
|
4
16
|
|
|
5
17
|
Faster LAZ.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "las-laz-js",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Zero-dependency JavaScript reader and writer for ASPRS LAS point clouds, and reader for LAZ (LASzip), for the browser and Node, built for files larger than memory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//==========================================================
|
|
7
7
|
|
|
8
8
|
/** Library version, matches package.json. */
|
|
9
|
-
export var VERSION = "0.3.
|
|
9
|
+
export var VERSION = "0.3.1";
|
|
10
10
|
|
|
11
11
|
export { LasError, ERROR_CODES } from "./errors.js";
|
|
12
12
|
export { openLas, LasReader } from "./LasReader.js";
|
package/src/laz/chunkWorker.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// Runs as a browser module Worker or a Node worker_threads Worker.
|
|
6
6
|
// In: { id, vlr, recordLength, packed: ArrayBuffer, chunks: [{ s, e, count }], base }
|
|
7
7
|
// (s / e index into packed; base is packed's file offset)
|
|
8
|
-
// Out: {
|
|
8
|
+
// Out: { ready: true } once listening, then per task
|
|
9
|
+
// { id, records: ArrayBuffer } or { id, error: { code, message, offset } }
|
|
9
10
|
//
|
|
10
11
|
// Uses the chunk decoder translated from laz-rs (https://github.com/laz-rs/laz-rs),
|
|
11
12
|
// itself a port of LASzip by rapidlasso. (c) 2007-2015 martin isenburg,
|
|
@@ -28,6 +29,7 @@ if (typeof self !== "undefined" && typeof self.postMessage === "function") {
|
|
|
28
29
|
port = { post: function (m, t) { wt.parentPort.postMessage(m, t); } };
|
|
29
30
|
wt.parentPort.on("message", handle);
|
|
30
31
|
}
|
|
32
|
+
port.post({ ready: true }); // the pool waits for this before sending work
|
|
31
33
|
|
|
32
34
|
var cached = { key: null, decode: null };
|
|
33
35
|
|
package/src/laz/pool.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// queue and go to the next free worker; results come back as transferred
|
|
7
7
|
// buffers. close() terminates every worker and fails what is still
|
|
8
8
|
// queued, so nothing outlives a read. When workers cannot start (no
|
|
9
|
-
// Worker, a blocked script), createLazPool returns null and
|
|
10
|
-
// decodes on its own thread.
|
|
9
|
+
// Worker, a blocked or failing script), createLazPool returns null and
|
|
10
|
+
// the reader decodes on its own thread.
|
|
11
11
|
//
|
|
12
12
|
// Uses the chunk decoder translated from laz-rs (https://github.com/laz-rs/laz-rs),
|
|
13
13
|
// itself a port of LASzip by rapidlasso. (c) 2007-2015 martin isenburg,
|
|
@@ -38,28 +38,52 @@ export async function defaultThreads() {
|
|
|
38
38
|
return Math.max(1, Math.min(MAX_THREADS, cores - 1));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
|
|
41
|
+
// Node worker options: the parent's flags are inherited, except --input-type
|
|
42
|
+
// (node --input-type=module -e passes it on, and a worker has no string input
|
|
43
|
+
// for it). Only then is execArgv given: an explicit one is checked flag by
|
|
44
|
+
// flag, and some parents (the test runner) hold flags a worker may not take.
|
|
45
|
+
function nodeWorkerOptions(argv) {
|
|
46
|
+
var isInputType = function (a, i) { return /^--input-type(=|$)/.test(a) || argv[i - 1] === "--input-type"; };
|
|
47
|
+
if (!argv.some(isInputType)) return undefined;
|
|
48
|
+
return { execArgv: argv.filter(function (a, i) { return !isInputType(a, i); }) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// One worker, browser or Node, behind the same small interface. Resolves once
|
|
52
|
+
// the worker says it is ready; rejects if it fails or exits first (a blocked
|
|
53
|
+
// or broken script), so a pool that cannot start is known before any task.
|
|
54
|
+
async function spawn(onMessage, onFail, nodeUrl) {
|
|
55
|
+
var started = false, ready, failed;
|
|
56
|
+
var up = new Promise(function (res, rej) { ready = res; failed = rej; });
|
|
57
|
+
function message(m) { if (m && m.ready) { started = true; ready(); } else onMessage(m); }
|
|
58
|
+
function error(e) { if (started) onFail(e); else failed(e); }
|
|
59
|
+
var handle;
|
|
43
60
|
if (typeof Worker === "function") {
|
|
44
61
|
// written inline so bundlers (Vite, webpack 5) see the worker and emit it
|
|
45
62
|
var w = new Worker(new URL("./chunkWorker.js", import.meta.url), { type: "module" });
|
|
46
|
-
w.onmessage = function (e) {
|
|
47
|
-
w.onerror = function (e) { if (e && e.preventDefault) e.preventDefault();
|
|
48
|
-
|
|
63
|
+
w.onmessage = function (e) { message(e.data); };
|
|
64
|
+
w.onerror = function (e) { if (e && e.preventDefault) e.preventDefault(); error(new Error(e && e.message ? e.message : "LAZ worker failed")); };
|
|
65
|
+
handle = { post: function (m, t) { w.postMessage(m, t); }, close: function () { w.terminate(); } };
|
|
66
|
+
} else {
|
|
67
|
+
var wt = await import(/* webpackIgnore: true */ /* @vite-ignore */ NODE_WORKER_THREADS);
|
|
68
|
+
var nw = new wt.Worker(nodeUrl || new URL("./chunkWorker.js", import.meta.url), nodeWorkerOptions(process.execArgv));
|
|
69
|
+
nw.on("message", message);
|
|
70
|
+
nw.on("error", error);
|
|
71
|
+
nw.on("exit", function (code) { if (!started) failed(new Error("LAZ worker exited (" + code + ") before it started")); });
|
|
72
|
+
// Node runs while a worker has a task and not while it is idle, so a forgotten
|
|
73
|
+
// pool never keeps it running (unref after the "message" listener, which refs again)
|
|
74
|
+
handle = { post: function (m, t) { nw.postMessage(m, t); }, close: function () { return nw.terminate(); }, hold: function () { nw.ref(); }, release: function () { nw.unref(); } };
|
|
49
75
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
nw.on("message", onMessage);
|
|
54
|
-
nw.on("error", onFail);
|
|
55
|
-
return { post: function (m, t) { nw.postMessage(m, t); }, close: function () { return nw.terminate(); } };
|
|
76
|
+
try { await up; } catch (e) { handle.close(); throw e; }
|
|
77
|
+
if (handle.release) handle.release();
|
|
78
|
+
return handle;
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
/**
|
|
59
82
|
* @param {number} n - workers to start (2 or more)
|
|
83
|
+
* @param {URL} [nodeUrl] - a Node worker entry in place of chunkWorker.js (tests)
|
|
60
84
|
* @returns {Promise<{size:number, decode:function(object): Promise<Uint8Array>, close:function(): void}|null>}
|
|
61
85
|
*/
|
|
62
|
-
export async function createLazPool(n) {
|
|
86
|
+
export async function createLazPool(n, nodeUrl) {
|
|
63
87
|
var workers = [], idle = [], queue = [], waiting = new Map(), nextId = 1, closed = false;
|
|
64
88
|
|
|
65
89
|
function fail(err) {
|
|
@@ -76,6 +100,7 @@ export async function createLazPool(n) {
|
|
|
76
100
|
var w = idle.pop(), job = queue.shift();
|
|
77
101
|
job.worker = w;
|
|
78
102
|
waiting.set(job.msg.id, job);
|
|
103
|
+
if (w.hold) w.hold();
|
|
79
104
|
w.post(job.msg, [job.msg.packed]);
|
|
80
105
|
}
|
|
81
106
|
}
|
|
@@ -84,15 +109,19 @@ export async function createLazPool(n) {
|
|
|
84
109
|
var job = waiting.get(m.id);
|
|
85
110
|
if (!job) return;
|
|
86
111
|
waiting.delete(m.id);
|
|
112
|
+
if (job.worker.release) job.worker.release();
|
|
87
113
|
idle.push(job.worker);
|
|
88
114
|
if (m.error) job.reject(new LasError(m.error.code || ERROR_CODES.UNSUPPORTED, m.error.message, m.error.offset));
|
|
89
115
|
else job.resolve(new Uint8Array(m.records));
|
|
90
116
|
pump();
|
|
91
117
|
}
|
|
92
118
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
119
|
+
var starts = [];
|
|
120
|
+
for (var i = 0; i < n; i++) starts.push(spawn(onMessage, fail, nodeUrl));
|
|
121
|
+
var settled = await Promise.allSettled(starts);
|
|
122
|
+
workers = settled.filter(function (s) { return s.status === "fulfilled"; }).map(function (s) { return s.value; });
|
|
123
|
+
if (workers.length < n) {
|
|
124
|
+
// any worker that cannot start means the reader decodes on its own thread
|
|
96
125
|
workers.forEach(function (w) { w.close(); });
|
|
97
126
|
return null;
|
|
98
127
|
}
|