@forgeax/engine-ecs 0.1.23 → 0.1.25
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 +42 -1
- package/dist/buffer-pool.d.ts +4 -5
- package/dist/buffer-pool.d.ts.map +1 -1
- package/dist/component.d.ts +9 -6
- package/dist/component.d.ts.map +1 -1
- package/dist/errors/query-and-component-errors.d.ts +1 -1
- package/dist/errors/query-and-component-errors.d.ts.map +1 -1
- package/dist/externalization/index.mjs.map +1 -1
- package/dist/index.mjs +597 -152
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.ts +3 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +6 -1
- package/dist/internal.mjs.map +1 -1
- package/dist/projection/index.mjs.map +1 -1
- package/dist/query/derived-range-writer.d.ts +14 -0
- package/dist/query/derived-range-writer.d.ts.map +1 -1
- package/dist/query/query.d.ts.map +1 -1
- package/dist/shared.mjs.map +1 -1
- package/dist/world-component-access.d.ts +63 -18
- package/dist/world-component-access.d.ts.map +1 -1
- package/dist/world-component-storage.d.ts +26 -10
- package/dist/world-component-storage.d.ts.map +1 -1
- package/dist/world-entity-lifecycle.d.ts.map +1 -1
- package/dist/world-internal.d.ts +1 -1
- package/dist/world-internal.d.ts.map +1 -1
- package/dist/world.d.ts +5 -0
- package/dist/world.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/archetype.unit.test.ts +38 -10
- package/src/__tests__/relationship-index.test.ts +147 -1
- package/src/buffer-pool.ts +47 -74
- package/src/component.ts +9 -6
- package/src/errors/query-and-component-errors.ts +4 -1
- package/src/internal.ts +3 -0
- package/src/query/derived-range-writer.ts +111 -0
- package/src/query/query.ts +26 -1
- package/src/world-component-access.ts +427 -127
- package/src/world-component-storage.ts +104 -18
- package/src/world-entity-lifecycle.ts +49 -9
- package/src/world-internal.ts +5 -0
- package/src/world.ts +52 -3
|
@@ -114,6 +114,79 @@ export class ComponentStorage {
|
|
|
114
114
|
return reinterpretSlotBytes(liveBytes, arrayMeta.elementType, elementCount);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Read the live length of an array field without materialising a typed view.
|
|
119
|
+
* Relationship consumers use this narrow storage seam for hot reverse-list
|
|
120
|
+
* walks; the target array remains owned by the ECS column and no snapshot is
|
|
121
|
+
* created per entity.
|
|
122
|
+
*/
|
|
123
|
+
readArrayLength(
|
|
124
|
+
arch: Archetype,
|
|
125
|
+
component: Component,
|
|
126
|
+
row: number,
|
|
127
|
+
fieldName: string,
|
|
128
|
+
): number | undefined {
|
|
129
|
+
const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
|
|
130
|
+
if (fieldCols === undefined) return undefined;
|
|
131
|
+
const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
|
|
132
|
+
if (arrayMeta === undefined) return undefined;
|
|
133
|
+
if (arrayMeta.length !== undefined) return arrayMeta.length;
|
|
134
|
+
const count = fieldCols.get(arrayCountColumnName(fieldName))?.view[row];
|
|
135
|
+
return typeof count === 'number' ? count : 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Read one array element directly from its backing column/BufferPool slot.
|
|
140
|
+
* The relationship target vocabulary is `array<entity>`, so its hot path
|
|
141
|
+
* decodes the packed u32 in-place and does not allocate a TypedArray view.
|
|
142
|
+
*/
|
|
143
|
+
readArrayElement(
|
|
144
|
+
arch: Archetype,
|
|
145
|
+
component: Component,
|
|
146
|
+
row: number,
|
|
147
|
+
fieldName: string,
|
|
148
|
+
index: number,
|
|
149
|
+
): number | undefined {
|
|
150
|
+
if (!Number.isSafeInteger(index) || index < 0) return undefined;
|
|
151
|
+
const fieldCols = this.table(arch).storage.get(componentId(component))?.fields;
|
|
152
|
+
if (fieldCols === undefined) return undefined;
|
|
153
|
+
const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
|
|
154
|
+
if (arrayMeta === undefined) return undefined;
|
|
155
|
+
const length = this.readArrayLength(arch, component, row, fieldName);
|
|
156
|
+
if (length === undefined || index >= length) return undefined;
|
|
157
|
+
const col = fieldCols.get(fieldName);
|
|
158
|
+
if (col === undefined) return undefined;
|
|
159
|
+
if (arrayMeta.length !== undefined) {
|
|
160
|
+
return col.view[row * col.arity + index] as number;
|
|
161
|
+
}
|
|
162
|
+
const slotId = col.view[row] as number;
|
|
163
|
+
const bytes = this.bufferPool.view(slotId);
|
|
164
|
+
if (arrayMeta.elementType === 'entity') {
|
|
165
|
+
const byteOffset = index * 4;
|
|
166
|
+
if (byteOffset + 4 > bytes.byteLength) return undefined;
|
|
167
|
+
return (
|
|
168
|
+
((bytes[byteOffset] ?? 0) |
|
|
169
|
+
((bytes[byteOffset + 1] ?? 0) << 8) |
|
|
170
|
+
((bytes[byteOffset + 2] ?? 0) << 16) |
|
|
171
|
+
((bytes[byteOffset + 3] ?? 0) << 24)) >>>
|
|
172
|
+
0
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return this.readArrayElementAt(bytes, index, arrayMeta.elementType);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Read one scalar field without constructing a component shape. */
|
|
179
|
+
readFieldValue(
|
|
180
|
+
arch: Archetype,
|
|
181
|
+
component: Component,
|
|
182
|
+
row: number,
|
|
183
|
+
fieldName: string,
|
|
184
|
+
): number | undefined {
|
|
185
|
+
return this.table(arch).storage.get(componentId(component))?.fields.get(fieldName)?.view[row] as
|
|
186
|
+
| number
|
|
187
|
+
| undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
117
190
|
// ──────────────────────────────────────────────────────────────────────────
|
|
118
191
|
// Internal — archetype data read/write
|
|
119
192
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -175,10 +248,10 @@ export class ComponentStorage {
|
|
|
175
248
|
} else {
|
|
176
249
|
const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
|
|
177
250
|
if (arrayMeta !== undefined) {
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
251
|
+
// Internal row path: materialise a fresh TypedArray view each call
|
|
252
|
+
// without copying its backing bytes. Public World.get detaches a
|
|
253
|
+
// relationship target array after this read; ECS relationship and
|
|
254
|
+
// Scene owners use this readRow seam as zero-copy storage access.
|
|
182
255
|
(out as Record<string, unknown>)[fieldName] = this.materializeArrayView(
|
|
183
256
|
arch,
|
|
184
257
|
component,
|
|
@@ -215,6 +288,7 @@ export class ComponentStorage {
|
|
|
215
288
|
component: Component<string, S>,
|
|
216
289
|
row: number,
|
|
217
290
|
value: ShapeOf<S>,
|
|
291
|
+
options?: { readonly skipVariableArrayInitialization?: boolean },
|
|
218
292
|
): void {
|
|
219
293
|
const localId = componentId(component);
|
|
220
294
|
const fieldCols = this.table(arch).storage.get(localId)?.fields;
|
|
@@ -291,6 +365,16 @@ export class ComponentStorage {
|
|
|
291
365
|
} else {
|
|
292
366
|
const arrayMeta = componentDefinition(component).fields[fieldName]?.arrayMeta;
|
|
293
367
|
if (arrayMeta !== undefined) {
|
|
368
|
+
if (options?.skipVariableArrayInitialization === true && arrayMeta.length === undefined) {
|
|
369
|
+
// Relationship preparation can reserve the first target slot
|
|
370
|
+
// before the mirror archetype is created. Leave the fresh row at
|
|
371
|
+
// the sentinel until the owner installs that reservation; this
|
|
372
|
+
// keeps an injected alloc failure out of the structural path.
|
|
373
|
+
col.view[row] = 0;
|
|
374
|
+
const countCol = fieldCols.get(arrayCountColumnName(fieldName));
|
|
375
|
+
if (countCol !== undefined) countCol.view[row] = 0;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
294
378
|
// M1 spawn path for array<T> / array<T,N> fields (D-3 double-
|
|
295
379
|
// column for variable; single column for fixed).
|
|
296
380
|
// Spawn path: no prior-slot release (fresh rows carry stale debris
|
|
@@ -810,20 +894,19 @@ export class ComponentStorage {
|
|
|
810
894
|
}
|
|
811
895
|
|
|
812
896
|
/**
|
|
813
|
-
* Materialise a fresh `TypedArray`
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
897
|
+
* Materialise a fresh `TypedArray` view for an `array<T,N>` / `array<T>`
|
|
898
|
+
* field at `row`. This is the internal row-storage path: the returned view
|
|
899
|
+
* aliases the live column or BufferPool slot. `WorldComponentAccess.get`
|
|
900
|
+
* detaches relationship target arrays at the public boundary; internal ECS
|
|
901
|
+
* relationship maintenance and Scene traversal keep this zero-copy path.
|
|
817
902
|
*
|
|
818
903
|
* **Transient view contract (feat-20260602):** for fixed `array<T,N>`
|
|
819
904
|
* columns the returned `TypedArray` aliases the inline column buffer
|
|
820
905
|
* directly (`col.view.subarray(row * arity, ...)`); for variable
|
|
821
906
|
* `array<T>` columns it aliases the live `BufferPool` slot bytes
|
|
822
|
-
* (zero-copy; `pool.view(slotId)` is the SSOT byte region). In both
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
* and the implementation may switch to a copy in the future without
|
|
826
|
-
* breaking AI users who consume only `length` / index reads.
|
|
907
|
+
* (zero-copy; `pool.view(slotId)` is the SSOT byte region). In both cases
|
|
908
|
+
* the view is valid only until the next structural change. Internal callers
|
|
909
|
+
* must not write through it except via the owner mutation helpers.
|
|
827
910
|
*
|
|
828
911
|
* For variable arrays the typed-array length matches the live count from
|
|
829
912
|
* the sidecar `<fieldName>:count` column; for fixed arrays it matches the
|
|
@@ -1011,11 +1094,14 @@ export class ComponentStorage {
|
|
|
1011
1094
|
|
|
1012
1095
|
/**
|
|
1013
1096
|
* Reinterpret a `BufferPool` slot's `Uint8Array` byte region as the typed
|
|
1014
|
-
* view for `elementType`, sliced to `elementCount` elements.
|
|
1015
|
-
*
|
|
1016
|
-
* aliases live slot bytes
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1097
|
+
* view for `elementType`, sliced to `elementCount` elements. This is the
|
|
1098
|
+
* internal materialization used by `readRow`/`materializeArrayView`: the
|
|
1099
|
+
* returned view aliases live slot bytes. `WorldComponentAccess.get` detaches
|
|
1100
|
+
* relationship-target `array<entity>` values at the public boundary; other
|
|
1101
|
+
* public array fields retain the existing transient live-view contract.
|
|
1102
|
+
* `_getArrayView` and other internal owners always retain zero-copy access.
|
|
1103
|
+
* `entity` element fields surface as `Uint32Array` (Entity packs slot+gen
|
|
1104
|
+
* into u32).
|
|
1019
1105
|
*/
|
|
1020
1106
|
/**
|
|
1021
1107
|
* Element-byte-width for a managed-array element type, read off the global
|
|
@@ -65,6 +65,34 @@ export function spawnCore(
|
|
|
65
65
|
if (enumErr !== null) return err(enumErr as unknown as EcsError);
|
|
66
66
|
filledData.push(filled as Record<string, unknown>);
|
|
67
67
|
}
|
|
68
|
+
// Reserve every relationship target before allocating the new source row.
|
|
69
|
+
// In particular, an injected BufferPool failure must not leave a source
|
|
70
|
+
// entity, mirror component, or mutation epoch behind.
|
|
71
|
+
const relationshipPreparations: unknown[] = [];
|
|
72
|
+
if (!internal) {
|
|
73
|
+
for (let index = 0; index < componentDatas.length; index += 1) {
|
|
74
|
+
const componentData = componentDatas[index];
|
|
75
|
+
const value = filledData[index];
|
|
76
|
+
if (
|
|
77
|
+
componentData === undefined ||
|
|
78
|
+
value === undefined ||
|
|
79
|
+
relationshipRole(componentData.component as Component)?.kind !== 'source'
|
|
80
|
+
) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const prepared = world[worldInternal].prepareRelationshipInsert(
|
|
84
|
+
componentData.component as Component,
|
|
85
|
+
value,
|
|
86
|
+
);
|
|
87
|
+
if (!prepared.ok) {
|
|
88
|
+
for (const reservation of relationshipPreparations) {
|
|
89
|
+
world[worldInternal].releaseRelationshipPreparation(reservation);
|
|
90
|
+
}
|
|
91
|
+
return prepared;
|
|
92
|
+
}
|
|
93
|
+
relationshipPreparations[index] = prepared.value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
68
96
|
const indexSlot = world[worldInternal].allocateIndex();
|
|
69
97
|
const record = world[worldInternal].getRecords()[indexSlot];
|
|
70
98
|
if (record === undefined)
|
|
@@ -101,8 +129,14 @@ export function spawnCore(
|
|
|
101
129
|
spawnedEntity,
|
|
102
130
|
cd.component as Component,
|
|
103
131
|
filled,
|
|
132
|
+
relationshipPreparations[i],
|
|
104
133
|
);
|
|
105
|
-
if (!relation.ok)
|
|
134
|
+
if (!relation.ok) {
|
|
135
|
+
for (const reservation of relationshipPreparations) {
|
|
136
|
+
world[worldInternal].releaseRelationshipPreparation(reservation);
|
|
137
|
+
}
|
|
138
|
+
return relation;
|
|
139
|
+
}
|
|
106
140
|
}
|
|
107
141
|
}
|
|
108
142
|
world[worldInternal].markStructureChanged();
|
|
@@ -199,7 +233,8 @@ export function worldAddChild<S extends ComponentSchema>(
|
|
|
199
233
|
data: Partial<InputShapeOf<S>>,
|
|
200
234
|
): Result<void, EcsError> {
|
|
201
235
|
const holderComp = component as Component;
|
|
202
|
-
|
|
236
|
+
const role = relationshipRole(holderComp);
|
|
237
|
+
if (role?.kind !== 'source') {
|
|
203
238
|
return err(new ComponentNotPresentError(child as number, component.name));
|
|
204
239
|
}
|
|
205
240
|
|
|
@@ -231,7 +266,6 @@ export function worldAddChild<S extends ComponentSchema>(
|
|
|
231
266
|
);
|
|
232
267
|
}
|
|
233
268
|
|
|
234
|
-
const role = relationshipRole(holderComp);
|
|
235
269
|
if (child === parent && !(role?.kind === 'source' && role.allowSelf)) {
|
|
236
270
|
return err(new RelationshipSelfCycleError(component.name, child as number, child as number));
|
|
237
271
|
}
|
|
@@ -308,10 +342,10 @@ export function worldReparent<S extends ComponentSchema>(
|
|
|
308
342
|
data: Partial<InputShapeOf<S>>,
|
|
309
343
|
): Result<void, EcsError> {
|
|
310
344
|
const holderComp = component as Component;
|
|
311
|
-
|
|
345
|
+
const role = relationshipRole(holderComp);
|
|
346
|
+
if (role?.kind !== 'source') {
|
|
312
347
|
return err(new ComponentNotPresentError(child as number, component.name));
|
|
313
348
|
}
|
|
314
|
-
const role = relationshipRole(holderComp);
|
|
315
349
|
if (child === newParent && !(role?.kind === 'source' && role.allowSelf)) {
|
|
316
350
|
return err(
|
|
317
351
|
new RelationshipSelfCycleError(component.name, child as number, newParent as number),
|
|
@@ -348,13 +382,19 @@ export function worldReparent<S extends ComponentSchema>(
|
|
|
348
382
|
}),
|
|
349
383
|
);
|
|
350
384
|
}
|
|
385
|
+
const payload = {
|
|
386
|
+
...(data as Record<string, unknown>),
|
|
387
|
+
[role.sourceField]: newParent,
|
|
388
|
+
} as Partial<InputShapeOf<S>>;
|
|
351
389
|
if (
|
|
352
|
-
childArch.components.some((
|
|
390
|
+
childArch.components.some((candidate) => componentId(candidate) === componentId(holderComp))
|
|
353
391
|
) {
|
|
354
|
-
|
|
355
|
-
|
|
392
|
+
// Existing exclusive sources are updated through the same owner-level
|
|
393
|
+
// write barrier as `world.set`; remove+add would expose a partial mirror
|
|
394
|
+
// state and would invalidate unrelated query spans.
|
|
395
|
+
return world.set(child, component as never, payload as never);
|
|
356
396
|
}
|
|
357
|
-
return world.addComponent(child, { component, data });
|
|
397
|
+
return world.addComponent(child, { component, data: payload });
|
|
358
398
|
}
|
|
359
399
|
|
|
360
400
|
/** Iterate ancestors in child-to-root order while safely terminating corrupt cycles. */
|
package/src/world-internal.ts
CHANGED
|
@@ -22,6 +22,9 @@ type InternalName =
|
|
|
22
22
|
| 'cancelPendingEntity'
|
|
23
23
|
| 'despawnCore'
|
|
24
24
|
| 'getArrayView'
|
|
25
|
+
| 'getArrayLength'
|
|
26
|
+
| 'getArrayElement'
|
|
27
|
+
| 'getFieldValue'
|
|
25
28
|
| 'getBufferPool'
|
|
26
29
|
| 'getClockWriter'
|
|
27
30
|
| 'getComponentChange'
|
|
@@ -53,6 +56,7 @@ type InternalName =
|
|
|
53
56
|
| 'nextMutationEpoch'
|
|
54
57
|
| 'poisonExecution'
|
|
55
58
|
| 'publishDerivedRange'
|
|
59
|
+
| 'prepareRelationshipInsert'
|
|
56
60
|
| 'preflightComponentData'
|
|
57
61
|
| 'readRow'
|
|
58
62
|
| 'recordStructuralEvidence'
|
|
@@ -60,6 +64,7 @@ type InternalName =
|
|
|
60
64
|
| 'relationshipOnInsert'
|
|
61
65
|
| 'relationshipOnRemove'
|
|
62
66
|
| 'releaseManagedRefsOnRow'
|
|
67
|
+
| 'releaseRelationshipPreparation'
|
|
63
68
|
| 'removeComponentCore'
|
|
64
69
|
| 'routeError'
|
|
65
70
|
| 'restoreMutationEpoch'
|
package/src/world.ts
CHANGED
|
@@ -60,7 +60,11 @@ import {
|
|
|
60
60
|
} from './execution/shared-kernel';
|
|
61
61
|
import type { QueryDescriptor } from './query/query';
|
|
62
62
|
import { createQuery, type Query, type QueryCreationError } from './query/query';
|
|
63
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
isRelationshipTarget,
|
|
65
|
+
type RelationshipTargetComponent,
|
|
66
|
+
relationshipRole,
|
|
67
|
+
} from './relationship-index';
|
|
64
68
|
import { createResourceStore, type ResourceStore } from './resource';
|
|
65
69
|
import { createSchedule, type Schedule, type SystemDescriptor, type SystemSet } from './schedule';
|
|
66
70
|
import { FixedUpdate, Update } from './schedule-token';
|
|
@@ -433,6 +437,9 @@ export class World {
|
|
|
433
437
|
cancelPendingEntity: this.internalcancelPendingEntity.bind(this),
|
|
434
438
|
despawnCore: this.internaldespawnCore.bind(this),
|
|
435
439
|
getArrayView: this.internalgetArrayView.bind(this),
|
|
440
|
+
getArrayLength: this.internalgetArrayLength.bind(this),
|
|
441
|
+
getArrayElement: this.internalgetArrayElement.bind(this),
|
|
442
|
+
getFieldValue: this.internalgetFieldValue.bind(this),
|
|
436
443
|
getBufferPool: this.internalgetBufferPool.bind(this),
|
|
437
444
|
getClockWriter: this.internalgetClockWriter.bind(this),
|
|
438
445
|
getComponentChange: this.internalgetComponentChange.bind(this),
|
|
@@ -463,6 +470,7 @@ export class World {
|
|
|
463
470
|
materializePendingEntity: this.internalmaterializePendingEntity.bind(this),
|
|
464
471
|
nextMutationEpoch: this.internalnextMutationEpoch.bind(this),
|
|
465
472
|
poisonExecution: this.internalpoisonExecution.bind(this),
|
|
473
|
+
prepareRelationshipInsert: this.internalprepareRelationshipInsert.bind(this),
|
|
466
474
|
publishDerivedRange: this.internalpublishDerivedRange.bind(this),
|
|
467
475
|
preflightComponentData: this.internalpreflightComponentData.bind(this),
|
|
468
476
|
readRow: this.internalreadRow.bind(this),
|
|
@@ -471,6 +479,7 @@ export class World {
|
|
|
471
479
|
relationshipOnInsert: this.internalrelationshipOnInsert.bind(this),
|
|
472
480
|
relationshipOnRemove: this.internalrelationshipOnRemove.bind(this),
|
|
473
481
|
releaseManagedRefsOnRow: this.internalreleaseManagedRefsOnRow.bind(this),
|
|
482
|
+
releaseRelationshipPreparation: this.internalreleaseRelationshipPreparation.bind(this),
|
|
474
483
|
removeComponentCore: this.internalremoveComponentCore.bind(this),
|
|
475
484
|
routeError: this.internalrouteError.bind(this),
|
|
476
485
|
restoreMutationEpoch: this.internalrestoreMutationEpoch.bind(this),
|
|
@@ -679,7 +688,12 @@ export class World {
|
|
|
679
688
|
component: Component,
|
|
680
689
|
value: Record<string, unknown>,
|
|
681
690
|
): Result<void, EcsError> {
|
|
682
|
-
return this.componentAccess.set(
|
|
691
|
+
return this.componentAccess.set(
|
|
692
|
+
entity,
|
|
693
|
+
component,
|
|
694
|
+
value as never,
|
|
695
|
+
relationshipRole(component)?.kind === 'source',
|
|
696
|
+
);
|
|
683
697
|
}
|
|
684
698
|
|
|
685
699
|
/** Query facade read that does not re-enter the public World API. */
|
|
@@ -1109,6 +1123,31 @@ export class World {
|
|
|
1109
1123
|
return this.componentAccess._getArrayView(entity, component, fieldName);
|
|
1110
1124
|
}
|
|
1111
1125
|
|
|
1126
|
+
private internalgetArrayLength(
|
|
1127
|
+
entity: EntityHandle,
|
|
1128
|
+
component: Component,
|
|
1129
|
+
fieldName: string,
|
|
1130
|
+
): number | undefined {
|
|
1131
|
+
return this.componentAccess._getArrayLength(entity, component, fieldName);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
private internalgetArrayElement(
|
|
1135
|
+
entity: EntityHandle,
|
|
1136
|
+
component: Component,
|
|
1137
|
+
fieldName: string,
|
|
1138
|
+
index: number,
|
|
1139
|
+
): number | undefined {
|
|
1140
|
+
return this.componentAccess._getArrayElement(entity, component, fieldName, index);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
private internalgetFieldValue(
|
|
1144
|
+
entity: EntityHandle,
|
|
1145
|
+
component: Component,
|
|
1146
|
+
fieldName: string,
|
|
1147
|
+
): number | undefined {
|
|
1148
|
+
return this.componentAccess._getFieldValue(entity, component, fieldName);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1112
1151
|
set<S extends ComponentSchema, C extends Component<string, S>>(
|
|
1113
1152
|
entity: EntityHandle,
|
|
1114
1153
|
component: C & WritableComponent<C>,
|
|
@@ -1222,8 +1261,18 @@ export class World {
|
|
|
1222
1261
|
h: EntityHandle,
|
|
1223
1262
|
c: Component,
|
|
1224
1263
|
v: Record<string, unknown>,
|
|
1264
|
+
preparation?: unknown,
|
|
1225
1265
|
): Result<void, EcsError> {
|
|
1226
|
-
return this.componentAccess.relationshipOnInsert(h, c, v);
|
|
1266
|
+
return this.componentAccess.relationshipOnInsert(h, c, v, preparation as never);
|
|
1267
|
+
}
|
|
1268
|
+
/** */ private internalprepareRelationshipInsert(
|
|
1269
|
+
c: Component,
|
|
1270
|
+
v: Record<string, unknown>,
|
|
1271
|
+
): Result<unknown, EcsError> {
|
|
1272
|
+
return this.componentAccess.prepareRelationshipInsert(c, v);
|
|
1273
|
+
}
|
|
1274
|
+
/** */ private internalreleaseRelationshipPreparation(preparation: unknown): void {
|
|
1275
|
+
this.componentAccess.releaseRelationshipPreparation(preparation as never);
|
|
1227
1276
|
}
|
|
1228
1277
|
/** */ private internalrelationshipOnRemove(
|
|
1229
1278
|
h: EntityHandle,
|