@codehz/ecs 0.3.8 → 0.3.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.
Files changed (4) hide show
  1. package/index.d.mts +161 -151
  2. package/index.mjs +391 -174
  3. package/index.mjs.map +1 -1
  4. package/package.json +1 -1
package/index.d.mts CHANGED
@@ -23,28 +23,6 @@ type EntityRelationId<T = void> = EntityId<T, "entity-relation">;
23
23
  type ComponentRelationId<T = void> = EntityId<T, "component-relation">;
24
24
  type WildcardRelationId<T = void> = EntityId<T, "wildcard-relation">;
25
25
  type RelationId<T = void> = EntityRelationId<T> | ComponentRelationId<T> | WildcardRelationId<T>;
26
- /**
27
- * Constants for ID ranges
28
- */
29
- declare const INVALID_COMPONENT_ID = 0;
30
- declare const COMPONENT_ID_MAX = 1023;
31
- declare const ENTITY_ID_START = 1024;
32
- /**
33
- * Constants for relation ID encoding
34
- */
35
- declare const RELATION_SHIFT: number;
36
- declare const WILDCARD_TARGET_ID = 0;
37
- /**
38
- * Create a component ID
39
- * @param id Component identifier (1-1023)
40
- * @see component
41
- */
42
- declare function createComponentId<T = void>(id: number): ComponentId<T>;
43
- /**
44
- * Create an entity ID
45
- * @param id Entity identifier (starting from 1024)
46
- */
47
- declare function createEntityId(id: number): EntityId;
48
26
  /**
49
27
  * Type for relation ID based on component and target types
50
28
  */
@@ -83,102 +61,50 @@ declare function decodeRelationId(relationId: RelationId<any>): {
83
61
  type: "entity" | "component" | "wildcard";
84
62
  };
85
63
  /**
86
- * Get the string representation of an ID type
87
- */
88
- declare function getIdType(id: EntityId<any>): "component" | "entity" | "entity-relation" | "component-relation" | "wildcard-relation" | "invalid";
89
- /**
90
- * Get detailed type information for an EntityId
91
- * @param id The EntityId to analyze
92
- * @returns Detailed type information including relation subtypes
93
- */
94
- declare function getDetailedIdType(id: EntityId<any>): {
95
- type: "component" | "entity" | "invalid";
96
- componentId?: never;
97
- targetId?: never;
98
- } | {
99
- type: "entity-relation" | "wildcard-relation";
100
- componentId: ComponentId<any>;
101
- targetId: EntityId<any>;
102
- } | {
103
- type: "component-relation";
104
- componentId: ComponentId<any>;
105
- targetId: ComponentId<any>;
106
- };
107
- /**
108
- * Inspect an EntityId and return a human-readable string representation
109
- * @param id The EntityId to inspect
110
- * @returns A friendly string representation of the ID
64
+ * Component options that define intrinsic properties
111
65
  */
