From 32fa6b266b20bb7cd64ade07f7ba6890699050d8 Mon Sep 17 00:00:00 2001 From: Trevor Gerhardt Date: Sat, 9 Aug 2025 18:30:56 +0800 Subject: [PATCH] Add SharedArrayBuffer support This PR adds support for SharedArrayBuffer which is a fully compatible alternative to ArrayBuffer. The implementation follows the style used for the PR that added SharedArrayBuffer support in flatbush. --- README.md | 6 ++++-- index.js | 17 +++++++++-------- test.js | 15 +++++++++++++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ac7664d..822c18a 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,14 @@ Alternatively, there's a browser bundle with a `KDBush` global variable: ## API -#### new KDBush(numItems[, nodeSize, ArrayType]) +#### new KDBush(numItems[, nodeSize, ArrayType, ArrayBufferType]) Creates an index that will hold a given number of points (`numItems`). Additionally accepts: - `nodeSize`: Size of the KD-tree node, `64` by default. Higher means faster indexing but slower search, and vise versa. - `ArrayType`: Array type to use for storing coordinate values. `Float64Array` by default, but if your coordinates are integer values, `Int32Array` makes the index faster and smaller. +- `ArrayBufferType`: the array buffer type used to store data (`ArrayBuffer` by default); +you may prefer `SharedArrayBuffer` if you want to share the index between threads (multiple `Worker`, `SharedWorker` or `ServiceWorker`). #### index.add(x, y) @@ -89,7 +91,7 @@ Finds all items within a given radius from the query point and returns an array #### `KDBush.from(data)` -Recreates a KDBush index from raw `ArrayBuffer` data +Recreates a KDBush index from raw `ArrayBuffer` or `SharedArrayBuffer` data (that's exposed as `index.data` on a previously indexed KDBush instance). Very useful for transferring or sharing indices between threads or storing them in a file. diff --git a/index.js b/index.js index 27c6ba7..8d3d504 100644 --- a/index.js +++ b/index.js @@ -13,11 +13,11 @@ export default class KDBush { /** * Creates an index from raw `ArrayBuffer` data. - * @param {ArrayBuffer} data + * @param {ArrayBufferLike} data */ static from(data) { - if (!(data instanceof ArrayBuffer)) { - throw new Error('Data must be an instance of ArrayBuffer.'); + if (!(data instanceof ArrayBuffer) && !(data instanceof SharedArrayBuffer)) { + throw new Error('Data must be an instance of ArrayBuffer or SharedArrayBuffer.'); } const [magic, versionAndType] = new Uint8Array(data, 0, 2); if (magic !== 0xdb) { @@ -34,7 +34,7 @@ export default class KDBush { const [nodeSize] = new Uint16Array(data, 2, 1); const [numItems] = new Uint32Array(data, 4, 1); - return new KDBush(numItems, nodeSize, ArrayType, data); + return new KDBush(numItems, nodeSize, ArrayType, undefined, data); } /** @@ -42,9 +42,10 @@ export default class KDBush { * @param {number} numItems * @param {number} [nodeSize=64] Size of the KD-tree node (64 by default). * @param {TypedArrayConstructor} [ArrayType=Float64Array] The array type used for coordinates storage (`Float64Array` by default). - * @param {ArrayBuffer} [data] (For internal use only) + * @param {ArrayBufferConstructor} [ArrayBufferType=ArrayBuffer] The array buffer type used for storage (`ArrayBuffer` by default). + * @param {ArrayBufferLike} [data] (For internal use only) */ - constructor(numItems, nodeSize = 64, ArrayType = Float64Array, data) { + constructor(numItems, nodeSize = 64, ArrayType = Float64Array, ArrayBufferType = ArrayBuffer, data) { if (isNaN(numItems) || numItems < 0) throw new Error(`Unexpected numItems value: ${numItems}.`); this.numItems = +numItems; @@ -61,14 +62,14 @@ export default class KDBush { throw new Error(`Unexpected typed array class: ${ArrayType}.`); } - if (data && (data instanceof ArrayBuffer)) { // reconstruct an index from a buffer + if ((data instanceof ArrayBuffer) || (data instanceof SharedArrayBuffer)) { // reconstruct an index from a buffer this.data = data; this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems); this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2); this._pos = numItems * 2; this._finished = true; } else { // initialize a new index - this.data = new ArrayBuffer(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords); + this.data = new ArrayBufferType(HEADER_SIZE + coordsByteSize + idsByteSize + padCoords); this.ids = new this.IndexArrayType(this.data, HEADER_SIZE, numItems); this.coords = new this.ArrayType(this.data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2); this._pos = 0; diff --git a/test.js b/test.js index 49440e5..d7c904f 100644 --- a/test.js +++ b/test.js @@ -27,8 +27,8 @@ const coords = [ 53,49,60,50,68,57,70,56,77,63,86,71,90,52,83,71,82,72,81,94,51,75,53,95,39,78,53,88,62,84,72,77,73,99,76,73,81,88, 87,96,98,96,82]; -function makeIndex() { - const index = new KDBush(points.length, 10); +function makeIndex(ArrayBufferType = ArrayBuffer) { + const index = new KDBush(points.length, 10, undefined, ArrayBufferType); for (const [x, y] of points) index.add(x, y); return index.finish(); } @@ -120,6 +120,17 @@ test('does not complain about zero items', () => { }); }); +test('creates an index using SharedArrayBuffer', () => { + const index = makeIndex(SharedArrayBuffer); + assert(index.data instanceof SharedArrayBuffer); +}); + +test('reconstructs an index from a SharedArrayBuffer', () => { + const index = makeIndex(SharedArrayBuffer); + const index2 = KDBush.from(index.data); + assert.deepEqual(index, index2); +}); + function sqDist(a, b) { const dx = a[0] - b[0]; const dy = a[1] - b[1];