@codehz/ecs 0.1.8 → 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);
@@ -1183,6 +1216,7 @@ export {
1183
1216
  relation,
1184
1217
  isWildcardRelationId,
1185
1218
  isRelationId,
1219
+ isOptionalEntityId,
1186
1220
  isEntityId,
1187
1221
  isComponentId,
1188
1222
  inspectEntityId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.1.8",
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
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
  };