ecspresso 0.16.1 → 0.16.3

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.
@@ -10,14 +10,40 @@ export interface SpatialEntry {
10
10
  y: number;
11
11
  halfW: number;
12
12
  halfH: number;
13
+ /** Generation stamp used by query functions to dedup multi-cell hits without a Set. Internal. */
14
+ _lastSeenGen: number;
15
+ /** Rebuild generation when this entry was last inserted. Internal. */
16
+ _aliveGen: number;
17
+ }
18
+ /**
19
+ * A cell bucket — entries plus the alive-gen at which the bucket was last
20
+ * filled. Buckets are reset lazily on the next insert in a new generation
21
+ * (see `insertEntity`); queries skip buckets whose `_gen` is stale.
22
+ *
23
+ * Internal — exposed only through `SpatialHashGrid.cells`.
24
+ */
25
+ interface CellBucket extends Array<SpatialEntry> {
26
+ _gen: number;
13
27
  }
14
28
  export interface SpatialHashGrid {
15
29
  cellSize: number;
16
30
  invCellSize: number;
17
- cells: Map<number, number[]>;
18
- entries: Map<number, SpatialEntry>;
19
- /** Previous-frame entries held for in-place reuse during rebuild. Internal. */
20
- _entriesPrev: Map<number, SpatialEntry>;
31
+ cells: Map<number, CellBucket>;
32
+ /**
33
+ * Dense, indexed by entityId. Holes are `undefined`. Entries from previous
34
+ * rebuilds remain in place for in-place reuse (zero allocation in steady
35
+ * state); liveness is determined by `entry._aliveGen === grid._aliveGen`.
36
+ * Internal — read live entries via `getEntry` / `liveEntryCount` helpers.
37
+ *
38
+ * High-water-mark grows with max entityId ever inserted; despawned ids
39
+ * leave their slot occupied by a stale entry. Acceptable when the entity
40
+ * manager recycles ids or peak count is bounded.
41
+ */
42
+ entries: (SpatialEntry | undefined)[];
43
+ /** Monotonic counter bumped by each `clearGrid` call. Internal. */
44
+ _aliveGen: number;
45
+ /** Monotonic counter bumped on each query; entries record their last-seen gen for O(1) dedup. Internal. */
46
+ _queryGen: number;
21
47
  }
22
48
  /**
23
49
  * Hash a cell coordinate pair to a single integer key.
@@ -31,13 +57,15 @@ export declare function createGrid(cellSize: number): SpatialHashGrid;
31
57
  /**
32
58
  * Prepare the grid for a rebuild.
33
59
  *
34
- * Swaps `entries` with `_entriesPrev` so `insertEntity` can reuse existing
35
- * `SpatialEntry` objects in place (steady-state rebuilds allocate zero
36
- * entries). Any stale entries left in `_entriesPrev` from the previous
37
- * rebuild are dropped here.
60
+ * O(1): bumps the alive-generation counter so entries inserted prior to this
61
+ * call are implicitly stale. `getLiveEntry` / `liveEntryCount` filter
62
+ * entries by the current gen; queries skip buckets whose own `_gen` lags
63
+ * behind the alive gen; `insertEntity` resets a bucket's `length` lazily
64
+ * the first time it is touched in a new generation.
38
65
  *
39
- * Cell buckets are cleared in place keys are retained so subsequent
40
- * inserts hit the existing array rather than allocating a fresh one.
66
+ * Existing `SpatialEntry` objects and `CellBucket` arrays remain in place
67
+ * for reuse, so steady-state rebuilds allocate zero entries and zero
68
+ * buckets, regardless of how many cells have ever been touched.
41
69
  */
42
70
  export declare function clearGrid(grid: SpatialHashGrid): void;
43
71
  /**
@@ -46,18 +74,37 @@ export declare function clearGrid(grid: SpatialHashGrid): void;
46
74
  export declare function insertEntity(grid: SpatialHashGrid, entityId: number, x: number, y: number, halfW: number, halfH: number): void;
47
75
  /**
48
76
  * Collect entity IDs from all cells overlapping the given rectangle.
77
+ *
78
+ * Appends to `result` (caller clears/truncates first if reusing). Multi-cell
79
+ * entries are deduplicated via a per-grid generation stamp on each
80
+ * `SpatialEntry`.
81
+ *
82
+ * When `minId` is provided, only entries with `entityId > minId` are added —
83
+ * used for symmetric broadphase pair generation.
84
+ */
85
+ export declare function gridQueryRect(grid: SpatialHashGrid, minX: number, minY: number, maxX: number, maxY: number, result: number[], minId?: number): void;
86
+ /**
87
+ * Collect entity IDs within a circle. AABB-to-point distance filter against
88
+ * the cells overlapping the circle's bounding rect. Appends to `result`.
89
+ */
90
+ export declare function gridQueryRadius(grid: SpatialHashGrid, cx: number, cy: number, radius: number, result: number[]): void;
91
+ /**
92
+ * Get the current-generation entry for an entityId, or `undefined` if the
93
+ * entity isn't in the index for this rebuild. Stale entries from previous
94
+ * rebuilds remain in `entries` for in-place reuse but are filtered here.
49
95
  */
