@forgeax/engine-ecs 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -342,3 +342,12 @@ Physics, or the explicitly named ECS subpath.
342
342
 
343
343
  For the full migration rationale and acceptance matrix, see the canonical
344
344
  [ECS 80% architecture design](../../.forgeax-harness/docs/specs/2026-08-22-ecs-core-80-percent-architecture-reduction-design.md).
345
+
346
+ ### Large managed arrays
347
+
348
+ The eight BufferPool size classes bound pooling, not field capacity. Larger
349
+ arrays use dedicated allocations; release drops their storage instead of
350
+ retaining a large free bucket. Growth preserves bytes and slot identity, and
351
+ allocation failure returns the existing structured managed-buffer error. Small
352
+ fields keep their existing allocation and reuse behavior. Game data, including
353
+ instance transforms, remains World-owned regardless of its size.
@@ -6,8 +6,7 @@ import { ManagedBufferOutOfBoundsError, ManagedBufferShrinkNotSupportedError } f
6
6
  *
7
7
  * `alloc(byteLength)` rounds up to `SIZE_CLASSES[i]` for the smallest `i`
8
8
  * with `byteLength <= SIZE_CLASSES[i]`. byteLength === 0 is the special path
9
- * (no bucket); byteLength > SIZE_CLASSES[7] (262_144) returns
10
- * `managed-buffer-out-of-bounds`.
9
+ * (no bucket); larger requests use dedicated, unpooled allocations.
11
10
  */
12
11
  export declare const SIZE_CLASSES: readonly number[];
13
12
  /** Result envelope for a fresh alloc - id + initial view. */
