Skip to content

Commit 3d0d8ac

Browse files
committed
docs: add comprehensive JSDoc comments to World methods
- Documented new(), exists(), set(), remove(), delete() - Added docs for has(), get(), getOptional() - Updated hook() and sync() documentation - Documented createQuery(), spawn(), spawnMany()
1 parent 8dfd554 commit 3d0d8ac

File tree

1 file changed

+292
-1
lines changed

1 file changed

+292
-1
lines changed

src/core/world.ts

Lines changed: 292 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ export class World {
120120
return componentTypes.join(",");
121121
}
122122

123+
/**
124+
* Creates a new entity.
125+
* The entity is created with an empty component set and can be configured using `set()`.
126+
*
127+
* @template T - The initial component type (defaults to void if not specified)
128+
* @returns A unique identifier for the new entity
129+
*
130+
* @example
131+
* const entity = world.new<MyComponent>();
132+
* world.set(entity, MyComponent, { value: 42 });
133+
* world.sync();
134+
*/
123135
new<T = void>(): EntityId<T> {
124136
const entityId = this.entityIdManager.allocate();
125137
let emptyArchetype = this.ensureArchetype([]);
@@ -167,10 +179,40 @@ export class World {
167179
}
168180
}
169181

182+
/**
183+
* Checks if an entity exists in the world.
184+
*
185+
* @param entityId - The entity identifier to check
186+
* @returns `true` if the entity exists, `false` otherwise
187+
*
188+
* @example
189+
* if (world.exists(entityId)) {
190+
* console.log("Entity exists");
191+
* }
192+
*/
170193
exists(entityId: EntityId): boolean {
171194
return this.entityToArchetype.has(entityId);
172195
}
173196

