@codehz/ecs 0.1.8 → 0.2.0
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/archetype.d.ts +27 -3
- package/changeset.d.ts +6 -0
- package/entity.d.ts +17 -2
- package/index.js +161 -44
- package/package.json +1 -1
- package/query.d.ts +4 -5
- package/types.d.ts +12 -2
- package/world.d.ts +19 -2
package/archetype.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { EntityId, WildcardRelationId } from "./entity";
|
|
2
|
-
import type
|
|
2
|
+
import { type ComponentTuple, type ComponentType } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Special value to represent missing component data
|
|
5
|
+
*/
|
|
6
|
+
export declare const MISSING_COMPONENT: unique symbol;
|
|
3
7
|
/**
|
|
4
8
|
* Archetype class for ECS architecture
|
|
5
9
|
* Represents a group of entities that share the same set of components
|
|
@@ -49,6 +53,20 @@ export declare class Archetype {
|
|
|
49
53
|
* @param componentData Map of component type to component data
|
|
50
54
|
*/
|
|
51
55
|
addEntity(entityId: EntityId, componentData: Map<EntityId<any>, any>): void;
|
|
56
|
+
/**
|
|
57
|
+
* Get all component data for a specific entity
|
|
58
|
+
* @param entityId The entity to get data for
|
|
59
|
+
* @returns Map of component type to component data
|
|
60
|
+
*/
|
|
61
|
+
getEntity(entityId: EntityId): Map<EntityId<any>, any> | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Dump all entities and their component data in this archetype
|
|
64
|
+
* @returns Array of objects with entity and component data
|
|
65
|
+
*/
|
|
66
|
+
dump(): Array<{
|
|
67
|
+
entity: EntityId;
|
|
68
|
+
components: Map<EntityId<any>, any>;
|
|
69
|
+
}>;
|
|
52
70
|
/**
|
|
53
71
|
* Remove an entity from this archetype
|
|
54
72
|
* @param entityId The entity to remove
|
|
@@ -89,13 +107,19 @@ export declare class Archetype {
|
|
|
89
107
|
* @param componentType The component type
|
|
90
108
|
*/
|
|
91
109
|
getComponentData<T>(componentType: EntityId<T>): T[];
|
|
110
|
+
/**
|
|
111
|
+
* Get optional component data for all entities of a specific component type
|
|
112
|
+
* @param componentType The component type
|
|
113
|
+
* @returns An array of component data or undefined if not present
|
|
114
|
+
*/
|
|
115
|
+
getOptionalComponentData<T>(componentType: EntityId<T>): T[] | undefined;
|
|
92
116
|
/**
|
|
93
117
|
* Get entities with their component data for specified component types
|
|
94
118
|
* Optimized for bulk component access with pre-computed indices
|
|
95
119
|
* @param componentTypes Array of component types to retrieve
|
|
96
120
|
* @returns Array of objects with entity and component data
|
|
97
121
|
*/
|
|
98
|
-
getEntitiesWithComponents<const T extends readonly
|
|
122
|
+
getEntitiesWithComponents<const T extends readonly ComponentType<any>[]>(componentTypes: T): Array<{
|
|
99
123
|
entity: EntityId;
|
|
100
124
|
components: ComponentTuple<T>;
|
|
101
125
|
}>;
|
|
@@ -105,7 +129,7 @@ export declare class Archetype {
|
|
|
105
129
|
* @param componentTypes Array of component types to retrieve
|
|
106
130
|
* @param callback Function called for each entity with its components
|
|
107
131
|
*/
|
|
108
|
-
forEachWithComponents<const T extends readonly
|
|
132
|
+
forEachWithComponents<const T extends readonly ComponentType<any>[]>(componentTypes: T, callback: (entity: EntityId, ...components: ComponentTuple<T>) => void): void;
|
|
109
133
|
/**
|
|
110
134
|
* Iterate over all entities with their component data
|
|
111
135
|
* @param callback Function called for each entity with its component data
|
package/changeset.d.ts
CHANGED
|
@@ -29,4 +29,10 @@ export declare class ComponentChangeset {
|
|
|
29
29
|
* Apply the changeset to existing components and return the final state
|
|
30
30
|
*/
|
|
31
31
|
applyTo(existingComponents: Map<EntityId<any>, any>): Map<EntityId<any>, any>;
|
|
32
|
+
/**
|
|
33
|
+
* Get the final component types after applying the changeset
|
|
34
|
+
* @param existingComponentTypes - The current component types on the entity
|
|
35
|
+
* @returns The final component types or undefined if no changes
|
|
36
|
+
*/
|
|
37
|
+
getFinalComponentTypes(existingComponentTypes: EntityId<any>[]): EntityId<any>[] | undefined;
|
|
32
38
|
}
|
package/entity.d.ts
CHANGED
|
@@ -167,7 +167,22 @@ export declare class ComponentIdAllocator {
|
|
|
167
167
|
hasAvailableIds(): boolean;
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
|
-
* Allocate a new component ID from the global allocator
|
|
170
|
+
* Allocate a new component ID from the global allocator.
|
|
171
|
+
* Optionally register a name for the component.
|
|
172
|
+
* The name is only for serialization/debugging and does not affect base functionality.
|
|
173
|
+
* @param name Optional name for the component
|
|
174
|
+
* @returns The allocated component ID
|
|
171
175
|
*/
|
|
172
|
-
export declare function component<T = void>(): ComponentId<T>;
|
|
176
|
+
export declare function component<T = void>(name?: string): ComponentId<T>;
|
|
177
|
+
/**
|
|
178
|
+
* Get a component ID by its registered name
|
|
179
|
+
* @param name The component name
|
|
180
|
+
* @returns The component ID if found, undefined otherwise
|
|
181
|
+
*/
|
|
182
|
+
export declare function getComponentIdByName(name: string): ComponentId<any> | undefined;
|
|
183
|
+
/** Get a component name by its ID
|
|
184
|
+
* @param id The component ID
|
|
185
|
+
* @returns The component name if found, undefined otherwise
|
|
186
|
+
*/
|
|
187
|
+
export declare function getComponentNameById(id: ComponentId<any>): string | undefined;
|
|
173
188
|
export {};
|
package/index.js
CHANGED
|
@@ -214,9 +214,30 @@ class ComponentIdAllocator {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
var globalComponentIdAllocator = new ComponentIdAllocator;
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
var ComponentNames = new Map;
|
|
218
|
+
var ComponentIdForNames = new Map;
|
|
219
|
+
function component(name) {
|
|
220
|
+
const id = globalComponentIdAllocator.allocate();
|
|
221
|
+
if (name) {
|
|
222
|
+
if (ComponentIdForNames.has(name)) {
|
|
223
|
+
throw new Error(`Component name "${name}" is already registered`);
|
|
224
|
+
}
|
|
225
|
+
ComponentNames.set(id, name);
|
|
226
|
+
ComponentIdForNames.set(name, id);
|
|
227
|
+
}
|
|
228
|
+
return id;
|
|
229
|
+
}
|
|
230
|
+
function getComponentIdByName(name) {
|
|
231
|
+
return ComponentIdForNames.get(name);
|
|
219
232
|
}
|
|
233
|
+
function getComponentNameById(id) {
|
|
234
|
+
return ComponentNames.get(id);
|
|
235
|
+
}
|
|
236
|
+
// src/types.ts
|
|
237
|
+
function isOptionalEntityId(type) {
|
|
238
|
+
return typeof type === "object" && type !== null && "optional" in type;
|
|
239
|
+
}
|
|
240
|
+
|
|
220
241
|
// src/utils.ts
|
|
221
242
|
function getOrComputeCache(cache, key, compute) {
|
|
222
243
|
let value = cache.get(key);
|
|
@@ -272,6 +293,33 @@ class Archetype {
|
|
|
272
293
|
this.getComponentData(componentType).push(data === undefined ? MISSING_COMPONENT : data);
|
|
273
294
|
}
|
|
274
295
|
}
|
|
296
|
+
getEntity(entityId) {
|
|
297
|
+
const index = this.entityToIndex.get(entityId);
|
|
298
|
+
if (index === undefined) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const entityData = new Map;
|
|
302
|
+
for (const componentType of this.componentTypes) {
|
|
303
|
+
const dataArray = this.getComponentData(componentType);
|
|
304
|
+
const data = dataArray[index];
|
|
305
|
+
entityData.set(componentType, data === MISSING_COMPONENT ? undefined : data);
|
|
306
|
+
}
|
|
307
|
+
return entityData;
|
|
308
|
+
}
|
|
309
|
+
dump() {
|
|
310
|
+
const result = [];
|
|
311
|
+
for (let i = 0;i < this.entities.length; i++) {
|
|
312
|
+
const entity = this.entities[i];
|
|
313
|
+
const components = new Map;
|
|
314
|
+
for (const componentType of this.componentTypes) {
|
|
315
|
+
const dataArray = this.getComponentData(componentType);
|
|
316
|
+
const data = dataArray[i];
|
|
317
|
+
components.set(componentType, data === MISSING_COMPONENT ? undefined : data);
|
|
318
|
+
}
|
|
319
|
+
result.push({ entity, components });
|
|
320
|
+
}
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
275
323
|
removeEntity(entityId) {
|
|
276
324
|
const index = this.entityToIndex.get(entityId);
|
|
277
325
|
if (index === undefined) {
|
|
@@ -348,6 +396,9 @@ class Archetype {
|
|
|
348
396
|
}
|
|
349
397
|
return data;
|
|
350
398
|
}
|
|
399
|
+
getOptionalComponentData(componentType) {
|
|
400
|
+
return this.componentData.get(componentType);
|
|
401
|
+
}
|
|
351
402
|
getEntitiesWithComponents(componentTypes) {
|
|
352
403
|
const result = [];
|
|
353
404
|
this.forEachWithComponents(componentTypes, (entity, ...components) => {
|
|
@@ -356,9 +407,14 @@ class Archetype {
|
|
|
356
407
|
return result;
|
|
357
408
|
}
|
|
358
409
|
forEachWithComponents(componentTypes, callback) {
|
|
359
|
-
const cacheKey = componentTypes.map((id) => id.
|
|
410
|
+
const cacheKey = componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
|
|
360
411
|
const componentDataSources = getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => {
|
|
361
412
|
return componentTypes.map((compType) => {
|
|
413
|
+
let optional = false;
|
|
414
|
+
if (isOptionalEntityId(compType)) {
|
|
415
|
+
compType = compType.optional;
|
|
416
|
+
optional = true;
|
|
417
|
+
}
|
|
362
418
|
const detailedType = getDetailedIdType(compType);
|
|
363
419
|
if (detailedType.type === "wildcard-relation") {
|
|
364
420
|
const componentId = detailedType.componentId;
|
|
@@ -368,17 +424,29 @@ class Archetype {
|
|
|
368
424
|
return false;
|
|
369
425
|
return detailedCt.componentId === componentId;
|
|
370
426
|
});
|
|
371
|
-
return matchingRelations;
|
|
427
|
+
return optional ? matchingRelations.length > 0 ? matchingRelations : undefined : matchingRelations;
|
|
372
428
|
} else {
|
|
373
|
-
return this.getComponentData(compType);
|
|
429
|
+
return optional ? this.getOptionalComponentData(compType) : this.getComponentData(compType);
|
|
374
430
|
}
|
|
375
431
|
});
|
|
376
432
|
});
|
|
377
433
|
for (let entityIndex = 0;entityIndex < this.entities.length; entityIndex++) {
|
|
378
434
|
const entity = this.entities[entityIndex];
|
|
379
435
|
const components = componentDataSources.map((dataSource, i) => {
|
|
380
|
-
|
|
436
|
+
let compType = componentTypes[i];
|
|
437
|
+
let optional = false;
|
|
438
|
+
if (isOptionalEntityId(compType)) {
|
|
439
|
+
compType = compType.optional;
|
|
440
|
+
optional = true;
|
|
441
|
+
}
|
|
381
442
|
if (getIdType(compType) === "wildcard-relation") {
|
|
443
|
+
if (dataSource === undefined) {
|
|
444
|
+
if (optional) {
|
|
445
|
+
return;
|
|
446
|
+
} else {
|
|
447
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component type`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
382
450
|
const matchingRelations = dataSource;
|
|
383
451
|
const relations = [];
|
|
384
452
|
for (const relType of matchingRelations) {
|
|
@@ -387,11 +455,19 @@ class Archetype {
|
|
|
387
455
|
const decodedRel = decodeRelationId(relType);
|
|
388
456
|
relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? undefined : data]);
|
|
389
457
|
}
|
|
390
|
-
return relations;
|
|
458
|
+
return optional ? { value: relations } : relations;
|
|
391
459
|
} else {
|
|
460
|
+
if (dataSource === undefined) {
|
|
461
|
+
if (optional) {
|
|
462
|
+
return;
|
|
463
|
+
} else {
|
|
464
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component type`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
392
467
|
const dataArray = dataSource;
|
|
393
|
-
const data = dataArray
|
|
394
|
-
|
|
468
|
+
const data = dataArray[entityIndex];
|
|
469
|
+
const result = data === MISSING_COMPONENT ? undefined : data;
|
|
470
|
+
return optional ? { value: result } : result;
|
|
395
471
|
}
|
|
396
472
|
});
|
|
397
473
|
callback(entity, ...components);
|
|
@@ -447,6 +523,25 @@ class ComponentChangeset {
|
|
|
447
523
|
}
|
|
448
524
|
return existingComponents;
|
|
449
525
|
}
|
|
526
|
+
getFinalComponentTypes(existingComponentTypes) {
|
|
527
|
+
const finalComponentTypes = new Set(existingComponentTypes);
|
|
528
|
+
let changed = false;
|
|
529
|
+
for (const componentType of this.removes) {
|
|
530
|
+
if (!finalComponentTypes.has(componentType)) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
changed = true;
|
|
534
|
+
finalComponentTypes.delete(componentType);
|
|
535
|
+
}
|
|
536
|
+
for (const componentType of this.adds.keys()) {
|
|
537
|
+
if (finalComponentTypes.has(componentType)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
changed = true;
|
|
541
|
+
finalComponentTypes.add(componentType);
|
|
542
|
+
}
|
|
543
|
+
return changed ? Array.from(finalComponentTypes) : undefined;
|
|
544
|
+
}
|
|
450
545
|
}
|
|
451
546
|
|
|
452
547
|
// src/command-buffer.ts
|
|
@@ -531,17 +626,15 @@ function matchesFilter(archetype, filter) {
|
|
|
531
626
|
|
|
532
627
|
// src/query.ts
|
|
533
628
|
class Query {
|
|
534
|
-
key;
|
|
535
629
|
world;
|
|
536
630
|
componentTypes;
|
|
537
631
|
filter;
|
|
538
632
|
cachedArchetypes = [];
|
|
539
633
|
isDisposed = false;
|
|
540
|
-
constructor(world, componentTypes, filter = {}
|
|
634
|
+
constructor(world, componentTypes, filter = {}) {
|
|
541
635
|
this.world = world;
|
|
542
636
|
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
|
|
543
637
|
this.filter = filter;
|
|
544
|
-
this.key = key ?? `${this.componentTypes.join(",")}|`;
|
|
545
638
|
this.updateCache();
|
|
546
639
|
world._registerQuery(this);
|
|
547
640
|
}
|
|
@@ -711,8 +804,27 @@ class World {
|
|
|
711
804
|
const componentMap = new Map;
|
|
712
805
|
const componentTypes = [];
|
|
713
806
|
for (const componentEntry of componentsArray) {
|
|
714
|
-
|
|
715
|
-
|
|
807
|
+
const componentTypeRaw = componentEntry.type;
|
|
808
|
+
let componentType;
|
|
809
|
+
if (typeof componentTypeRaw === "number") {
|
|
810
|
+
componentType = componentTypeRaw;
|
|
811
|
+
} else if (typeof componentTypeRaw === "string") {
|
|
812
|
+
const compId = getComponentIdByName(componentTypeRaw);
|
|
813
|
+
if (compId === undefined) {
|
|
814
|
+
throw new Error(`Unknown component name in snapshot: ${componentTypeRaw}`);
|
|
815
|
+
}
|
|
816
|
+
componentType = compId;
|
|
817
|
+
} else if (typeof componentTypeRaw === "object" && componentTypeRaw !== null && typeof componentTypeRaw.component === "string" && typeof componentTypeRaw.target === "number") {
|
|
818
|
+
const compId = getComponentIdByName(componentTypeRaw.component);
|
|
819
|
+
if (compId === undefined) {
|
|
820
|
+
throw new Error(`Unknown component name in snapshot: ${componentTypeRaw.component}`);
|
|
821
|
+
}
|
|
822
|
+
componentType = relation(compId, componentTypeRaw.target);
|
|
823
|
+
} else {
|
|
824
|
+
throw new Error(`Invalid component type in snapshot: ${JSON.stringify(componentTypeRaw)}`);
|
|
825
|
+
}
|
|
826
|
+
componentMap.set(componentType, componentEntry.value);
|
|
827
|
+
componentTypes.push(componentType);
|
|
716
828
|
}
|
|
717
829
|
const archetype = this.ensureArchetype(componentTypes);
|
|
718
830
|
archetype.addEntity(entityId, componentMap);
|
|
@@ -862,7 +974,7 @@ class World {
|
|
|
862
974
|
cached.refCount++;
|
|
863
975
|
return cached.query;
|
|
864
976
|
}
|
|
865
|
-
const query = new Query(this, sortedTypes, filter
|
|
977
|
+
const query = new Query(this, sortedTypes, filter);
|
|
866
978
|
this.queryCache.set(key, { query, refCount: 1 });
|
|
867
979
|
return query;
|
|
868
980
|
}
|
|
@@ -876,27 +988,17 @@ class World {
|
|
|
876
988
|
}
|
|
877
989
|
}
|
|
878
990
|
releaseQuery(query) {
|
|
879
|
-
const key = query.key;
|
|
880
991
|
for (const [k, v] of this.queryCache.entries()) {
|
|
881
992
|
if (v.query === query) {
|
|
882
993
|
v.refCount--;
|
|
883
994
|
if (v.refCount <= 0) {
|
|
884
995
|
this.queryCache.delete(k);
|
|
996
|
+
this._unregisterQuery(query);
|
|
885
997
|
v.query._disposeInternal();
|
|
886
998
|
}
|
|
887
999
|
return;
|
|
888
1000
|
}
|
|
889
1001
|
}
|
|
890
|
-
const entry = this.queryCache.get(key);
|
|
891
|
-
if (!entry) {
|
|
892
|
-
this._unregisterQuery(query);
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
entry.refCount--;
|
|
896
|
-
if (entry.refCount <= 0) {
|
|
897
|
-
this.queryCache.delete(key);
|
|
898
|
-
entry.query._disposeInternal();
|
|
899
|
-
}
|
|
900
1002
|
}
|
|
901
1003
|
getMatchingArchetypes(componentTypes) {
|
|
902
1004
|
if (componentTypes.length === 0) {
|
|
@@ -977,11 +1079,6 @@ class World {
|
|
|
977
1079
|
if (!currentArchetype) {
|
|
978
1080
|
return changeset;
|
|
979
1081
|
}
|
|
980
|
-
const currentComponents = new Map;
|
|
981
|
-
for (const componentType of currentArchetype.componentTypes) {
|
|
982
|
-
const componentData = currentArchetype.get(entityId, componentType);
|
|
983
|
-
currentComponents.set(componentType, componentData);
|
|
984
|
-
}
|
|
985
1082
|
for (const command of commands) {
|
|
986
1083
|
switch (command.type) {
|
|
987
1084
|
case "set":
|
|
@@ -1018,13 +1115,11 @@ class World {
|
|
|
1018
1115
|
break;
|
|
1019
1116
|
}
|
|
1020
1117
|
}
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
currentArchetype.removeEntity(entityId);
|
|
1027
|
-
newArchetype.addEntity(entityId, finalComponents);
|
|
1118
|
+
const finalComponentTypes = changeset.getFinalComponentTypes(currentArchetype.componentTypes);
|
|
1119
|
+
if (finalComponentTypes) {
|
|
1120
|
+
const newArchetype = this.ensureArchetype(finalComponentTypes);
|
|
1121
|
+
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1122
|
+
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1028
1123
|
this.entityToArchetype.set(entityId, newArchetype);
|
|
1029
1124
|
} else {
|
|
1030
1125
|
for (const [componentType, component2] of changeset.adds) {
|
|
@@ -1163,13 +1258,31 @@ class World {
|
|
|
1163
1258
|
}
|
|
1164
1259
|
serialize() {
|
|
1165
1260
|
const entities = [];
|
|
1166
|
-
for (const
|
|
1167
|
-
const
|
|
1168
|
-
for (const
|
|
1169
|
-
|
|
1170
|
-
|
|
1261
|
+
for (const archetype of this.archetypes) {
|
|
1262
|
+
const dumpedEntities = archetype.dump();
|
|
1263
|
+
for (const { entity, components } of dumpedEntities) {
|
|
1264
|
+
entities.push({
|
|
1265
|
+
id: entity,
|
|
1266
|
+
components: Array.from(components.entries()).map(([rawType, value]) => {
|
|
1267
|
+
const detailedType = getDetailedIdType(rawType);
|
|
1268
|
+
let type = rawType;
|
|
1269
|
+
let componentName;
|
|
1270
|
+
switch (detailedType.type) {
|
|
1271
|
+
case "component":
|
|
1272
|
+
type = getComponentNameById(rawType) || rawType;
|
|
1273
|
+
break;
|
|
1274
|
+
case "entity-relation":
|
|
1275
|
+
case "component-relation":
|
|
1276
|
+
componentName = getComponentNameById(detailedType.componentId);
|
|
1277
|
+
if (componentName) {
|
|
1278
|
+
type = { component: componentName, target: detailedType.targetId };
|
|
1279
|
+
}
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
return { type, value: value === MISSING_COMPONENT ? undefined : value };
|
|
1283
|
+
})
|
|
1284
|
+
});
|
|
1171
1285
|
}
|
|
1172
|
-
entities.push({ id: entityId, components: compEntries });
|
|
1173
1286
|
}
|
|
1174
1287
|
return {
|
|
1175
1288
|
version: 1,
|
|
@@ -1183,11 +1296,14 @@ export {
|
|
|
1183
1296
|
relation,
|
|
1184
1297
|
isWildcardRelationId,
|
|
1185
1298
|
isRelationId,
|
|
1299
|
+
isOptionalEntityId,
|
|
1186
1300
|
isEntityId,
|
|
1187
1301
|
isComponentId,
|
|
1188
1302
|
inspectEntityId,
|
|
1189
1303
|
getIdType,
|
|
1190
1304
|
getDetailedIdType,
|
|
1305
|
+
getComponentNameById,
|
|
1306
|
+
getComponentIdByName,
|
|
1191
1307
|
decodeRelationId,
|
|
1192
1308
|
createEntityId,
|
|
1193
1309
|
createComponentId,
|
|
@@ -1197,6 +1313,7 @@ export {
|
|
|
1197
1313
|
SystemScheduler,
|
|
1198
1314
|
RELATION_SHIFT,
|
|
1199
1315
|
Query,
|
|
1316
|
+
MISSING_COMPONENT,
|
|
1200
1317
|
INVALID_COMPONENT_ID,
|
|
1201
1318
|
EntityIdManager,
|
|
1202
1319
|
ENTITY_ID_START,
|
package/package.json
CHANGED
package/query.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { Archetype } from "./archetype";
|
|
2
2
|
import type { EntityId } from "./entity";
|
|
3
3
|
import { type QueryFilter } from "./query-filter";
|
|
4
|
-
import type { ComponentTuple } from "./types";
|
|
4
|
+
import type { ComponentTuple, ComponentType } from "./types";
|
|
5
5
|
import type { World } from "./world";
|
|
6
6
|
/**
|
|
7
7
|
* Query class for efficient entity queries with cached archetypes
|
|
8
8
|
*/
|
|
9
9
|
export declare class Query {
|
|
10
|
-
readonly key: string;
|
|
11
10
|
private world;
|
|
12
11
|
private componentTypes;
|
|
13
12
|
private filter;
|
|
14
13
|
private cachedArchetypes;
|
|
15
14
|
private isDisposed;
|
|
16
|
-
constructor(world: World<any[]>, componentTypes: EntityId<any>[], filter?: QueryFilter
|
|
15
|
+
constructor(world: World<any[]>, componentTypes: EntityId<any>[], filter?: QueryFilter);
|
|
17
16
|
/**
|
|
18
17
|
* Get all entities matching the query
|
|
19
18
|
*/
|
|
@@ -23,7 +22,7 @@ export declare class Query {
|
|
|
23
22
|
* @param componentTypes Array of component types to retrieve
|
|
24
23
|
* @returns Array of objects with entity and component data
|
|
25
24
|
*/
|
|
26
|
-
getEntitiesWithComponents<const T extends readonly
|
|
25
|
+
getEntitiesWithComponents<const T extends readonly ComponentType<any>[]>(componentTypes: T): Array<{
|
|
27
26
|
entity: EntityId;
|
|
28
27
|
components: ComponentTuple<T>;
|
|
29
28
|
}>;
|
|
@@ -32,7 +31,7 @@ export declare class Query {
|
|
|
32
31
|
* @param componentTypes Array of component types to retrieve
|
|
33
32
|
* @param callback Function called for each entity with its components
|
|
34
33
|
*/
|
|
35
|
-
forEach<const T extends readonly
|
|
34
|
+
forEach<const T extends readonly ComponentType<any>[]>(componentTypes: T, callback: (entity: EntityId, ...components: ComponentTuple<T>) => void): void;
|
|
36
35
|
/**
|
|
37
36
|
* Get component data arrays for all matching entities
|
|
38
37
|
* @param componentType The component type to retrieve
|
package/types.d.ts
CHANGED
|
@@ -12,9 +12,19 @@ export interface LifecycleHook<T = unknown> {
|
|
|
12
12
|
*/
|
|
13
13
|
onRemoved?: (entityId: EntityId, componentType: EntityId<T>) => void;
|
|
14
14
|
}
|
|
15
|
+
export type ComponentType<T> = EntityId<T> | OptionalEntityId<T>;
|
|
16
|
+
export type OptionalEntityId<T> = {
|
|
17
|
+
optional: EntityId<T>;
|
|
18
|
+
};
|
|
19
|
+
export declare function isOptionalEntityId<T>(type: ComponentType<T>): type is OptionalEntityId<T>;
|
|
20
|
+
export type ComponentTypeToData<T> = T extends {
|
|
21
|
+
optional: infer U;
|
|
22
|
+
} ? {
|
|
23
|
+
value: ComponentTypeToData<U>;
|
|
24
|
+
} | undefined : T extends WildcardRelationId<infer U> ? [EntityId<unknown>, U][] : T extends EntityId<infer U> ? U : never;
|
|
15
25
|
/**
|
|
16
26
|
* Type helper for component tuples extracted from EntityId array
|
|
17
27
|
*/
|
|
18
|
-
export type ComponentTuple<T extends readonly
|
|
19
|
-
readonly [K in keyof T]:
|
|
28
|
+
export type ComponentTuple<T extends readonly ComponentType<any>[]> = {
|
|
29
|
+
readonly [K in keyof T]: ComponentTypeToData<T[K]>;
|
|
20
30
|
};
|
package/world.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
40
40
|
* If an optional snapshot object is provided (previously produced by `world.serialize()`),
|
|
41
41
|
* the world will be restored from that snapshot. The snapshot may contain non-JSON values.
|
|
42
42
|
*/
|
|
43
|
-
constructor(snapshot?:
|
|
43
|
+
constructor(snapshot?: SerializedWorld);
|
|
44
44
|
/**
|
|
45
45
|
* Generate a signature string for component types array
|
|
46
46
|
* @returns A string signature for the component types
|
|
@@ -192,5 +192,22 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
192
192
|
* This returns an in-memory structure and does not perform JSON stringification.
|
|
193
193
|
* Component values are stored as-is (they may be non-JSON-serializable).
|
|
194
194
|
*/
|
|
195
|
-
serialize():
|
|
195
|
+
serialize(): SerializedWorld;
|
|
196
196
|
}
|
|
197
|
+
export type SerializedWorld = {
|
|
198
|
+
version: number;
|
|
199
|
+
entityManager: any;
|
|
200
|
+
exclusiveComponents: EntityId[];
|
|
201
|
+
entities: SerializedEntity[];
|
|
202
|
+
};
|
|
203
|
+
export type SerializedEntity = {
|
|
204
|
+
id: number;
|
|
205
|
+
components: SerializedComponent[];
|
|
206
|
+
};
|
|
207
|
+
export type SerializedComponent = {
|
|
208
|
+
type: number | string | {
|
|
209
|
+
component: string;
|
|
210
|
+
target: number;
|
|
211
|
+
};
|
|
212
|
+
value: any;
|
|
213
|
+
};
|