@idleflowgames/anotherecs 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4
4
+
5
+ - `SpatialHash`: `clear()` is now O(1). Buckets carry a frame stamp and a live
6
+ prefix count, so a stamp bump retires the whole grid and a periodic sweep
7
+ reclaims dead buckets. Queries additionally clamp their cell span to an
8
+ occupied bounding box and a per-column occupied row range, both supersets of
9
+ the true occupied set, so they only ever skip cells that are provably empty.
10
+ Szudzik pairing is inlined at its three call sites with its cx-only half
11
+ hoisted out of the inner loop.
12
+ Result arrays are unchanged in membership AND in order, so a consumer that
13
+ relies on iteration order for determinism is unaffected.
14
+ A whole broadphase frame is 2.32x faster at 50 bodies, 1.90x at 250 and 1.13x
15
+ at 1000; a 253k-op trace recorded from a real game run replays 48% faster.
16
+
3
17
  ## 0.1.2
4
18
 
5
19
  - No source changes. Toolchain/deps only: Node 24, pnpm 11.5.0, lock-file
package/dist/index.js CHANGED
@@ -1218,17 +1218,18 @@ var T = 65536, E = -1, D = class {
1218
1218
  for (let e = 0; e < this.count; e++) this.sparse[this.entities[e]] = E;
1219
1219
  this.entities.length = 0, this.data.length = 0, this.count = 0, this.freeList.length = 0, this._added.length = 0, this._removed.length = 0, this._changed.length = 0, this._addedFired = 0, this._removedFired = 0, this._version++;
1220
1220
  }