197+
/**
198+
* Adds or updates a component on an entity (or marks void component as present).
199+
* The change is buffered and takes effect after calling `world.sync()`.
200+
* If the entity does not exist, throws an error.
201+
*
202+
* @overload set(entityId: EntityId, componentType: EntityId<void>): void
203+
* Marks a void component as present on the entity
204+
*
205+
* @overload set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void
206+
* Adds or updates a component with data on the entity
207+
*
208+
* @throws {Error} If the entity does not exist
209+
* @throws {Error} If the component type is invalid or is a wildcard relation
210+
*
211+
* @example
212+
* world.set(entity, Position, { x: 10, y: 20 });
213+
* world.set(entity, Marker); // void component
214+
* world.sync(); // Apply changes
215+
*/
174216
set(entityId: EntityId, componentType: EntityId<void>): void;
175217
set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
176218
set(entityId: EntityId, componentType: EntityId, component?: any): void {
@@ -189,6 +231,22 @@ export class World {
189231
this.commandBuffer.set(entityId, componentType, component);
190232
}
191233

234+
/**
235+
* Removes a component from an entity.
236+
* The change is buffered and takes effect after calling `world.sync()`.
237+
* If the entity does not exist, throws an error.
238+
*
239+
* @template T - The component data type
240+
* @param entityId - The entity identifier
241+
* @param componentType - The component type to remove
242+
*
243+
* @throws {Error} If the entity does not exist
244+
* @throws {Error} If the component type is invalid
245+
*
246+
* @example
247+
* world.remove(entity, Position);
248+
* world.sync(); // Apply changes
249+
*/
192250
remove<T>(entityId: EntityId, componentType: EntityId<T>): void {
193251
if (!this.exists(entityId)) {
194252
throw new Error(`Entity ${entityId} does not exist`);
@@ -202,10 +260,35 @@ export class World {
202260
this.commandBuffer.remove(entityId, componentType);
203261
}
204262

263+
/**
264+
* Deletes an entity and all its components from the world.
265+
* The change is buffered and takes effect after calling `world.sync()`.
266+
* Related entities may trigger cascade delete hooks if configured.
267+
*
268+
* @param entityId - The entity identifier to delete
269+
*
270+
* @example
271+
* world.delete(entity);
272+
* world.sync(); // Apply changes
273+
*/
205274
delete(entityId: EntityId): void {
206275
this.commandBuffer.delete(entityId);
207276
}
208277

278+
/**
279+
* Checks if an entity has a specific component.
280+
* Immediately reflects the current state without waiting for `sync()`.
281+
*
282+
* @template T - The component data type
283+
* @param entityId - The entity identifier
284+
* @param componentType - The component type to check
285+
* @returns `true` if the entity has the component, `false` otherwise
286+
*
287+
* @example
288+
* if (world.has(entity, Position)) {
289+
* const pos = world.get(entity, Position);
290+
* }
291+
*/
209292
has<T>(entityId: EntityId, componentType: EntityId<T>): boolean {
210293
const archetype = this.entityToArchetype.get(entityId);
211294
if (!archetype) return false;
@@ -219,6 +302,27 @@ export class World {
219302
return false;
220303
}
221304

305+
/**
306+
* Retrieves a component from an entity.
307+
* For wildcard relations, returns all relations of that type.
308+
* Throws an error if the component does not exist; use `has()` to check first or use `getOptional()`.
309+
*
310+
* @overload get<T>(entityId: EntityId<T>): T
311+
* When called with only an entity ID, retrieves the entity's primary component.
312+
*
313+
* @overload get<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, T][]
314+
* For wildcard relations, returns an array of [target entity, component value] pairs.
315+
*
316+
* @overload get<T>(entityId: EntityId, componentType: EntityId<T>): T
317+
* Retrieves a specific component from the entity.
318+
*
319+
* @throws {Error} If the entity does not exist
320+
* @throws {Error} If the component does not exist on the entity
321+
*
322+
* @example
323+
* const position = world.get(entity, Position); // Throws if no Position
324+
* const relations = world.get(entity, relation(Parent, "*")); // Wildcard relation
325+
*/
222326
get<T>(entityId: EntityId<T>): T;
223327
get<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, T][];
224328
get<T>(entityId: EntityId, componentType: EntityId<T>): T;
@@ -247,6 +351,26 @@ export class World {
247351
return archetype.get(entityId, componentType);
248352
}
249353

354+
/**
355+
* Safely retrieves a component from an entity without throwing an error.
356+
* Returns `undefined` if the component does not exist.
357+
* For wildcard relations, returns `undefined` if there are no relations.
358+
*
359+
* @template T - The component data type
360+
* @overload getOptional<T>(entityId: EntityId<T>): { value: T } | undefined
361+
* Retrieves the entity's primary component safely.
362+
*
363+
* @overload getOptional<T>(entityId: EntityId, componentType: EntityId<T>): { value: T } | undefined
364+
* Retrieves a specific component safely.
365+
*
366+
* @throws {Error} If the entity does not exist
367+
*
368+
* @example
369+
* const position = world.getOptional(entity, Position);
370+
* if (position) {
371+
* console.log(position.value.x);
372+
* }
373+
*/
250374
getOptional<T>(entityId: EntityId<T>): { value: T } | undefined;
251375
getOptional<T>(entityId: EntityId, componentType: EntityId<T>): { value: T } | undefined;
252376
getOptional<T>(entityId: EntityId, componentType: EntityId<T> = entityId as EntityId<T>): { value: T } | undefined {
@@ -268,7 +392,40 @@ export class World {
268392
}
269393

270394
/**
271-
* @deprecated use array overload with LifecycleCallback
395+
* Registers a lifecycle hook that responds to component changes.
396+
* The hook callback is invoked when components matching the specified types are added, updated, or removed.
397+
*
398+
* @deprecated For single components, use the array overload with LifecycleCallback for better multi-component support
399+
*
400+
* @overload hook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T> | LegacyLifecycleCallback<T>): () => void
401+
* Registers a hook for a single component type (legacy API).
402+
*
403+
* @overload hook<const T extends readonly ComponentType<any>[]>(
404+
* componentTypes: T,
405+
* hook: LifecycleHook<T> | LifecycleCallback<T>,
406+
* ): () => void
407+
* Registers a hook for multiple component types.
408+
* The hook is triggered when all required components change together.
409+
*
410+
* @param componentTypesOrSingle - A single component type or an array of component types
411+
* @param hook - Either a hook object with on_init/on_set/on_remove handlers, or a callback function
412+
* @returns A function that unsubscribes the hook when called
413+
*
414+
* @throws {Error} If no required components are specified in array overload
415+
*
416+
* @example
417+
* // Array overload (recommended)
418+
* const unsubscribe = world.hook([Position, Velocity], {
419+
* on_init: (entityId, position, velocity) => console.log("Initialized"),
420+
* on_set: (entityId, position, velocity) => console.log("Updated"),
421+
* on_remove: (entityId, position, velocity) => console.log("Removed"),
422+
* });
423+
* unsubscribe(); // Remove hook
424+
*
425+
* // Callback style
426+
* const unsubscribe = world.hook([Position], (event, entityId, position) => {
427+
* if (event === "init") console.log("Initialized");
428+
* });
272429
*/
273430
hook<T>(componentType: EntityId<T>, hook: LegacyLifecycleHook<T> | LegacyLifecycleCallback<T>): () => void;
274431
hook<const T extends readonly ComponentType<any>[]>(
@@ -411,10 +568,48 @@ export class World {
411568
}
412569
}
413570

571+
/**
572+
* Synchronizes all buffered commands (set/remove/delete) to the world.
573+
* This method must be called after making changes via `set()`, `remove()`, or `delete()` for them to take effect.
574+
* Typically called once per frame at the end of your game loop.
575+
*
576+
* @example
577+
* world.set(entity, Position, { x: 10, y: 20 });
578+
* world.remove(entity, OldComponent);
579+
* world.sync(); // Apply all buffered changes
580+
*/
414581
sync(): void {
415582
this.commandBuffer.execute();
416583
}
417584

585+
/**
586+
* Creates a cached query for efficiently iterating entities with specific components.
587+
* The query is cached internally and reused across calls with the same component types and filter.
588+
*
589+
* **Important:** Store the query reference and reuse it across frames for optimal performance.
590+
* Creating a new query each frame defeats the caching mechanism.
591+
*
592+
* @param componentTypes - Array of component types to match
593+
* @param filter - Optional filter for additional constraints (e.g., without specific components)
594+
* @returns A Query instance that can be used to iterate matching entities
595+
*
596+
* @example
597+
* // Create once, reuse many times
598+
* const movementQuery = world.createQuery([Position, Velocity]);
599+
*
600+
* // In game loop
601+
* movementQuery.forEach((entity) => {
602+
* const pos = world.get(entity, Position);
603+
* const vel = world.get(entity, Velocity);
604+
* pos.x += vel.x;
605+
* pos.y += vel.y;
606+
* });
607+
*
608+
* // With filter
609+
* const activeQuery = world.createQuery([Position], {
610+
* without: [Disabled]
611+
* });
612+
*/
418613
createQuery(componentTypes: EntityId<any>[], filter: QueryFilter = {}): Query {
419614
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
420615
const filterKey = serializeQueryFilter(filter);
@@ -431,10 +626,39 @@ export class World {
431626
return query;
432627
}
433628

629+
/**
630+
* Creates a new entity builder for fluent entity configuration.
631+
* Useful for building entities with multiple components in a single expression.
632+
*
633+
* @returns An EntityBuilder instance
634+
*
635+
* @example
636+
* const entity = world.spawn()
637+
* .with(Position, { x: 0, y: 0 })
638+
* .with(Velocity, { x: 1, y: 1 })
639+
* .build();
640+
* world.sync(); // Apply changes
641+
*/
434642
spawn(): EntityBuilder {
435643
return new EntityBuilder(this);
436644
}
437645

646+
/**
647+
* Spawns multiple entities with a configuration callback.
648+
* More efficient than calling `spawn()` multiple times when creating many entities.
649+
*
650+
* @param count - Number of entities to spawn
651+
* @param configure - Callback that receives an EntityBuilder and index; must return the configured builder
652+
* @returns Array of created entity IDs
653+
*
654+
* @example
655+
* const entities = world.spawnMany(100, (builder, index) => {
656+
* return builder
657+
* .with(Position, { x: index * 10, y: 0 })
658+
* .with(Velocity, { x: 0, y: 1 });
659+
* });
660+
* world.sync();
661+
*/
438662
spawnMany(count: number, configure: (builder: EntityBuilder, index: number) => EntityBuilder): EntityId[] {
439663
const entities: EntityId[] = [];
440664
for (let i = 0; i < count; i++) {
@@ -455,6 +679,17 @@ export class World {
455679
}
456680
}
457681

682+
/**
683+
* Releases a cached query and frees its resources if no longer needed.
684+
* Call this when you're done using a query to allow the world to clean up its cache entry.
685+
*
686+
* @param query - The query to release
687+
*
688+
* @example
689+
* const query = world.createQuery([Position]);
690+
* // ... use query ...
691+
* world.releaseQuery(query); // Optional cleanup
692+
*/
458693
releaseQuery(query: Query): void {
459694
for (const [k, v] of this.queryCache.entries()) {
460695
if (v.query === query) {
@@ -469,6 +704,14 @@ export class World {
469704
}
470705
}
471706

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+
*/
472715
getMatchingArchetypes(componentTypes: EntityId<any>[]): Archetype[] {
473716
if (componentTypes.length === 0) {
474717
return [...this.archetypes];
@@ -512,6 +755,33 @@ export class World {
512755
return firstList.filter((archetype) => archetypeLists.slice(1).every((list) => list.includes(archetype)));
513756
}
514757

758+
/**
759+
* Queries entities with specific components.
760+
* For simpler use cases, prefer using `createQuery()` with `forEach()` which is cached and more efficient.
761+
*
762+
* @overload query(componentTypes: EntityId<any>[]): EntityId[]
763+
* Returns an array of entity IDs that have all specified components.
764+
*
765+
* @overload query<const T extends readonly EntityId<any>[]>(
766+
* componentTypes: T,
767+
* includeComponents: true,
768+
* ): Array<{ entity: EntityId; components: ComponentTuple<T> }>
769+
* Returns entities along with their component data.
770+
*
771+
* @param componentTypes - Array of component types to query
772+
* @param includeComponents - If true, includes component data in results
773+
* @returns Array of entity IDs or objects with entities and components
774+
*
775+
* @example
776+
* // Just entity IDs
777+
* const entities = world.query([Position, Velocity]);
778+
*
779+
* // With components
780+
* const results = world.query([Position, Velocity], true);
781+
* results.forEach(({ entity, components: [pos, vel] }) => {
782+
* pos.x += vel.x;
783+
* });
784+
*/
515785
query(componentTypes: EntityId<any>[]): EntityId[];
516786
query<const T extends readonly EntityId<any>[]>(
517787
componentTypes: T,
@@ -726,6 +996,27 @@ export class World {
726996
}
727997
}
728998

999+
/**
1000+
* Serializes the entire world state to a plain JavaScript object.
1001+
* This creates a "memory snapshot" that can be stored or transmitted.
1002+
* The snapshot can be restored using `new World(snapshot)`.
1003+
*
1004+
* **Note:** This is NOT automatically persistent storage. To persist data,
1005+
* you must serialize the returned object to JSON or another format yourself.
1006+
*
1007+
* @returns A serializable object representing the world state
1008+
*
1009+
* @example
1010+
* // Create snapshot
1011+
* const snapshot = world.serialize();
1012+
*
1013+
* // Save to storage (example)
1014+
* localStorage.setItem('save', JSON.stringify(snapshot));
1015+
*
1016+
* // Later, restore from snapshot
1017+
* const savedData = JSON.parse(localStorage.getItem('save'));
1018+
* const newWorld = new World(savedData);
1019+
*/
7291020
serialize(): SerializedWorld {
7301021
const entities: SerializedEntity[] = [];
7311022

0 commit comments

Comments
 (0)