50
- export declare function gridQueryRect(grid: SpatialHashGrid, minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void;
96
+ export declare function getLiveEntry(grid: SpatialHashGrid, entityId: number): SpatialEntry | undefined;
51
97
  /**
52
- * Collect entity IDs within a circle. Uses rect broadphase then
53
- * AABB-to-point distance filter.
98
+ * Count entries inserted in the current rebuild generation. Linear scan
99
+ * intended for tests and diagnostics, not hot paths.
54
100
  */
55
- export declare function gridQueryRadius(grid: SpatialHashGrid, cx: number, cy: number, radius: number, result: Set<number>): void;
101
+ export declare function liveEntryCount(grid: SpatialHashGrid): number;
56
102
  export interface SpatialIndex {
57
103
  readonly grid: SpatialHashGrid;
58
104
  queryRect(minX: number, minY: number, maxX: number, maxY: number): number[];
59
- queryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void;
105
+ queryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: number[], minId?: number): void;
60
106
  queryRadius(cx: number, cy: number, radius: number): number[];
61
- queryRadiusInto(cx: number, cy: number, radius: number, result: Set<number>): void;
107
+ queryRadiusInto(cx: number, cy: number, radius: number, result: number[]): void;
62
108
  getEntry(entityId: number): SpatialEntry | undefined;
63
109
  }
110
+ export {};
@@ -12,14 +12,40 @@ export interface SpatialEntry3D {
12
12
  halfW: number;
13
13
  halfH: number;
14
14
  halfD: number;
15
+ /** Generation stamp used by query functions to dedup multi-cell hits without a Set. Internal. */
16
+ _lastSeenGen: number;
17
+ /** Rebuild generation when this entry was last inserted. Internal. */
18
+ _aliveGen: number;
19
+ }
20
+ /**
21
+ * A cell bucket — entries plus the alive-gen at which the bucket was last
22
+ * filled. Buckets are reset lazily on the next insert in a new generation
23
+ * (see `insertEntity3D`); queries skip buckets whose `_gen` is stale.
24
+ *
25
+ * Internal — exposed only through `SpatialHashGrid3D.cells`.
26
+ */
27
+ interface CellBucket3D extends Array<SpatialEntry3D> {
28
+ _gen: number;
15
29
  }
16
30
  export interface SpatialHashGrid3D {
17
31
  cellSize: number;
18
32
  invCellSize: number;
19
- cells: Map<number, number[]>;
20
- entries: Map<number, SpatialEntry3D>;
21
- /** Previous-frame entries held for in-place reuse during rebuild. Internal. */
22
- _entriesPrev: Map<number, SpatialEntry3D>;
33
+ cells: Map<number, CellBucket3D>;
34
+ /**
35
+ * Dense, indexed by entityId. Holes are `undefined`. Entries from previous
36
+ * rebuilds remain in place for in-place reuse (zero allocation in steady
37
+ * state); liveness is determined by `entry._aliveGen === grid._aliveGen`.
38
+ * Internal — read live entries via `getLiveEntry3D` / `liveEntryCount3D` helpers.
39
+ *
40
+ * High-water-mark grows with max entityId ever inserted; despawned ids
41
+ * leave their slot occupied by a stale entry. Acceptable when the entity
42
+ * manager recycles ids or peak count is bounded.
43
+ */
44
+ entries: (SpatialEntry3D | undefined)[];
45
+ /** Monotonic counter bumped by each `clearGrid3D` call. Internal. */
46
+ _aliveGen: number;
47
+ /** Monotonic counter bumped on each query; entries record their last-seen gen for O(1) dedup. Internal. */
48
+ _queryGen: number;
23
49
  }
