@forgeax/engine-ecs 0.1.21 → 0.1.24
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 +83 -17
- package/dist/__tests__/changed-block-summary.candidate.test.d.ts +2 -0
- package/dist/__tests__/changed-block-summary.candidate.test.d.ts.map +1 -0
- package/dist/__tests__/component-requirements.unit.test.d.ts +2 -0
- package/dist/__tests__/component-requirements.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-version-surface.test.d.ts +2 -0
- package/dist/__tests__/component-version-surface.test.d.ts.map +1 -0
- package/dist/__tests__/derived-range-writer.contract.test-d.d.ts +2 -0
- package/dist/__tests__/derived-range-writer.contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/derived-range-writer.contract.test.d.ts +2 -0
- package/dist/__tests__/derived-range-writer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts +2 -0
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/structural-evidence.contract.test.d.ts +2 -0
- package/dist/__tests__/structural-evidence.contract.test.d.ts.map +1 -0
- package/dist/component-schema.d.ts +17 -0
- package/dist/component-schema.d.ts.map +1 -1
- package/dist/component.d.ts +16 -7
- 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/errors/validation-errors.d.ts +24 -0
- package/dist/errors/validation-errors.d.ts.map +1 -1
- package/dist/errors.d.ts +14 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/externalization/index.mjs.map +1 -1
- package/dist/index.mjs +977 -255
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.ts +18 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +20 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/projection/index.d.ts +27 -52
- package/dist/projection/index.d.ts.map +1 -1
- package/dist/projection/index.mjs +66 -76
- package/dist/projection/index.mjs.map +1 -1
- package/dist/query/derived-range-writer.d.ts +62 -0
- package/dist/query/derived-range-writer.d.ts.map +1 -0
- package/dist/query/query.d.ts +1 -1
- package/dist/query/query.d.ts.map +1 -1
- package/dist/relationship-index.d.ts +8 -0
- package/dist/relationship-index.d.ts.map +1 -1
- package/dist/shared-ref-store.d.ts +5 -9
- package/dist/shared-ref-store.d.ts.map +1 -1
- package/dist/shared.mjs.map +1 -1
- package/dist/storage/change-detection.d.ts +1 -3
- package/dist/storage/change-detection.d.ts.map +1 -1
- package/dist/storage/structural-evidence.d.ts +30 -0
- package/dist/storage/structural-evidence.d.ts.map +1 -0
- package/dist/world-component-access.d.ts +65 -19
- 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-core.d.ts +2 -2
- package/dist/world-core.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 +15 -14
- package/dist/world.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/changed-block-summary.candidate.test.ts +57 -0
- package/src/__tests__/component-requirements.unit.test.ts +76 -0
- package/src/__tests__/component-version-surface.test.ts +45 -0
- package/src/__tests__/derived-range-writer.contract.test-d.ts +35 -0
- package/src/__tests__/derived-range-writer.contract.test.ts +339 -0
- package/src/__tests__/errors.unit.test.ts +21 -5
- package/src/__tests__/externalization-render-read-lease.test-d.ts +1 -1
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +15 -15
- package/src/__tests__/query-storage-trends.perf.test.ts +19 -0
- package/src/__tests__/query.unit.test.ts +10 -6
- package/src/__tests__/relationship-index.test.ts +147 -1
- package/src/__tests__/shared-ref-store.unit.test.ts +3 -8
- package/src/__tests__/structural-evidence.contract.test-d.ts +6 -0
- package/src/__tests__/structural-evidence.contract.test.ts +49 -0
- package/src/component-schema.ts +51 -0
- package/src/component.ts +21 -8
- package/src/errors/query-and-component-errors.ts +4 -1
- package/src/errors/validation-errors.ts +30 -0
- package/src/errors.ts +29 -5
- package/src/internal.ts +59 -0
- package/src/projection/index.ts +68 -149
- package/src/query/derived-range-writer.ts +443 -0
- package/src/query/query.ts +115 -16
- package/src/relationship-index.ts +13 -1
- package/src/shared-ref-store.ts +16 -24
- package/src/storage/change-detection.ts +0 -15
- package/src/storage/structural-evidence.ts +64 -0
- package/src/world-component-access.ts +479 -130
- package/src/world-component-storage.ts +104 -18
- package/src/world-core.ts +2 -2
- package/src/world-entity-lifecycle.ts +59 -10
- package/src/world-internal.ts +10 -5
- package/src/world.ts +97 -53
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/world-change-journal.unit.test.d.ts +0 -2
- package/dist/__tests__/world-change-journal.unit.test.d.ts.map +0 -1
- package/dist/__tests__/world-internal-identity.unit.test.d.ts +0 -2
- package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +0 -1
- package/dist/world-change-journal.d.ts +0 -34
- package/dist/world-change-journal.d.ts.map +0 -1
- package/src/__tests__/world-change-journal.unit.test.ts +0 -97
- package/src/__tests__/world-internal-identity.unit.test.ts +0 -17
- package/src/world-change-journal.ts +0 -84
|
@@ -24,6 +24,14 @@ export interface RelationshipOptions {
|
|
|
24
24
|
readonly sourceField: string;
|
|
25
25
|
readonly targetName: string;
|
|
26
26
|
readonly targetField: string;
|
|
27
|
+
/**
|
|
28
|
+
* Components materialized with the writable source side. This is the
|
|
29
|
+
* relationship equivalent of `defineComponent(..., { requires })`: the
|
|
30
|
+
* dependency is resolved once at the structural boundary, never by a frame
|
|
31
|
+
* system. It applies only to the source; the reverse target remains an
|
|
32
|
+
* engine-owned projection.
|
|
33
|
+
*/
|
|
34
|
+
readonly sourceRequires?: readonly Component[];
|
|
27
35
|
readonly exclusive?: boolean;
|
|
28
36
|
readonly linkedSpawn?: boolean;
|
|
29
37
|
/** Allow a source to point at its own holder (e.g. self-animated entities). */
|
|
@@ -75,7 +83,11 @@ export function defineRelationship<
|
|
|
75
83
|
};
|
|
76
84
|
const target = defineComponent(options.targetName, targetFields);
|
|
77
85
|
const sourceFields: FieldsInput = { [options.sourceField]: { type: 'entity' } };
|
|
78
|
-
const source = defineComponent(
|
|
86
|
+
const source = defineComponent(
|
|
87
|
+
options.sourceName,
|
|
88
|
+
sourceFields,
|
|
89
|
+
options.sourceRequires === undefined ? undefined : { requires: options.sourceRequires },
|
|
90
|
+
);
|
|
79
91
|
|
|
80
92
|
roles.set(source, {
|
|
81
93
|
kind: 'source',
|
package/src/shared-ref-store.ts
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
SharedRefStaleError,
|
|
85
85
|
} from './errors';
|
|
86
86
|
|
|
87
|
-
const
|
|
87
|
+
const SHARED_REF_RELEASE_EVIDENCE_CAPACITY = 4096;
|
|
88
88
|
|
|
89
89
|
export interface SharedRefMutation {
|
|
90
90
|
readonly epoch: number;
|
|
@@ -98,17 +98,10 @@ export interface SharedRefReleaseEvidence {
|
|
|
98
98
|
readonly evidence: 'released';
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
export
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
readonly records: readonly SharedRefMutation[];
|
|
106
|
-
}
|
|
107
|
-
| {
|
|
108
|
-
readonly status: 'overflow';
|
|
109
|
-
readonly cursor: number;
|
|
110
|
-
readonly oldestAvailable: number;
|
|
111
|
-
};
|
|
101
|
+
export interface SharedRefMutationRead {
|
|
102
|
+
readonly cursor: number;
|
|
103
|
+
readonly records: readonly SharedRefMutation[];
|
|
104
|
+
}
|
|
112
105
|
|
|
113
106
|
// MAX_SLOT is now imported from @forgeax/engine-types (codec SSOT, D-1).
|
|
114
107
|
// The local constant is removed to avoid drift (AC-15).
|
|
@@ -147,7 +140,8 @@ export class SharedRefStore {
|
|
|
147
140
|
>();
|
|
148
141
|
private nextSlot = BUILTIN_BASE;
|
|
149
142
|
private mutationEpoch = 0;
|
|
150
|
-
|
|
143
|
+
/** Latest published mutation epoch per live handle; not an event journal. */
|
|
144
|
+
private readonly mutationEpochs = new Map<number, number>();
|
|
151
145
|
private readonly releaseJournal: SharedRefReleaseEvidence[] = [];
|
|
152
146
|
|
|
153
147
|
/**
|
|
@@ -271,10 +265,7 @@ export class SharedRefStore {
|
|
|
271
265
|
throw new RangeError('SharedRefStore mutation epoch exhausted');
|
|
272
266
|
}
|
|
273
267
|
this.mutationEpoch += 1;
|
|
274
|
-
this.
|
|
275
|
-
if (this.mutationJournal.length > SHARED_REF_MUTATION_JOURNAL_CAPACITY) {
|
|
276
|
-
this.mutationJournal.shift();
|
|
277
|
-
}
|
|
268
|
+
this.mutationEpochs.set(unwrapHandle(handle), this.mutationEpoch);
|
|
278
269
|
return ok(undefined);
|
|
279
270
|
}
|
|
280
271
|
|
|
@@ -283,16 +274,16 @@ export class SharedRefStore {
|
|
|
283
274
|
return this.mutationEpoch;
|
|
284
275
|
}
|
|
285
276
|
|
|
286
|
-
/** Read
|
|
277
|
+
/** Read each live handle whose latest published mutation is after `cursor`. */
|
|
287
278
|
readChangesSince(cursor: number): SharedRefMutationRead {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
279
|
+
const records: SharedRefMutation[] = [];
|
|
280
|
+
for (const [handle, epoch] of this.mutationEpochs) {
|
|
281
|
+
if (epoch > cursor) records.push({ epoch, handle });
|
|
291
282
|
}
|
|
283
|
+
records.sort((left, right) => left.epoch - right.epoch || left.handle - right.handle);
|
|
292
284
|
return {
|
|
293
|
-
status: 'ok',
|
|
294
285
|
cursor: this.mutationEpoch,
|
|
295
|
-
records
|
|
286
|
+
records,
|
|
296
287
|
};
|
|
297
288
|
}
|
|
298
289
|
|
|
@@ -372,6 +363,7 @@ export class SharedRefStore {
|
|
|
372
363
|
}
|
|
373
364
|
this.refcounts.delete(raw);
|
|
374
365
|
this.payloads.delete(raw);
|
|
366
|
+
this.mutationEpochs.delete(raw);
|
|
375
367
|
// Gen increment + retire (AC-07): bump gen; once it would exceed MAX_GEN
|
|
376
368
|
// (gen 255 is still usable; the bump to 256 triggers retire) the slot is
|
|
377
369
|
// permanently retired — NOT pushed to freeSlots. This prevents handle
|
|
@@ -389,7 +381,7 @@ export class SharedRefStore {
|
|
|
389
381
|
evidence: 'released' as const,
|
|
390
382
|
});
|
|
391
383
|
this.releaseJournal.push(evidence);
|
|
392
|
-
if (this.releaseJournal.length >
|
|
384
|
+
if (this.releaseJournal.length > SHARED_REF_RELEASE_EVIDENCE_CAPACITY) {
|
|
393
385
|
this.releaseJournal.shift();
|
|
394
386
|
}
|
|
395
387
|
return ok(evidence);
|
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Component } from '../component';
|
|
4
4
|
import * as componentOwner from '../component';
|
|
5
|
-
import { Entity } from '../entity';
|
|
6
5
|
import { type EntityHandle, entityIndex } from '../entity-handle';
|
|
7
6
|
|
|
8
|
-
export { WorldChangeJournal, type WorldChangeRead } from '../world-change-journal';
|
|
9
|
-
|
|
10
7
|
import type { ArchetypeGraph } from './archetype-graph';
|
|
11
8
|
import { getOrCreateSparseTagSet } from './archetype-graph';
|
|
12
|
-
import type { Table } from './table';
|
|
13
9
|
|
|
14
10
|
const INITIAL_SPARSE_CAPACITY = 64;
|
|
15
11
|
|
|
@@ -218,14 +214,3 @@ export function markComponentChanged(
|
|
|
218
214
|
const tableRow = archetype.rows[location.archetypeRow] ?? -1;
|
|
219
215
|
epochs.changed[tableRow] = epoch();
|
|
220
216
|
}
|
|
221
|
-
|
|
222
|
-
export function readTableEntityRange(
|
|
223
|
-
table: Table,
|
|
224
|
-
rowStart: number,
|
|
225
|
-
rowCount: number,
|
|
226
|
-
): readonly EntityHandle[] {
|
|
227
|
-
const entities = table.storage.get(componentOwner.componentId(Entity))?.fields.get('self')
|
|
228
|
-
?.view as Uint32Array | undefined;
|
|
229
|
-
if (entities === undefined) return [];
|
|
230
|
-
return entities.subarray(rowStart, rowStart + rowCount) as unknown as readonly EntityHandle[];
|
|
231
|
-
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { EntityHandle } from '../entity-handle';
|
|
2
|
+
|
|
3
|
+
export type StructuralEvidenceKind = 'spawn' | 'despawn' | 'component-added' | 'component-removed';
|
|
4
|
+
|
|
5
|
+
export interface StructuralEvidenceInput {
|
|
6
|
+
readonly kind: StructuralEvidenceKind;
|
|
7
|
+
readonly entity: EntityHandle;
|
|
8
|
+
readonly componentId?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StructuralEvidence extends StructuralEvidenceInput {
|
|
12
|
+
readonly sequence: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type StructuralEvidenceRead =
|
|
16
|
+
| {
|
|
17
|
+
readonly status: 'ok';
|
|
18
|
+
readonly cursor: number;
|
|
19
|
+
readonly events: readonly StructuralEvidence[];
|
|
20
|
+
}
|
|
21
|
+
| { readonly status: 'overflow'; readonly cursor: number; readonly oldestAvailable: number };
|
|
22
|
+
|
|
23
|
+
/** Bounded producer-owned structural evidence; consumers never infer facts by scanning. */
|
|
24
|
+
export class StructuralEvidenceRing {
|
|
25
|
+
private readonly events: Array<StructuralEvidence | undefined>;
|
|
26
|
+
private nextSequence = 1;
|
|
27
|
+
|
|
28
|
+
constructor(readonly capacity = 1024) {
|
|
29
|
+
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
30
|
+
throw new RangeError('StructuralEvidenceRing capacity must be a positive safe integer');
|
|
31
|
+
}
|
|
32
|
+
this.events = new Array<StructuralEvidence | undefined>(capacity);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get cursor(): number {
|
|
36
|
+
return this.nextSequence - 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
append(input: StructuralEvidenceInput): number {
|
|
40
|
+
const sequence = this.nextSequence;
|
|
41
|
+
this.nextSequence += 1;
|
|
42
|
+
this.events[(sequence - 1) % this.capacity] = { ...input, sequence };
|
|
43
|
+
return sequence;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
readAfter(cursor: number): StructuralEvidenceRead {
|
|
47
|
+
const latest = this.cursor;
|
|
48
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
|
|
49
|
+
throw new RangeError(`StructuralEvidenceRing cursor ${cursor} is outside 0..${latest}`);
|
|
50
|
+
}
|
|
51
|
+
const oldestAvailable = Math.max(1, latest - this.capacity + 1);
|
|
52
|
+
if (cursor < oldestAvailable - 1)
|
|
53
|
+
return { status: 'overflow', cursor: latest, oldestAvailable };
|
|
54
|
+
const events: StructuralEvidence[] = [];
|
|
55
|
+
for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
|
|
56
|
+
const event = this.events[(sequence - 1) % this.capacity];
|
|
57
|
+
if (event === undefined || event.sequence !== sequence) {
|
|
58
|
+
return { status: 'overflow', cursor: latest, oldestAvailable };
|
|
59
|
+
}
|
|
60
|
+
events.push(event);
|
|
61
|
+
}
|
|
62
|
+
return { status: 'ok', cursor: latest, events };
|
|
63
|
+
}
|
|
64
|
+
}
|