@codehz/ecs 0.6.4 → 0.6.6

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/builder.d.mts CHANGED
@@ -436,21 +436,182 @@ declare class World {
436
436
  constructor(snapshot?: SerializedWorld);
437
437
  private deserializeSnapshot;
438
438
  private createArchetypeSignature;
439
+ /**
440
+ * Creates a new entity.
441
+ * The entity is created with an empty component set and can be configured using `set()`.
442
+ *
443
+ * @template T - The initial component type (defaults to void if not specified)
444
+ * @returns A unique identifier for the new entity
445
+ *
446
+ * @example
447
+ * const entity = world.new<MyComponent>();
448
+ * world.set(entity, MyComponent, { value: 42 });
449
+ * world.sync();
450
+ */
439
451
  new<T = void>(): EntityId<T>;
440
452
  private destroyEntityImmediate;
453
+ /**
454
+ * Checks if an entity exists in the world.
455
+ *
456
+ * @param entityId - The entity identifier to check
457
+ * @returns `true` if the entity exists, `false` otherwise
458
+ *
459
+ * @example
460
+ * if (world.exists(entityId)) {
461
+ * console.log("Entity exists");
462
+ * }
463
+ */
441
464
  exists(entityId: EntityId): boolean;
465
+ /**
466
+ * Adds or updates a component on an entity (or marks void component as present).
467
+ * The change is buffered and takes effect after calling `world.sync()`.
468
+ * If the entity does not exist, throws an error.
469
+ *
470
+ * @overload set(entityId: EntityId, componentType: EntityId<void>): void
471
+ * Marks a void component as present on the entity
472
+ *
473
+ * @overload set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void
474
+ * Adds or updates a component with data on the entity
475
+ *
476
+ * @throws {Error} If the entity does not exist
477
+ * @throws {Error} If the component type is invalid or is a wildcard relation
478
+ *
479
+ * @example
480
+ * world.set(entity, Position, { x: 10, y: 20 });
481
+ * world.set(entity, Marker); // void component
482
+ * world.sync(); // Apply changes
483
+ */
442
484
  set(entityId: EntityId, componentType: EntityId<void>): void;
443
485
  set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
486
+ /**
487
+ * Removes a component from an entity.
488
+ * The change is buffered and takes effect after calling `world.sync()`.
489
+ * If the entity does not exist, throws an error.
490
+ *
491
+ * @template T - The component data type
492
+ * @param entityId - The entity identifier
493
+ * @param componentType - The component type to remove
494
+ *
495
+ * @throws {Error} If the entity does not exist
496
+ * @throws {Error} If the component type is invalid
497
+ *
498
+ * @example
499
+ * world.remove(entity, Position);
500
+ * world.sync(); // Apply changes
501
+ */
444
502
  remove<T>(entityId: EntityId, componentType: EntityId<T>): void;
503
+ /**
504
+ * Deletes an entity and all its components from the world.
505
+ * The change is buffered and takes effect after calling `world.sync()`.
506
+ * Related entities may trigger cascade delete hooks if configured.
507
+ *
508
+ * @param entityId - The entity identifier to delete
509
+ *
510
+ * @example
511
+ * world.delete(entity);
512
+ * world.sync(); // Apply changes
513
+ */
445
514
  delete(entityId: EntityId): void;
515
+ /**
516
+ * Checks if an entity has a specific component.
517
+ * Immediately reflects the current state without waiting for `sync()`.
518
+ *
519
+ * @template T - The component data type
520
+ * @param entityId - The entity identifier
521
+ * @param componentType - The component type to check
522
+ * @returns `true` if the entity has the component, `false` otherwise
523
+ *
524
+ * @example
525
+ * if (world.has(entity, Position)) {
526
+ * const pos = world.get(entity, Position);
527
+ * }
528
+ */
446
529
  has<T>(entityId: EntityId, componentType: EntityId<T>): boolean;
530
+ /**
531
+ * Retrieves a component from an entity.
532
+ * For wildcard relations, returns all relations of that type.
533
+ * Throws an error if the component does not exist; use `has()` to check first or use `getOptional()`.
534
+ *
535
+ * @overload get<T>(entityId: EntityId<T>): T
536
+ * When called with only an entity ID, retrieves the entity's primary component.
537
+ *
538
+ * @overload get<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, T][]
539
+ * For wildcard relations, returns an array of [target entity, component value] pairs.
540
+ *
541
+ * @overload get<T>(entityId: EntityId, componentType: EntityId<T>): T
542
+ * Retrieves a specific component from the entity.
543
+ *
544
+ * @throws {Error} If the entity does not exist
545
+ * @throws {Error} If the component does not exist on the entity
546
+ *
547
+ * @example
548
+ * const position = world.get(entity, Position); // Throws if no Position
549
+ * const relations = world.get(entity, relation(Parent, "*")); // Wildcard relation
550
+ */
551
+ get<T>(entityId: EntityId<T>): T;
447
552
  get<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, T][];
