@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
package/src/projection/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Component } from '../component';
|
|
|
3
3
|
import { componentId } from '../component';
|
|
4
4
|
import type { EntityHandle } from '../entity-handle';
|
|
5
5
|
import type { SharedRefMutationRead } from '../shared-ref-store';
|
|
6
|
-
import type {
|
|
6
|
+
import type { StructuralEvidenceRead } from '../storage/structural-evidence';
|
|
7
7
|
import type { EcsError, World } from '../world';
|
|
8
8
|
import { worldInternal } from '../world-internal';
|
|
9
9
|
|
|
@@ -24,41 +24,9 @@ export {
|
|
|
24
24
|
StaleEntityError,
|
|
25
25
|
} from '../errors';
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
| 'component-removed'
|
|
31
|
-
| 'derived-component-changed'
|
|
32
|
-
| 'entity-removed';
|
|
33
|
-
|
|
34
|
-
export interface ProjectionChange {
|
|
35
|
-
readonly entity: EntityHandle;
|
|
36
|
-
readonly kind: ProjectionChangeKind;
|
|
37
|
-
/** Token identity is the owner-facing component discriminator. */
|
|
38
|
-
readonly component?: Component;
|
|
39
|
-
/** Numeric id remains journal evidence for ECS-internal consumers. */
|
|
40
|
-
readonly componentId?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type ProjectionRead =
|
|
44
|
-
| {
|
|
45
|
-
readonly status: 'delta';
|
|
46
|
-
readonly cursor: number;
|
|
47
|
-
readonly changes: readonly ProjectionChange[];
|
|
48
|
-
}
|
|
49
|
-
| {
|
|
50
|
-
readonly status: 'rebuild';
|
|
51
|
-
readonly cursor: number;
|
|
52
|
-
readonly reason: 'journal-overflow';
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export interface WorldProjection {
|
|
56
|
-
readonly cursor: number;
|
|
57
|
-
poll(): ProjectionRead;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface WorldProjectionOptions {
|
|
61
|
-
readonly components?: readonly Component[];
|
|
27
|
+
/** Read producer-owned typed structural facts without inferring from a World scan. */
|
|
28
|
+
export function readStructuralEvidence(world: World, cursor: number): StructuralEvidenceRead {
|
|
29
|
+
return world[worldInternal].getStructuralEvidence().readAfter(cursor) as StructuralEvidenceRead;
|
|
62
30
|
}
|
|
63
31
|
|
|
64
32
|
export interface RenderProjectionComponentRequest {
|
|
@@ -83,28 +51,34 @@ export interface RenderProjectionSpans {
|
|
|
83
51
|
|
|
84
52
|
export interface RenderChangeBatchOk {
|
|
85
53
|
readonly status: 'ok';
|
|
86
|
-
readonly
|
|
87
|
-
readonly world:
|
|
88
|
-
readonly sharedRefs:
|
|
54
|
+
readonly version: RenderReadVersion;
|
|
55
|
+
readonly world: RenderWorldChanges;
|
|
56
|
+
readonly sharedRefs: SharedRefMutationRead;
|
|
89
57
|
}
|
|
90
58
|
|
|
91
|
-
export interface
|
|
92
|
-
readonly status: '
|
|
93
|
-
readonly
|
|
59
|
+
export interface RenderChangeBatchRebuild {
|
|
60
|
+
readonly status: 'rebuild';
|
|
61
|
+
readonly version: RenderReadVersion;
|
|
94
62
|
readonly resync: true;
|
|
95
|
-
readonly
|
|
96
|
-
readonly world:
|
|
63
|
+
readonly reason: 'structure-changed';
|
|
64
|
+
readonly world: RenderWorldChanges;
|
|
97
65
|
readonly sharedRefs: SharedRefMutationRead;
|
|
98
66
|
}
|
|
99
67
|
|
|
100
|
-
export
|
|
68
|
+
export interface RenderWorldChanges {
|
|
69
|
+
readonly fromEpoch: number;
|
|
70
|
+
readonly toEpoch: number;
|
|
71
|
+
readonly changedComponentIds: readonly number[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type RenderChangeBatch = RenderChangeBatchOk | RenderChangeBatchRebuild;
|
|
101
75
|
|
|
102
76
|
export interface RenderReadLease {
|
|
103
77
|
readonly worldIdentity: string;
|
|
104
78
|
readonly generation: number;
|
|
105
|
-
readChanges(
|
|
79
|
+
readChanges(version: RenderReadVersion): RenderChangeBatch;
|
|
106
80
|
querySpans(request: RenderProjectionRequest): RenderProjectionSpans;
|
|
107
|
-
|
|
81
|
+
captureVersion(): RenderReadVersion;
|
|
108
82
|
dispose(): void;
|
|
109
83
|
}
|
|
110
84
|
|
|
@@ -124,9 +98,10 @@ export function readRenderArrayView(
|
|
|
124
98
|
| undefined;
|
|
125
99
|
}
|
|
126
100
|
|
|
127
|
-
interface
|
|
128
|
-
readonly
|
|
129
|
-
readonly
|
|
101
|
+
export interface RenderReadVersion {
|
|
102
|
+
readonly mutationEpoch: number;
|
|
103
|
+
readonly structureEpoch: number;
|
|
104
|
+
readonly sharedRefEpoch: number;
|
|
130
105
|
}
|
|
131
106
|
|
|
132
107
|
function readProjectionSpans(
|
|
@@ -167,80 +142,78 @@ function readProjectionSpans(
|
|
|
167
142
|
export function createRenderReadLease(world: World, token: object = {}): RenderReadLease {
|
|
168
143
|
void token;
|
|
169
144
|
const sharedRefs = world[worldInternal].getSharedRefs();
|
|
170
|
-
const generation = Math.max(1, world[worldInternal].getStructureEpoch());
|
|
171
145
|
let disposed = false;
|
|
172
|
-
let nextCursor = 1;
|
|
173
|
-
const cursors = new Map<number, ProjectionCursor>([
|
|
174
|
-
[
|
|
175
|
-
0,
|
|
176
|
-
{ world: world[worldInternal].getChangeCursor(), sharedRefs: sharedRefs.getMutationEpoch() },
|
|
177
|
-
],
|
|
178
|
-
]);
|
|
179
146
|
|
|
180
147
|
const assertLive = (): void => {
|
|
181
148
|
if (disposed) throw new Error('RenderReadLease is disposed.');
|
|
182
149
|
};
|
|
183
150
|
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
if (cursors.size > 8) {
|
|
191
|
-
const oldest = cursors.keys().next().value;
|
|
192
|
-
if (typeof oldest === 'number' && oldest !== cursor) cursors.delete(oldest);
|
|
193
|
-
}
|
|
194
|
-
return cursor;
|
|
151
|
+
const captureVersion = (): RenderReadVersion => {
|
|
152
|
+
return {
|
|
153
|
+
mutationEpoch: world[worldInternal].getMutationEpoch(),
|
|
154
|
+
structureEpoch: world[worldInternal].getStructureEpoch(),
|
|
155
|
+
sharedRefEpoch: sharedRefs.getMutationEpoch(),
|
|
156
|
+
};
|
|
195
157
|
};
|
|
196
158
|
|
|
197
159
|
return {
|
|
198
160
|
worldIdentity: world.identity,
|
|
199
|
-
generation
|
|
200
|
-
|
|
161
|
+
get generation(): number {
|
|
162
|
+
return Math.max(1, world[worldInternal].getStructureEpoch());
|
|
163
|
+
},
|
|
164
|
+
captureVersion(): RenderReadVersion {
|
|
201
165
|
assertLive();
|
|
202
|
-
return
|
|
166
|
+
return captureVersion();
|
|
203
167
|
},
|
|
204
|
-
readChanges(
|
|
168
|
+
readChanges(start: RenderReadVersion): RenderChangeBatch {
|
|
205
169
|
assertLive();
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
170
|
+
const toEpoch = world[worldInternal].getMutationEpoch() as number;
|
|
171
|
+
const componentEpochs = world[
|
|
172
|
+
worldInternal
|
|
173
|
+
].getComponentMutationEpochs() as readonly number[];
|
|
174
|
+
const changedComponentIds: number[] = [];
|
|
175
|
+
for (let componentId = 0; componentId < componentEpochs.length; componentId += 1) {
|
|
176
|
+
const epoch = componentEpochs[componentId] ?? 0;
|
|
177
|
+
if (epoch > start.mutationEpoch && epoch <= toEpoch) changedComponentIds.push(componentId);
|
|
178
|
+
}
|
|
179
|
+
const worldRead: RenderWorldChanges = {
|
|
180
|
+
fromEpoch: start.mutationEpoch,
|
|
181
|
+
toEpoch,
|
|
182
|
+
changedComponentIds,
|
|
183
|
+
};
|
|
184
|
+
const sharedRead = sharedRefs.readChangesSince(start.sharedRefEpoch);
|
|
185
|
+
const version = captureVersion();
|
|
186
|
+
const structureChanged = start.structureEpoch !== world[worldInternal].getStructureEpoch();
|
|
187
|
+
if (structureChanged) {
|
|
216
188
|
return {
|
|
217
|
-
status: '
|
|
218
|
-
|
|
189
|
+
status: 'rebuild',
|
|
190
|
+
version,
|
|
219
191
|
resync: true,
|
|
220
|
-
|
|
192
|
+
reason: 'structure-changed',
|
|
221
193
|
world: worldRead,
|
|
222
194
|
sharedRefs: sharedRead,
|
|
223
195
|
};
|
|
224
196
|
}
|
|
225
|
-
return { status: 'ok',
|
|
197
|
+
return { status: 'ok', version, world: worldRead, sharedRefs: sharedRead };
|
|
226
198
|
},
|
|
227
199
|
querySpans(request: RenderProjectionRequest): RenderProjectionSpans {
|
|
228
200
|
assertLive();
|
|
229
|
-
return readProjectionSpans(
|
|
201
|
+
return readProjectionSpans(
|
|
202
|
+
world,
|
|
203
|
+
Math.max(1, world[worldInternal].getStructureEpoch()),
|
|
204
|
+
request,
|
|
205
|
+
);
|
|
230
206
|
},
|
|
231
207
|
dispose(): void {
|
|
232
208
|
disposed = true;
|
|
233
|
-
cursors.clear();
|
|
234
209
|
},
|
|
235
210
|
};
|
|
236
211
|
}
|
|
237
212
|
|
|
238
213
|
/**
|
|
239
|
-
* Publish one owner-derived component value
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* storage, while the journal records a `derived-component-changed` fact for
|
|
243
|
-
* incremental projections.
|
|
214
|
+
* Publish one owner-derived component value through the component's ordinary
|
|
215
|
+
* version. Numeric consumers observe the same row epoch regardless of which
|
|
216
|
+
* owner computed the value; there is no parallel derived-change vocabulary.
|
|
244
217
|
*/
|
|
245
218
|
export function setDerivedComponent(
|
|
246
219
|
world: World,
|
|
@@ -250,7 +223,7 @@ export function setDerivedComponent(
|
|
|
250
223
|
): Result<void, EcsError> {
|
|
251
224
|
const result = world[worldInternal].setQueryRow(entity, component, value);
|
|
252
225
|
if (!result.ok) return result;
|
|
253
|
-
world[worldInternal].
|
|
226
|
+
world[worldInternal].markComponentChanged(entity, componentId(component));
|
|
254
227
|
return result;
|
|
255
228
|
}
|
|
256
229
|
|
|
@@ -262,57 +235,3 @@ export function routeWorldError(
|
|
|
262
235
|
): void {
|
|
263
236
|
world[worldInternal].routeError(error, context);
|
|
264
237
|
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Create the narrow incremental evidence surface for an owner projection.
|
|
268
|
-
* Rebuilds intentionally return a reason; complete data always comes from
|
|
269
|
-
* the owner's regular Query/QuerySpan path.
|
|
270
|
-
*/
|
|
271
|
-
export function createWorldProjection(
|
|
272
|
-
world: World,
|
|
273
|
-
options: WorldProjectionOptions = {},
|
|
274
|
-
): WorldProjection {
|
|
275
|
-
const componentById =
|
|
276
|
-
options.components === undefined
|
|
277
|
-
? undefined
|
|
278
|
-
: new Map(options.components.map((component) => [componentId(component), component]));
|
|
279
|
-
const componentIds = componentById === undefined ? undefined : new Set(componentById.keys());
|
|
280
|
-
let cursor = world[worldInternal].getChangeCursor();
|
|
281
|
-
|
|
282
|
-
const pollSince = (requestedCursor: number): ProjectionRead => {
|
|
283
|
-
const result = world[worldInternal].readChangesSince(requestedCursor);
|
|
284
|
-
if (result.status === 'overflow') {
|
|
285
|
-
cursor = result.cursor;
|
|
286
|
-
return { status: 'rebuild', cursor, reason: 'journal-overflow' };
|
|
287
|
-
}
|
|
288
|
-
cursor = result.cursor;
|
|
289
|
-
const changes: ProjectionChange[] = [];
|
|
290
|
-
for (const record of result.records) {
|
|
291
|
-
if (
|
|
292
|
-
componentIds !== undefined &&
|
|
293
|
-
record.kind !== 'entity-removed' &&
|
|
294
|
-
(record.componentId === undefined || !componentIds.has(record.componentId))
|
|
295
|
-
) {
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
const component =
|
|
299
|
-
record.componentId === undefined ? undefined : componentById?.get(record.componentId);
|
|
300
|
-
changes.push({
|
|
301
|
-
entity: record.entity,
|
|
302
|
-
kind: record.kind,
|
|
303
|
-
...(component === undefined ? {} : { component }),
|
|
304
|
-
...(record.componentId === undefined ? {} : { componentId: record.componentId }),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
return { status: 'delta', cursor, changes };
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
get cursor(): number {
|
|
312
|
-
return cursor;
|
|
313
|
-
},
|
|
314
|
-
poll(): ProjectionRead {
|
|
315
|
-
return pollSince(cursor);
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
}
|