@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world-component-access.d.ts","sourceRoot":"","sources":["../src/world-component-access.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"world-component-access.d.ts","sourceRoot":"","sources":["../src/world-component-access.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-ecs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"LICENSE"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@forgeax/engine-math": "0.1.
|
|
42
|
-
"@forgeax/engine-plugin": "0.1.
|
|
43
|
-
"@forgeax/engine-types": "0.1.
|
|
41
|
+
"@forgeax/engine-math": "0.1.26",
|
|
42
|
+
"@forgeax/engine-plugin": "0.1.26",
|
|
43
|
+
"@forgeax/engine-types": "0.1.26"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^20.14.0"
|
|
@@ -395,17 +395,45 @@ function appendEntity(table: Table, entity: number): number {
|
|
|
395
395
|
expect(refreshed.byteLength).toBe(4096);
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
-
it('
|
|
398
|
+
it('allocates beyond the pooled classes without retaining a large free bucket', () => {
|
|
399
399
|
const pool = new BufferPool();
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
expect(
|
|
407
|
-
|
|
408
|
-
expect(
|
|
400
|
+
const bytes = 20_000 * 64;
|
|
401
|
+
const large = pool.alloc(bytes).unwrap();
|
|
402
|
+
expect(large.view.byteLength).toBe(bytes);
|
|
403
|
+
expect(pool.byteCapacity(large.id)).toBe(bytes);
|
|
404
|
+
large.view[0] = 42;
|
|
405
|
+
pool.grow(large.id, bytes * 2).unwrap();
|
|
406
|
+
expect(pool.view(large.id)[0]).toBe(42);
|
|
407
|
+
pool.setLogicalLength(large.id, 0).unwrap();
|
|
408
|
+
expect(pool.byteCapacity(large.id)).toBe(bytes * 2);
|
|
409
|
+
pool.release(large.id).unwrap();
|
|
410
|
+
expect(pool.byteCapacity(large.id)).toBe(0);
|
|
411
|
+
expect(pool.view(large.id)).toHaveLength(0);
|
|
412
|
+
expect(pool.alloc(bytes).unwrap().id).not.toBe(large.id);
|
|
413
|
+
expect(SIZE_CLASSES.at(-1)).toBe(262_144);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('grows from a small pooled field into a dedicated large field', () => {
|
|
417
|
+
const pool = new BufferPool();
|
|
418
|
+
const small = pool.alloc(128).unwrap();
|
|
419
|
+
small.view[127] = 71;
|
|
420
|
+
const grown = pool.grow(small.id, 20_000 * 64).unwrap();
|
|
421
|
+
expect(grown[127]).toBe(71);
|
|
422
|
+
expect(grown[128]).toBe(0);
|
|
423
|
+
expect(pool._liveCount()).toBe(1);
|
|
424
|
+
pool.release(small.id).unwrap();
|
|
425
|
+
expect(pool._liveCount()).toBe(0);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it.each([
|
|
429
|
+
-1,
|
|
430
|
+
1.5,
|
|
431
|
+
Number.NaN,
|
|
432
|
+
Number.POSITIVE_INFINITY,
|
|
433
|
+
])('rejects invalid allocation size %s', (bytes) => {
|
|
434
|
+
const result = new BufferPool().alloc(bytes);
|
|
435
|
+
expect(result.ok).toBe(false);
|
|
436
|
+
if (!result.ok) expect(result.error.code).toBe('managed-buffer-out-of-bounds');
|
|
409
437
|
});
|
|
410
438
|
|
|
411
439
|
it('M4 prelude: distinct ids on consecutive alloc with no release in between', () => {
|
|
@@ -58,8 +58,12 @@ import { World } from '../world';
|
|
|
58
58
|
const NAME = 'register' + 'Component';
|
|
59
59
|
const CALL_PATTERN = `[.](${NAME}|${NAME}Checked)[(]`;
|
|
60
60
|
const FIELD_PATTERN = `${NAME}:`;
|
|
61
|
+
// The repo-wide source scan runs inside the coverage pool alongside 180+
|
|
62
|
+
// instrumented files. Keep the default unit-test budget for normal cases,
|
|
63
|
+
// but allow a contended runner to finish this I/O-only contract gate.
|
|
64
|
+
const SOURCE_SCAN_TIMEOUT_MS = 30_000;
|
|
61
65
|
|
|
62
|
-
function
|
|
66
|
+
function sourceHitsFallback(pattern: string): string[] {
|
|
63
67
|
const expression = new RegExp(pattern);
|
|
64
68
|
const hits: string[] = [];
|
|
65
69
|
const visit = (relativeDir: string): void => {
|
|
@@ -85,20 +89,68 @@ import { World } from '../world';
|
|
|
85
89
|
return hits;
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
function sourceHits(pattern: string): string[] {
|
|
93
|
+
const result = spawnSync(
|
|
94
|
+
'rg',
|
|
95
|
+
[
|
|
96
|
+
'--no-heading',
|
|
97
|
+
'--color=never',
|
|
98
|
+
'--line-number',
|
|
99
|
+
'--glob',
|
|
100
|
+
'*.ts',
|
|
101
|
+
'--glob',
|
|
102
|
+
'*.mjs',
|
|
103
|
+
'--glob',
|
|
104
|
+
'!dist/**',
|
|
105
|
+
'--glob',
|
|
106
|
+
'!node_modules/**',
|
|
107
|
+
pattern,
|
|
108
|
+
...SCAN_DIRS,
|
|
109
|
+
],
|
|
110
|
+
{ cwd: repoRoot, encoding: 'utf8' },
|
|
111
|
+
);
|
|
112
|
+
if (result.error === undefined) {
|
|
113
|
+
if (result.status === 1) return [];
|
|
114
|
+
if (result.status !== 0) {
|
|
115
|
+
throw result.error ?? new Error(`rg exited with status ${result.status}`);
|
|
116
|
+
}
|
|
117
|
+
return result.stdout
|
|
118
|
+
.trimEnd()
|
|
119
|
+
.split('\n')
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.map((line) => {
|
|
122
|
+
const separator = line.indexOf(':');
|
|
123
|
+
const lineEnd = line.indexOf(':', separator + 1);
|
|
124
|
+
return `${line.slice(0, separator)}:${line.slice(separator + 1, lineEnd)}:${line
|
|
125
|
+
.slice(lineEnd + 1)
|
|
126
|
+
.trim()}`;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return sourceHitsFallback(pattern);
|
|
130
|
+
}
|
|
131
|
+
|
|
88
132
|
describe('ac16-register-component-grep-gate.test.ts', () => {
|
|
89
133
|
describe('AC-16 - register-component call surface is zero repo-wide (w20)', () => {
|
|
90
|
-
it(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
it(
|
|
135
|
+
'layer 1: zero method-call sites for the deleted register* methods',
|
|
136
|
+
() => {
|
|
137
|
+
const hits = sourceHits(CALL_PATTERN);
|
|
138
|
+
expect(hits, `unexpected register-component call sites:\n${hits.join('\n')}`).toEqual([]);
|
|
139
|
+
},
|
|
140
|
+
SOURCE_SCAN_TIMEOUT_MS,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
it(
|
|
144
|
+
'layer 2: zero mock interface field declarations for the deleted method',
|
|
145
|
+
() => {
|
|
146
|
+
const hits = sourceHits(FIELD_PATTERN);
|
|
147
|
+
expect(
|
|
148
|
+
hits,
|
|
149
|
+
`unexpected register-component field declarations:\n${hits.join('\n')}`,
|
|
150
|
+
).toEqual([]);
|
|
151
|
+
},
|
|
152
|
+
SOURCE_SCAN_TIMEOUT_MS,
|
|
153
|
+
);
|
|
102
154
|
});
|
|
103
155
|
});
|
|
104
156
|
}
|
package/src/buffer-pool.ts
CHANGED
|
@@ -1,45 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// free-lists (radix 4) and uses ArrayBuffer.transfer for cross-bucket
|
|
6
|
-
// growth. The integer `id` returned by `alloc` is the runtime stand-in for
|
|
7
|
-
// the buffer slot; archetype columns store the `id` as u32 (M4 carry-over
|
|
8
|
-
// reuses this id across archetype migrate without copying bytes).
|
|
9
|
-
//
|
|
10
|
-
// §contract — managed handles are operational, not persistent
|
|
11
|
-
// Spec: docs/specs/2026-06-14-ecs-managed-lifecycle-ssot-design.md §3.3.
|
|
12
|
-
// The slot id never escapes ECS internals — there is no public Handle<Buffer>
|
|
13
|
-
// surface today. There is no generation tag on BufferPool; `release` is
|
|
14
|
-
// typed `Result<void, never>` (no stale-slot error arm) and the dual
|
|
15
|
-
// guards live at packages/ecs/src/__tests__/buffer-pool.test-d.ts (compile
|
|
16
|
-
// time) + packages/ecs/README.md §"Managed handles are operational, not
|
|
17
|
-
// persistent" (AI-user-facing). Should a future feat introduce a public
|
|
18
|
-
// Handle<Buffer> surface, this design (no gen tag) MUST be re-debated —
|
|
19
|
-
// the silent-resolve contract that UniqueRefStore tolerates relies on
|
|
20
|
-
// the holder being a single ECS field, not a free-floating cache; a
|
|
21
|
-
// public Handle<Buffer> changes that calculus.
|
|
22
|
-
//
|
|
23
|
-
// Design contract (frozen by plan-decisions):
|
|
24
|
-
// D-5 size-class 8 buckets (radix 4):
|
|
25
|
-
// 16 / 64 / 256 / 1K / 4K / 16K / 64K / 256K bytes.
|
|
26
|
-
// Allocation rounds up to the smallest bucket >= byteLength; alloc(0)
|
|
27
|
-
// is legal and returns a zero-length view (no bucket touched).
|
|
28
|
-
// Requests > 256K surface 'managed-buffer-out-of-bounds'.
|
|
29
|
-
// D-6 grow(id, newBytes) returns Result<Uint8Array, EcsError> - never
|
|
30
|
-
// throws, never mutates the prior view in place. Cross-bucket growth
|
|
31
|
-
// detaches the old ArrayBuffer (ES2024 transfer when available, copy
|
|
32
|
-
// fallback otherwise) and installs a fresh view backed by the new
|
|
33
|
-
// bucket's ArrayBuffer; same-bucket growth re-slices the existing
|
|
34
|
-
// backing buffer to the new byteLength.
|
|
35
|
-
// D-7 v1 forbids shrink (newBytes < current -> err); newBytes == current
|
|
36
|
-
// is a legal no-op that returns the same view. Bucket free-lists are
|
|
37
|
-
// NEVER trimmed - released slots stay parked on their bucket forever
|
|
38
|
-
// in v1 (memory bloat is acceptable until M5/M6 telemetry).
|
|
39
|
-
//
|
|
40
|
-
// The pool is a `class` rather than a frozen module-level singleton because
|
|
41
|
-
// World owns one BufferPool per instance; M2 wires it into the release loop
|
|
42
|
-
// alongside UniqueRefStore (D-2 - per-World lifecycle).
|
|
1
|
+
// World-owned managed array storage. Small fields use eight radix-4 free
|
|
2
|
+
// lists; larger fields allocate dedicated buffers and drop them on release.
|
|
3
|
+
// Pooling policy never limits a field to the largest pooled class. Slot IDs
|
|
4
|
+
// remain private to ECS and survive archetype migration and growth.
|
|
43
5
|
|
|
44
6
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
45
7
|
import { ManagedBufferOutOfBoundsError, ManagedBufferShrinkNotSupportedError } from './errors';
|
|
@@ -50,8 +12,7 @@ import { ManagedBufferOutOfBoundsError, ManagedBufferShrinkNotSupportedError } f
|
|
|
50
12
|
*
|
|
51
13
|
* `alloc(byteLength)` rounds up to `SIZE_CLASSES[i]` for the smallest `i`
|
|
52
14
|
* with `byteLength <= SIZE_CLASSES[i]`. byteLength === 0 is the special path
|
|
53
|
-
* (no bucket);
|
|
54
|
-
* `managed-buffer-out-of-bounds`.
|
|
15
|
+
* (no bucket); larger requests use dedicated, unpooled allocations.
|
|
55
16
|
*/
|
|
56
17
|
export const SIZE_CLASSES: readonly number[] = Object.freeze([
|
|
57
18
|
16, 64, 256, 1024, 4096, 16384, 65536, 262144,
|
|
@@ -77,7 +38,7 @@ const HAS_TRANSFER: boolean =
|
|
|
77
38
|
typeof (ArrayBuffer.prototype as { transfer?: unknown }).transfer === 'function';
|
|
78
39
|
|
|
79
40
|
interface SlotState {
|
|
80
|
-
/** Bucket index
|
|
41
|
+
/** Bucket index, SIZE_CLASSES.length for dedicated storage, or -1 for zero length. */
|
|
81
42
|
sizeClassIdx: number;
|
|
82
43
|
/** Underlying ArrayBuffer for the slot's current bucket (zero-length for sizeClassIdx === -1). */
|
|
83
44
|
buffer: ArrayBuffer;
|
|
@@ -92,8 +53,7 @@ interface SlotState {
|
|
|
92
53
|
/**
|
|
93
54
|
* Round `byteLength` up to a bucket index. Returns -1 for the legal zero
|
|
94
55
|
* path; returns `SIZE_CLASSES.length` (out-of-range) for byteLength larger
|
|
95
|
-
* than the top bucket so the caller can
|
|
96
|
-
* `managed-buffer-out-of-bounds` error.
|
|
56
|
+
* than the top bucket so the caller can use a dedicated allocation.
|
|
97
57
|
*/
|
|
98
58
|
function bucketIndex(byteLength: number): number {
|
|
99
59
|
if (byteLength === 0) return -1;
|
|
@@ -130,10 +90,10 @@ export class BufferPool {
|
|
|
130
90
|
* Allocate a managed buffer slot of at least `byteLength` bytes.
|
|
131
91
|
*
|
|
132
92
|
* Routes:
|
|
133
|
-
* - byteLength
|
|
93
|
+
* - invalid byteLength -> structured out-of-bounds error.
|
|
134
94
|
* - byteLength == 0 -> ok({ id, view: zero-length Uint8Array }) (no bucket).
|
|
135
95
|
* - byteLength <= 262144 -> ok({ id, view }), bucket = smallest >= byteLength.
|
|
136
|
-
* - byteLength
|
|
96
|
+
* - byteLength > 262144 -> a dedicated allocation; allocation failure is structured.
|
|
137
97
|
*
|
|
138
98
|
* D-5: size classes are radix-4 (16 / 64 / 256 / 1K / 4K / 16K / 64K / 256K).
|
|
139
99
|
* Free-list pop reuses the most recently released slot id at the same bucket;
|
|
@@ -147,11 +107,22 @@ export class BufferPool {
|
|
|
147
107
|
this.slots.set(id, { sizeClassIdx: -1, buffer, view, byteLength: 0, live: true });
|
|
148
108
|
return ok({ id, view });
|
|
149
109
|
}
|
|
110
|
+
if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
|
|
111
|
+
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
112
|
+
}
|
|
150
113
|
const idx = bucketIndex(byteLength);
|
|
151
114
|
if (idx === SIZE_CLASSES.length) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
115
|
+
// Large fields are dedicated allocations, not permanently retained
|
|
116
|
+
// pool buckets. Small fields keep exactly the existing allocation cost.
|
|
117
|
+
try {
|
|
118
|
+
const buffer = new ArrayBuffer(byteLength);
|
|
119
|
+
const view = new Uint8Array(buffer);
|
|
120
|
+
const id = this.nextId++;
|
|
121
|
+
this.slots.set(id, { sizeClassIdx: idx, buffer, view, byteLength, live: true });
|
|
122
|
+
return ok({ id, view });
|
|
123
|
+
} catch {
|
|
124
|
+
return err(new ManagedBufferOutOfBoundsError(byteLength, 0));
|
|
125
|
+
}
|
|
155
126
|
}
|
|
156
127
|
const bucketBytes = SIZE_CLASSES[idx];
|
|
157
128
|
/* istanbul ignore next -- bucketIndex returns < SIZE_CLASSES.length here */
|
|
@@ -204,7 +175,7 @@ export class BufferPool {
|
|
|
204
175
|
* the caller become detached / orphaned - callers must use `pool.view(id)`
|
|
205
176
|
* after grow to read the refreshed view (the `release` loop refreshes
|
|
206
177
|
* automatically).
|
|
207
|
-
* - newBytes
|
|
178
|
+
* - newBytes beyond the last pooled class -> dedicated allocation.
|
|
208
179
|
*/
|
|
209
180
|
grow(
|
|
210
181
|
id: number,
|
|
@@ -222,13 +193,11 @@ export class BufferPool {
|
|
|
222
193
|
if (newBytes === slot.byteLength) {
|
|
223
194
|
return ok(slot.view);
|
|
224
195
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return err(
|
|
228
|
-
new ManagedBufferOutOfBoundsError(newBytes, SIZE_CLASSES[SIZE_CLASSES.length - 1] ?? 0),
|
|
229
|
-
);
|
|
196
|
+
if (!Number.isSafeInteger(newBytes) || newBytes < 0) {
|
|
197
|
+
return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
|
|
230
198
|
}
|
|
231
|
-
|
|
199
|
+
const newIdx = bucketIndex(newBytes);
|
|
200
|
+
if (newBytes <= slot.buffer.byteLength) {
|
|
232
201
|
// Same bucket: re-slice the existing backing ArrayBuffer to the new
|
|
233
202
|
// logical length. No transfer; old view stays valid until next grow.
|
|
234
203
|
slot.byteLength = newBytes;
|
|
@@ -236,19 +205,23 @@ export class BufferPool {
|
|
|
236
205
|
return ok(slot.view);
|
|
237
206
|
}
|
|
238
207
|
// Cross-bucket: detach old buffer + carry old bytes into the new bucket.
|
|
239
|
-
const newBucketBytes = SIZE_CLASSES[newIdx]
|
|
208
|
+
const newBucketBytes = SIZE_CLASSES[newIdx] ?? Math.max(newBytes, slot.buffer.byteLength * 2);
|
|
240
209
|
const oldByteLength = slot.byteLength;
|
|
241
210
|
let nextBuffer: ArrayBuffer;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
211
|
+
try {
|
|
212
|
+
if (HAS_TRANSFER) {
|
|
213
|
+
// ES2024 transfer: copy contents into a fresh ArrayBuffer of the new
|
|
214
|
+
// bucket size and detach the source. The transferred buffer keeps the
|
|
215
|
+
// old prefix bytes intact.
|
|
216
|
+
nextBuffer = (
|
|
217
|
+
slot.buffer as unknown as { transfer(newByteLength: number): ArrayBuffer }
|
|
218
|
+
).transfer(newBucketBytes);
|
|
219
|
+
} /* istanbul ignore next -- fallback only on runtimes without ES2024 transfer() */ else {
|
|
220
|
+
nextBuffer = new ArrayBuffer(newBucketBytes);
|
|
221
|
+
new Uint8Array(nextBuffer).set(new Uint8Array(slot.buffer, 0, oldByteLength));
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
return err(new ManagedBufferOutOfBoundsError(newBytes, slot.buffer.byteLength));
|
|
252
225
|
}
|
|
253
226
|
slot.sizeClassIdx = newIdx;
|
|
254
227
|
slot.buffer = nextBuffer;
|
|
@@ -268,11 +241,11 @@ export class BufferPool {
|
|
|
268
241
|
if (slot === undefined) return ok(undefined);
|
|
269
242
|
if (!slot.live) return ok(undefined);
|
|
270
243
|
slot.live = false;
|
|
271
|
-
if (slot.sizeClassIdx >= 0) {
|
|
244
|
+
if (slot.sizeClassIdx >= 0 && slot.sizeClassIdx < SIZE_CLASSES.length) {
|
|
272
245
|
const bucket = this.freeBuckets[slot.sizeClassIdx];
|
|
273
246
|
if (bucket !== undefined) bucket.push(id);
|
|
274
247
|
} else {
|
|
275
|
-
// Zero-length slot:
|
|
248
|
+
// Zero-length or dedicated slot: drop the slot record entirely so
|
|
276
249
|
// the id is not retained.
|
|
277
250
|
this.slots.delete(id);
|
|
278
251
|
}
|
|
@@ -301,7 +274,7 @@ export class BufferPool {
|
|
|
301
274
|
const slot = this.slots.get(id);
|
|
302
275
|
if (slot === undefined || !slot.live) return 0;
|
|
303
276
|
if (slot.sizeClassIdx < 0) return 0;
|
|
304
|
-
return
|
|
277
|
+
return slot.buffer.byteLength;
|
|
305
278
|
}
|
|
306
279
|
|
|
307
280
|
/**
|
|
@@ -334,7 +307,7 @@ export class BufferPool {
|
|
|
334
307
|
}
|
|
335
308
|
return err(new ManagedBufferOutOfBoundsError(newByteLength, 0));
|
|
336
309
|
}
|
|
337
|
-
const bucketBytes =
|
|
310
|
+
const bucketBytes = slot.buffer.byteLength;
|
|
338
311
|
if (newByteLength > bucketBytes) {
|
|
339
312
|
return err(new ManagedBufferOutOfBoundsError(newByteLength, bucketBytes));
|
|
340
313
|
}
|
|
@@ -432,9 +432,8 @@ export class WorldComponentAccess {
|
|
|
432
432
|
slotId = column.view[row] as number;
|
|
433
433
|
}
|
|
434
434
|
const requiredBytes = (currentLength + 1) * meta.byteSize;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return err(new ManagedBufferOutOfBoundsError(requiredBytes, 262_144));
|
|
435
|
+
if (!Number.isSafeInteger(requiredBytes)) {
|
|
436
|
+
return err(new ManagedBufferOutOfBoundsError(requiredBytes, Number.MAX_SAFE_INTEGER));
|
|
438
437
|
}
|
|
439
438
|
|
|
440
439
|
const preparation: RelationshipPreparation = {
|