448
553
  get<T>(entityId: EntityId, componentType: EntityId<T>): T;
554
+ /**
555
+ * Safely retrieves a component from an entity without throwing an error.
556
+ * Returns `undefined` if the component does not exist.
557
+ * For wildcard relations, returns `undefined` if there are no relations.
558
+ *
559
+ * @template T - The component data type
560
+ * @overload getOptional<T>(entityId: EntityId<T>): { value: T } | undefined
561
+ * Retrieves the entity's primary component safely.
562
+ *
563
+ * @overload getOptional<T>(entityId: EntityId, componentType: EntityId<T>): { value: T } | undefined
564
+ * Retrieves a specific component safely.
565
+ *
566
+ * @throws {Error} If the entity does not exist
567
+ *
568
+ * @example
569
+ * const position = world.getOptional(entity, Position);
570
+ * if (position) {
571
+ * console.log(position.value.x);
572
+ * }
573
+ */
574
+ getOptional<T>(entityId: EntityId<T>): {
575
+ value: T;
576
+ } | undefined;
449
577
  getOptional<T>(entityId: EntityId, componentType: EntityId<T>): {
450
578
  value: T;
451
579
  } | undefined;
452
580
  /**
453
- * @deprecated use array overload with LifecycleCallback
581
+ * Registers a lifecycle hook that responds to component changes.
582
+ * The hook callback is invoked when components matching the specified types are added, updated, or removed.
583
+ *
584
+ * @deprecated For single components, use the array overload with LifecycleCallback for better multi-component support
585
+ *
586
+ * @overload hook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T> | LegacyLifecycleCallback<T>): () => void
587
+ * Registers a hook for a single component type (legacy API).
588
+ *
589
+ * @overload hook<const T extends readonly ComponentType<any>[]>(
590
+ * componentTypes: T,
591
+ * hook: LifecycleHook<T> | LifecycleCallback<T>,
592
+ * ): () => void
593
+ * Registers a hook for multiple component types.
594
+ * The hook is triggered when all required components change together.
595
+ *
596
+ * @param componentTypesOrSingle - A single component type or an array of component types
597
+ * @param hook - Either a hook object with on_init/on_set/on_remove handlers, or a callback function
598
+ * @returns A function that unsubscribes the hook when called
599
+ *
600
+ * @throws {Error} If no required components are specified in array overload
601
+ *
602
+ * @example
603
+ * // Array overload (recommended)
604
+ * const unsubscribe = world.hook([Position, Velocity], {
605
+ * on_init: (entityId, position, velocity) => console.log("Initialized"),
606
+ * on_set: (entityId, position, velocity) => console.log("Updated"),
607
+ * on_remove: (entityId, position, velocity) => console.log("Removed"),
608
+ * });
609
+ * unsubscribe(); // Remove hook
610
+ *
611
+ * // Callback style
612
+ * const unsubscribe = world.hook([Position], (event, entityId, position) => {
613
+ * if (event === "init") console.log("Initialized");
614
+ * });
454
615
  */
455
616
  hook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T> | LegacyLifecycleCallback<T>): () => void;
456
617
  hook<const T extends readonly ComponentType<any>[]>(componentTypes: T, hook: LifecycleHook<T> | LifecycleCallback<T>): () => void;
@@ -458,15 +619,128 @@ declare class World {
458
619
  unhook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T>): void;
459
620
  /** @deprecated use the unsubscribe function returned by hook() instead */
460
621
  unhook<const T extends readonly ComponentType<any>[]>(componentTypes: T, hook: LifecycleHook<T>): void;
622
+ /**
623
+ * Synchronizes all buffered commands (set/remove/delete) to the world.
624
+ * This method must be called after making changes via `set()`, `remove()`, or `delete()` for them to take effect.
625
+ * Typically called once per frame at the end of your game loop.
626
+ *
627
+ * @example
628
+ * world.set(entity, Position, { x: 10, y: 20 });
629
+ * world.remove(entity, OldComponent);
630
+ * world.sync(); // Apply all buffered changes
631
+ */
461
632
  sync(): void;
