@daneren2005/shared-memory-objects 1.4.0 → 1.5.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 (50) hide show
  1. package/README.md +137 -8
  2. package/dist/allocated-memory.js +1 -1
  3. package/dist/allocated-memory.js.map +1 -1
  4. package/dist/constant-string.js +2 -2
  5. package/dist/constant-string.js.map +1 -1
  6. package/dist/local-pool.js +2 -2
  7. package/dist/local-pool.js.map +1 -1
  8. package/dist/lock/read-write-lock.js +25 -11
  9. package/dist/lock/read-write-lock.js.map +1 -1
  10. package/dist/memory-heap.d.ts +2 -0
  11. package/dist/memory-heap.js +28 -23
  12. package/dist/memory-heap.js.map +1 -1
  13. package/dist/serialize-object.js +1 -1
  14. package/dist/serialize-object.js.map +1 -1
  15. package/dist/shared-list.d.ts +2 -0
  16. package/dist/shared-list.js +32 -29
  17. package/dist/shared-list.js.map +1 -1
  18. package/dist/shared-map.d.ts +11 -3
  19. package/dist/shared-map.js +90 -47
  20. package/dist/shared-map.js.map +1 -1
  21. package/dist/shared-pointer-list.js +3 -3
  22. package/dist/shared-pointer-list.js.map +1 -1
  23. package/dist/shared-pool.d.ts +11 -2
  24. package/dist/shared-pool.js +55 -26
  25. package/dist/shared-pool.js.map +1 -1
  26. package/dist/shared-stack.js +8 -6
  27. package/dist/shared-stack.js.map +1 -1
  28. package/dist/shared-string.js +3 -3
  29. package/dist/shared-string.js.map +1 -1
  30. package/dist/shared-vector.js +6 -6
  31. package/dist/shared-vector.js.map +1 -1
  32. package/dist/spatial/shared-quadtree.d.ts +67 -0
  33. package/dist/spatial/shared-quadtree.js +361 -0
  34. package/dist/spatial/shared-quadtree.js.map +1 -0
  35. package/dist/spatial/shared-spatial-grid.d.ts +78 -0
  36. package/dist/spatial/shared-spatial-grid.js +426 -0
  37. package/dist/spatial/shared-spatial-grid.js.map +1 -0
  38. package/dist/spatial/shared-spatial-map.d.ts +87 -0
  39. package/dist/spatial/shared-spatial-map.js +555 -0
  40. package/dist/spatial/shared-spatial-map.js.map +1 -0
  41. package/dist/spatial/spatial-entity.d.ts +11 -0
  42. package/dist/spatial/spatial-entity.js +19 -0
  43. package/dist/spatial/spatial-entity.js.map +1 -0
  44. package/dist/spatial/spatial-neighbors.d.ts +21 -0
  45. package/dist/spatial/spatial-neighbors.js +55 -0
  46. package/dist/spatial/spatial-neighbors.js.map +1 -0
  47. package/dist/utils/pointer.d.ts +8 -6
  48. package/dist/utils/pointer.js +22 -17
  49. package/dist/utils/pointer.js.map +1 -1
  50. package/package.json +6 -1
package/README.md CHANGED
@@ -65,19 +65,38 @@ let secondList = new SharedList(memory, mainList.getSharedMemory());
65
65
  - SharedStack - push/pop with exponentially growing internal segments.
66
66
  - SharedString
67
67
  - ConstantString - an immutable SharedString: the value is written once and never changes, so it drops the lock word and all of SharedString's read/write locking
68
+ - SharedQuadtree - a fixed-depth region quadtree for live, concurrent spatial updates. The tree shape is allocated once and never subdivides at runtime, so different cells never contend and entities can be inserted/moved/removed from any thread. Each entity lives in the single deepest cell that fully contains it; `search(x, y, width, height)` returns a broad-phase superset of candidate ids (same guarantee as quadtree-ts). Configure with `{ bounds: { x, y, width, height }, maxLevels, maxEntities, type }`, where `type` is `Float32Array` (default) or `Float64Array` for the stored coordinate precision.
69
+ - SharedSpatialGrid - a uniform fixed grid for live, concurrent spatial updates, the flat-grid counterpart to `SharedQuadtree`. The `cols x rows` array of cells is allocated once and never reshaped, so different cells never contend and entities can be inserted/moved/removed from any thread. Unlike the quadtree, an entity is linked into *every* cell its rect overlaps, so a large or edge-straddling entity spans several cells; `search(x, y, width, height)` still returns each candidate id at most once (a per-slot anchor cell dedups without any shared state, so concurrent queries stay safe). Locating a cell is O(1) arithmetic instead of a tree descent, which tends to beat the quadtree for evenly distributed, similarly-sized entities. Configure with `{ bounds: { x, y, width, height }, gridSize, maxEntities, maxSlots, type }`, where `gridSize` defaults to `50` and `type` is `Float32Array` (default) or `Float64Array` for the stored coordinate precision.
70
+ - SharedSpatialMap - an unbounded spatial hash for live, concurrent spatial updates, the boundless counterpart to `SharedSpatialGrid`. It indexes the same way (an entity is linked into *every* virtual cell of side `gridSize` its rect overlaps) but drops the fixed `cols x rows` extent, so the world can be any size and cells may sit at any coordinate, positive or negative - there are no `bounds`. The table starts with `256` buckets by default and doubles when its slot load exceeds `0.5`, rehashing under an exclusive table lock while ordinary operations share the read side. Bucket storage grows in 256-bucket pages tracked by a `SharedStack`, so neither the final table nor its pointer spine is reserved up front. Configure with `{ gridSize, buckets, maxBuckets, maxEntities, maxSlots, type }`; `buckets` sets the initial count and `maxBuckets` optionally caps growth. Power-of-two counts use a bitmask instead of integer modulo.
71
+
72
+ All three spatial structures accept an optional ID predicate as the final search argument, such as `search(x, y, width, height, id => enemies.has(id))`. `searchInto(out, x, y, width, height, filter?)` provides the same filtering while appending matches to a reusable result array.
68
73
 
69
74
  ## Memory Usage
70
75
  `SharedList`, `SharedVector`, `SharedPool`, and `SharedStack` each expose a `usedMemory` getter that returns the number of bytes the structure occupies in the heap, including every pointer resource it owns. For example, `SharedStack.usedMemory` is its own internal memory plus the combined memory of each already allocated segment, and `SharedPool.usedMemory` includes each chunk buffer and the segments of its two internal stacks (but not those stacks' internal memory, since that is inlined into the pool's own internal memory). The number is the aligned data size of each allocation and excludes the allocator's per-block header. SharedStack and SharedPool get a lot of their close to native performance by allocating more memory than maybe needed and locking in max length up front. This works well when used for a few high-performance structures, but if you are giving each entity in an ECS framework multiple SharedPools that memory is going to add up quickly. Both SharedPool and SharedStack initial memory can be tweaked by lowering the maxLength param.
71
76
 
72
77
  Freshly allocated with the default options:
73
78
 
74
- | Structure | Used Memory |
75
- | ------------- | ----------- |
76
- | SharedList | 16 bytes |
77
- | SharedVector | 32 bytes |
78
- | SharedStack | 144 bytes |
79
- | SharedPool | 656 bytes |
80
- | SharedMap | 216 bytes |
79
+ | Structure | Used Memory |
80
+ | ------------------------------- | ----------- |
81
+ | SharedList | 16 bytes |
82
+ | SharedVector | 32 bytes |
83
+ | SharedStack | 144 bytes |
84
+ | SharedPool | 664 bytes |
85
+ | SharedMap | 216 bytes |
86
+ | SharedQuadtree | 6064 bytes |
87
+ | SharedQuadtree(6) | 47024 bytes |
88
+ | SharedSpatialGrid (20x20 cells) | 8816 bytes |
89
+ | SharedSpatialGrid (80x80 cells) | 56816 bytes |
90
+ | SharedSpatialMap (default) | 8984 bytes |
91
+ | SharedSpatialMap (2048 buckets) | 23384 bytes |
92
+
93
+ `SharedQuadtree`'s footprint is dominated by its fixed node array, which is allocated once up front and never grows. The default `maxLevels` of 4 is a complete tree of 341 nodes (`(4^(maxLevels+1) - 1) / 3`), so the number scales with `maxLevels`: a deeper tree resolves smaller entities but costs `~2 u32` per node. The rest is its internal `SharedPool` of entity records and the `SharedMap` that indexes entities by id, whose header blocks are inlined into the tree's own allocation (the same way `SharedPool` inlines its two stacks) so the whole spine is one contiguous block instead of three separate pointer resources. The record (id, `x/y/w/h`, node, bucket link) is stored at the configured precision, defaulting to `Float32` to match a physics engine that keeps positions in `Float32`. `Float32` stays exact as long as entity ids and the entity count both stay under 2^24 (~16.7M); pass `type: Float64Array` if you need ids or a capacity beyond that (constructing a `Float32` tree with `maxEntities` over 2^24 throws), at the cost of more memory per entity.
94
+
95
+ `SharedSpatialGrid`'s footprint is likewise dominated by its fixed cell array (`cols x rows` cells at `~2 u32` each), so it scales with cell count rather than `maxLevels`: `cols = ceil(width / gridSize)` and `rows = ceil(height / gridSize)`, so a smaller `gridSize` resolves finer at the cost of more cells (the 20x20 grid above is a 1000x1000 world at `gridSize` 50; the 80x80 grid is a 4000x4000 world at `gridSize` 50). The rest is two inlined `SharedPool`s - one of entity records (the occupied cell rectangle, keyed by an inlined `SharedMap` of id -> index) and one of per-cell "slot" records (`id`, anchor cell, bucket link) - since a multi-cell entity needs one slot per cell it overlaps. `maxSlots` therefore defaults to `maxEntities * 4` to leave headroom for spanning entities. Coordinate precision follows the same `Float32`-default rule as the quadtree (ids, both pools' indices, and cell indices must stay under 2^24 for `Float32`; pass `type: Float64Array` otherwise).
96
+
97
+ `SharedSpatialMap` starts at 8984 bytes with its default 256 buckets, about 87% less than the former fixed 8192-bucket table's 70312 bytes. When the number of allocated slots exceeds half the active bucket count, it doubles the count and rebuilds the bucket chains. Its 256-bucket pages remain at stable addresses, while their pointers live in a `SharedStack` that grows in segments. `buckets` is therefore an initial size hint rather than a permanent sizing decision; `map.buckets` reports the current count. `maxBuckets` can stop growth for a deliberately collision-heavy or memory-capped table and otherwise defaults high enough to preserve the target load through `maxSlots`. The rest is the same two inlined `SharedPool`s as the grid - one of entity records and one of per-cell slot records. `maxSlots` defaults to `maxEntities * 4`, and coordinate precision follows the same `Float32`-default rule.
98
+
99
+ All three spatial indexes accept `bulkInsert(entities)`, where each entity has `{ id, x, y, width?, height? }`. Duplicate ids keep their last value. The method retains the normal thread-safety guarantees while reserving entity and slot ranges with one atomic operation, sizing the ID map once, and writing records directly into their pool blocks; `SharedSpatialMap` also sizes its bucket table once before linking a new batch. When an explicit entity or slot capacity is at most 16384, its pool uses one bounded contiguous chunk instead of a series of 100-record chunks. Larger and default capacities retain incremental allocation so an empty index does not reserve excessive memory.
81
100
 
82
101
  ## Thread Safety
83
102
  - Memory allocations is thread safe as long as it does not need to create a new buffer. Right now that can only be done from the main thread. I just make sure there is always an extra empty buffer with SharedHeap.ensureSpareBuffer() before sending more work to worker threads.
@@ -85,6 +104,9 @@ Freshly allocated with the default options:
85
104
  - SharedVector is *not* thread safe and probably never will be - it is useful for updating in main thread and then sending off to works for processing
86
105
  - SharedString is thread safe with a lock on read/write with a cached version of the string so it doesn't need to lock after the first read unless the string has changed.
87
106
  - ConstantString is safe to read from any thread without a lock precisely because it is never written after construction
107
+ - SharedQuadtree is thread safe. Its node structure never changes shape, so concurrency reduces to per-cell content updates guarded by a per-node lock
108
+ - SharedSpatialGrid is thread safe. Its cell array never changes shape, so concurrency reduces to per-cell content updates guarded by a per-cell lock; a query racing an in-flight move sees the entity in its old or new cells (or briefly both), never neither
109
+ - SharedSpatialMap is thread safe. Normal operations share a table read lock and retain their per-bucket locks; a resize takes the exclusive table lock, doubles the bucket count, and rebuilds every live slot before atomically publishing the new count. Like the grid, a query racing an in-flight move sees the entity in its old or new cells (or briefly both), never neither.
88
110
 