112
- declare function inspectEntityId(id: EntityId<any>): string;
113
- /**
114
- * Entity ID Manager for automatic allocation and freelist recycling
115
- */
116
- declare class EntityIdManager {
117
- private nextId;
118
- private freelist;
119
- /**
120
- * Allocate a new entity ID
121
- * Uses freelist if available, otherwise increments counter
122
- */
123
- allocate(): EntityId;
124
- /**
125
- * Deallocate an entity ID, adding it to the freelist for reuse
126
- * @param id The entity ID to deallocate
127
- */
128
- deallocate(id: EntityId<any>): void;
129
- /**
130
- * Get the current freelist size (for debugging/monitoring)
131
- */
132
- getFreelistSize(): number;
133
- /**
134
- * Get the next ID that would be allocated (for debugging)
135
- */
136
- getNextId(): number;
66
+ interface ComponentOptions {
137
67
  /**
138
- * Serialize internal state for persistence.
139
- * Returns a plain object representing allocator state. Values may be non-JSON-serializable.
68
+ * Optional name for the component (for serialization/debugging)
140
69
  */
141
- serializeState(): {
142
- nextId: number;
143
- freelist: number[];
144
- };
70
+ name?: string;
145
71
  /**
146
- * Restore internal state from a previously-serialized object.
147
- * Overwrites the current nextId and freelist.
72
+ * If true, an entity can have at most one relation per base component.
73
+ * When adding a new relation with the same base component, any existing relations
74
+ * with that base component are automatically removed.
75
+ * Only applicable to relation components.
148
76
  */
149
- deserializeState(state: {
150
- nextId: number;
151
- freelist?: number[];
152
- }): void;
153
- }
154
- /**
155
- * Component ID Manager for automatic allocation
156
- * Components are typically registered once and not recycled
157
- */
158
- declare class ComponentIdAllocator {
159
- private nextId;
77
+ exclusive?: boolean;
160
78
  /**
161
- * Allocate a new component ID
162
- * Increments counter sequentially from 1
79
+ * If true, when a relation target entity is deleted, all entities that reference
80
+ * it through this component will also be deleted (cascade delete).
81
+ * Only applicable to entity-relation components.
163
82
  */
164
- allocate<T = void>(): ComponentId<T>;
83
+ cascadeDelete?: boolean;
165
84
  /**
166
- * Get the next ID that would be allocated (for debugging)
85
+ * If true, relations with this component will not cause archetype fragmentation.
86
+ * Entities with different target entities for this relation component will be stored
87
+ * in the same archetype, preventing fragmentation when there are many different targets.
88
+ * Only applicable to relation components.
89
+ * Inspired by Flecs' DontFragment trait.
167
90
  */
168
- getNextId(): number;
169
- /**
170
- * Check if more component IDs are available
171
- */
172
- hasAvailableIds(): boolean;
91
+ dontFragment?: boolean;
173
92
  }
174
93
  /**
175
94
  * Allocate a new component ID from the global allocator.
176
- * Optionally register a name for the component.
177
- * The name is only for serialization/debugging and does not affect base functionality.
178
- * @param name Optional name for the component
95
+ * @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
179
96
  * @returns The allocated component ID
180
- */
181
- declare function component<T = void>(name?: string): ComponentId<T>;
97
+ * @example
98
+ * // Just a name
99
+ * const Position = component<Position>("Position");
100
+ *
101
+ * // With options
102
+ * const ChildOf = component({ exclusive: true, cascadeDelete: true });
103
+ *
104
+ * // With name and options
105
+ * const ChildOf = component({ name: "ChildOf", exclusive: true });
106
+ */
107
+ declare function component<T = void>(nameOrOptions?: string | ComponentOptions): ComponentId<T>;
182
108
  /**
183
109
  * Get a component ID by its registered name
184
110
  * @param name The component name
@@ -213,7 +139,6 @@ type ComponentType<T> = EntityId<T> | OptionalEntityId<T>;
213
139
  type OptionalEntityId<T> = {
214
140
  optional: EntityId<T>;
215
141
  };
216
- declare function isOptionalEntityId<T>(type: ComponentType<T>): type is OptionalEntityId<T>;
217
142
  type ComponentTypeToData<T> = T extends {
218
143
  optional: infer U;
219
144
  } ? {
@@ -225,10 +150,6 @@ type ComponentTypeToData<T> = T extends {
225
150
  type ComponentTuple<T extends readonly ComponentType<any>[]> = { readonly [K in keyof T]: ComponentTypeToData<T[K]> };
226
151
  //#endregion
227
152
  //#region src/archetype.d.ts
228
- /**
229
- * Special value to represent missing component data
230
- */
231
- declare const MISSING_COMPONENT: unique symbol;
232
153
  /**
233
154
  * Archetype class for ECS architecture
234
155
  * Represents a group of entities that share the same set of components
@@ -252,6 +173,12 @@ declare class Archetype {
252
173
  * Reverse mapping from entity to its index in this archetype
253
174
  */
254
175
  private entityToIndex;
176
+ /**
177
+ * Reference to dontFragment relations storage from World
178
+ * This allows entities with different relation targets to share the same archetype
179
+ * Stored in World to avoid migration overhead when entities change archetypes
180
+ */
181
+ private dontFragmentRelations;
255
182
  /**
256
183
  * Cache for pre-computed component data sources to avoid repeated calculations
257
184
  * For regular components: data array
@@ -261,8 +188,9 @@ declare class Archetype {
261
188
  /**
262
189
  * Create a new archetype with the specified component types
263
190
  * @param componentTypes The component types that define this archetype
191
+ * @param dontFragmentRelations Reference to the World's dontFragmentRelations storage
264
192
  */
265
- constructor(componentTypes: EntityId<any>[]);
193
+ constructor(componentTypes: EntityId<any>[], dontFragmentRelations: Map<EntityId, Map<EntityId<any>, any>>);
266
194
  /**
267
195
  * Get the number of entities in this archetype
268
196
  */
@@ -275,18 +203,18 @@ declare class Archetype {
275
203
  /**
276
204
  * Add an entity to this archetype with initial component data
277
205
  * @param entityId The entity to add
278
- * @param componentData Map of component type to component data
206
+ * @param componentData Map of component type to component data (includes both regular and dontFragment components)
279
207
  */
280
208
  addEntity(entityId: EntityId, componentData: Map<EntityId<any>, any>): void;
281
209
  /**
282
210
  * Get all component data for a specific entity
283
211
  * @param entityId The entity to get data for
284
- * @returns Map of component type to component data
212
+ * @returns Map of component type to component data (includes both regular and dontFragment components)
285
213
  */
286
214
  getEntity(entityId: EntityId): Map<EntityId<any>, any> | undefined;
287
215
  /**
288
216
  * Dump all entities and their component data in this archetype
289
- * @returns Array of objects with entity and component data
217
+ * @returns Array of objects with entity and component data (includes both regular and dontFragment components)
290
218
  */
291
219
  dump(): Array<{
292
220
  entity: EntityId;
@@ -295,7 +223,7 @@ declare class Archetype {
295
223
  /**
296
224
  * Remove an entity from this archetype
297
225
  * @param entityId The entity to remove
298
- * @returns The component data of the removed entity
226
+ * @returns The component data of the removed entity (includes both regular and dontFragment components)
299
227
  */
300
228
  removeEntity(entityId: EntityId): Map<EntityId<any>, any> | undefined;
301
229
  /**
@@ -346,10 +274,34 @@ declare class Archetype {
346
274
  * Helper: compute or return cached data sources for provided componentTypes
347
275
  */
348
276
  private getCachedComponentDataSources;
277
+ /**
278
+ * Build cache key for component types
279
+ */
280
+ private buildCacheKey;
281
+ /**
282
+ * Get data source for a single component type
283
+ */
284
+ private getComponentDataSource;
285
+ /**
286
+ * Get data source for wildcard relations
287
+ */
288
+ private getWildcardRelationDataSource;
349
289
  /**
350
290
  * Helper: build component tuples for a specific entity index using precomputed data sources
351
291
  */
352
292
  private buildComponentsForIndex;
293
+ /**
294
+ * Build a single component value from its data source
295
+ */
296
+ private buildSingleComponent;
297
+ /**
298
+ * Build wildcard relation value from matching relations
299
+ */
300
+ private buildWildcardRelationValue;
301
+ /**
302
+ * Build regular component value from data source
303
+ */
304
+ private buildRegularComponentValue;
353
305
  /**
354
306
  * Get entities with their component data for specified component types
355
307
  * Optimized for bulk component access with pre-computed indices
@@ -449,6 +401,10 @@ declare class Query {
449
401
  private cachedArchetypes;
450
402
  private isDisposed;
451
403
  constructor(world: World<any[]>, componentTypes: EntityId<any>[], filter?: QueryFilter);
404
+ /**
405
+ * Check if query is disposed and throw error if so
406
+ */
407
+ private ensureNotDisposed;
452
408
  /**
453
409
  * Get all entities matching the query
454
410
  */
@@ -548,6 +504,8 @@ declare class World<UpdateParams extends any[] = []> {
548
504
  private archetypesByComponent;
549
505
  /** Tracks which entities reference each entity as a component type */
550
506
  private entityReferences;
507
+ /** Storage for dontFragment relations - maps entity ID to a map of relation type to component data */
508
+ private dontFragmentRelations;
551
509
  /** Array of all active queries for archetype change notifications */
552
510
  private queries;
553
511
  /** Cache for queries keyed by component types and filter signatures */
@@ -558,10 +516,6 @@ declare class World<UpdateParams extends any[] = []> {
558
516
  private commandBuffer;
559
517
  /** Stores lifecycle hooks for component and relation events */
560
518
  private hooks;
561
- /** Set of component IDs marked as exclusive relations */
562
- private exclusiveComponents;
563
- /** Set of component IDs that will cascade delete when the relation target is deleted */
564
- private cascadeDeleteComponents;
565
519
  /**
566
520
  * Create a new World.
567
521
  * If an optional snapshot object is provided (previously produced by `world.serialize()`),
@@ -632,14 +586,14 @@ declare class World<UpdateParams extends any[] = []> {
632
586
  unhook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
633
587
  /**
634
588
  * Mark a component as exclusive relation
635
- * For exclusive relations, an entity can have at most one relation per base component
589
+ * @deprecated This method has been removed. Use component options instead: component({ exclusive: true })
590
+ * @throws Always throws an error directing to the new API
636
591
  */
637
592
  setExclusive(componentId: EntityId): void;
638
593
  /**
639
594
  * Mark a component as cascade-delete relation
640
- * For cascade relations, when the relation target entity is deleted,
641
- * the referencing entity will also be deleted (cascade).
642
- * Only applicable to entity-relation components
595
+ * @deprecated This method has been removed. Use component options instead: component({ cascadeDelete: true })
596
+ * @throws Always throws an error directing to the new API
643
597
  */
644
598
  setCascadeDelete(componentId: EntityId): void;
645
599
  /**
@@ -688,11 +642,77 @@ declare class World<UpdateParams extends any[] = []> {
688
642
  * @returns ComponentChangeset describing the changes made
689
643
  */
690
644
  executeEntityCommands(entityId: EntityId, commands: Command[]): ComponentChangeset;
645
+ /**
646
+ * Process commands and populate the changeset
647
+ */
648
+ private processCommands;
649
+ /**
650
+ * Process a set command, handling exclusive relations
651
+ */
652
+ private processSetCommand;
653
+ /**
654
+ * Remove all relations with the same base component (for exclusive relations)
655
+ */
656
+ private removeExclusiveRelations;
657
+ /**
658
+ * Check if a component type is a relation with the given base component
659
+ */
660
+ private isRelationWithComponent;
661
+ /**
662
+ * Process a delete command, handling wildcard relations
663
+ */
664
+ private processDeleteCommand;
665
+ /**
666
+ * Remove all relations matching a wildcard component ID
667
+ */
668
+ private removeWildcardRelations;
669
+ /**
670
+ * Apply changeset to entity, moving to new archetype if needed
671
+ * @returns Map of removed components with their data
672
+ */
673
+ private applyChangeset;
674
+ /**
675
+ * Move entity to a new archetype with updated components
676
+ */
677
+ private moveEntityToNewArchetype;
678
+ /**
679
+ * Update entity in same archetype (no archetype change needed)
680
+ */
681
+ private updateEntityInSameArchetype;
682
+ /**
683
+ * Check if changeset contains dontFragment relation changes
684
+ */
685
+ private hasDontFragmentChanges;
686
+ /**
687
+ * Remove and re-add entity with updated components (for dontFragment changes)
688
+ */
689
+ private readdEntityWithUpdatedComponents;
690
+ /**
691
+ * Update entity reference tracking based on changeset
692
+ */
693
+ private updateEntityReferences;
691
694
  /**
692
695
  * Get or create an archetype for the given component types
693
- * @returns The archetype for the given component types
696
+ * Filters out dontFragment relations from the archetype signature
697
+ * @returns The archetype for the given component types (excluding dontFragment relations)
694
698
  */
695
699
  private ensureArchetype;
700
+ /**
701
+ * Filter out dontFragment relations from component types
702
+ */
703
+ private filterRegularComponentTypes;
704
+ /**
705
+ * Create a new archetype and register it with all tracking structures
706
+ */
707
+ private createNewArchetype;
708
+ /**
709
+ * Register archetype in the component-to-archetype index
710
+ */
711
+ private registerArchetypeInComponentIndex;
712
+ /**
713
+ * Notify all queries to check the new archetype
714
+ */
715
+ private notifyQueriesOfNewArchetype;
696
716
  /**
697
717
  * Add a component reference to the reverse index when an entity is used as a component type
698
718
  * @param sourceEntityId The entity that has the component
@@ -717,6 +737,22 @@ declare class World<UpdateParams extends any[] = []> {
717
737
  * Remove an empty archetype from all internal data structures
718
738
  */
719
739
  private cleanupEmptyArchetype;
740
+ /**
741
+ * Remove archetype from the main archetypes list
742
+ */
743
+ private removeArchetypeFromList;
744
+ /**
745
+ * Remove archetype from the signature-to-archetype map
746
+ */
747
+ private removeArchetypeFromSignatureMap;
748
+ /**
749
+ * Remove archetype from the component-to-archetypes index
750
+ */
751
+ private removeArchetypeFromComponentIndex;
752
+ /**
753
+ * Remove archetype from all queries
754
+ */
755
+ private removeArchetypeFromQueries;
720
756
  /**
721
757
  * Execute component lifecycle hooks for added and removed components
722
758
  */
@@ -745,31 +781,5 @@ type SerializedComponent = {
745
781
  value: any;
746
782
  };
747
783
  //#endregion
748
- //#region src/system-scheduler.d.ts
749
- /**
750
- * System Scheduler for managing system dependencies and execution order
751
- */
752
- declare class SystemScheduler<UpdateParams extends any[] = []> {
753
- private systems;
754
- private systemDependencies;
755
- private cachedExecutionOrder;
756
- /**
757
- * Add a system with optional dependencies
758
- * @param system The system to add
759
- * @param additionalDeps Additional dependencies for the system
760
- */
761
- addSystem(system: System<UpdateParams>, additionalDeps?: System<UpdateParams>[]): void;
762
- /**
763
- * Get the execution order of systems based on dependencies
764
- * Uses topological sort
765
- */
766
- getExecutionOrder(): System<UpdateParams>[];
767
- update(...params: UpdateParams): Promise<void[]> | void;
768
- /**
769
- * Clear all systems and dependencies
770
- */
771
- clear(): void;
772
- }
773
- //#endregion
774
- export { Archetype, COMPONENT_ID_MAX, ComponentId, ComponentIdAllocator, ComponentRelationId, ComponentTuple, ComponentType, ComponentTypeToData, ENTITY_ID_START, EntityId, EntityIdManager, EntityRelationId, INVALID_COMPONENT_ID, LifecycleHook, MISSING_COMPONENT, OptionalEntityId, Query, RELATION_SHIFT, RelationId, SerializedComponent, SerializedEntity, SerializedWorld, System, SystemScheduler, WILDCARD_TARGET_ID, WildcardRelationId, World, component, createComponentId, createEntityId, decodeRelationId, getComponentIdByName, getComponentNameById, getDetailedIdType, getIdType, inspectEntityId, isComponentId, isEntityId, isOptionalEntityId, isRelationId, isWildcardRelationId, relation };
784
+ export { type ComponentId, type ComponentOptions, type ComponentRelationId, type ComponentTuple, type ComponentType, type EntityId, type EntityRelationId, type LifecycleHook, Query, type RelationId, type SerializedWorld, type System, type WildcardRelationId, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
775
785
  //# sourceMappingURL=index.d.mts.map