633
+ /**
634
+ * Creates a cached query for efficiently iterating entities with specific components.
635
+ * The query is cached internally and reused across calls with the same component types and filter.
636
+ *
637
+ * **Important:** Store the query reference and reuse it across frames for optimal performance.
638
+ * Creating a new query each frame defeats the caching mechanism.
639
+ *
640
+ * @param componentTypes - Array of component types to match
641
+ * @param filter - Optional filter for additional constraints (e.g., without specific components)
642
+ * @returns A Query instance that can be used to iterate matching entities
643
+ *
644
+ * @example
645
+ * // Create once, reuse many times
646
+ * const movementQuery = world.createQuery([Position, Velocity]);
647
+ *
648
+ * // In game loop
649
+ * movementQuery.forEach((entity) => {
650
+ * const pos = world.get(entity, Position);
651
+ * const vel = world.get(entity, Velocity);
652
+ * pos.x += vel.x;
653
+ * pos.y += vel.y;
654
+ * });
655
+ *
656
+ * // With filter
657
+ * const activeQuery = world.createQuery([Position], {
658
+ * without: [Disabled]
659
+ * });
660
+ */
462
661
  createQuery(componentTypes: EntityId<any>[], filter?: QueryFilter): Query;
662
+ /**
663
+ * Creates a new entity builder for fluent entity configuration.
664
+ * Useful for building entities with multiple components in a single expression.
665
+ *
666
+ * @returns An EntityBuilder instance
667
+ *
668
+ * @example
669
+ * const entity = world.spawn()
670
+ * .with(Position, { x: 0, y: 0 })
671
+ * .with(Velocity, { x: 1, y: 1 })
672
+ * .build();
673
+ * world.sync(); // Apply changes
674
+ */
463
675
  spawn(): EntityBuilder;
676
+ /**
677
+ * Spawns multiple entities with a configuration callback.
678
+ * More efficient than calling `spawn()` multiple times when creating many entities.
679
+ *
680
+ * @param count - Number of entities to spawn
681
+ * @param configure - Callback that receives an EntityBuilder and index; must return the configured builder
682
+ * @returns Array of created entity IDs
683
+ *
684
+ * @example
685
+ * const entities = world.spawnMany(100, (builder, index) => {
686
+ * return builder
687
+ * .with(Position, { x: index * 10, y: 0 })
688
+ * .with(Velocity, { x: 0, y: 1 });
689
+ * });
690
+ * world.sync();
691
+ */
464
692
  spawnMany(count: number, configure: (builder: EntityBuilder, index: number) => EntityBuilder): EntityId[];
465
693
  _registerQuery(query: Query): void;
466
694
  _unregisterQuery(query: Query): void;
695
+ /**
696
+ * Releases a cached query and frees its resources if no longer needed.
697
+ * Call this when you're done using a query to allow the world to clean up its cache entry.
698
+ *
699
+ * @param query - The query to release
700
+ *
701
+ * @example
702
+ * const query = world.createQuery([Position]);
703
+ * // ... use query ...
704
+ * world.releaseQuery(query); // Optional cleanup
705
+ */
467
706
  releaseQuery(query: Query): void;
707
+ /**
708
+ * Returns all archetypes that contain entities with the specified components.
709
+ * Used internally for query optimization but can be useful for debugging.
710
+ *
711
+ * @param componentTypes - Array of component types to match
712
+ * @returns Array of Archetype objects containing matching components
713
+ * @internal
714
+ */
468
715
  getMatchingArchetypes(componentTypes: EntityId<any>[]): Archetype[];
469
716
  private getArchetypesWithComponents;
