From 1c2dee57d3a6745e18eb45fd6a8f9a8e51ff9a28 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sat, 26 Jul 2025 02:37:51 +0200 Subject: [PATCH] Add some examples --- docs/api/jecs.md | 11 +++ docs/api/observers.md | 196 ++++++++++++++++++++++++++++++++++++++++++ docs/api/query.md | 21 ++++- docs/api/world.md | 122 +++++++++++++++++++++++++- docs/index.md | 3 + 5 files changed, 348 insertions(+), 5 deletions(-) create mode 100755 docs/api/observers.md diff --git a/docs/api/jecs.md b/docs/api/jecs.md index bdc712d..2b5ea33 100755 --- a/docs/api/jecs.md +++ b/docs/api/jecs.md @@ -123,6 +123,17 @@ function jecs.pair_first( ``` Returns the first element (the relation part) of a pair ID. +**Example:** +```luau +local Likes = world:component() +local alice = world:entity() +local bob = world:entity() + +local pair_id = pair(Likes, alice) +local relation = jecs.pair_first(pair_id) +print(relation == Likes) -- true +``` + ## pair_second() ```luau function jecs.pair_second( diff --git a/docs/api/observers.md b/docs/api/observers.md new file mode 100755 index 0000000..509225a --- /dev/null +++ b/docs/api/observers.md @@ -0,0 +1,196 @@ +# Observers + +The observers addon extends the World with signal-based reactivity and query-based observers. This addon provides a more ergonomic way to handle component lifecycle events and query changes. + +## Installation + +The observers addon is included with jecs and can be imported directly: + +```luau +local jecs = require(path/to/jecs) +local observers_add = require(path/to/jecs/addons/observers) + +local world = observers_add(jecs.world()) +``` + +## Methods + +### added + +Registers a callback that is invoked when a component is added to any entity. + +```luau +function World:added( + component: Id, + callback: (entity: Entity, id: Id, value: T?) -> () +): () -> () -- Returns an unsubscribe function +``` + +**Parameters:** +- `component` - The component ID to listen for additions +- `callback` - Function called when component is added, receives entity, component ID, and value + +**Returns:** An unsubscribe function that removes the listener when called + +**Example:** +```luau +local Health = world:component() :: jecs.Entity + +local unsubscribe = world:added(Health, function(entity, id, value) + print("Health component added to entity", entity, "with value", value) +end) + +-- Later, to stop listening: +unsubscribe() +``` + +### removed + +Registers a callback that is invoked when a component is removed from any entity. + +```luau +function World:removed( + component: Id, + callback: (entity: Entity, id: Id) -> () +): () -> () -- Returns an unsubscribe function +``` + +**Parameters:** +- `component` - The component ID to listen for removals +- `callback` - Function called when component is removed, receives entity and component ID + +**Returns:** An unsubscribe function that removes the listener when called + +**Example:** +```luau +local Health = world:component() :: jecs.Entity + +local unsubscribe = world:removed(Health, function(entity, id) + print("Health component removed from entity", entity) +end) +``` + +### changed + +Registers a callback that is invoked when a component's value is changed on any entity. + +```luau +function World:changed( + component: Id, + callback: (entity: Entity, id: Id, value: T) -> () +): () -> () -- Returns an unsubscribe function +``` + +**Parameters:** +- `component` - The component ID to listen for changes +- `callback` - Function called when component value changes, receives entity, component ID, and new value + +**Returns:** An unsubscribe function that removes the listener when called + +**Example:** +```luau +local Health = world:component() :: jecs.Entity + +local unsubscribe = world:changed(Health, function(entity, id, value) + print("Health changed to", value, "for entity", entity) +end) +``` + +### observer + +Creates a query-based observer that triggers when entities match or stop matching a query. + +```luau +function World:observer( + query: Query, + callback: ((entity: Entity, id: Id, value: any?) -> ())? +): () -> () -> Entity -- Returns an iterator function +``` + +**Parameters:** +- `query` - The query to observe for changes +- `callback` - Optional function called when entities match the query + +**Returns:** An iterator function that returns entities that matched the query since last iteration + +**Example:** +```luau +local Position = world:component() :: jecs.Id +local Velocity = world:component() :: jecs.Id + +local moving_entities = world:observer( + world:query(Position, Velocity), + function(entity, id, value) + print("Entity", entity, "started moving") + end +) + +-- In your game loop: +for entity in moving_entities() do + -- Process newly moving entities +end +``` + +### monitor + +Creates a query-based monitor that triggers when entities are added to or removed from a query. + +```luau +function World:monitor( + query: Query, + callback: ((entity: Entity, id: Id, value: any?) -> ())? +): () -> () -> Entity -- Returns an iterator function +``` + +**Parameters:** +- `query` - The query to monitor for additions/removals +- `callback` - Optional function called when entities are added or removed from the query + +**Returns:** An iterator function that returns entities that were added or removed since last iteration + +**Example:** +```luau +local Health = world:component() :: jecs.Id + +local health_changes = world:monitor( + world:query(Health), + function(entity, id, value) + print("Health component changed for entity", entity) + end +) + +-- In your game loop: +for entity in health_changes() do + -- Process entities with health changes +end +``` + +## Usage Patterns + +### Component Lifecycle Tracking + +```luau +local Player = world:component() +local Health = world:component() :: jecs.Id + +-- Track when players are created +world:added(Player, function(entity, id, instance) + instance:SetAttribute("entityid", entity) +end) + +world:removed(Player, function(entity, id) + world:add(entity, Destroy) -- process its deletion later! +end) +``` + +## Performance Considerations + +- **Signal listeners** are called immediately when components are added/removed/changed +- **Query observers** cache the query for better performance +- **Multiple listeners** for the same component are supported and called in registration order +- **Unsubscribe functions** should be called when listeners are no longer needed to prevent memory leaks +- **Observer iterators** should be called regularly to clear the internal buffer + +## Integration with Built-in Hooks + +The observers addon integrates with the built-in component hooks (`OnAdd`, `OnRemove`, `OnChange`). If a component already has these hooks configured, the observers addon will preserve them and call both the original hook and any registered signal listeners. diff --git a/docs/api/query.md b/docs/api/query.md index 8a2f8fd..cd372c4 100755 --- a/docs/api/query.md +++ b/docs/api/query.md @@ -120,9 +120,26 @@ This function is meant for people who want to really customize their query behav ::: ## iter - -If you are on the old solver, to get types for the returned values, requires usage of `:iter` to get an explicit returntype of an iterator function. +In most cases, you can iterate over queries directly using `for entity, ... in query do`. The `:iter()` method is mainly useful if you are on the old solver, to get types for the returned values. ```luau function Query:iter(): () -> (Entity, ...) ``` + +Example: + +::: code-group +```luau [luau] +local query = world:query(Position, Velocity) + +-- Direct iteration (recommended) +for entity, position, velocity in query do + -- Process entity +end + +-- Using explicit iterator (when needed for the old solver) +local iterator = query:iter() +for entity, position, velocity in iterator do + -- Process entity +end +``` \ No newline at end of file diff --git a/docs/api/world.md b/docs/api/world.md index ea9fd87..fe6a2c7 100755 --- a/docs/api/world.md +++ b/docs/api/world.md @@ -6,7 +6,7 @@ A World contains entities which have components. The World is queryable and can ## new -`World` utilizes a class, meaning JECS allows you to create multiple worlds. +`World` utilizes a class, meaning jecs allows you to create multiple worlds. ```luau function World.new(): World @@ -55,7 +55,7 @@ const entity = world.entity(); ## component -Creates a new component. Do note components are entities as well, meaning JECS allows you to add other components onto them. +Creates a new component. Do note components are entities as well, meaning jecs allows you to add other components onto them. These are meant to be added onto other entities through `add` and `set` @@ -545,6 +545,122 @@ Enforces a check for entities to be created within a desired range. ```luau function World:range( range_begin: number -- The starting point, - range_begin: number? -- The end point (optional) + range_end: number? -- The end point (optional) ) ``` + +Example: + +::: code-group +```luau [luau] +world:range(1000, 5000) -- Entities will be created with IDs 1000-5000 + +local entity = world:entity() +print(entity) -- Will be >= 1000 and < 5000 +``` +```ts [typescript] +world.range(1000, 5000) // Entities will be created with IDs 1000-5000 + +const entity = world.entity() +print(entity) // Will be >= 1000 and < 5000 +``` +::: + +## parent + +Gets the parent entity of the specified entity using the built-in `ChildOf` relationship. + +```luau +function World:parent( + entity: Entity +): Entity? -- Returns the parent entity or nil if no parent +``` + +Example: + +::: code-group +```luau [luau] +local parent = world:entity() +local child = world:entity() + +world:add(child, pair(jecs.ChildOf, parent)) + +local retrieved_parent = world:parent(child) +print(retrieved_parent == parent) -- true +``` +```ts [typescript] +const parent = world.entity() +const child = world.entity() + +world.add(child, pair(jecs.ChildOf, parent)) + +const retrievedParent = world.parent(child) +print(retrievedParent === parent) // true +``` +::: + +## contains + +Checks if an entity exists and is alive in the world. + +```luau +function World:contains( + entity: Entity +): boolean +``` + +Example: + +::: code-group +```luau [luau] +local entity = world:entity() +print(world:contains(entity)) -- true + +world:delete(entity) +print(world:contains(entity)) -- false +``` +```ts [typescript] +const entity = world.entity() +print(world.contains(entity)) // true + +world.delete(entity) +print(world.contains(entity)) // false +``` +::: + +## exists + +Alias for `contains`. Checks if an entity exists and is alive in the world. + +```luau +function World:exists( + entity: Entity +): boolean +``` + +## cleanup + +Cleans up deleted entities and their associated data. This is automatically called by jecs, but can be called manually if needed. + +```luau +function World:cleanup(): void +``` + +Example: + +::: code-group +```luau [luau] +local entity = world:entity() +world:delete(entity) + +-- Cleanup is usually automatic, but can be called manually +world:cleanup() +``` +```ts [typescript] +const entity = world.entity() +world.delete(entity) + +// Cleanup is usually automatic, but can be called manually +world.cleanup() +``` +::: diff --git a/docs/index.md b/docs/index.md index f80ca32..a46c274 100755 --- a/docs/index.md +++ b/docs/index.md @@ -15,6 +15,9 @@ hero: - theme: alt text: API References link: /api/jecs.md + - theme: alt + text: Observers + link: /api/observers.md features: - title: Stupidly Fast