1221
- };
1222
- //#endregion
1223
- //#region src/spatial-hash.ts
1224
- function O(e, t) {
1225
- e.length === 0 ? this.delete(t) : e.length = 0;
1226
- }
1227
- var k = class {
1221
+ }, O = 256, k = O - 1, A = 64, j = -2147483648, M = 2147483647, N = class {
1228
1222
  invCellSize;
1229
1223
  cells = /* @__PURE__ */ new Map();
1230
1224
  generation = 0;
1231
1225
  seen;
1226
+ occMinCX = 0;
1227
+ occMaxCX = -1;
1228
+ occMinCY = 0;
1229
+ occMaxCY = -1;
1230
+ col = new Int32Array(O * 3);
1231
+ frameGen = 1;
1232
+ sweepIn = A;
1232
1233
  constructor(e = 64, t = T) {
1233
1234
  if (!(e > 0)) throw Error(`SpatialHash: cellSize must be > 0 (got ${e})`);
1234
1235
  this.invCellSize = 1 / e, this.seen = new Int32Array(t);
@@ -1237,85 +1238,126 @@ var k = class {
1237
1238
  return this.generation = this.generation + 1 | 0, this.generation === 0 && (this.generation = 1), this.generation;
1238
1239
  }
1239
1240
  clear() {
1240
- this.cells.forEach(O, this.cells);
1241
+ this.occMinCX = 0, this.occMaxCX = -1, this.occMinCY = 0, this.occMaxCY = -1;
1242
+ let e = this.frameGen + 1;
1243
+ if (e >= M) {
1244
+ this.cells.clear(), this.col.fill(0), this.frameGen = 1, this.sweepIn = A;
1245
+ return;
1246
+ }
1247
+ this.frameGen = e, --this.sweepIn <= 0 && (this.sweepIn = A, this.cells.forEach(this.pruneStale, this));
1248
+ }
1249
+ pruneStale(e, t) {
1250
+ this.frameGen - e.g >= A && this.cells.delete(t);
1241
1251
  }
1242
1252
  insert(e, t, n, r) {
1243
- if (e >= this.seen.length) throw Error(`SpatialHash.insert(): entity id ${e} exceeds maxEntities (${this.seen.length}). Construct the SpatialHash with a maxEntities that matches the consuming World's capacity.`);
1244
- let i = Math.floor((t - r) * this.invCellSize), a = Math.floor((t + r) * this.invCellSize), o = Math.floor((n - r) * this.invCellSize), s = Math.floor((n + r) * this.invCellSize);
1245
- for (let t = i; t <= a; t++) for (let n = o; n <= s; n++) {
1246
- let r = this.hashKey(t, n), i = this.cells.get(r);
1247
- i || (i = [], this.cells.set(r, i)), i.push(e);
1253
+ let i = this.seen;
1254
+ if (e >= i.length) throw Error(`SpatialHash.insert(): entity id ${e} exceeds maxEntities (${i.length}). Construct the SpatialHash with a maxEntities that matches the consuming World's capacity.`);
1255
+ let a = this.cells, o = this.invCellSize, s = Math.floor((t - r) * o), c = Math.floor((t + r) * o), l = Math.floor((n - r) * o), u = Math.floor((n + r) * o);
1256
+ this.occMaxCX < this.occMinCX ? (this.occMinCX = s, this.occMaxCX = c, this.occMinCY = l, this.occMaxCY = u) : (s < this.occMinCX && (this.occMinCX = s), c > this.occMaxCX && (this.occMaxCX = c), l < this.occMinCY && (this.occMinCY = l), u > this.occMaxCY && (this.occMaxCY = u));
1257
+ let d = this.col, f = this.frameGen, p = l < j ? j : l, m = u > M ? M : u;
1258
+ for (let t = s; t <= c; t++) {
1259
+ let n = (t & k) * 3;
1260
+ d[n] === f ? (p < d[n + 1] && (d[n + 1] = p), m > d[n + 2] && (d[n + 2] = m)) : (d[n] = f, d[n + 1] = p, d[n + 2] = m);
1261
+ let r = t >= 0 ? 2 * t : -2 * t - 1, i = r * r + r;
1262
+ for (let t = l; t <= u; t++) {
1263
+ let n = t >= 0 ? 2 * t : -2 * t - 1, o = r >= n ? i + n : n * n + r, s = a.get(o);
1264
+ s === void 0 ? (s = {
1265
+ e: [],
1266
+ n: 0,
1267
+ g: f
1268
+ }, a.set(o, s)) : s.g !== f && (s.g = f, s.n = 0);
1269
+ let c = s.n, l = s.e;
1270
+ c < l.length ? l[c] = e : l.push(e), s.n = c + 1;
1271
+ }
1248
1272
  }
1249
1273
  }
1250
1274
  query(e, t, n, r) {
1251
1275
  r.length = 0;
1252
- let i = this.nextGen(), a = Math.floor((e - n) * this.invCellSize), o = Math.floor((e + n) * this.invCellSize), s = Math.floor((t - n) * this.invCellSize), c = Math.floor((t + n) * this.invCellSize);
1253
- for (let e = a; e <= o; e++) for (let t = s; t <= c; t++) {
1254
- let n = this.hashKey(e, t), a = this.cells.get(n);
1255
- if (a) for (let e = 0; e < a.length; e++) {
1256
- let t = a[e];
1257
- this.seen[t] !== i && (this.seen[t] = i, r.push(t));
1276
+ let i = this.nextGen(), a = this.cells, o = this.seen, s = this.invCellSize, c = Math.floor((e - n) * s), l = Math.floor((e + n) * s), u = Math.floor((t - n) * s), d = Math.floor((t + n) * s);
1277
+ c < this.occMinCX && (c = this.occMinCX), l > this.occMaxCX && (l = this.occMaxCX), u < this.occMinCY && (u = this.occMinCY), d > this.occMaxCY && (d = this.occMaxCY);
1278
+ let f = this.col, p = this.frameGen;
1279
+ for (let e = c; e <= l; e++) {
1280
+ let t = (e & k) * 3;
1281
+ if (f[t] !== p) continue;
1282
+ let n = u, s = d;
1283
+ n < f[t + 1] && (n = f[t + 1]), s > f[t + 2] && (s = f[t + 2]);
1284
+ let c = e >= 0 ? 2 * e : -2 * e - 1, l = c * c + c;
1285
+ for (let e = n; e <= s; e++) {
1286
+ let t = e >= 0 ? 2 * e : -2 * e - 1, n = a.get(c >= t ? l + t : t * t + c);
1287
+ if (n === void 0 || n.g !== p) continue;
1288
+ let s = n.e;
1289
+ for (let e = 0, t = n.n; e < t; e++) {
1290
+ let t = s[e];
1291
+ o[t] !== i && (o[t] = i, r.push(t));
1292
+ }
1258
1293
  }
1259
1294
  }
1260
1295
  }
1261
1296
  queryRadius(e, t, n, r, i, a) {
1262
1297
  a.length = 0;
1263
- let o = this.nextGen(), s = Math.floor((e - n) * this.invCellSize), c = Math.floor((e + n) * this.invCellSize), l = Math.floor((t - n) * this.invCellSize), u = Math.floor((t + n) * this.invCellSize);
1264
- for (let d = s; d <= c; d++) for (let s = l; s <= u; s++) {
1265
- let c = this.hashKey(d, s), l = this.cells.get(c);
1266
- if (l) for (let s = 0; s < l.length; s++) {
1267
- let c = l[s];
1268
- if (this.seen[c] === o) continue;
1269
- this.seen[c] = o;
1270
- let u = r(c);
1271
- if (!u) continue;
1272
- let d = i(c), f = u.x - e, p = u.y - t, m = f * f + p * p, h = n + d;
1273
- m <= h * h && a.push(c);
1298
+ let o = this.nextGen(), s = this.cells, c = this.seen, l = this.invCellSize, u = Math.floor((e - n) * l), d = Math.floor((e + n) * l), f = Math.floor((t - n) * l), p = Math.floor((t + n) * l);
1299
+ u < this.occMinCX && (u = this.occMinCX), d > this.occMaxCX && (d = this.occMaxCX), f < this.occMinCY && (f = this.occMinCY), p > this.occMaxCY && (p = this.occMaxCY);
1300
+ let m = this.col, h = this.frameGen;
1301
+ for (let l = u; l <= d; l++) {
1302
+ let u = (l & k) * 3;
1303
+ if (m[u] !== h) continue;
1304
+ let d = f, g = p;
1305
+ d < m[u + 1] && (d = m[u + 1]), g > m[u + 2] && (g = m[u + 2]);
1306
+ let _ = l >= 0 ? 2 * l : -2 * l - 1, v = _ * _ + _;
1307
+ for (let l = d; l <= g; l++) {
1308
+ let u = l >= 0 ? 2 * l : -2 * l - 1, d = s.get(_ >= u ? v + u : u * u + _);
1309
+ if (d === void 0 || d.g !== h) continue;
1310
+ let f = d.e;
1311
+ for (let s = 0, l = d.n; s < l; s++) {
1312
+ let l = f[s];
1313
+ if (c[l] === o) continue;
1314
+ c[l] = o;
1315
+ let u = r(l);
1316
+ if (!u) continue;
1317
+ let d = i(l), p = u.x - e, m = u.y - t, h = p * p + m * m, g = n + d;
1318
+ h <= g * g && a.push(l);
1319
+ }
1274
1320
  }
1275
1321
  }
1276
1322
  }
1277
- hashKey(e, t) {
1278
- let n = e >= 0 ? 2 * e : -2 * e - 1, r = t >= 0 ? 2 * t : -2 * t - 1;
1279
- return n >= r ? n * n + n + r : r * r + n;
1280
- }
1281
- }, A = 0, j = 0, M = 0, N = 0, P = 0, F = !0;
1282
- function I(e, t, n) {
1323
+ }, P = 0, F = 0, I = 0, L = 0, R = 0, z = !0;
1324
+ function B(e, t, n) {
1283
1325
  return {
1284
- id: j++,
1326
+ id: F++,
1285
1327
  name: e,
1286
1328
  create: t,
1287
1329
  reset: n
1288
1330
  };
1289
1331
  }
1290
- function L(e) {
1332
+ function V(e) {
1291
1333
  return {
1292
- id: j++,
1334
+ id: F++,
1293
1335
  name: e,
1294
1336
  tag: !0
1295
1337
  };
1296
1338
  }
1297
- function R(e) {
1339
+ function H(e) {
1298
1340
  return {
1299
- id: M++,
1341
+ id: I++,
1300
1342
  name: e
1301
1343
  };
1302
1344
  }
1303
- function z(e) {
1345
+ function U(e) {
1304
1346
  return {
1305
- id: N++,
1347
+ id: L++,
1306
1348
  name: e
1307
1349
  };
1308
1350
  }
1309
- function B(e, t) {
1351
+ function W(e, t) {
1310
1352
  return {
1311
- id: P++,
1353
+ id: R++,
1312
1354
  name: e,
1313
1355
  init: t
1314
1356
  };
1315
1357
  }
1316
1358
  //#endregion
1317
1359
  //#region src/world.ts
1318
- var V = class {
1360
+ var G = class {
1319
1361
  nextId = 1;
1320
1362
  maxEntities;
1321
1363
  alive = /* @__PURE__ */ new Set();
@@ -1527,13 +1569,13 @@ var V = class {
1527
1569
  t.isTracking() || (t.enableTracking(), this.trackedStores.push(t));
1528
1570
  }
1529
1571
  added(e) {
1530
- return this.stores[e.id]?.iterAdded() ?? U;
1572
+ return this.stores[e.id]?.iterAdded() ?? q;
1531
1573
  }
1532
1574
  removed(e) {
1533
- return this.stores[e.id]?.iterRemoved() ?? U;
1575
+ return this.stores[e.id]?.iterRemoved() ?? q;
1534
1576
  }
1535
1577
  changed(e) {
1536
- return this.stores[e.id]?.iterChanged() ?? U;
1578
+ return this.stores[e.id]?.iterChanged() ?? q;
1537
1579
  }
1538
1580
  markChanged(e, t) {
1539
1581
  this.stores[t.id]?.markChanged(e);
@@ -1649,9 +1691,9 @@ var V = class {
1649
1691
  return this.backrefsEnabled;
1650
1692
  }
1651
1693
  backrefs(e) {
1652
- if (!this.backrefsEnabled || this.backrefEdges === null) return H;
1694
+ if (!this.backrefsEnabled || this.backrefEdges === null) return K;
1653
1695
  let t = this.backrefEdges.get(e);
1654
- if (t === void 0 || t.length === 0) return H;
1696
+ if (t === void 0 || t.length === 0) return K;
1655
1697
  let n = !0;
1656
1698
  for (let e = 0; e < t.length; e++) if (!this.alive.has(t[e])) {
1657
1699
  n = !1;
@@ -1660,7 +1702,7 @@ var V = class {
1660
1702
  if (n) return t;
1661
1703
  let r = [];
1662
1704
  for (let e = 0; e < t.length; e++) this.alive.has(t[e]) && r.push(t[e]);
1663
- return r.length === 0 ? H : r;
1705
+ return r.length === 0 ? K : r;
1664
1706
  }
1665
1707
  unref(e, t) {
1666
1708
  if (!this.backrefsEnabled || this.backrefEdges === null) return;
@@ -1671,8 +1713,8 @@ var V = class {
1671
1713
  let a = this.holderToTargets?.get(t);
1672
1714
  a !== void 0 && (a.delete(n), a.size === 0 && this.holderToTargets?.delete(t));
1673
1715
  }
1674
- }, H = Object.freeze([]), U = [];
1716
+ }, K = Object.freeze([]), q = [];
1675
1717
  //#endregion
1676
- export { e as Bitmask, t as CommandBuffer, D as ComponentStore, T as DEFAULT_MAX_ENTITIES, n as EventBus, i as IncrementalQuery, a as MigrationError, o as MigrationRegistry, A as NULL_REF, d as QueryEngine, f as Schedule, x as Serializer, k as SpatialHash, F as TAG_VALUE, V as World, l as any, I as defineComponent, z as defineEvent, B as defineLocal, R as defineResource, L as defineTag, w as jsonCodec, c as maybe, s as without };
1718
+ export { e as Bitmask, t as CommandBuffer, D as ComponentStore, T as DEFAULT_MAX_ENTITIES, n as EventBus, i as IncrementalQuery, a as MigrationError, o as MigrationRegistry, P as NULL_REF, d as QueryEngine, f as Schedule, x as Serializer, N as SpatialHash, z as TAG_VALUE, G as World, l as any, B as defineComponent, U as defineEvent, W as defineLocal, H as defineResource, V as defineTag, w as jsonCodec, c as maybe, s as without };
1677
1719
 
1678
1720
  //# sourceMappingURL=index.js.map