717
+ /**
718
+ * Queries entities with specific components.
719
+ * For simpler use cases, prefer using `createQuery()` with `forEach()` which is cached and more efficient.
720
+ *
721
+ * @overload query(componentTypes: EntityId<any>[]): EntityId[]
722
+ * Returns an array of entity IDs that have all specified components.
723
+ *
724
+ * @overload query<const T extends readonly EntityId<any>[]>(
725
+ * componentTypes: T,
726
+ * includeComponents: true,
727
+ * ): Array<{ entity: EntityId; components: ComponentTuple<T> }>
728
+ * Returns entities along with their component data.
729
+ *
730
+ * @param componentTypes - Array of component types to query
731
+ * @param includeComponents - If true, includes component data in results
732
+ * @returns Array of entity IDs or objects with entities and components
733
+ *
734
+ * @example
735
+ * // Just entity IDs
736
+ * const entities = world.query([Position, Velocity]);
737
+ *
738
+ * // With components
739
+ * const results = world.query([Position, Velocity], true);
740
+ * results.forEach(({ entity, components: [pos, vel] }) => {
741
+ * pos.x += vel.x;
742
+ * });
743
+ */
470
744
  query(componentTypes: EntityId<any>[]): EntityId[];
471
745
  query<const T extends readonly EntityId<any>[]>(componentTypes: T, includeComponents: true): Array<{
472
746
  entity: EntityId;
@@ -483,6 +757,27 @@ declare class World {
483
757
  private archetypeReferencesEntity;
484
758
  private cleanupArchetypesReferencingEntity;
485
759
  private removeArchetype;
760
+ /**
761
+ * Serializes the entire world state to a plain JavaScript object.
762
+ * This creates a "memory snapshot" that can be stored or transmitted.
763
+ * The snapshot can be restored using `new World(snapshot)`.
764
+ *
765
+ * **Note:** This is NOT automatically persistent storage. To persist data,
766
+ * you must serialize the returned object to JSON or another format yourself.
767
+ *
768
+ * @returns A serializable object representing the world state
769
+ *
770
+ * @example
771
+ * // Create snapshot
772
+ * const snapshot = world.serialize();
773
+ *
774
+ * // Save to storage (example)
775
+ * localStorage.setItem('save', JSON.stringify(snapshot));
776
+ *
777
+ * // Later, restore from snapshot
778
+ * const savedData = JSON.parse(localStorage.getItem('save'));
779
+ * const newWorld = new World(savedData);
780
+ */
486
781
  serialize(): SerializedWorld;
487
782
  }
488
783
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ecs",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "repository": {
5
5
  "url": "https://github.com/codehz/ecs"
6
6
  },
package/world.mjs CHANGED
@@ -1534,6 +1534,23 @@ function triggerLifecycleHooks(ctx, entityId, addedComponents, removedComponents
1534
1534
  invokeHooksForComponents(ctx.hooks, entityId, removedComponents, "on_remove");
1535
1535
  triggerMultiComponentHooks(ctx, entityId, addedComponents, removedComponents, oldArchetype, newArchetype);
1536
1536
  }
1537
+ /**
1538
+ * Fast path for triggering lifecycle hooks when an entity is being deleted.
1539
+ * This avoids unnecessary archetype lookups and on_set checks since the entity
1540
+ * is being completely removed.
1541
+ */
1542
+ function triggerRemoveHooksForEntityDeletion(ctx, entityId, removedComponents, oldArchetype) {
1543
+ if (removedComponents.size === 0) return;
1544
+ invokeHooksForComponents(ctx.hooks, entityId, removedComponents, "on_remove");
1545
+ for (const entry of oldArchetype.matchingMultiHooks) {
1546
+ const { hook, requiredComponents, componentTypes } = entry;
1547
+ if (!hook.on_remove) continue;
1548
+ if (!requiredComponents.some((c) => anyComponentMatches(removedComponents, c))) continue;
1549
+ if (!requiredComponents.every((c) => anyComponentMatches(removedComponents, c))) continue;
1550
+ const components = collectComponentsFromRemoved(componentTypes, removedComponents);
1551
+ hook.on_remove(entityId, ...components);
1552
+ }
1553
+ }
1537
1554
  function invokeHooksForComponents(hooks, entityId, components, hookType) {
1538
1555
  for (const [componentType, component$1] of components) {
1539
1556
  const directHooks = hooks.get(componentType);
@@ -1616,6 +1633,38 @@ function collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, re
1616
1633
  return match ? match[1] : ctx.get(entityId, compId);
1617
1634
  });
1618
1635
  }
