@codehz/ecs 0.1.7 → 0.1.9
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 +9 -3
- package/index.js +63 -10
- package/package.json +1 -1
- package/query.d.ts +3 -3
- package/system-scheduler.d.ts +2 -1
- package/system.d.ts +1 -1
- package/types.d.ts +12 -2
- package/world.d.ts +3 -1
package/archetype.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityId, WildcardRelationId } from "./entity";
|
|
2
|
-
import type
|
|
2
|
+
import { type ComponentTuple, type ComponentType } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Archetype class for ECS architecture
|
|
5
5
|
* Represents a group of entities that share the same set of components
|
|
@@ -89,13 +89,19 @@ export declare class Archetype {
|
|
|
89
89
|
* @param componentType The component type
|
|
90
90
|
*/
|
|
91
91
|
getComponentData<T>(componentType: EntityId<T>): T[];
|
|
92
|
+
/**
|
|
93
|
+
* Get optional component data for all entities of a specific component type
|
|
94
|
+
* @param componentType The component type
|
|
95
|
+
* @returns An array of component data or undefined if not present
|
|
96
|
+
*/
|
|
97
|
+
getOptionalComponentData<T>(componentType: EntityId<T>): T[] | undefined;
|
|
92
98
|
/**
|
|
93
99
|
* Get entities with their component data for specified component types
|
|
94
100
|
* Optimized for bulk component access with pre-computed indices
|
|
95
101
|
* @param componentTypes Array of component types to retrieve
|
|
96
102
|
* @returns Array of objects with entity and component data
|
|
97
103
|
*/
|
|
98
|
-
getEntitiesWithComponents<const T extends readonly
|
|
104
|
+
getEntitiesWithComponents<const T extends readonly ComponentType<any>[]>(componentTypes: T): Array<{
|
|
99
105
|
entity: EntityId;
|
|
100
106
|
components: ComponentTuple<T>;
|
|
101
107
|
}>;
|
|
@@ -105,7 +111,7 @@ export declare class Archetype {
|
|
|
105
111
|
* @param componentTypes Array of component types to retrieve
|
|
106
112
|
* @param callback Function called for each entity with its components
|
|
107
113
|
*/
|
|
108
|
-
forEachWithComponents<const T extends readonly
|
|
114
|
+
forEachWithComponents<const T extends readonly ComponentType<any>[]>(componentTypes: T, callback: (entity: EntityId, ...components: ComponentTuple<T>) => void): void;
|
|
109
115
|
/**
|
|
110
116
|
* Iterate over all entities with their component data
|
|
111
117
|
* @param callback Function called for each entity with its component data
|
package/index.js
CHANGED
|
@@ -217,6 +217,11 @@ var globalComponentIdAllocator = new ComponentIdAllocator;
|
|
|
217
217
|
function component() {
|
|
218
218
|
return globalComponentIdAllocator.allocate();
|
|
219
219
|
}
|
|
220
|
+
// src/types.ts
|
|
221
|
+
function isOptionalEntityId(type) {
|
|
222
|
+
return typeof type === "object" && type !== null && "optional" in type;
|
|
223
|
+
}
|
|
224
|
+
|
|
220
225
|
// src/utils.ts
|
|
221
226
|
function getOrComputeCache(cache, key, compute) {
|
|
222
227
|
let value = cache.get(key);
|
|
@@ -348,6 +353,9 @@ class Archetype {
|
|
|
348
353
|
}
|
|
349
354
|
return data;
|
|
350
355
|
}
|
|
356
|
+
getOptionalComponentData(componentType) {
|
|
357
|
+
return this.componentData.get(componentType);
|
|
358
|
+
}
|
|
351
359
|
getEntitiesWithComponents(componentTypes) {
|
|
352
360
|
const result = [];
|
|
353
361
|
this.forEachWithComponents(componentTypes, (entity, ...components) => {
|
|
@@ -359,6 +367,11 @@ class Archetype {
|
|
|
359
367
|
const cacheKey = componentTypes.map((id) => id.toString()).join(",");
|
|
360
368
|
const componentDataSources = getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => {
|
|
361
369
|
return componentTypes.map((compType) => {
|
|
370
|
+
let optional = false;
|
|
371
|
+
if (isOptionalEntityId(compType)) {
|
|
372
|
+
compType = compType.optional;
|
|
373
|
+
optional = true;
|
|
374
|
+
}
|
|
362
375
|
const detailedType = getDetailedIdType(compType);
|
|
363
376
|
if (detailedType.type === "wildcard-relation") {
|
|
364
377
|
const componentId = detailedType.componentId;
|
|
@@ -368,17 +381,29 @@ class Archetype {
|
|
|
368
381
|
return false;
|
|
369
382
|
return detailedCt.componentId === componentId;
|
|
370
383
|
});
|
|
371
|
-
return matchingRelations;
|
|
384
|
+
return optional ? matchingRelations.length > 0 ? matchingRelations : undefined : matchingRelations;
|
|
372
385
|
} else {
|
|
373
|
-
return this.getComponentData(compType);
|
|
386
|
+
return optional ? this.getOptionalComponentData(compType) : this.getComponentData(compType);
|
|
374
387
|
}
|
|
375
388
|
});
|
|
376
389
|
});
|
|
377
390
|
for (let entityIndex = 0;entityIndex < this.entities.length; entityIndex++) {
|
|
378
391
|
const entity = this.entities[entityIndex];
|
|
379
392
|
const components = componentDataSources.map((dataSource, i) => {
|
|
380
|
-
|
|
393
|
+
let compType = componentTypes[i];
|
|
394
|
+
let optional = false;
|
|
395
|
+
if (isOptionalEntityId(compType)) {
|
|
396
|
+
compType = compType.optional;
|
|
397
|
+
optional = true;
|
|
398
|
+
}
|
|
381
399
|
if (getIdType(compType) === "wildcard-relation") {
|
|
400
|
+
if (dataSource === undefined) {
|
|
401
|
+
if (optional) {
|
|
402
|
+
return;
|
|
403
|
+
} else {
|
|
404
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component type`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
382
407
|
const matchingRelations = dataSource;
|
|
383
408
|
const relations = [];
|
|
384
409
|
for (const relType of matchingRelations) {
|
|
@@ -387,11 +412,19 @@ class Archetype {
|
|
|
387
412
|
const decodedRel = decodeRelationId(relType);
|
|
388
413
|
relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? undefined : data]);
|
|
389
414
|
}
|
|
390
|
-
return relations;
|
|
415
|
+
return optional ? { value: relations } : relations;
|
|
391
416
|
} else {
|
|
417
|
+
if (dataSource === undefined) {
|
|
418
|
+
if (optional) {
|
|
419
|
+
return;
|
|
420
|
+
} else {
|
|
421
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component type`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
392
424
|
const dataArray = dataSource;
|
|
393
|
-
const data = dataArray
|
|
394
|
-
|
|
425
|
+
const data = dataArray[entityIndex];
|
|
426
|
+
const result = data === MISSING_COMPONENT ? undefined : data;
|
|
427
|
+
return optional ? { value: result } : result;
|
|
395
428
|
}
|
|
396
429
|
});
|
|
397
430
|
callback(entity, ...components);
|
|
@@ -656,6 +689,24 @@ class SystemScheduler {
|
|
|
656
689
|
this.cachedExecutionOrder = result;
|
|
657
690
|
return result;
|
|
658
691
|
}
|
|
692
|
+
update(...params) {
|
|
693
|
+
const executionOrder = this.getExecutionOrder();
|
|
694
|
+
const systemPromises = new Map;
|
|
695
|
+
for (const system of executionOrder) {
|
|
696
|
+
const deps = Array.from(this.systemDependencies.get(system) || []);
|
|
697
|
+
const depPromises = deps.map((dep) => systemPromises.get(dep)).filter(Boolean);
|
|
698
|
+
if (depPromises.length > 0) {
|
|
699
|
+
const promise = Promise.all(depPromises).then(() => system.update(...params));
|
|
700
|
+
systemPromises.set(system, promise);
|
|
701
|
+
} else {
|
|
702
|
+
const result = system.update(...params);
|
|
703
|
+
if (result instanceof Promise) {
|
|
704
|
+
systemPromises.set(system, result);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return Promise.all(systemPromises.values());
|
|
709
|
+
}
|
|
659
710
|
clear() {
|
|
660
711
|
this.systems.clear();
|
|
661
712
|
this.cachedExecutionOrder = null;
|
|
@@ -825,11 +876,12 @@ class World {
|
|
|
825
876
|
this.exclusiveComponents.add(componentId);
|
|
826
877
|
}
|
|
827
878
|
update(...params) {
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
879
|
+
const result = this.systemScheduler.update(...params);
|
|
880
|
+
if (result instanceof Promise) {
|
|
881
|
+
return result.then(() => this.commandBuffer.execute());
|
|
882
|
+
} else {
|
|
883
|
+
this.commandBuffer.execute();
|
|
831
884
|
}
|
|
832
|
-
this.commandBuffer.execute();
|
|
833
885
|
}
|
|
834
886
|
sync() {
|
|
835
887
|
this.commandBuffer.execute();
|
|
@@ -1164,6 +1216,7 @@ export {
|
|
|
1164
1216
|
relation,
|
|
1165
1217
|
isWildcardRelationId,
|
|
1166
1218
|
isRelationId,
|
|
1219
|
+
isOptionalEntityId,
|
|
1167
1220
|
isEntityId,
|
|
1168
1221
|
isComponentId,
|
|
1169
1222
|
inspectEntityId,
|
package/package.json
CHANGED
package/query.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
@@ -23,7 +23,7 @@ export declare class Query {
|
|
|
23
23
|
* @param componentTypes Array of component types to retrieve
|
|
24
24
|
* @returns Array of objects with entity and component data
|
|
25
25
|
*/
|
|
26
|
-
getEntitiesWithComponents<const T extends readonly
|
|
26
|
+
getEntitiesWithComponents<const T extends readonly ComponentType<any>[]>(componentTypes: T): Array<{
|
|
27
27
|
entity: EntityId;
|
|
28
28
|
components: ComponentTuple<T>;
|
|
29
29
|
}>;
|
|
@@ -32,7 +32,7 @@ export declare class Query {
|
|
|
32
32
|
* @param componentTypes Array of component types to retrieve
|
|
33
33
|
* @param callback Function called for each entity with its components
|
|
34
34
|
*/
|
|
35
|
-
forEach<const T extends readonly
|
|
35
|
+
forEach<const T extends readonly ComponentType<any>[]>(componentTypes: T, callback: (entity: EntityId, ...components: ComponentTuple<T>) => void): void;
|
|
36
36
|
/**
|
|
37
37
|
* Get component data arrays for all matching entities
|
|
38
38
|
* @param componentType The component type to retrieve
|
package/system-scheduler.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { System } from "./system";
|
|
|
2
2
|
/**
|
|
3
3
|
* System Scheduler for managing system dependencies and execution order
|
|
4
4
|
*/
|
|
5
|
-
export declare class SystemScheduler<UpdateParams extends any[] = [
|
|
5
|
+
export declare class SystemScheduler<UpdateParams extends any[] = []> {
|
|
6
6
|
private systems;
|
|
7
7
|
private systemDependencies;
|
|
8
8
|
private cachedExecutionOrder;
|
|
@@ -17,6 +17,7 @@ export declare class SystemScheduler<UpdateParams extends any[] = [deltaTime: nu
|
|
|
17
17
|
* Uses topological sort
|
|
18
18
|
*/
|
|
19
19
|
getExecutionOrder(): System<UpdateParams>[];
|
|
20
|
+
update(...params: UpdateParams): Promise<void[]> | void;
|
|
20
21
|
/**
|
|
21
22
|
* Clear all systems and dependencies
|
|
22
23
|
*/
|
package/system.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface System<UpdateParams extends any[] = []> {
|
|
|
5
5
|
/**
|
|
6
6
|
* Update the system
|
|
7
7
|
*/
|
|
8
|
-
update(...params: UpdateParams): void
|
|
8
|
+
update(...params: UpdateParams): void | Promise<void>;
|
|
9
9
|
/**
|
|
10
10
|
* Dependencies of this system (systems that must run before this one)
|
|
11
11
|
*/
|
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
|
@@ -110,8 +110,10 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
110
110
|
setExclusive(componentId: EntityId): void;
|
|
111
111
|
/**
|
|
112
112
|
* Update the world (run all systems in dependency order)
|
|
113
|
+
* This function is synchronous when all systems are synchronous,
|
|
114
|
+
* and asynchronous (returns a Promise) when any system is asynchronous.
|
|
113
115
|
*/
|
|
114
|
-
update(...params: UpdateParams): void;
|
|
116
|
+
update(...params: UpdateParams): Promise<void> | void;
|
|
115
117
|
/**
|
|
116
118
|
* Execute all deferred commands immediately without running systems
|
|
117
119
|
*/
|