@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 +9 -0
- package/dist/buffer-pool.d.ts +4 -5
- package/dist/buffer-pool.d.ts.map +1 -1
- package/dist/index.mjs +34 -24
- package/dist/index.mjs.map +1 -1
- package/dist/world-component-access.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/archetype.unit.test.ts +38 -10
- package/src/__tests__/errors.unit.test.ts +65 -13
- package/src/buffer-pool.ts +47 -74
- package/src/world-component-access.ts +2 -3
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.
|
package/dist/buffer-pool.d.ts
CHANGED
|
@@ -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);
|
|
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
|
|
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
|
|
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
|
|
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":"
|
|
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
|
-
|
|
4792
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
5873
|
-
new
|
|
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
|
|
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
|
-
|
|
5932
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
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
|
|
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 =
|
|
6035
|
+
const bucketBytes = slot.buffer.byteLength;
|
|
6026
6036
|
if (newByteLength > bucketBytes) {
|
|
6027
6037
|
return err(new ManagedBufferOutOfBoundsError(newByteLength, bucketBytes));
|
|
6028
6038
|
}
|