1636
+ /**
1637
+ * Collect component values directly from removedComponents map.
1638
+ * Used for entity deletion fast path where the entity no longer exists.
1639
+ */
1640
+ function collectComponentsFromRemoved(componentTypes, removedComponents) {
1641
+ return componentTypes.map((ct) => {
1642
+ if (isOptionalEntityId(ct)) {
1643
+ const optionalId = ct.optional;
1644
+ if (isWildcardRelationId(optionalId)) {
1645
+ const result = collectWildcardFromRemoved(optionalId, removedComponents);
1646
+ return result.length > 0 ? { value: result } : void 0;
1647
+ }
1648
+ const match$1 = findMatchingComponent(removedComponents, optionalId);
1649
+ return match$1 ? { value: match$1[1] } : void 0;
1650
+ }
1651
+ const compId = ct;
1652
+ if (isWildcardRelationId(compId)) return collectWildcardFromRemoved(compId, removedComponents);
1653
+ const match = findMatchingComponent(removedComponents, compId);
1654
+ return match ? match[1] : void 0;
1655
+ });
1656
+ }
1657
+ /**
1658
+ * Collect all matching wildcard relation data from removed components.
1659
+ */
1660
+ function collectWildcardFromRemoved(wildcardId, removedComponents) {
1661
+ const result = [];
1662
+ for (const [removedCompId, removedValue] of removedComponents.entries()) if (componentMatchesHookType(removedCompId, wildcardId)) {
1663
+ const targetId = getTargetIdFromRelationId(removedCompId);
1664
+ if (targetId !== void 0) result.push([targetId, removedValue]);
1665
+ }
1666
+ return result;
1667
+ }
1619
1668
 
1620
1669
  //#endregion
1621
1670
  //#region src/utils/multi-map.ts