@@ -42,10 +41,10 @@ export declare class BufferPool {
42
41
  * Allocate a managed buffer slot of at least `byteLength` bytes.
43
42
  *
44
43
  * Routes:
45
- * - byteLength < 0 -> not in current contract (caller responsibility).
44
+ * - invalid byteLength -> structured out-of-bounds error.
46
45
  * - byteLength == 0 -> ok({ id, view: zero-length Uint8Array }) (no bucket).
47
46
  * - byteLength <= 262144 -> ok({ id, view }), bucket = smallest >= byteLength.
48
- * - byteLength > 262144 -> err(managed-buffer-out-of-bounds).
47
+ * - byteLength > 262144 -> a dedicated allocation; allocation failure is structured.
49
48
  *
50
49
  * D-5: size classes are radix-4 (16 / 64 / 256 / 1K / 4K / 16K / 64K / 256K).
51
50
  * Free-list pop reuses the most recently released slot id at the same bucket;
@@ -65,7 +64,7 @@ export declare class BufferPool {
65
64
  * the caller become detached / orphaned - callers must use `pool.view(id)`
66
65
  * after grow to read the refreshed view (the `release` loop refreshes
67
66
  * automatically).
68
- * - newBytes > 262144 -> err(managed-buffer-out-of-bounds).
67
+ * - newBytes beyond the last pooled class -> dedicated allocation.
69
68
  */
70
69
  grow(id: number, newBytes: number): Result<Uint8Array, ManagedBufferShrinkNotSupportedError | ManagedBufferOutOfBoundsError>;
71
70
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"buffer-pool.d.ts","sourceRoot":"","sources":["../src/buffer-pool.ts"],"names":[],"mappings":"AA2CA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,6BAA6B,EAAE,oCAAoC,EAAE,MAAM,UAAU,CAAC;AAE/F;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAExC,CAAC;AAEH,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,oFAAoF;IACpF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAyCD;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsD;IAClF,OAAO,CAAC,MAAM,CAAK;IAEnB;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,iBAAiB,EAAE,6BAA6B,CAAC;IAoDnF;;;;;;;;;;;;;;OAcG;IACH,IAAI,CACF,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,UAAU,EAAE,oCAAoC,GAAG,6BAA6B,CAAC;IAgD3F;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;IAgBxC;;;;OAIG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU;IAM5B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAOhC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CACd,EAAE,EAAE,MAAM,EACV,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,UAAU,EAAE,oCAAoC,GAAG,6BAA6B,CAAC;IA4B3F,+EAA+E;IAC/E,UAAU,IAAI,MAAM;CAKrB"}
1
+ {"version":3,"file":"buffer-pool.d.ts","sourceRoot":"","sources":["../src/buffer-pool.ts"],"names":[],"mappings":"AAKA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,6BAA6B,EAAE,oCAAoC,EAAE,MAAM,UAAU,CAAC;AAE/F;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAExC,CAAC;AAEH,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,oFAAoF;IACpF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAwCD;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsD;IAClF,OAAO,CAAC,MAAM,CAAK;IAEnB;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,iBAAiB,EAAE,6BAA6B,CAAC;IA+DnF;;;;;;;;;;;;;;OAcG;IACH,IAAI,CACF,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,UAAU,EAAE,oCAAoC,GAAG,6BAA6B,CAAC;IAkD3F;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;IAgBxC;;;;OAIG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU;IAM5B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAOhC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CACd,EAAE,EAAE,MAAM,EACV,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,UAAU,EAAE,oCAAoC,GAAG,6BAA6B,CAAC;IA4B3F,+EAA+E;IAC/E,UAAU,IAAI,MAAM;CAKrB"}
package/dist/index.mjs CHANGED
@@ -4788,9 +4788,8 @@ var WorldComponentAccess = class {
4788
4788
  slotId = column.view[row];
4789
4789
  }
4790
4790
  const requiredBytes = (currentLength + 1) * meta.byteSize;
4791
- const maximum = Math.floor(262144 / meta.byteSize);
4792
- if (!Number.isSafeInteger(currentLength + 1) || currentLength + 1 > maximum) {
4793
- return err(new ManagedBufferOutOfBoundsError(requiredBytes, 262144));
4791
+ if (!Number.isSafeInteger(requiredBytes)) {
4792
+ return err(new ManagedBufferOutOfBoundsError(requiredBytes, Number.MAX_SAFE_INTEGER));
4794
4793
  }
4795
4794
  const preparation = {
4796
4795
  target,
@@ -5850,10 +5849,10 @@ var BufferPool = class {
5850
5849
  * Allocate a managed buffer slot of at least `byteLength` bytes.
5851
5850
  *
5852
5851
  * Routes:
5853
- * - byteLength < 0 -> not in current contract (caller responsibility).
5852
+ * - invalid byteLength -> structured out-of-bounds error.
5854
5853
  * - byteLength == 0 -> ok({ id, view: zero-length Uint8Array }) (no bucket).
5855
5854
  * - byteLength <= 262144 -> ok({ id, view }), bucket = smallest >= byteLength.
5856
- * - byteLength > 262144 -> err(managed-buffer-out-of-bounds).
5855
+ * - byteLength > 262144 -> a dedicated allocation; allocation failure is structured.
5857
5856
  *
5858
5857
  * D-5: size classes are radix-4 (16 / 64 / 256 / 1K / 4K / 16K / 64K / 256K).
5859
5858
  * Free-list pop reuses the most recently released slot id at the same bucket;
@@ -5867,11 +5866,20 @@ var BufferPool = class {
5867
5866
  this.slots.set(id2, { sizeClassIdx: -1, buffer: buffer2, view: view2, byteLength: 0, live: true });
5868
5867
  return ok({ id: id2, view: view2 });
5869
5868
  }
5869
+ if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
5870
+ return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
5871
+ }
5870
5872
  const idx = bucketIndex(byteLength);
5871
5873
  if (idx === SIZE_CLASSES.length) {
5872
- return err(
5873
- new ManagedBufferOutOfBoundsError(byteLength, SIZE_CLASSES[SIZE_CLASSES.length - 1] ?? 0)
5874
- );
5874
+ try {
5875
+ const buffer2 = new ArrayBuffer(byteLength);
5876
+ const view2 = new Uint8Array(buffer2);
5877
+ const id2 = this.nextId++;
5878
+ this.slots.set(id2, { sizeClassIdx: idx, buffer: buffer2, view: view2, byteLength, live: true });
5879
+ return ok({ id: id2, view: view2 });
5880
+ } catch {
5881
+ return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
5882
+ }
5875
5883
  }
5876
5884
  const bucketBytes = SIZE_CLASSES[idx];
5877
5885
  if (bucketBytes === void 0) {
@@ -5915,7 +5923,7 @@ var BufferPool = class {
5915
5923
  * the caller become detached / orphaned - callers must use `pool.view(id)`
5916
5924
  * after grow to read the refreshed view (the `release` loop refreshes
5917
5925
  * automatically).
5918
- * - newBytes > 262144 -> err(managed-buffer-out-of-bounds).
5926
+ * - newBytes beyond the last pooled class -> dedicated allocation.
5919
5927
  */
5920
5928
  grow(id, newBytes) {
5921
5929
  const slot = this.slots.get(id);
@@ -5928,25 +5936,27 @@ var BufferPool = class {
5928
5936
  if (newBytes === slot.byteLength) {
5929
5937
  return ok(slot.view);
5930
5938
  }
5931
- const newIdx = bucketIndex(newBytes);
5932
- if (newIdx === SIZE_CLASSES.length) {
5933
- return err(
5934
- new ManagedBufferOutOfBoundsError(newBytes, SIZE_CLASSES[SIZE_CLASSES.length - 1] ?? 0)
5935
- );
5939
+ if (!Number.isSafeInteger(newBytes) || newBytes < 0) {
5940
+ return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
5936
5941
  }
5937
- if (newIdx === slot.sizeClassIdx) {
5942
+ const newIdx = bucketIndex(newBytes);
5943
+ if (newBytes <= slot.buffer.byteLength) {
5938
5944
  slot.byteLength = newBytes;
5939
5945
  slot.view = new Uint8Array(slot.buffer, 0, newBytes);
5940
5946
  return ok(slot.view);
5941
5947
  }
5942
- const newBucketBytes = SIZE_CLASSES[newIdx];
5948
+ const newBucketBytes = SIZE_CLASSES[newIdx] ?? Math.max(newBytes, slot.buffer.byteLength * 2);
5943
5949
  const oldByteLength = slot.byteLength;
5944
5950
  let nextBuffer;
5945
- if (HAS_TRANSFER2) {
5946
- nextBuffer = slot.buffer.transfer(newBucketBytes);
5947
- } else {
5948
- nextBuffer = new ArrayBuffer(newBucketBytes);
5949
- new Uint8Array(nextBuffer).set(new Uint8Array(slot.buffer, 0, oldByteLength));
5951
+ try {
5952
+ if (HAS_TRANSFER2) {
5953
+ nextBuffer = slot.buffer.transfer(newBucketBytes);
5954
+ } else {
5955
+ nextBuffer = new ArrayBuffer(newBucketBytes);
5956
+ new Uint8Array(nextBuffer).set(new Uint8Array(slot.buffer, 0, oldByteLength));
5957
+ }
5958
+ } catch {
5959
+ return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
5950
5960
  }
5951
5961
  slot.sizeClassIdx = newIdx;
5952
5962
  slot.buffer = nextBuffer;
@@ -5965,7 +5975,7 @@ var BufferPool = class {
5965
5975
  if (slot === void 0) return ok(void 0);
5966
5976
  if (!slot.live) return ok(void 0);
5967
5977
  slot.live = false;
5968
- if (slot.sizeClassIdx >= 0) {
5978
+ if (slot.sizeClassIdx >= 0 && slot.sizeClassIdx < SIZE_CLASSES.length) {
5969
5979
  const bucket = this.freeBuckets[slot.sizeClassIdx];
5970
5980
  if (bucket !== void 0) bucket.push(id);
5971
5981
  } else {
@@ -5994,7 +6004,7 @@ var BufferPool = class {
5994
6004
  const slot = this.slots.get(id);
5995
6005
  if (slot === void 0 || !slot.live) return 0;
5996
6006
  if (slot.sizeClassIdx < 0) return 0;
5997
- return SIZE_CLASSES[slot.sizeClassIdx] ?? 0;
6007
+ return slot.buffer.byteLength;
5998
6008
  }
5999
6009
  /**
6000
6010
  * Reset the logical byteLength of slot `id` to `newByteLength` while
@@ -6022,7 +6032,7 @@ var BufferPool = class {
6022
6032
  }
6023
6033
  return err(new ManagedBufferOutOfBoundsError(newByteLength, 0));
6024
6034
  }
6025
- const bucketBytes = SIZE_CLASSES[slot.sizeClassIdx] ?? 0;
6035
+ const bucketBytes = slot.buffer.byteLength;
6026
6036
  if (newByteLength > bucketBytes) {
6027
6037
  return err(new ManagedBufferOutOfBoundsError(newByteLength, bucketBytes));
6028
6038
  }