24
50
  /**
25
51
  * Hash a cell coordinate triple to a single integer key.
@@ -33,13 +59,15 @@ export declare function createGrid3D(cellSize: number): SpatialHashGrid3D;
33
59
  /**
34
60
  * Prepare the grid for a rebuild.
35
61
  *
36
- * Swaps `entries` with `_entriesPrev` so `insertEntity3D` can reuse existing
37
- * `SpatialEntry3D` objects in place (steady-state rebuilds allocate zero
38
- * entries). Any stale entries left in `_entriesPrev` from the previous
39
- * rebuild are dropped here.
62
+ * O(1): bumps the alive-generation counter so entries inserted prior to this
63
+ * call are implicitly stale. `getLiveEntry3D` / `liveEntryCount3D` filter
64
+ * entries by the current gen; queries skip buckets whose own `_gen` lags
65
+ * behind the alive gen; `insertEntity3D` resets a bucket's `length` lazily
66
+ * the first time it is touched in a new generation.
40
67
  *
41
- * Cell buckets are cleared in place keys are retained so subsequent
42
- * inserts hit the existing array rather than allocating a fresh one.
68
+ * Existing `SpatialEntry3D` objects and `CellBucket3D` arrays remain in
69
+ * place for reuse, so steady-state rebuilds allocate zero entries and zero
70
+ * buckets, regardless of how many cells have ever been touched.
43
71
  */
44
72
  export declare function clearGrid3D(grid: SpatialHashGrid3D): void;
45
73
  /**
@@ -48,13 +76,31 @@ export declare function clearGrid3D(grid: SpatialHashGrid3D): void;
48
76
  export declare function insertEntity3D(grid: SpatialHashGrid3D, entityId: number, x: number, y: number, z: number, halfW: number, halfH: number, halfD: number): void;
49
77
  /**
50
78
  * Collect entity IDs from all cells overlapping the given 3D box.
79
+ *
80
+ * Appends to `result` (caller clears/truncates first if reusing). Multi-cell
81
+ * entries are deduplicated via a per-grid generation stamp on each
82
+ * `SpatialEntry3D`.
83
+ *
84
+ * When `minId` is provided, only entries with `entityId > minId` are added —
85
+ * used for symmetric broadphase pair generation.
86
+ */
87
+ export declare function gridQueryBox3D(grid: SpatialHashGrid3D, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: number[], minId?: number): void;
88
+ /**
89
+ * Collect entity IDs within a sphere. AABB-to-point distance filter against
90
+ * the cells overlapping the sphere's bounding box. Appends to `result`.
91
+ */
92
+ export declare function gridQueryRadius3D(grid: SpatialHashGrid3D, cx: number, cy: number, cz: number, radius: number, result: number[]): void;
93
+ /**
94
+ * Get the current-generation entry for an entityId, or `undefined` if the
95
+ * entity isn't in the index for this rebuild. Stale entries from previous
96
+ * rebuilds remain in `entries` for in-place reuse but are filtered here.
51
97
  */
52
- export declare function gridQueryBox3D(grid: SpatialHashGrid3D, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Set<number>): void;
98
+ export declare function getLiveEntry3D(grid: SpatialHashGrid3D, entityId: number): SpatialEntry3D | undefined;
53
99
  /**
54
- * Collect entity IDs within a sphere. Uses box broadphase then
55
- * 3D AABB-to-point distance filter.
100
+ * Count entries inserted in the current rebuild generation. Linear scan
101
+ * intended for tests and diagnostics, not hot paths.
56
102
  */
57
- export declare function gridQueryRadius3D(grid: SpatialHashGrid3D, cx: number, cy: number, cz: number, radius: number, result: Set<number>): void;
103
+ export declare function liveEntryCount3D(grid: SpatialHashGrid3D): number;
58
104
  /**
59
105
  * High-level spatial index API for 3D broadphase queries.
60
106
  *
@@ -65,8 +111,9 @@ export declare function gridQueryRadius3D(grid: SpatialHashGrid3D, cx: number, c
65
111
  export interface SpatialIndex3D {
66
112
  readonly grid: SpatialHashGrid3D;
67
113
  queryBox(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number[];
68
- queryBoxInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Set<number>): void;
114
+ queryBoxInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: number[], minId?: number): void;
69
115
  queryRadius(cx: number, cy: number, cz: number, radius: number): number[];
70
- queryRadiusInto(cx: number, cy: number, cz: number, radius: number, result: Set<number>): void;
116
+ queryRadiusInto(cx: number, cy: number, cz: number, radius: number, result: number[]): void;
71
117
  getEntry(entityId: number): SpatialEntry3D | undefined;
72
118
  }
119
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecspresso",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",