@@ -1750,6 +1799,18 @@ var World = class {
1750
1799
  createArchetypeSignature(componentTypes) {
1751
1800
  return componentTypes.join(",");
1752
1801
  }
1802
+ /**
1803
+ * Creates a new entity.
1804
+ * The entity is created with an empty component set and can be configured using `set()`.
1805
+ *
1806
+ * @template T - The initial component type (defaults to void if not specified)
1807
+ * @returns A unique identifier for the new entity
1808
+ *
1809
+ * @example
1810
+ * const entity = world.new<MyComponent>();
1811
+ * world.set(entity, MyComponent, { value: 42 });
1812
+ * world.sync();
1813
+ */
1753
1814
  new() {
1754
1815
  const entityId = this.entityIdManager.allocate();
1755
1816
  let emptyArchetype = this.ensureArchetype([]);
@@ -1775,14 +1836,22 @@ var World = class {
1775
1836
  this.entityReferences.delete(cur);
1776
1837
  const removedComponents = archetype.removeEntity(cur);
1777
1838
  this.entityToArchetype.delete(cur);
1778
- if (removedComponents.size > 0) {
1779
- const emptyArchetype = this.ensureArchetype([]);
1780
- triggerLifecycleHooks(this.createHooksContext(), cur, /* @__PURE__ */ new Map(), removedComponents, archetype, emptyArchetype);
1781
- }
1839
+ triggerRemoveHooksForEntityDeletion(this.createHooksContext(), cur, removedComponents, archetype);
1782
1840
  this.cleanupArchetypesReferencingEntity(cur);
1783
1841
  this.entityIdManager.deallocate(cur);
1784
1842
  }
1785
1843
  }
1844
+ /**
1845
+ * Checks if an entity exists in the world.
1846
+ *
1847
+ * @param entityId - The entity identifier to check
1848
+ * @returns `true` if the entity exists, `false` otherwise
1849
+ *
1850
+ * @example
1851
+ * if (world.exists(entityId)) {
1852
+ * console.log("Entity exists");
1853
+ * }
1854
+ */
1786
1855
  exists(entityId) {
1787
1856
  return this.entityToArchetype.has(entityId);
1788
1857
  }
@@ -1793,14 +1862,55 @@ var World = class {
1793
1862
  if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
1794
1863
  this.commandBuffer.set(entityId, componentType, component$1);
1795
1864
  }
1865
+ /**
1866
+ * Removes a component from an entity.
1867
+ * The change is buffered and takes effect after calling `world.sync()`.
1868
+ * If the entity does not exist, throws an error.
1869
+ *
1870
+ * @template T - The component data type
1871
+ * @param entityId - The entity identifier
1872
+ * @param componentType - The component type to remove
1873
+ *
1874
+ * @throws {Error} If the entity does not exist
1875
+ * @throws {Error} If the component type is invalid
1876
+ *
1877
+ * @example
1878
+ * world.remove(entity, Position);
1879
+ * world.sync(); // Apply changes
1880
+ */
1796
1881
  remove(entityId, componentType) {
1797
1882
  if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
1798
1883
  if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
1799
1884
  this.commandBuffer.remove(entityId, componentType);
1800
1885
  }
1886
+ /**
1887
+ * Deletes an entity and all its components from the world.
1888
+ * The change is buffered and takes effect after calling `world.sync()`.
1889
+ * Related entities may trigger cascade delete hooks if configured.
1890
+ *
1891
+ * @param entityId - The entity identifier to delete
1892
+ *
1893
+ * @example
1894
+ * world.delete(entity);
1895
+ * world.sync(); // Apply changes
1896
+ */
1801
1897
  delete(entityId) {
1802
1898
  this.commandBuffer.delete(entityId);
1803
1899
  }
1900
+ /**
1901
+ * Checks if an entity has a specific component.
1902
+ * Immediately reflects the current state without waiting for `sync()`.
1903
+ *
1904
+ * @template T - The component data type
1905
+ * @param entityId - The entity identifier
1906
+ * @param componentType - The component type to check
1907
+ * @returns `true` if the entity has the component, `false` otherwise
1908
+ *
1909
+ * @example
1910
+ * if (world.has(entity, Position)) {
1911
+ * const pos = world.get(entity, Position);
1912
+ * }
1913
+ */
1804
1914
  has(entityId, componentType) {
1805
1915
  const archetype = this.entityToArchetype.get(entityId);
1806
1916
  if (!archetype) return false;
@@ -1808,7 +1918,7 @@ var World = class {
1808
1918
  if (isDontFragmentRelation(componentType)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
1809
1919
  return false;
1810
1920
  }
1811
- get(entityId, componentType) {
1921
+ get(entityId, componentType = entityId) {
1812
1922
  const archetype = this.entityToArchetype.get(entityId);
1813
1923
  if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
1814
1924
  if (componentType >= 0 || componentType % 2 ** 42 !== 0) {
@@ -1818,7 +1928,7 @@ var World = class {
1818
1928
  }
1819
1929
  return archetype.get(entityId, componentType);
1820
1930
  }
1821
- getOptional(entityId, componentType) {
1931
+ getOptional(entityId, componentType = entityId) {
1822
1932
  const archetype = this.entityToArchetype.get(entityId);
1823
1933
  if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
1824
1934
  if (isWildcardRelationId(componentType)) {
@@ -1911,9 +2021,47 @@ var World = class {
1911
2021
  }
1912
2022
  }
1913
2023
  }
2024
+ /**
2025
+ * Synchronizes all buffered commands (set/remove/delete) to the world.
2026
+ * This method must be called after making changes via `set()`, `remove()`, or `delete()` for them to take effect.
2027
+ * Typically called once per frame at the end of your game loop.
2028
+ *
2029
+ * @example
2030
+ * world.set(entity, Position, { x: 10, y: 20 });
2031
+ * world.remove(entity, OldComponent);
2032
+ * world.sync(); // Apply all buffered changes
2033
+ */
1914
2034
  sync() {
1915
2035
  this.commandBuffer.execute();
1916
2036
  }
2037
+ /**
2038
+ * Creates a cached query for efficiently iterating entities with specific components.
2039
+ * The query is cached internally and reused across calls with the same component types and filter.
2040
+ *
2041
+ * **Important:** Store the query reference and reuse it across frames for optimal performance.
2042
+ * Creating a new query each frame defeats the caching mechanism.
2043
+ *
2044
+ * @param componentTypes - Array of component types to match
2045
+ * @param filter - Optional filter for additional constraints (e.g., without specific components)
2046
+ * @returns A Query instance that can be used to iterate matching entities
2047
+ *
2048
+ * @example
2049
+ * // Create once, reuse many times
2050
+ * const movementQuery = world.createQuery([Position, Velocity]);
2051
+ *
2052
+ * // In game loop
2053
+ * movementQuery.forEach((entity) => {
2054
+ * const pos = world.get(entity, Position);
2055
+ * const vel = world.get(entity, Velocity);
2056
+ * pos.x += vel.x;
2057
+ * pos.y += vel.y;
2058
+ * });
2059
+ *
2060
+ * // With filter
2061
+ * const activeQuery = world.createQuery([Position], {
2062
+ * without: [Disabled]
2063
+ * });
2064
+ */
1917
2065
  createQuery(componentTypes, filter = {}) {
1918
2066
  const sortedTypes = [...componentTypes].sort((a, b) => a - b);
1919
2067
  const filterKey = serializeQueryFilter(filter);
@@ -1930,9 +2078,38 @@ var World = class {
1930
2078
  });
1931
2079
  return query;
1932
2080
  }
2081
+ /**
2082
+ * Creates a new entity builder for fluent entity configuration.
2083
+ * Useful for building entities with multiple components in a single expression.
2084
+ *
2085
+ * @returns An EntityBuilder instance
2086
+ *
2087
+ * @example
2088
+ * const entity = world.spawn()
2089
+ * .with(Position, { x: 0, y: 0 })
2090
+ * .with(Velocity, { x: 1, y: 1 })
2091
+ * .build();
2092
+ * world.sync(); // Apply changes
2093
+ */
1933
2094
  spawn() {
1934
2095
  return new EntityBuilder(this);
1935
2096
  }
2097
+ /**
2098
+ * Spawns multiple entities with a configuration callback.
2099
+ * More efficient than calling `spawn()` multiple times when creating many entities.
2100
+ *
2101
+ * @param count - Number of entities to spawn
2102
+ * @param configure - Callback that receives an EntityBuilder and index; must return the configured builder
2103
+ * @returns Array of created entity IDs
2104
+ *
2105
+ * @example
2106
+ * const entities = world.spawnMany(100, (builder, index) => {
2107
+ * return builder
2108
+ * .with(Position, { x: index * 10, y: 0 })
2109
+ * .with(Velocity, { x: 0, y: 1 });
2110
+ * });
2111
+ * world.sync();
2112
+ */
1936
2113
  spawnMany(count, configure) {
1937
2114
  const entities = [];
1938
2115
  for (let i = 0; i < count; i++) {
@@ -1948,6 +2125,17 @@ var World = class {
1948
2125
  const index = this.queries.indexOf(query);
1949
2126
  if (index !== -1) this.queries.splice(index, 1);
1950
2127
  }
2128
+ /**
2129
+ * Releases a cached query and frees its resources if no longer needed.
2130
+ * Call this when you're done using a query to allow the world to clean up its cache entry.
2131
+ *
2132
+ * @param query - The query to release
2133
+ *
2134
+ * @example
2135
+ * const query = world.createQuery([Position]);
2136
+ * // ... use query ...
2137
+ * world.releaseQuery(query); // Optional cleanup
2138
+ */
1951
2139
  releaseQuery(query) {
1952
2140
  for (const [k, v] of this.queryCache.entries()) if (v.query === query) {
1953
2141
  v.refCount--;
@@ -1959,6 +2147,14 @@ var World = class {
1959
2147
  return;
1960
2148
  }
1961
2149
  }
2150
+ /**
2151
+ * Returns all archetypes that contain entities with the specified components.
2152
+ * Used internally for query optimization but can be useful for debugging.
2153
+ *
2154
+ * @param componentTypes - Array of component types to match
2155
+ * @returns Array of Archetype objects containing matching components
2156
+ * @internal
2157
+ */
1962
2158
  getMatchingArchetypes(componentTypes) {
1963
2159
  if (componentTypes.length === 0) return [...this.archetypes];
1964
2160
  const regularComponents = [];
@@ -2102,6 +2298,27 @@ var World = class {
2102
2298
  }
2103
2299
  for (const query of this.queries) query.removeArchetype(archetype);
2104
2300
  }
2301
+ /**
2302
+ * Serializes the entire world state to a plain JavaScript object.
2303
+ * This creates a "memory snapshot" that can be stored or transmitted.
2304
+ * The snapshot can be restored using `new World(snapshot)`.
2305
+ *
2306
+ * **Note:** This is NOT automatically persistent storage. To persist data,
2307
+ * you must serialize the returned object to JSON or another format yourself.
2308
+ *
2309
+ * @returns A serializable object representing the world state
2310
+ *
2311
+ * @example
2312
+ * // Create snapshot
2313
+ * const snapshot = world.serialize();
2314
+ *
2315
+ * // Save to storage (example)
2316
+ * localStorage.setItem('save', JSON.stringify(snapshot));
2317
+ *
2318
+ * // Later, restore from snapshot
2319
+ * const savedData = JSON.parse(localStorage.getItem('save'));
2320
+ * const newWorld = new World(savedData);
2321
+ */
2105
2322
  serialize() {
2106
2323
  const entities = [];
2107
2324
  for (const archetype of this.archetypes) {