89
111
 
90
112
  ## Performance
@@ -249,5 +271,112 @@ native map
249
271
  2.92x faster than shared map
250
272
  ```
251
273
 
274
+ ### SharedQuadtree, SharedSpatialGrid & SharedSpatialMap vs quadtree-ts and Flatbush
275
+ `SharedQuadtree`, `SharedSpatialGrid`, and `SharedSpatialMap` are compared against [quadtree-ts](https://github.com/timohausmann/quadtree-ts) and [Flatbush](https://github.com/mourner/flatbush). 2000 entities are indexed in a 4000x4000 world at `maxLevels` 6 (the grid and map use a matching `gridSize` of `4000 / 2^6`; the map starts with its default 256 buckets and grows automatically), followed by 2000 broad-phase queries and separate nearest-neighbor workloads for one and ten entities within 500 units. The individual and bulk-insert benchmarks are separate; Flatbush rebuilds the same packed static index in both because it cannot insert into an existing index. Bulk insertion improves throughput by 35% for the quadtree, 38% for the grid, and 141% for the spatial map in this run. Flatbush remains 4.34-5.19x faster because it writes a static packed index without maintaining entity records, an ID map, mutable linked buckets, or concurrency locks. An exclusive-access prototype that skipped spatial locks was only another 5%, 16%, and 8% faster, respectively, so no unsafe API was retained. The 20% movement benchmark compares updating 400 entities in place against rebuilding the complete Flatbush index.
276
+
277
+ Spatial: insert 2000 entities individually
278
+ ```
279
+ name hz min max mean p75 p99 p995 p999 rme samples
280
+ shared quadtree 1,212.63 0.7296 1.3859 0.8247 0.8331 1.2833 1.2873 1.3859 ±1.04% 607
281
+ shared spatial grid 1,375.28 0.6699 2.1926 0.7271 0.7180 1.1583 1.3865 2.1926 ±1.06% 688
282
+ shared spatial map 657.46 1.3579 3.0925 1.5210 1.6021 2.2632 2.8197 3.0925 ±1.48% 329
283
+ quadtree-ts 2,987.46 0.2852 0.8549 0.3347 0.3314 0.5781 0.6212 0.7504 ±1.08% 1494
284
+ flatbush 7,952.20 0.0842 3.5122 0.1258 0.1213 0.2721 0.3678 3.0778 ±3.40% 3977
285
+
286
+ flatbush
287
+ 2.66x faster than quadtree-ts
288
+ 5.78x faster than shared spatial grid
289
+ 6.56x faster than shared quadtree
290
+ 12.10x faster than shared spatial map
291
+ ```
292
+
293
+ Spatial: bulk insert 2000 entities
294
+ ```
295
+ name hz min max mean p75 p99 p995 p999 rme samples
296
+ shared quadtree 1,639.98 0.5159 1.2655 0.6098 0.6029 1.0288 1.1218 1.2655 ±1.24% 820
297
+ shared spatial grid 1,896.56 0.4640 1.2814 0.5273 0.5231 0.8856 0.9616 1.2814 ±1.03% 949
298
+ shared spatial map 1,587.58 0.5441 1.4163 0.6299 0.6269 1.0970 1.1545 1.4163 ±1.23% 794
299
+ quadtree-ts 2,949.02 0.2851 0.8573 0.3391 0.3366 0.6154 0.6671 0.7323 ±1.14% 1475
300
+ flatbush 8,239.33 0.0839 3.2123 0.1214 0.1167 0.2709 0.3513 2.8912 ±3.38% 4120
301
+
302
+ flatbush
303
+ 2.79x faster than quadtree-ts
304
+ 4.34x faster than shared spatial grid
305
+ 5.02x faster than shared quadtree
306
+ 5.19x faster than shared spatial map
307
+ ```
308
+
309
+ Spatial: 2000 broad-phase queries
310
+ ```
311
+ name hz min max mean p75 p99 p995 p999 rme samples
312
+ shared quadtree 366.51 2.5524 4.3675 2.7284 2.7761 3.8666 4.3675 4.3675 ±1.29% 184
313
+ shared spatial grid 1,030.39 0.9007 1.6533 0.9705 0.9815 1.3996 1.4830 1.6533 ±0.87% 516
314
+ shared spatial map 695.37 1.3453 2.4798 1.4381 1.4295 2.1762 2.2132 2.4798 ±1.16% 348
315
+ quadtree-ts 486.60 1.9084 2.9569 2.0551 2.1063 2.7593 2.8674 2.9569 ±0.97% 244
316
+ flatbush 1,373.81 0.6734 1.1711 0.7279 0.7272 1.0091 1.0517 1.1711 ±0.75% 687
317
+
318
+ flatbush
319
+ 1.33x faster than shared spatial grid
320
+ 1.98x faster than shared spatial map
321
+ 2.82x faster than quadtree-ts
322
+ 3.75x faster than shared quadtree
323
+ ```
324
+
325
+ Spatial: 2000 single nearest-neighbor queries (within 500 units)
326
+ ```
327
+ name hz min max mean p75 p99 p995 p999 rme samples
328
+ shared quadtree 202.09 4.6839 5.7693 4.9483 5.0355 5.7382 5.7693 5.7693 ±0.87% 102
329
+ shared spatial grid 335.06 2.6652 4.6020 2.9845 3.0485 4.4999 4.6020 4.6020 ±1.85% 168
330
+ shared spatial map 411.41 2.0371 3.9992 2.4307 2.6217 3.6954 3.8876 3.9992 ±2.67% 206
331
+ flatbush 590.88 1.5631 3.1001 1.6924 1.6861 2.8048 2.9393 3.1001 ±1.27% 296
332
+
333
+ flatbush
334
+ 1.44x faster than shared spatial map
335
+ 1.76x faster than shared spatial grid
336
+ 2.92x faster than shared quadtree
337
+ ```
338
+
339
+ Spatial: 2000 ten nearest-neighbor queries (within 500 units)
340
+ ```
341
+ name hz min max mean p75 p99 p995 p999 rme samples
342
+ shared quadtree 30.6419 30.8902 35.3604 32.6350 33.3034 35.3604 35.3604 35.3604 ±2.30% 16
343
+ shared spatial grid 37.4153 24.4494 31.1661 26.7271 28.9389 31.1661 31.1661 31.1661 ±3.97% 19
344
+ shared spatial map 82.6032 11.3958 14.0954 12.1061 12.3320 14.0954 14.0954 14.0954 ±1.24% 42
345
+ flatbush 264.77 3.5514 4.9065 3.7768 3.8120 4.8969 4.9065 4.9065 ±1.17% 133
346
+
347
+ flatbush
348
+ 3.21x faster than shared spatial map
349
+ 7.08x faster than shared spatial grid
350
+ 8.64x faster than shared quadtree
351
+ ```
352
+
353
+ Spatial: move 400 of 2000 entities one step
354
+ ```
355
+ name hz min max mean p75 p99 p995 p999 rme samples
356
+ shared quadtree 12,139.41 0.0768 0.3493 0.0824 0.0800 0.1575 0.1898 0.2901 ±0.50% 6070
357
+ shared spatial grid 22,487.06 0.0337 3.0813 0.0445 0.0532 0.0983 0.1459 0.4174 ±1.72% 11244
358
+ shared spatial map 19,975.95 0.0296 0.1885 0.0501 0.0554 0.0707 0.0819 0.1385 ±0.45% 9989
359
+ flatbush (rebuild) 5,505.80 0.0860 3.4945 0.1816 0.1934 0.3027 0.3760 2.9520 ±2.87% 2754
360
+
361
+ shared spatial grid
362
+ 1.13x faster than shared spatial map
363
+ 1.85x faster than shared quadtree
364
+ 4.08x faster than flatbush (rebuild)
365
+ ```
366
+
367
+ Spatial: move all 2000 entities one step
368
+ ```
369
+ name hz min max mean p75 p99 p995 p999 rme samples
370
+ shared quadtree 1,355.57 0.4382 1.2586 0.7377 0.8367 1.0493 1.1394 1.2586 ±1.37% 678
371
+ shared spatial grid 3,121.59 0.2021 0.7684 0.3203 0.3794 0.4725 0.5177 0.7191 ±1.26% 1561
372
+ shared spatial map 3,450.37 0.1655 0.6403 0.2898 0.3147 0.4075 0.5172 0.5991 ±0.98% 1726
373
+ quadtree-ts 52.9839 13.9189 21.4902 18.8737 20.2757 21.4902 21.4902 21.4902 ±4.16% 27
374
+
375
+ shared spatial map
376
+ 1.11x faster than shared spatial grid
377
+ 2.55x faster than shared quadtree
378
+ 65.12x faster than quadtree-ts
379
+ ```
380
+
252
381
  ## Credit
253
- The entire core of this library is based on a fork of @thi.ng/malloc found at https://github.com/thi-ng/umbrella/blob/develop/packages/malloc. The only big difference between our MemoryBuffer and their MemPool is making allocations/freeing memory thread safe.
382
+ The entire core of this library is based on a fork of @thi.ng/malloc found at https://github.com/thi-ng/umbrella/blob/develop/packages/malloc. The only big difference between our MemoryBuffer and their MemPool is making allocations/freeing memory thread safe.
@@ -7,7 +7,7 @@ var t = class t {
7
7
  return this.data.byteOffset;
8
8
  }
9
9
  get pointer() {
10
- return e(this.bufferPosition, this.bufferByteOffset);
10
+ return e(this.bufferPosition, this.bufferByteOffset, this.memory.positionBits);
11
11
  }
12
12
  buffer;
13
13
  data;
@@ -1 +1 @@
1
- {"version":3,"file":"allocated-memory.js","names":[],"sources":["../src/allocated-memory.ts"],"sourcesContent":["import type { TypedArray } from './interfaces/typed-array';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryBuffer from './memory-buffer';\nimport type MemoryHeap from './memory-heap';\nimport { createPointer } from './utils/pointer';\n\nexport default class AllocatedMemory {\n\tprivate readonly memory: MemoryHeap;\n\n\treadonly bufferPosition: number;\n\tget bufferByteOffset(): number {\n\t\treturn this.data.byteOffset;\n\t}\n\tget pointer(): number {\n\t\treturn createPointer(this.bufferPosition, this.bufferByteOffset);\n\t}\n\tprivate buffer: MemoryBuffer;\n\tdata: Uint32Array;\n\n\t// Bytes this allocation occupies in its buffer (aligned data size, excluding the block header)\n\tget usedMemory(): number {\n\t\treturn this.buffer.bytesFor(this.data.byteOffset) ?? 0;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: AllocatedMemoryConfig | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif('buffer' in config) {\n\t\t\tthis.data = config.data;\n\t\t\tthis.buffer = config.buffer;\n\t\t\tthis.bufferPosition = this.memory.buffers.indexOf(config.buffer);\n\t\t} else {\n\t\t\tthis.bufferPosition = config.bufferPosition;\n\t\t\tthis.buffer = memory.buffers[config.bufferPosition];\n\n\t\t\t// Making sure these are the correct size is slow but in dev we want to make sure we aren't allowing to go out of bounds\n\t\t\tif(import.meta.env.MODE === 'production') {\n\t\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset);\n\t\t\t} else {\n\t\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset, this.buffer.lengthOf(config.bufferByteOffset));\n\t\t\t}\n\t\t}\n\t}\n\n\tgetArray<T extends TypedArray>(type: TypedArrayConstructor<T>, offset: number, length: number): T {\n\t\tif(import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {\n\t\t\tif((offset + length) * type.BYTES_PER_ELEMENT > this.data.byteLength) {\n\t\t\t\tconst message = 'Trying to grab more memory from AllocatedMemory.getArray then we have: '\n\t\t\t\t\t+ `${offset * type.BYTES_PER_ELEMENT} + ${length * type.BYTES_PER_ELEMENT} > ${this.data.byteLength}`;\n\t\t\t\tif(import.meta.env.MODE === 'test') {\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn new type(this.data.buffer, this.data.byteOffset + offset * type.BYTES_PER_ELEMENT, length);\n\t}\n\t// A child AllocatedMemory over a slice of this block, sharing the same buffer. Built through the {data, buffer} path\n\t// (same as a fresh allocUI32) so callers stay on the cheap, monomorphic construction branch instead of re-resolving a\n\t// pointer. Used to inline sub-structures (e.g. SharedPool's stacks) inside one contiguous allocation.\n\tgetSubAllocation(offset: number, length: number): AllocatedMemory {\n\t\treturn new AllocatedMemory(this.memory, {\n\t\t\tdata: new Uint32Array(this.data.buffer, this.bufferByteOffset + offset * Uint32Array.BYTES_PER_ELEMENT, length),\n\t\t\tbuffer: this.buffer,\n\t\t});\n\t}\n\n\tgetArrayMemory(offset: number, length: number): SharedAllocatedMemory {\n\t\tif(import.meta.env.MODE === 'development') {\n\t\t\tif(offset + length > this.data.length) {\n\t\t\t\tconsole.warn(`Trying to grab more memory from AllocatedMemory.getArrayMemory then we have: ${offset} + ${length} > ${this.data.length}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset + offset * this.data.BYTES_PER_ELEMENT,\n\t\t};\n\t}\n\n\tfree() {\n\t\t// NOTE: From worker thread you can't pass the array, you have to pass an explicit address to free\n\t\tthis.buffer.free(this.data.byteOffset);\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset,\n\t\t};\n\t}\n}\n\ninterface AllocatedMemoryConfig {\n\tdata: Uint32Array\n\tbuffer: MemoryBuffer\n}\n\ninterface SharedAllocatedMemory {\n\tbufferPosition: number\n\tbufferByteOffset: number\n}\nexport type { SharedAllocatedMemory };"],"mappings":";;AAMA,IAAqB,IAArB,MAAqB,EAAgB;CACpC;CAEA;CACA,IAAI,mBAA2B;EAC9B,OAAO,KAAK,KAAK;CAClB;CACA,IAAI,UAAkB;EACrB,OAAO,EAAc,KAAK,gBAAgB,KAAK,gBAAgB;CAChE;CACA;CACA;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,UAAU,KAAK;CACtD;CAEA,YAAY,GAAoB,GAAuD;EAGtF,AAFA,KAAK,SAAS,GAEX,YAAY,KACd,KAAK,OAAO,EAAO,MACnB,KAAK,SAAS,EAAO,QACrB,KAAK,iBAAiB,KAAK,OAAO,QAAQ,QAAQ,EAAO,MAAM,MAE/D,KAAK,iBAAiB,EAAO,gBAC7B,KAAK,SAAS,EAAO,QAAQ,EAAO,iBAInC,KAAK,OAAO,IAAI,YAAY,KAAK,OAAO,KAAK,EAAO,gBAAgB;CAKvE;CAEA,SAA+B,GAAgC,GAAgB,GAAmB;EAajG,OAAO,IAAI,EAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,aAAa,IAAS,EAAK,mBAAmB,CAAM;CACjG;CAIA,iBAAiB,GAAgB,GAAiC;EACjE,OAAO,IAAI,EAAgB,KAAK,QAAQ;GACvC,MAAM,IAAI,YAAY,KAAK,KAAK,QAAQ,KAAK,mBAAmB,IAAS,YAAY,mBAAmB,CAAM;GAC9G,QAAQ,KAAK;EACd,CAAC;CACF;CAEA,eAAe,GAAgB,GAAuC;EAOrE,OAAO;GACN,gBAAgB,KAAK;GACrB,kBAAkB,KAAK,mBAAmB,IAAS,KAAK,KAAK;EAC9D;CACD;CAEA,OAAO;EAEN,KAAK,OAAO,KAAK,KAAK,KAAK,UAAU;CACtC;CAEA,kBAAyC;EACxC,OAAO;GACN,gBAAgB,KAAK;GACrB,kBAAkB,KAAK;EACxB;CACD;AACD"}
1
+ {"version":3,"file":"allocated-memory.js","names":[],"sources":["../src/allocated-memory.ts"],"sourcesContent":["import type { TypedArray } from './interfaces/typed-array';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryBuffer from './memory-buffer';\nimport type MemoryHeap from './memory-heap';\nimport { createPointer } from './utils/pointer';\n\nexport default class AllocatedMemory {\n\tprivate readonly memory: MemoryHeap;\n\n\treadonly bufferPosition: number;\n\tget bufferByteOffset(): number {\n\t\treturn this.data.byteOffset;\n\t}\n\tget pointer(): number {\n\t\treturn createPointer(this.bufferPosition, this.bufferByteOffset, this.memory.positionBits);\n\t}\n\tprivate buffer: MemoryBuffer;\n\tdata: Uint32Array;\n\n\t// Bytes this allocation occupies in its buffer (aligned data size, excluding the block header)\n\tget usedMemory(): number {\n\t\treturn this.buffer.bytesFor(this.data.byteOffset) ?? 0;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: AllocatedMemoryConfig | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif('buffer' in config) {\n\t\t\tthis.data = config.data;\n\t\t\tthis.buffer = config.buffer;\n\t\t\tthis.bufferPosition = this.memory.buffers.indexOf(config.buffer);\n\t\t} else {\n\t\t\tthis.bufferPosition = config.bufferPosition;\n\t\t\tthis.buffer = memory.buffers[config.bufferPosition];\n\n\t\t\t// Making sure these are the correct size is slow but in dev we want to make sure we aren't allowing to go out of bounds\n\t\t\tif(import.meta.env.MODE === 'production') {\n\t\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset);\n\t\t\t} else {\n\t\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset, this.buffer.lengthOf(config.bufferByteOffset));\n\t\t\t}\n\t\t}\n\t}\n\n\tgetArray<T extends TypedArray>(type: TypedArrayConstructor<T>, offset: number, length: number): T {\n\t\tif(import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {\n\t\t\tif((offset + length) * type.BYTES_PER_ELEMENT > this.data.byteLength) {\n\t\t\t\tconst message = 'Trying to grab more memory from AllocatedMemory.getArray then we have: '\n\t\t\t\t\t+ `${offset * type.BYTES_PER_ELEMENT} + ${length * type.BYTES_PER_ELEMENT} > ${this.data.byteLength}`;\n\t\t\t\tif(import.meta.env.MODE === 'test') {\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn new type(this.data.buffer, this.data.byteOffset + offset * type.BYTES_PER_ELEMENT, length);\n\t}\n\t// A child AllocatedMemory over a slice of this block, sharing the same buffer. Built through the {data, buffer} path\n\t// (same as a fresh allocUI32) so callers stay on the cheap, monomorphic construction branch instead of re-resolving a\n\t// pointer. Used to inline sub-structures (e.g. SharedPool's stacks) inside one contiguous allocation.\n\tgetSubAllocation(offset: number, length: number): AllocatedMemory {\n\t\treturn new AllocatedMemory(this.memory, {\n\t\t\tdata: new Uint32Array(this.data.buffer, this.bufferByteOffset + offset * Uint32Array.BYTES_PER_ELEMENT, length),\n\t\t\tbuffer: this.buffer,\n\t\t});\n\t}\n\n\tgetArrayMemory(offset: number, length: number): SharedAllocatedMemory {\n\t\tif(import.meta.env.MODE === 'development') {\n\t\t\tif(offset + length > this.data.length) {\n\t\t\t\tconsole.warn(`Trying to grab more memory from AllocatedMemory.getArrayMemory then we have: ${offset} + ${length} > ${this.data.length}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset + offset * this.data.BYTES_PER_ELEMENT,\n\t\t};\n\t}\n\n\tfree() {\n\t\t// NOTE: From worker thread you can't pass the array, you have to pass an explicit address to free\n\t\tthis.buffer.free(this.data.byteOffset);\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset,\n\t\t};\n\t}\n}\n\ninterface AllocatedMemoryConfig {\n\tdata: Uint32Array\n\tbuffer: MemoryBuffer\n}\n\ninterface SharedAllocatedMemory {\n\tbufferPosition: number\n\tbufferByteOffset: number\n}\nexport type { SharedAllocatedMemory };"],"mappings":";;AAMA,IAAqB,IAArB,MAAqB,EAAgB;CACpC;CAEA;CACA,IAAI,mBAA2B;EAC9B,OAAO,KAAK,KAAK;CAClB;CACA,IAAI,UAAkB;EACrB,OAAO,EAAc,KAAK,gBAAgB,KAAK,kBAAkB,KAAK,OAAO,YAAY;CAC1F;CACA;CACA;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,UAAU,KAAK;CACtD;CAEA,YAAY,GAAoB,GAAuD;EAGtF,AAFA,KAAK,SAAS,GAEX,YAAY,KACd,KAAK,OAAO,EAAO,MACnB,KAAK,SAAS,EAAO,QACrB,KAAK,iBAAiB,KAAK,OAAO,QAAQ,QAAQ,EAAO,MAAM,MAE/D,KAAK,iBAAiB,EAAO,gBAC7B,KAAK,SAAS,EAAO,QAAQ,EAAO,iBAInC,KAAK,OAAO,IAAI,YAAY,KAAK,OAAO,KAAK,EAAO,gBAAgB;CAKvE;CAEA,SAA+B,GAAgC,GAAgB,GAAmB;EAajG,OAAO,IAAI,EAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,aAAa,IAAS,EAAK,mBAAmB,CAAM;CACjG;CAIA,iBAAiB,GAAgB,GAAiC;EACjE,OAAO,IAAI,EAAgB,KAAK,QAAQ;GACvC,MAAM,IAAI,YAAY,KAAK,KAAK,QAAQ,KAAK,mBAAmB,IAAS,YAAY,mBAAmB,CAAM;GAC9G,QAAQ,KAAK;EACd,CAAC;CACF;CAEA,eAAe,GAAgB,GAAuC;EAOrE,OAAO;GACN,gBAAgB,KAAK;GACrB,kBAAkB,KAAK,mBAAmB,IAAS,KAAK,KAAK;EAC9D;CACD;CAEA,OAAO;EAEN,KAAK,OAAO,KAAK,KAAK,KAAK,UAAU;CACtC;CAEA,kBAAyC;EACxC,OAAO;GACN,gBAAgB,KAAK;GACrB,kBAAkB,KAAK;EACxB;CACD;AACD"}
@@ -19,7 +19,7 @@ var i = {
19
19
  readString() {
20
20
  let n = t(this.allocatedMemory.data, a);
21
21
  if (n === 0) return "";
22
- let { bufferPosition: r, bufferByteOffset: c } = e(n), u = i[Atomics.load(this.allocatedMemory.data, s)], d = Atomics.load(this.allocatedMemory.data, o);
22
+ let { bufferPosition: r, bufferByteOffset: c } = e(n, this.memory.positionBits), u = i[Atomics.load(this.allocatedMemory.data, s)], d = Atomics.load(this.allocatedMemory.data, o);
23
23
  return l(new u(this.memory.buffers[r].buf, c, d));
24
24
  }
25
25
  createString(e) {
@@ -48,7 +48,7 @@ var i = {
48
48
  free() {
49
49
  let n = t(this.allocatedMemory.data, a);
50
50
  if (n) {
51
- let { bufferPosition: t, bufferByteOffset: r } = e(n);
51
+ let { bufferPosition: t, bufferByteOffset: r } = e(n, this.memory.positionBits);
52
52
  this.memory.buffers[t].free(r);
53
53
  }
54
54
  this.allocatedMemory.free();
@@ -1 +1 @@
1
- {"version":3,"file":"constant-string.js","names":[],"sources":["../src/constant-string.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\n\nenum CHAR_TYPE {\n\tUNDEFINED,\n\tASCII,\n\tUTF16,\n}\nconst TYPED_ARRAY_MAP = {\n\t[CHAR_TYPE.ASCII]: Uint8Array,\n\t[CHAR_TYPE.UTF16]: Uint16Array,\n};\n\nconst POINTER_INDEX = 0;\nconst LENGTH_INDEX = 1;\nconst TYPE_INDEX = 2;\n\n// An immutable SharedString: the value is written once at construction and never changes\nexport default class ConstantString {\n\tstatic readonly ALLOCATE_COUNT = 3;\n\n\tprivate memory: MemoryHeap;\n\tprivate allocatedMemory: AllocatedMemory;\n\n\treadonly value: string;\n\n\tconstructor(memory: MemoryHeap, value: string | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(typeof value === 'string') {\n\t\t\tthis.allocatedMemory = this.memory.allocUI32(ConstantString.ALLOCATE_COUNT);\n\t\t\tthis.writeString(value);\n\t\t\tthis.value = value;\n\t\t} else {\n\t\t\t// A read-only view over a header another thread already wrote.\n\t\t\tthis.allocatedMemory = new AllocatedMemory(memory, value);\n\t\t\tthis.value = this.readString();\n\t\t}\n\t}\n\n\tprivate writeString(value: string) {\n\t\tconst stringMemory = this.createString(value);\n\n\t\tstoreRawPointer(this.allocatedMemory.data, POINTER_INDEX, stringMemory.pointer);\n\t\tAtomics.store(this.allocatedMemory.data, LENGTH_INDEX, value.length);\n\t\tAtomics.store(this.allocatedMemory.data, TYPE_INDEX, stringMemory.charType);\n\t}\n\n\tprivate readString(): string {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer === 0) {\n\t\t\treturn '';\n\t\t}\n\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer);\n\n\t\t// No lock: the value never changes after it is written once, so the memory it points at is never freed or\n\t\t// rewritten underneath us.\n\t\tlet charType = Atomics.load(this.allocatedMemory.data, TYPE_INDEX);\n\t\t// @ts-expect-error\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet bufferLength = Atomics.load(this.allocatedMemory.data, LENGTH_INDEX);\n\n\t\tlet data = new typedArray(this.memory.buffers[bufferPosition].buf, bufferByteOffset, bufferLength);\n\t\treturn fromCharCodes(data);\n\t}\n\tprivate createString(value: string) {\n\t\tif(value === '') {\n\t\t\treturn {\n\t\t\t\tpointer: 0,\n\t\t\t\tcharType: CHAR_TYPE.ASCII,\n\t\t\t};\n\t\t}\n\n\t\t// NOTE: Scan with a loop instead of Math.max(...charCodes) which blows the stack for large strings\n\t\tlet maxCharCode = 0;\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tlet charCode = value.charCodeAt(i);\n\t\t\tif(charCode > maxCharCode) {\n\t\t\t\tmaxCharCode = charCode;\n\t\t\t}\n\t\t}\n\t\tlet charType = maxCharCode > 255 ? CHAR_TYPE.UTF16 : CHAR_TYPE.ASCII;\n\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet allocatedMemory = this.memory.allocUI32(Math.ceil(value.length / (4 / typedArray.BYTES_PER_ELEMENT)));\n\t\tlet data = new typedArray(allocatedMemory.data.buffer as ArrayBuffer, allocatedMemory.data.byteOffset, value.length);\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tdata[i] = value.charCodeAt(i);\n\t\t}\n\n\t\treturn {\n\t\t\tpointer: allocatedMemory.pointer,\n\t\t\tcharType,\n\t\t};\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn this.allocatedMemory.getSharedMemory();\n\t}\n\n\tget pointer() {\n\t\treturn this.allocatedMemory.pointer;\n\t}\n\n\tfree() {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer) {\n\t\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer);\n\t\t\tthis.memory.buffers[bufferPosition].free(bufferByteOffset);\n\t\t}\n\t\tthis.allocatedMemory.free();\n\t}\n}\n\n// String.fromCharCode.apply throws a RangeError (Maximum call stack size exceeded) when handed too many\n// arguments at once, so decode large buffers in chunks to support arbitrarily long strings\nfunction fromCharCodes(data: Uint8Array | Uint16Array): string {\n\tconst CHUNK_SIZE = 8192;\n\tif(data.length <= CHUNK_SIZE) {\n\t\treturn String.fromCharCode.apply(null, data as unknown as Array<number>);\n\t}\n\n\tlet string = '';\n\tfor(let i = 0; i < data.length; i += CHUNK_SIZE) {\n\t\tstring += String.fromCharCode.apply(null, data.subarray(i, i + CHUNK_SIZE) as unknown as Array<number>);\n\t}\n\n\treturn string;\n}\n"],"mappings":";;;AAUA,IAAM,IAAkB;CACJ,GAAA;CACA,GAAA;AACpB,GAEM,IAAgB,GAChB,IAAe,GACf,IAAa,GAGE,IAArB,MAAqB,EAAe;CACnC,OAAgB,iBAAiB;CAEjC;CACA;CAEA;CAEA,YAAY,GAAoB,GAAuC;EAGtE,AAFA,KAAK,SAAS,GAEX,OAAO,KAAU,YACnB,KAAK,kBAAkB,KAAK,OAAO,UAAU,EAAe,cAAc,GAC1E,KAAK,YAAY,CAAK,GACtB,KAAK,QAAQ,MAGb,KAAK,kBAAkB,IAAI,EAAgB,GAAQ,CAAK,GACxD,KAAK,QAAQ,KAAK,WAAW;CAE/B;CAEA,YAAoB,GAAe;EAClC,IAAM,IAAe,KAAK,aAAa,CAAK;EAI5C,AAFA,EAAgB,KAAK,gBAAgB,MAAM,GAAe,EAAa,OAAO,GAC9E,QAAQ,MAAM,KAAK,gBAAgB,MAAM,GAAc,EAAM,MAAM,GACnE,QAAQ,MAAM,KAAK,gBAAgB,MAAM,GAAY,EAAa,QAAQ;CAC3E;CAEA,aAA6B;EAC5B,IAAI,IAAU,EAAe,KAAK,gBAAgB,MAAM,CAAa;EACrE,IAAG,MAAY,GACd,OAAO;EAER,IAAI,EAAE,mBAAgB,wBAAqB,EAAW,CAAO,GAMzD,IAAa,EAFF,QAAQ,KAAK,KAAK,gBAAgB,MAAM,CAEtB,IAC7B,IAAe,QAAQ,KAAK,KAAK,gBAAgB,MAAM,CAAY;EAGvE,OAAO,EAAc,IADN,EAAW,KAAK,OAAO,QAAQ,EAAe,CAAC,KAAK,GAAkB,CAChE,CAAI;CAC1B;CACA,aAAqB,GAAe;EACnC,IAAG,MAAU,IACZ,OAAO;GACN,SAAS;GACT,UAAA;EACD;EAID,IAAI,IAAc;EAClB,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK;GACrC,IAAI,IAAW,EAAM,WAAW,CAAC;GACjC,AAAG,IAAW,MACb,IAAc;EAEhB;EACA,IAAI,IAAW,IAAc,MAAA,IAAA,GAEzB,IAAa,EAAgB,IAC7B,IAAkB,KAAK,OAAO,UAAU,KAAK,KAAK,EAAM,UAAU,IAAI,EAAW,kBAAkB,CAAC,GACpG,IAAO,IAAI,EAAW,EAAgB,KAAK,QAAuB,EAAgB,KAAK,YAAY,EAAM,MAAM;EACnH,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAK,KAAK,EAAM,WAAW,CAAC;EAG7B,OAAO;GACN,SAAS,EAAgB;GACzB;EACD;CACD;CAEA,kBAAyC;EACxC,OAAO,KAAK,gBAAgB,gBAAgB;CAC7C;CAEA,IAAI,UAAU;EACb,OAAO,KAAK,gBAAgB;CAC7B;CAEA,OAAO;EACN,IAAI,IAAU,EAAe,KAAK,gBAAgB,MAAM,CAAa;EACrE,IAAG,GAAS;GACX,IAAI,EAAE,mBAAgB,wBAAqB,EAAW,CAAO;GAC7D,KAAK,OAAO,QAAQ,EAAe,CAAC,KAAK,CAAgB;EAC1D;EACA,KAAK,gBAAgB,KAAK;CAC3B;AACD;AAIA,SAAS,EAAc,GAAwC;CAC9D,IAAM,IAAa;CACnB,IAAG,EAAK,UAAU,GACjB,OAAO,OAAO,aAAa,MAAM,MAAM,CAAgC;CAGxE,IAAI,IAAS;CACb,KAAI,IAAI,IAAI,GAAG,IAAI,EAAK,QAAQ,KAAK,GACpC,KAAU,OAAO,aAAa,MAAM,MAAM,EAAK,SAAS,GAAG,IAAI,CAAU,CAA6B;CAGvG,OAAO;AACR"}
1
+ {"version":3,"file":"constant-string.js","names":[],"sources":["../src/constant-string.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\n\nenum CHAR_TYPE {\n\tUNDEFINED,\n\tASCII,\n\tUTF16,\n}\nconst TYPED_ARRAY_MAP = {\n\t[CHAR_TYPE.ASCII]: Uint8Array,\n\t[CHAR_TYPE.UTF16]: Uint16Array,\n};\n\nconst POINTER_INDEX = 0;\nconst LENGTH_INDEX = 1;\nconst TYPE_INDEX = 2;\n\n// An immutable SharedString: the value is written once at construction and never changes\nexport default class ConstantString {\n\tstatic readonly ALLOCATE_COUNT = 3;\n\n\tprivate memory: MemoryHeap;\n\tprivate allocatedMemory: AllocatedMemory;\n\n\treadonly value: string;\n\n\tconstructor(memory: MemoryHeap, value: string | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(typeof value === 'string') {\n\t\t\tthis.allocatedMemory = this.memory.allocUI32(ConstantString.ALLOCATE_COUNT);\n\t\t\tthis.writeString(value);\n\t\t\tthis.value = value;\n\t\t} else {\n\t\t\t// A read-only view over a header another thread already wrote.\n\t\t\tthis.allocatedMemory = new AllocatedMemory(memory, value);\n\t\t\tthis.value = this.readString();\n\t\t}\n\t}\n\n\tprivate writeString(value: string) {\n\t\tconst stringMemory = this.createString(value);\n\n\t\tstoreRawPointer(this.allocatedMemory.data, POINTER_INDEX, stringMemory.pointer);\n\t\tAtomics.store(this.allocatedMemory.data, LENGTH_INDEX, value.length);\n\t\tAtomics.store(this.allocatedMemory.data, TYPE_INDEX, stringMemory.charType);\n\t}\n\n\tprivate readString(): string {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer === 0) {\n\t\t\treturn '';\n\t\t}\n\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer, this.memory.positionBits);\n\n\t\t// No lock: the value never changes after it is written once, so the memory it points at is never freed or\n\t\t// rewritten underneath us.\n\t\tlet charType = Atomics.load(this.allocatedMemory.data, TYPE_INDEX);\n\t\t// @ts-expect-error\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet bufferLength = Atomics.load(this.allocatedMemory.data, LENGTH_INDEX);\n\n\t\tlet data = new typedArray(this.memory.buffers[bufferPosition].buf, bufferByteOffset, bufferLength);\n\t\treturn fromCharCodes(data);\n\t}\n\tprivate createString(value: string) {\n\t\tif(value === '') {\n\t\t\treturn {\n\t\t\t\tpointer: 0,\n\t\t\t\tcharType: CHAR_TYPE.ASCII,\n\t\t\t};\n\t\t}\n\n\t\t// NOTE: Scan with a loop instead of Math.max(...charCodes) which blows the stack for large strings\n\t\tlet maxCharCode = 0;\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tlet charCode = value.charCodeAt(i);\n\t\t\tif(charCode > maxCharCode) {\n\t\t\t\tmaxCharCode = charCode;\n\t\t\t}\n\t\t}\n\t\tlet charType = maxCharCode > 255 ? CHAR_TYPE.UTF16 : CHAR_TYPE.ASCII;\n\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet allocatedMemory = this.memory.allocUI32(Math.ceil(value.length / (4 / typedArray.BYTES_PER_ELEMENT)));\n\t\tlet data = new typedArray(allocatedMemory.data.buffer as ArrayBuffer, allocatedMemory.data.byteOffset, value.length);\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tdata[i] = value.charCodeAt(i);\n\t\t}\n\n\t\treturn {\n\t\t\tpointer: allocatedMemory.pointer,\n\t\t\tcharType,\n\t\t};\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn this.allocatedMemory.getSharedMemory();\n\t}\n\n\tget pointer() {\n\t\treturn this.allocatedMemory.pointer;\n\t}\n\n\tfree() {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer) {\n\t\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer, this.memory.positionBits);\n\t\t\tthis.memory.buffers[bufferPosition].free(bufferByteOffset);\n\t\t}\n\t\tthis.allocatedMemory.free();\n\t}\n}\n\n// String.fromCharCode.apply throws a RangeError (Maximum call stack size exceeded) when handed too many\n// arguments at once, so decode large buffers in chunks to support arbitrarily long strings\nfunction fromCharCodes(data: Uint8Array | Uint16Array): string {\n\tconst CHUNK_SIZE = 8192;\n\tif(data.length <= CHUNK_SIZE) {\n\t\treturn String.fromCharCode.apply(null, data as unknown as Array<number>);\n\t}\n\n\tlet string = '';\n\tfor(let i = 0; i < data.length; i += CHUNK_SIZE) {\n\t\tstring += String.fromCharCode.apply(null, data.subarray(i, i + CHUNK_SIZE) as unknown as Array<number>);\n\t}\n\n\treturn string;\n}\n"],"mappings":";;;AAUA,IAAM,IAAkB;CACJ,GAAA;CACA,GAAA;AACpB,GAEM,IAAgB,GAChB,IAAe,GACf,IAAa,GAGE,IAArB,MAAqB,EAAe;CACnC,OAAgB,iBAAiB;CAEjC;CACA;CAEA;CAEA,YAAY,GAAoB,GAAuC;EAGtE,AAFA,KAAK,SAAS,GAEX,OAAO,KAAU,YACnB,KAAK,kBAAkB,KAAK,OAAO,UAAU,EAAe,cAAc,GAC1E,KAAK,YAAY,CAAK,GACtB,KAAK,QAAQ,MAGb,KAAK,kBAAkB,IAAI,EAAgB,GAAQ,CAAK,GACxD,KAAK,QAAQ,KAAK,WAAW;CAE/B;CAEA,YAAoB,GAAe;EAClC,IAAM,IAAe,KAAK,aAAa,CAAK;EAI5C,AAFA,EAAgB,KAAK,gBAAgB,MAAM,GAAe,EAAa,OAAO,GAC9E,QAAQ,MAAM,KAAK,gBAAgB,MAAM,GAAc,EAAM,MAAM,GACnE,QAAQ,MAAM,KAAK,gBAAgB,MAAM,GAAY,EAAa,QAAQ;CAC3E;CAEA,aAA6B;EAC5B,IAAI,IAAU,EAAe,KAAK,gBAAgB,MAAM,CAAa;EACrE,IAAG,MAAY,GACd,OAAO;EAER,IAAI,EAAE,mBAAgB,wBAAqB,EAAW,GAAS,KAAK,OAAO,YAAY,GAMnF,IAAa,EAFF,QAAQ,KAAK,KAAK,gBAAgB,MAAM,CAEtB,IAC7B,IAAe,QAAQ,KAAK,KAAK,gBAAgB,MAAM,CAAY;EAGvE,OAAO,EAAc,IADN,EAAW,KAAK,OAAO,QAAQ,EAAe,CAAC,KAAK,GAAkB,CAChE,CAAI;CAC1B;CACA,aAAqB,GAAe;EACnC,IAAG,MAAU,IACZ,OAAO;GACN,SAAS;GACT,UAAA;EACD;EAID,IAAI,IAAc;EAClB,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK;GACrC,IAAI,IAAW,EAAM,WAAW,CAAC;GACjC,AAAG,IAAW,MACb,IAAc;EAEhB;EACA,IAAI,IAAW,IAAc,MAAA,IAAA,GAEzB,IAAa,EAAgB,IAC7B,IAAkB,KAAK,OAAO,UAAU,KAAK,KAAK,EAAM,UAAU,IAAI,EAAW,kBAAkB,CAAC,GACpG,IAAO,IAAI,EAAW,EAAgB,KAAK,QAAuB,EAAgB,KAAK,YAAY,EAAM,MAAM;EACnH,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAK,KAAK,EAAM,WAAW,CAAC;EAG7B,OAAO;GACN,SAAS,EAAgB;GACzB;EACD;CACD;CAEA,kBAAyC;EACxC,OAAO,KAAK,gBAAgB,gBAAgB;CAC7C;CAEA,IAAI,UAAU;EACb,OAAO,KAAK,gBAAgB;CAC7B;CAEA,OAAO;EACN,IAAI,IAAU,EAAe,KAAK,gBAAgB,MAAM,CAAa;EACrE,IAAG,GAAS;GACX,IAAI,EAAE,mBAAgB,wBAAqB,EAAW,GAAS,KAAK,OAAO,YAAY;GACvF,KAAK,OAAO,QAAQ,EAAe,CAAC,KAAK,CAAgB;EAC1D;EACA,KAAK,gBAAgB,KAAK;CAC3B;AACD;AAIA,SAAS,EAAc,GAAwC;CAC9D,IAAM,IAAa;CACnB,IAAG,EAAK,UAAU,GACjB,OAAO,OAAO,aAAa,MAAM,MAAM,CAAgC;CAGxE,IAAI,IAAS;CACb,KAAI,IAAI,IAAI,GAAG,IAAI,EAAK,QAAQ,KAAK,GACpC,KAAU,OAAO,aAAa,MAAM,MAAM,EAAK,SAAS,GAAG,IAAI,CAAU,CAA6B;CAGvG,OAAO;AACR"}
@@ -75,7 +75,7 @@ var i = class {
75
75
  let e = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);
76
76
  this.pointers.push(e.pointer);
77
77
  }
78
- let a = new t(this.memory, e(this.pointers[r])), o = new this.typeConstructor(a.data.buffer, a.bufferByteOffset, this.dataLength * this.maxChunkSize);
78
+ let a = new t(this.memory, e(this.pointers[r], this.memory.positionBits)), o = new this.typeConstructor(a.data.buffer, a.bufferByteOffset, this.dataLength * this.maxChunkSize);
79
79
  return this.fullDataBlocks[r] = o, o;
80
80
  }
81
81
  getDataBlock(e, t) {
@@ -83,7 +83,7 @@ var i = class {
83
83
  return e.subarray(n, n + this.dataLength);
84
84
  }
85
85
  free() {
86
- for (let n of this.pointers) new t(this.memory, e(n)).free();
86
+ for (let n of this.pointers) new t(this.memory, e(n, this.memory.positionBits)).free();
87
87
  this.pointers = [], this.recycledIndexes = [], this.totalLength = 0, this.fullDataBlocks = {};
88
88
  }
89
89
  };
@@ -1 +1 @@
1
- {"version":3,"file":"local-pool.js","names":[],"sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\tthis.byteMultipler = getByteMultipler(getArrayTypeCode(config.type));\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.dataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)] as NumericArrayIO;\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\n\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex] as NumericArrayIO;\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\n\t\tif(isSingleValue) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\tdataBlock[startIndex + i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex]));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"mappings":"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"}
1
+ {"version":3,"file":"local-pool.js","names":[],"sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\tthis.byteMultipler = getByteMultipler(getArrayTypeCode(config.type));\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.dataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)] as NumericArrayIO;\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\n\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex] as NumericArrayIO;\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\n\t\tif(isSingleValue) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\tdataBlock[startIndex + i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex], this.memory.positionBits));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"mappings":"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"}
@@ -1,19 +1,33 @@
1
- function e(e, t = 0) {
2
- for (; Atomics.compareExchange(e, t, 0, 1) === 2;) Atomics.wait(e, t, 2);
3
- Atomics.add(e, t + 1, 1);
1
+ function e() {
2
+ return typeof self < "u" && "WorkerGlobalScope" in self;
4
3
  }
5
- function t(e, t = 0) {
6
- let n = Atomics.compareExchange(e, t, 0, 2);
7
- for (; n !== 0;) Atomics.wait(e, t, n), n = Atomics.compareExchange(e, t, 0, 2);
4
+ function t(t, n = 0) {
5
+ for (;;) {
6
+ let r = t[n + 1];
7
+ if (r !== 0) {
8
+ e() && Atomics.wait(t, n + 1, r);
9
+ continue;
10
+ }
11
+ let i = t[n];
12
+ if (i !== -1 && Atomics.compareExchange(t, n, i, i + 1) === i) return;
13
+ e() && i === -1 && Atomics.wait(t, n, -1);
14
+ }
8
15
  }
9
- function n(e, t = 0) {
10
- Atomics.sub(e, t + 1, 1) - 1 <= 0 && (Atomics.compareExchange(e, t, 1, 0) !== 1 && console.warn("We are unlocking when it was not read locked!"), Atomics.notify(e, t));
16
+ function n(t, n = 0) {
17
+ for (Atomics.add(t, n + 1, 1); Atomics.compareExchange(t, n, 0, -1) !== 0;) {
18
+ let r = t[n];
19
+ e() && r !== 0 && Atomics.wait(t, n, r);
20
+ }
21
+ Atomics.sub(t, n + 1, 1);
11
22
  }
12
23
  function r(e, t = 0) {
13
- Atomics.compareExchange(e, t, 2, 0) !== 2 && console.warn("We are unlocking when it was not write locked!"), Atomics.notify(e, t);
24
+ Atomics.sub(e, t, 1) === 1 && Atomics.notify(e, t);
14
25
  }
15
- var i = 2;
26
+ function i(e, t = 0) {
27
+ Atomics.compareExchange(e, t, -1, 0) !== -1 && console.warn("We are unlocking when it was not write locked!"), Atomics.notify(e, t), Atomics.notify(e, t + 1);
28
+ }
29
+ var a = 2;
16
30
  //#endregion
17
- export { i as READ_WRITE_LOCK_ALLOCATE_COUNT, e as readLock, n as readUnlock, t as writeLock, r as writeUnlock };
31
+ export { a as READ_WRITE_LOCK_ALLOCATE_COUNT, t as readLock, r as readUnlock, n as writeLock, i as writeUnlock };
18
32
 
19
33
  //# sourceMappingURL=read-write-lock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"read-write-lock.js","names":[],"sources":["../../src/lock/read-write-lock.ts"],"sourcesContent":["const UNLOCKED = 0;\nconst READ_LOCKED = 1;\nconst WRITE_LOCKED = 2;\n\nexport function readLock(data: Int32Array, index: number = 0) {\n\t// Wait over and over again until we get that it was unlocked or read locked\n\twhile(Atomics.compareExchange(data, index, UNLOCKED, READ_LOCKED) === WRITE_LOCKED) {\n\t\tAtomics.wait(data, index, WRITE_LOCKED);\n\t}\n\n\tAtomics.add(data, index + 1, 1);\n}\nexport function writeLock(data: Int32Array, index: number = 0) {\n\t// Write lock needs to be exclusive - wait until we were in UNLOCKED to proceed\n\tlet oldValue = Atomics.compareExchange(data, index, UNLOCKED, WRITE_LOCKED);\n\twhile(oldValue !== UNLOCKED) {\n\t\tAtomics.wait(data, index, oldValue);\n\t\toldValue = Atomics.compareExchange(data, index, UNLOCKED, WRITE_LOCKED);\n\t}\n}\n\nexport function readUnlock(data: Int32Array, index: number = 0) {\n\tlet readCount = Atomics.sub(data, index + 1, 1) - 1;\n\n\tif(readCount <= 0) {\n\t\tif(Atomics.compareExchange(data, index, READ_LOCKED, UNLOCKED) !== READ_LOCKED) {\n\t\t\tconsole.warn('We are unlocking when it was not read locked!');\n\t\t}\n\n\t\tAtomics.notify(data, index);\n\t}\n}\nexport function writeUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, WRITE_LOCKED, UNLOCKED) !== WRITE_LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not write locked!');\n\t}\n\n\tAtomics.notify(data, index);\n}\n\nexport const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;"],"mappings":"AAIA,SAAgB,EAAS,GAAkB,IAAgB,GAAG;CAE7D,OAAM,QAAQ,gBAAgB,GAAM,GAAO,GAAU,CAAW,MAAM,IACrE,QAAQ,KAAK,GAAM,GAAO,CAAY;CAGvC,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC;AAC/B;AACA,SAAgB,EAAU,GAAkB,IAAgB,GAAG;CAE9D,IAAI,IAAW,QAAQ,gBAAgB,GAAM,GAAO,GAAU,CAAY;CAC1E,OAAM,MAAa,IAElB,AADA,QAAQ,KAAK,GAAM,GAAO,CAAQ,GAClC,IAAW,QAAQ,gBAAgB,GAAM,GAAO,GAAU,CAAY;AAExE;AAEA,SAAgB,EAAW,GAAkB,IAAgB,GAAG;CAG/D,AAFgB,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC,IAAI,KAElC,MACZ,QAAQ,gBAAgB,GAAM,GAAO,GAAa,CAAQ,MAAM,KAClE,QAAQ,KAAK,+CAA+C,GAG7D,QAAQ,OAAO,GAAM,CAAK;AAE5B;AACA,SAAgB,EAAY,GAAkB,IAAgB,GAAG;CAKhE,AAJG,QAAQ,gBAAgB,GAAM,GAAO,GAAc,CAAQ,MAAM,KACnE,QAAQ,KAAK,gDAAgD,GAG9D,QAAQ,OAAO,GAAM,CAAK;AAC3B;AAEA,IAAa,IAAiC"}
1
+ {"version":3,"file":"read-write-lock.js","names":[],"sources":["../../src/lock/read-write-lock.ts"],"sourcesContent":["const WRITE_LOCKED = -1;\n\nfunction canWait(): boolean {\n\treturn typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n}\n\nexport function readLock(data: Int32Array, index: number = 0) {\n\t// Pending writers block new readers so a steady stream of queries cannot starve a resize.\n\twhile(true) {\n\t\tlet writers = data[index + 1];\n\t\tif(writers !== 0) {\n\t\t\tif(canWait()) {\n\t\t\t\tAtomics.wait(data, index + 1, writers);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tlet readers = data[index];\n\t\tif(readers !== WRITE_LOCKED && Atomics.compareExchange(data, index, readers, readers + 1) === readers) {\n\t\t\treturn;\n\t\t}\n\t\tif(canWait() && readers === WRITE_LOCKED) {\n\t\t\tAtomics.wait(data, index, WRITE_LOCKED);\n\t\t}\n\t}\n}\nexport function writeLock(data: Int32Array, index: number = 0) {\n\tAtomics.add(data, index + 1, 1);\n\twhile(Atomics.compareExchange(data, index, 0, WRITE_LOCKED) !== 0) {\n\t\tlet state = data[index];\n\t\tif(canWait() && state !== 0) {\n\t\t\tAtomics.wait(data, index, state);\n\t\t}\n\t}\n\tAtomics.sub(data, index + 1, 1);\n}\n\nexport function readUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.sub(data, index, 1) === 1) {\n\t\tAtomics.notify(data, index);\n\t}\n}\nexport function writeUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, WRITE_LOCKED, 0) !== WRITE_LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not write locked!');\n\t}\n\tAtomics.notify(data, index);\n\tAtomics.notify(data, index + 1);\n}\n\nexport const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;\n"],"mappings":"AAEA,SAAS,IAAmB;CAC3B,OAAO,OAAO,OAAS,OAAe,uBAAuB;AAC9D;AAEA,SAAgB,EAAS,GAAkB,IAAgB,GAAG;CAE7D,SAAY;EACX,IAAI,IAAU,EAAK,IAAQ;EAC3B,IAAG,MAAY,GAAG;GACjB,AAAG,EAAQ,KACV,QAAQ,KAAK,GAAM,IAAQ,GAAG,CAAO;GAEtC;EACD;EACA,IAAI,IAAU,EAAK;EACnB,IAAG,MAAY,MAAgB,QAAQ,gBAAgB,GAAM,GAAO,GAAS,IAAU,CAAC,MAAM,GAC7F;EAED,AAAG,EAAQ,KAAK,MAAY,MAC3B,QAAQ,KAAK,GAAM,GAAO,EAAY;CAExC;AACD;AACA,SAAgB,EAAU,GAAkB,IAAgB,GAAG;CAE9D,KADA,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC,GACxB,QAAQ,gBAAgB,GAAM,GAAO,GAAG,EAAY,MAAM,IAAG;EAClE,IAAI,IAAQ,EAAK;EACjB,AAAG,EAAQ,KAAK,MAAU,KACzB,QAAQ,KAAK,GAAM,GAAO,CAAK;CAEjC;CACA,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC;AAC/B;AAEA,SAAgB,EAAW,GAAkB,IAAgB,GAAG;CAC/D,AAAG,QAAQ,IAAI,GAAM,GAAO,CAAC,MAAM,KAClC,QAAQ,OAAO,GAAM,CAAK;AAE5B;AACA,SAAgB,EAAY,GAAkB,IAAgB,GAAG;CAKhE,AAJG,QAAQ,gBAAgB,GAAM,GAAO,IAAc,CAAC,MAAM,MAC5D,QAAQ,KAAK,gDAAgD,GAE9D,QAAQ,OAAO,GAAM,CAAK,GAC1B,QAAQ,OAAO,GAAM,IAAQ,CAAC;AAC/B;AAEA,IAAa,IAAiC"}
@@ -5,6 +5,8 @@ export default class MemoryHeap {
5
5
  private onGrowBufferHandlers;
6
6
  isClone: boolean;
7
7
  private memory;
8
+ positionBits: number;
9
+ get maxBuffers(): number;
8
10
  get bufferSize(): number;
9
11
  get maxAllocationLength(): number;
10
12
  constructor(config?: MemoryHeapConfig | MemoryHeapMemory);
@@ -1,40 +1,45 @@
1
- import { MAX_BYTE_OFFSET_LENGTH as e, MAX_POSITION_LENGTH as t } from "./utils/pointer.js";
1
+ import { MAX_BYTE_OFFSET_LENGTH as e, positionBitsForBufferSize as t } from "./utils/pointer.js";
2
2
  import n from "./allocated-memory.js";
3
3
  import r from "./memory-buffer.js";
4
4
  //#region src/memory-heap.ts
5
- var i = e, a = 0, o = 1, s = 2, c = class {
5
+ var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
6
6
  buffers;
7
7
  onGrowBufferHandlers = [];
8
8
  isClone;
9
9
  memory;
10
+ positionBits;
11
+ get maxBuffers() {
12
+ return 2 ** this.positionBits;
13
+ }
10
14
  get bufferSize() {
11
- return this.memory.data[a];
15
+ return this.memory.data[o];
12
16
  }
13
17
  get maxAllocationLength() {
14
18
  return Math.floor((this.bufferSize - 32 - 8) / Uint32Array.BYTES_PER_ELEMENT);
15
19
  }
16
- constructor(t) {
17
- if (t && "buffers" in t) this.buffers = t.buffers.map((e) => new r({
20
+ constructor(e) {
21
+ if (e && "buffers" in e) this.buffers = e.buffers.map((e) => new r({
18
22
  buf: e,
19
23
  skipInitialization: !0
20
24
  })), this.memory = new n(this, {
21
25
  bufferPosition: 0,
22
26
  bufferByteOffset: 40
23
- }), this.isClone = !0;
27
+ }), this.positionBits = this.memory.data[l], this.isClone = !0;
24
28
  else {
25
29
  "SharedArrayBuffer" in globalThis || console.warn("SharedArrayBuffer is not working: falling back to ArrayBuffer");
26
- let r = t?.bufferSize ?? i;
27
- if (r > e) throw Error(`Buffer size ${r} is greater than max ${e} that we can reference with pointers`);
28
- let c = this.createBuffer(r);
29
- this.buffers = [c];
30
- let l = c.callocAs("u32", 3);
31
- if (l) this.memory = new n(this, {
30
+ let r = e?.bufferSize ?? i;
31
+ if (r > a) throw Error(`Buffer size ${r} is greater than max ${a} that we can reference with pointers`);
32
+ this.positionBits = t(r);
33
+ let d = this.createBuffer(r);
34
+ this.buffers = [d];
35
+ let f = d.callocAs("u32", u);
36
+ if (f) this.memory = new n(this, {
32
37
  bufferPosition: 0,
33
- bufferByteOffset: l.byteOffset
38
+ bufferByteOffset: f.byteOffset
34
39
  });
35
40
  else throw Error("Failed to initialize first byte from buffer");
36
- this.memory.data[a] = r, this.memory.data[o] = 1, this.memory.data[s] = t?.autoGrowSize ?? 100, this.isClone = !1;
37
- for (let e = 1; e < (t?.initialBuffers ?? 1); e++) this.buffers.push(this.createBuffer(r));
41
+ this.memory.data[o] = r, this.memory.data[s] = 1, this.memory.data[c] = e?.autoGrowSize ?? 100, this.memory.data[l] = this.positionBits, this.isClone = !1;
42
+ for (let t = 1; t < (e?.initialBuffers ?? 1); t++) this.buffers.push(this.createBuffer(r));
38
43
  }
39
44
  }
40
45
  addSharedBuffer(e) {
@@ -44,7 +49,7 @@ var i = e, a = 0, o = 1, s = 2, c = class {
44
49
  });
45
50
  }
46
51
  growBuffer() {
47
- let e = this.createBuffer(), t = Atomics.add(this.memory.data, o, 1);
52
+ let e = this.createBuffer(), t = Atomics.add(this.memory.data, s, 1);
48
53
  return this.buffers[t] = e, this.onGrowBufferHandlers.forEach((n) => n({
49
54
  bufferPosition: t,
50
55
  buffer: e.buf
@@ -74,16 +79,16 @@ var i = e, a = 0, o = 1, s = 2, c = class {
74
79
  let r = this.buffers[t];
75
80
  if (!r) continue;
76
81
  let i = r.callocAs("u32", e);
77
- if (i) return t === this.buffers.length - 1 && Atomics.load(this.memory.data, o) === this.buffers.length && this.memory.data[s] < 100 && this.memory.data[s] > 0 && r.top / r.end > this.memory.data[s] / 100 && this.growBuffer(), new n(this, {
82
+ if (i) return t === this.buffers.length - 1 && Atomics.load(this.memory.data, s) === this.buffers.length && this.memory.data[c] < 100 && this.memory.data[c] > 0 && r.top / r.end > this.memory.data[c] / 100 && this.growBuffer(), new n(this, {
78
83
  data: i,
79
84
  buffer: r
80
85
  });
81
86
  }
82
- if (this.buffers.length >= t) throw Error(`Can't initialize a new buffer since it would have a position greater than the max of ${t}`);
83
- let r = this.growBuffer(), i = r.callocAs("u32", e);
84
- if (i) return new n(this, {
85
- data: i,
86
- buffer: r
87
+ if (this.buffers.length >= this.maxBuffers) throw Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);
88
+ let t = this.growBuffer(), r = t.callocAs("u32", e);
89
+ if (r) return new n(this, {
90
+ data: r,
91
+ buffer: t
87
92
  });
88
93
  throw Error(`Unable to allocate ${e} numbers even after adding a new buffer`);
89
94
  }
@@ -101,6 +106,6 @@ var i = e, a = 0, o = 1, s = 2, c = class {
101
106
  }
102
107
  };
103
108
  //#endregion
104
- export { c as default };
109
+ export { d as default };
105
110
 
106
111
  //# sourceMappingURL=memory-heap.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-heap.js","names":[],"sources":["../src/memory-heap.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport { MAX_BYTE_OFFSET_LENGTH, MAX_POSITION_LENGTH } from './utils/pointer';\nimport MemoryBuffer, { SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\n\n\tget bufferSize() {\n\t\treturn this.memory.data[BUFFER_SIZE_INDEX];\n\t}\n\n\t// Largest u32 count a single fresh buffer can hand out in one allocation: the buffer minus the allocator state and the\n\t// block header. A structure that grows in segments must keep each segment within this or the allocation can never fit.\n\tget maxAllocationLength() {\n\t\treturn Math.floor((this.bufferSize - SIZEOF_STATE - SIZEOF_MEM_BLOCK) / Uint32Array.BYTES_PER_ELEMENT);\n\t}\n\n\tconstructor(config?: MemoryHeapConfig | MemoryHeapMemory) {\n\t\tif(config && 'buffers' in config) {\n\t\t\tthis.buffers = config.buffers.map(buffer => {\n\t\t\t\treturn new MemoryBuffer({\n\t\t\t\t\tbuf: buffer,\n\t\t\t\t\tskipInitialization: true,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// TODO: This should be programic instead of hoping the first allocation is always byte 40\n\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\tbufferPosition: 0,\n\t\t\t\tbufferByteOffset: 40,\n\t\t\t});\n\t\t\tthis.isClone = true;\n\t\t} else {\n\t\t\tif(!('SharedArrayBuffer' in globalThis)) {\n\t\t\t\tconsole.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');\n\t\t\t}\n\n\t\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;\n\t\t\tif(bufferSize > MAX_BYTE_OFFSET_LENGTH) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BYTE_OFFSET_LENGTH} that we can reference with pointers`);\n\t\t\t}\n\t\t\t\n\t\t\tlet startBuffer = this.createBuffer(bufferSize);\n\t\t\tthis.buffers = [\n\t\t\t\tstartBuffer,\n\t\t\t];\n\t\t\tconst data = startBuffer.callocAs('u32', 3);\n\t\t\tif(data) {\n\t\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\t\tbufferPosition: 0,\n\t\t\t\t\tbufferByteOffset: data.byteOffset,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('Failed to initialize first byte from buffer');\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_SIZE_INDEX] = bufferSize;\n\t\t\tthis.memory.data[BUFFER_COUNT_INDEX] = 1;\n\t\t\tthis.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;\n\t\t\tthis.isClone = false;\n\n\t\t\tfor(let i = 1; i < (config?.initialBuffers ?? 1); i++) {\n\t\t\t\tthis.buffers.push(this.createBuffer(bufferSize));\n\t\t\t}\n\t\t}\n\t}\n\n\taddSharedBuffer(data: GrowBufferData) {\n\t\tthis.buffers[data.bufferPosition] = new MemoryBuffer({\n\t\t\tbuf: data.buffer,\n\t\t\tskipInitialization: true,\n\t\t});\n\t}\n\n\tprivate growBuffer() {\n\t\tconst buffer = this.createBuffer();\n\t\tlet nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);\n\t\t// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent\n\t\tthis.buffers[nextBufferPosition] = buffer;\n\t\tthis.onGrowBufferHandlers.forEach(handler => handler({\n\t\t\tbufferPosition: nextBufferPosition,\n\t\t\tbuffer: buffer.buf as SharedArrayBuffer,\n\t\t}));\n\n\t\treturn buffer;\n\t}\n\tprivate createBuffer(bufferSize?: number): MemoryBuffer {\n\t\tconst usedBufferSize = bufferSize ?? this.bufferSize;\n\t\tlet buf: ArrayBuffer | SharedArrayBuffer;\n\t\tif('SharedArrayBuffer' in globalThis) {\n\t\t\tbuf = new SharedArrayBuffer(usedBufferSize);\n\t\t} else {\n\t\t\tbuf = new ArrayBuffer(usedBufferSize);\n\t\t}\n\n\t\treturn new MemoryBuffer({\n\t\t\tbuf,\n\n\t\t\t// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed\n\t\t\t// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution\n\t\t\t// on the old allocation can changes the internal state of the 4-byte allocation breaking everything\n\t\t\t// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are\n\t\t\tcompact: false,\n\t\t\tsplit: false,\n\t\t});\n\t}\n\n\taddOnGrowBufferHandlers(handler: OnGrowBuffer) {\n\t\tthis.onGrowBufferHandlers.push(handler);\n\t}\n\n\t// Guarantees at least one completely-empty buffer exists and (via the grow handlers) has been fanned out to every\n\t// thread. Call this on the owning thread before dispatching work that allocates on another thread (a worker): that\n\t// allocation then lands in a buffer everyone already holds instead of forcing growBuffer on the worker, which would\n\t// create a buffer only the worker can see and leave every other thread unable to resolve pointers into it. Returns\n\t// true if it had to grow one.\n\tensureSpareBuffer(): boolean {\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\tif(buffer && buffer.isEmpty) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.growBuffer();\n\t\treturn true;\n\t}\n\n\tallocUI32(count: number): AllocatedMemory {\n\t\tcount = Math.ceil(count);\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\t// Should just mean we haven't synced this buffer from another thread yet\n\t\t\tif(!buffer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s\n\t\t\tconst data = buffer.callocAs('u32', count);\n\t\t\tif(data) {\n\t\t\t\t// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it\n\t\t\t\tif(\n\t\t\t\t\ti === (this.buffers.length - 1)\n\t\t\t\t\t&& Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0\n\t\t\t\t) {\n\t\t\t\t\tconst percentFull = buffer.top / buffer.end;\n\t\t\t\t\tif(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {\n\t\t\t\t\t\tthis.growBuffer();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\t\tdata,\n\t\t\t\t\tbuffer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif(this.buffers.length >= MAX_POSITION_LENGTH) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${MAX_POSITION_LENGTH}`);\n\t\t}\n\n\t\t// If we get here we need to grow another buffer to continue allocating new memory\n\t\tlet buffer = this.growBuffer();\n\t\tconst data = buffer.callocAs('u32', count);\n\t\tif(data) {\n\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\tdata,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);\n\t\t}\n\t}\n\n\tgetSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {\n\t\t// Should just mean it hasn't synced to this thread yet\n\t\tif(this.buffers[shared.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new AllocatedMemory(this, shared);\n\t}\n\n\tget currentUsed() {\n\t\treturn this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);\n\t}\n\tget totalAllocated() {\n\t\treturn this.buffers[0].buf.byteLength * this.buffers.length;\n\t}\n\n\tgetSharedMemory(): MemoryHeapMemory {\n\t\treturn {\n\t\t\tbuffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer),\n\t\t};\n\t}\n}\n\ntype OnGrowBuffer = (newBuffer: GrowBufferData) => void;\ninterface GrowBufferData {\n\tbufferPosition: number\n\tbuffer: SharedArrayBuffer\n}\n\ninterface MemoryHeapConfig {\n\tbufferSize?: number\n\tinitialBuffers?: number\n\tautoGrowSize?: number\n}\ninterface MemoryHeapMemory {\n\tbuffers: Array<SharedArrayBuffer>\n}\n\nexport type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };"],"mappings":"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"}
1
+ {"version":3,"file":"memory-heap.js","names":[],"sources":["../src/memory-heap.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport { MAX_BYTE_OFFSET_LENGTH, positionBitsForBufferSize } from './utils/pointer';\nimport MemoryBuffer, { SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst MAX_BUFFER_SIZE = Math.pow(2, 31);\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nconst BUFFER_POSITION_BITS_INDEX = 3;\nconst HEAP_STATE_COUNT = 4;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\n\n\tpositionBits: number;\n\t// Max buffer position (count) and max byte offset the split can address\n\tget maxBuffers() {\n\t\treturn Math.pow(2, this.positionBits);\n\t}\n\n\tget bufferSize() {\n\t\treturn this.memory.data[BUFFER_SIZE_INDEX];\n\t}\n\n\t// Largest u32 count a single fresh buffer can hand out in one allocation: the buffer minus the allocator state and the\n\t// block header. A structure that grows in segments must keep each segment within this or the allocation can never fit.\n\tget maxAllocationLength() {\n\t\treturn Math.floor((this.bufferSize - SIZEOF_STATE - SIZEOF_MEM_BLOCK) / Uint32Array.BYTES_PER_ELEMENT);\n\t}\n\n\tconstructor(config?: MemoryHeapConfig | MemoryHeapMemory) {\n\t\tif(config && 'buffers' in config) {\n\t\t\tthis.buffers = config.buffers.map(buffer => {\n\t\t\t\treturn new MemoryBuffer({\n\t\t\t\t\tbuf: buffer,\n\t\t\t\t\tskipInitialization: true,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// TODO: This should be programic instead of hoping the first allocation is always byte 40\n\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\tbufferPosition: 0,\n\t\t\t\tbufferByteOffset: 40,\n\t\t\t});\n\t\t\tthis.positionBits = this.memory.data[BUFFER_POSITION_BITS_INDEX];\n\t\t\tthis.isClone = true;\n\t\t} else {\n\t\t\tif(!('SharedArrayBuffer' in globalThis)) {\n\t\t\t\tconsole.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');\n\t\t\t}\n\n\t\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;\n\t\t\tif(bufferSize > MAX_BUFFER_SIZE) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BUFFER_SIZE} that we can reference with pointers`);\n\t\t\t}\n\t\t\tthis.positionBits = positionBitsForBufferSize(bufferSize);\n\n\t\t\tlet startBuffer = this.createBuffer(bufferSize);\n\t\t\tthis.buffers = [\n\t\t\t\tstartBuffer,\n\t\t\t];\n\t\t\tconst data = startBuffer.callocAs('u32', HEAP_STATE_COUNT);\n\t\t\tif(data) {\n\t\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\t\tbufferPosition: 0,\n\t\t\t\t\tbufferByteOffset: data.byteOffset,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('Failed to initialize first byte from buffer');\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_SIZE_INDEX] = bufferSize;\n\t\t\tthis.memory.data[BUFFER_COUNT_INDEX] = 1;\n\t\t\tthis.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;\n\t\t\tthis.memory.data[BUFFER_POSITION_BITS_INDEX] = this.positionBits;\n\t\t\tthis.isClone = false;\n\n\t\t\tfor(let i = 1; i < (config?.initialBuffers ?? 1); i++) {\n\t\t\t\tthis.buffers.push(this.createBuffer(bufferSize));\n\t\t\t}\n\t\t}\n\t}\n\n\taddSharedBuffer(data: GrowBufferData) {\n\t\tthis.buffers[data.bufferPosition] = new MemoryBuffer({\n\t\t\tbuf: data.buffer,\n\t\t\tskipInitialization: true,\n\t\t});\n\t}\n\n\tprivate growBuffer() {\n\t\tconst buffer = this.createBuffer();\n\t\tlet nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);\n\t\t// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent\n\t\tthis.buffers[nextBufferPosition] = buffer;\n\t\tthis.onGrowBufferHandlers.forEach(handler => handler({\n\t\t\tbufferPosition: nextBufferPosition,\n\t\t\tbuffer: buffer.buf as SharedArrayBuffer,\n\t\t}));\n\n\t\treturn buffer;\n\t}\n\tprivate createBuffer(bufferSize?: number): MemoryBuffer {\n\t\tconst usedBufferSize = bufferSize ?? this.bufferSize;\n\t\tlet buf: ArrayBuffer | SharedArrayBuffer;\n\t\tif('SharedArrayBuffer' in globalThis) {\n\t\t\tbuf = new SharedArrayBuffer(usedBufferSize);\n\t\t} else {\n\t\t\tbuf = new ArrayBuffer(usedBufferSize);\n\t\t}\n\n\t\treturn new MemoryBuffer({\n\t\t\tbuf,\n\n\t\t\t// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed\n\t\t\t// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution\n\t\t\t// on the old allocation can changes the internal state of the 4-byte allocation breaking everything\n\t\t\t// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are\n\t\t\tcompact: false,\n\t\t\tsplit: false,\n\t\t});\n\t}\n\n\taddOnGrowBufferHandlers(handler: OnGrowBuffer) {\n\t\tthis.onGrowBufferHandlers.push(handler);\n\t}\n\n\t// Guarantees at least one completely-empty buffer exists and (via the grow handlers) has been fanned out to every\n\t// thread. Call this on the owning thread before dispatching work that allocates on another thread (a worker): that\n\t// allocation then lands in a buffer everyone already holds instead of forcing growBuffer on the worker, which would\n\t// create a buffer only the worker can see and leave every other thread unable to resolve pointers into it. Returns\n\t// true if it had to grow one.\n\tensureSpareBuffer(): boolean {\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\tif(buffer && buffer.isEmpty) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.growBuffer();\n\t\treturn true;\n\t}\n\n\tallocUI32(count: number): AllocatedMemory {\n\t\tcount = Math.ceil(count);\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\t// Should just mean we haven't synced this buffer from another thread yet\n\t\t\tif(!buffer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s\n\t\t\tconst data = buffer.callocAs('u32', count);\n\t\t\tif(data) {\n\t\t\t\t// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it\n\t\t\t\tif(\n\t\t\t\t\ti === (this.buffers.length - 1)\n\t\t\t\t\t&& Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0\n\t\t\t\t) {\n\t\t\t\t\tconst percentFull = buffer.top / buffer.end;\n\t\t\t\t\tif(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {\n\t\t\t\t\t\tthis.growBuffer();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\t\tdata,\n\t\t\t\t\tbuffer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif(this.buffers.length >= this.maxBuffers) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);\n\t\t}\n\n\t\t// If we get here we need to grow another buffer to continue allocating new memory\n\t\tlet buffer = this.growBuffer();\n\t\tconst data = buffer.callocAs('u32', count);\n\t\tif(data) {\n\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\tdata,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);\n\t\t}\n\t}\n\n\tgetSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {\n\t\t// Should just mean it hasn't synced to this thread yet\n\t\tif(this.buffers[shared.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new AllocatedMemory(this, shared);\n\t}\n\n\tget currentUsed() {\n\t\treturn this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);\n\t}\n\tget totalAllocated() {\n\t\treturn this.buffers[0].buf.byteLength * this.buffers.length;\n\t}\n\n\tgetSharedMemory(): MemoryHeapMemory {\n\t\treturn {\n\t\t\tbuffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer),\n\t\t};\n\t}\n}\n\ntype OnGrowBuffer = (newBuffer: GrowBufferData) => void;\ninterface GrowBufferData {\n\tbufferPosition: number\n\tbuffer: SharedArrayBuffer\n}\n\ninterface MemoryHeapConfig {\n\tbufferSize?: number\n\tinitialBuffers?: number\n\tautoGrowSize?: number\n}\ninterface MemoryHeapMemory {\n\tbuffers: Array<SharedArrayBuffer>\n}\n\nexport type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };"],"mappings":"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"}
@@ -16,7 +16,7 @@ function r(e, t) {
16
16
  return s.getArray(Float32Array, 0, r.length).set(r), s;
17
17
  }
18
18
  function i(n, r) {
19
- return a(new t(n, e(r)));
19
+ return a(new t(n, e(r, n.positionBits)));
20
20
  }
21
21
  function a(e) {
22
22
  let t = new Float32Array(e.data.buffer, e.bufferByteOffset);
@@ -1 +1 @@
1
- {"version":3,"file":"serialize-object.js","names":[],"sources":["../src/serialize-object.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\nenum VALUE_TYPE {\n\tUNDEFINED,\n\tNUMBER,\n\tSTRING,\n}\n\n// Be able to serialize a simple object into memory and re-create the object in another thread\n// This does NOT support updating objects across threads\nconst MAGIC_NUMBER = 52361700;\nexport function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {\n\tconst data: Array<number> = [];\n\tconst keys = Object.keys(object);\n\tdata.push(MAGIC_NUMBER);\n\tdata.push(keys.length);\n\n\t// Create index first so we can quickly construct index\n\tlet keyIndexes: { [key:string]:number } = {};\n\tkeys.forEach(key => {\n\t\tdata.push(key.length);\n\t\taddCharCodes(data, key);\n\n\t\tkeyIndexes[key] = data.length;\n\t\tdata.push(0);\n\t});\n\n\tkeys.forEach(key => {\n\t\tlet keyIndex = keyIndexes[key];\n\t\t// @ts-expect-error\n\t\tlet value = object[key];\n\t\tif(Number.isFinite(value)) {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.NUMBER);\n\t\t\tdata.push(value);\n\t\t} else if(typeof value === 'string') {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.STRING);\n\t\t\tdata.push(value.length);\n\t\t\taddCharCodes(data, value);\n\t\t}\n\t});\n\t\n\tlet memory = heap.allocUI32(data.length);\n\tlet sharedData = memory.getArray(Float32Array, 0, data.length);\n\tsharedData.set(data);\n\n\treturn memory;\n}\n\nexport function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {\n\tlet memory = new AllocatedMemory(heap, getPointer(pointer));\n\treturn createObjectFromMemory<T>(memory);\n}\nexport function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {\n\tlet sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);\n\tif(sharedData[0] !== MAGIC_NUMBER) {\n\t\tthrow new Error('Trying to create object from invalid memory location');\n\t}\n\n\tconst sharedObject: { [key:string]:any } = {};\n\tconst keyCount = sharedData[1];\n\tlet keyStartIndex = 2;\n\tfor(let i = 0; i < keyCount; i++) {\n\t\tlet keyLength = sharedData[keyStartIndex];\n\t\tlet keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);\n\t\tlet key = String.fromCharCode.apply(null, [...keyData]);\n\t\tlet valueIndex = sharedData[keyStartIndex + keyLength + 1];\n\t\tlet valueType = sharedData[valueIndex];\n\t\tswitch(valueType) {\n\t\t\tcase VALUE_TYPE.NUMBER: {\n\t\t\t\tsharedObject[key] = sharedData[valueIndex + 1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase VALUE_TYPE.STRING: {\n\t\t\t\tlet length = sharedData[valueIndex + 1];\n\t\t\t\tlet valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);\n\t\t\t\tsharedObject[key] = String.fromCharCode.apply(null, [...valueData]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tkeyStartIndex += 2 + keyLength;\n\t}\n\n\treturn sharedObject as T;\n}\n\nfunction addCharCodes(data: Array<number>, value: string) {\n\tfor(let i = 0; i < value.length; i++) {\n\t\tdata.push(value.charCodeAt(i));\n\t}\n}"],"mappings":";;;AAYA,IAAM,IAAe;AACrB,SAAgB,EAA0C,GAAkB,GAA4B;CACvG,IAAM,IAAsB,CAAC,GACvB,IAAO,OAAO,KAAK,CAAM;CAE/B,AADA,EAAK,KAAK,CAAY,GACtB,EAAK,KAAK,EAAK,MAAM;CAGrB,IAAI,IAAsC,CAAC;CAS3C,AARA,EAAK,SAAQ,MAAO;EAKnB,AAJA,EAAK,KAAK,EAAI,MAAM,GACpB,EAAa,GAAM,CAAG,GAEtB,EAAW,KAAO,EAAK,QACvB,EAAK,KAAK,CAAC;CACZ,CAAC,GAED,EAAK,SAAQ,MAAO;EACnB,IAAI,IAAW,EAAW,IAEtB,IAAQ,EAAO;EACnB,AAAG,OAAO,SAAS,CAAK,KACvB,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,CAAK,KACN,OAAO,KAAU,aAC1B,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,EAAM,MAAM,GACtB,EAAa,GAAM,CAAK;CAE1B,CAAC;CAED,IAAI,IAAS,EAAK,UAAU,EAAK,MAAM;CAIvC,OAFA,EADwB,SAAS,cAAc,GAAG,EAAK,MACvD,CAAA,CAAW,IAAI,CAAI,GAEZ;AACR;AAEA,SAAgB,EAA0C,GAAkB,GAAiB;CAE5F,OAAO,EAA0B,IADhB,EAAgB,GAAM,EAAW,CAAO,CACxB,CAAM;AACxC;AACA,SAAgB,EAAyC,GAA4B;CACpF,IAAI,IAAa,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,gBAAgB;CAC7E,IAAG,EAAW,OAAO,GACpB,MAAU,MAAM,sDAAsD;CAGvE,IAAM,IAAqC,CAAC,GACtC,IAAW,EAAW,IACxB,IAAgB;CACpB,KAAI,IAAI,IAAI,GAAG,IAAI,GAAU,KAAK;EACjC,IAAI,IAAY,EAAW,IACvB,IAAU,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAgB,KAAK,EAAW,mBAAmB,CAAS,GACtI,IAAM,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAO,CAAC,GAClD,IAAa,EAAW,IAAgB,IAAY;EAExD,QADgB,EAAW,IAC3B;GACC,KAAA;IACC,EAAa,KAAO,EAAW,IAAa;IAC5C;GAED,KAAA,GAAwB;IACvB,IAAI,IAAS,EAAW,IAAa,IACjC,IAAY,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAa,KAAK,EAAW,mBAAmB,CAAM;IACtI,EAAa,KAAO,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAS,CAAC;IAClE;GACD;EACD;EAEA,KAAiB,IAAI;CACtB;CAEA,OAAO;AACR;AAEA,SAAS,EAAa,GAAqB,GAAe;CACzD,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAK,KAAK,EAAM,WAAW,CAAC,CAAC;AAE/B"}
1
+ {"version":3,"file":"serialize-object.js","names":[],"sources":["../src/serialize-object.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\nenum VALUE_TYPE {\n\tUNDEFINED,\n\tNUMBER,\n\tSTRING,\n}\n\n// Be able to serialize a simple object into memory and re-create the object in another thread\n// This does NOT support updating objects across threads\nconst MAGIC_NUMBER = 52361700;\nexport function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {\n\tconst data: Array<number> = [];\n\tconst keys = Object.keys(object);\n\tdata.push(MAGIC_NUMBER);\n\tdata.push(keys.length);\n\n\t// Create index first so we can quickly construct index\n\tlet keyIndexes: { [key:string]:number } = {};\n\tkeys.forEach(key => {\n\t\tdata.push(key.length);\n\t\taddCharCodes(data, key);\n\n\t\tkeyIndexes[key] = data.length;\n\t\tdata.push(0);\n\t});\n\n\tkeys.forEach(key => {\n\t\tlet keyIndex = keyIndexes[key];\n\t\t// @ts-expect-error\n\t\tlet value = object[key];\n\t\tif(Number.isFinite(value)) {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.NUMBER);\n\t\t\tdata.push(value);\n\t\t} else if(typeof value === 'string') {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.STRING);\n\t\t\tdata.push(value.length);\n\t\t\taddCharCodes(data, value);\n\t\t}\n\t});\n\t\n\tlet memory = heap.allocUI32(data.length);\n\tlet sharedData = memory.getArray(Float32Array, 0, data.length);\n\tsharedData.set(data);\n\n\treturn memory;\n}\n\nexport function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {\n\tlet memory = new AllocatedMemory(heap, getPointer(pointer, heap.positionBits));\n\treturn createObjectFromMemory<T>(memory);\n}\nexport function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {\n\tlet sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);\n\tif(sharedData[0] !== MAGIC_NUMBER) {\n\t\tthrow new Error('Trying to create object from invalid memory location');\n\t}\n\n\tconst sharedObject: { [key:string]:any } = {};\n\tconst keyCount = sharedData[1];\n\tlet keyStartIndex = 2;\n\tfor(let i = 0; i < keyCount; i++) {\n\t\tlet keyLength = sharedData[keyStartIndex];\n\t\tlet keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);\n\t\tlet key = String.fromCharCode.apply(null, [...keyData]);\n\t\tlet valueIndex = sharedData[keyStartIndex + keyLength + 1];\n\t\tlet valueType = sharedData[valueIndex];\n\t\tswitch(valueType) {\n\t\t\tcase VALUE_TYPE.NUMBER: {\n\t\t\t\tsharedObject[key] = sharedData[valueIndex + 1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase VALUE_TYPE.STRING: {\n\t\t\t\tlet length = sharedData[valueIndex + 1];\n\t\t\t\tlet valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);\n\t\t\t\tsharedObject[key] = String.fromCharCode.apply(null, [...valueData]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tkeyStartIndex += 2 + keyLength;\n\t}\n\n\treturn sharedObject as T;\n}\n\nfunction addCharCodes(data: Array<number>, value: string) {\n\tfor(let i = 0; i < value.length; i++) {\n\t\tdata.push(value.charCodeAt(i));\n\t}\n}"],"mappings":";;;AAYA,IAAM,IAAe;AACrB,SAAgB,EAA0C,GAAkB,GAA4B;CACvG,IAAM,IAAsB,CAAC,GACvB,IAAO,OAAO,KAAK,CAAM;CAE/B,AADA,EAAK,KAAK,CAAY,GACtB,EAAK,KAAK,EAAK,MAAM;CAGrB,IAAI,IAAsC,CAAC;CAS3C,AARA,EAAK,SAAQ,MAAO;EAKnB,AAJA,EAAK,KAAK,EAAI,MAAM,GACpB,EAAa,GAAM,CAAG,GAEtB,EAAW,KAAO,EAAK,QACvB,EAAK,KAAK,CAAC;CACZ,CAAC,GAED,EAAK,SAAQ,MAAO;EACnB,IAAI,IAAW,EAAW,IAEtB,IAAQ,EAAO;EACnB,AAAG,OAAO,SAAS,CAAK,KACvB,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,CAAK,KACN,OAAO,KAAU,aAC1B,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,EAAM,MAAM,GACtB,EAAa,GAAM,CAAK;CAE1B,CAAC;CAED,IAAI,IAAS,EAAK,UAAU,EAAK,MAAM;CAIvC,OAFA,EADwB,SAAS,cAAc,GAAG,EAAK,MACvD,CAAA,CAAW,IAAI,CAAI,GAEZ;AACR;AAEA,SAAgB,EAA0C,GAAkB,GAAiB;CAE5F,OAAO,EAA0B,IADhB,EAAgB,GAAM,EAAW,GAAS,EAAK,YAAY,CAC3C,CAAM;AACxC;AACA,SAAgB,EAAyC,GAA4B;CACpF,IAAI,IAAa,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,gBAAgB;CAC7E,IAAG,EAAW,OAAO,GACpB,MAAU,MAAM,sDAAsD;CAGvE,IAAM,IAAqC,CAAC,GACtC,IAAW,EAAW,IACxB,IAAgB;CACpB,KAAI,IAAI,IAAI,GAAG,IAAI,GAAU,KAAK;EACjC,IAAI,IAAY,EAAW,IACvB,IAAU,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAgB,KAAK,EAAW,mBAAmB,CAAS,GACtI,IAAM,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAO,CAAC,GAClD,IAAa,EAAW,IAAgB,IAAY;EAExD,QADgB,EAAW,IAC3B;GACC,KAAA;IACC,EAAa,KAAO,EAAW,IAAa;IAC5C;GAED,KAAA,GAAwB;IACvB,IAAI,IAAS,EAAW,IAAa,IACjC,IAAY,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAa,KAAK,EAAW,mBAAmB,CAAM;IACtI,EAAa,KAAO,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAS,CAAC;IAClE;GACD;EACD;EAEA,KAAiB,IAAI;CACtB;CAEA,OAAO;AACR;AAEA,SAAS,EAAa,GAAqB,GAAe;CACzD,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAK,KAAK,EAAM,WAAW,CAAC,CAAC;AAE/B"}