@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { EntityId, WildcardRelationId } from "./entity";
2
- import type { ComponentTuple } from "./types";
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 EntityId<any>[]>(componentTypes: T): Array<{
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 EntityId<any>[]>(componentTypes: T, callback: (entity: EntityId, ...components: ComponentTuple<T>) => void): void;
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
- const compType = componentTypes[i];
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 ? dataArray[entityIndex] : undefined;
394
- return data === MISSING_COMPONENT ? undefined : data;
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 systems = this.systemScheduler.getExecutionOrder();
829
- for (const system of systems) {
830
- system.update(...params);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
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 EntityId<any>[]>(componentTypes: T): Array<{
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 EntityId<any>[]>(componentTypes: T, callback: (entity: EntityId, ...components: ComponentTuple<T>) => void): void;
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
@@ -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[] = [deltaTime: number]> {
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 EntityId<any>[]> = {
19
- readonly [K in keyof T]: T[K] extends WildcardRelationId<infer U> ? [EntityId<unknown>, U][] : T[K] extends EntityId<infer U> ? U : never;
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
  */