From 33f7c08025d046dba954497a8062b3ba309b0524 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Thu, 5 Feb 2026 00:32:59 +0100 Subject: [PATCH] Expand the How To series --- how_to/004_tags.luau | 6 ++ how_to/012_entity_liveliness.luau | 0 how_to/041_entity_relationships.luau | 86 +--------------------------- how_to/042_target.luau | 37 ++++++++++++ how_to/043_wildcards.luau | 58 +++++++++++++++++++ 5 files changed, 103 insertions(+), 84 deletions(-) create mode 100644 how_to/012_entity_liveliness.luau create mode 100644 how_to/042_target.luau create mode 100644 how_to/043_wildcards.luau diff --git a/how_to/004_tags.luau b/how_to/004_tags.luau index 53133fa..b285a31 100755 --- a/how_to/004_tags.luau +++ b/how_to/004_tags.luau @@ -43,6 +43,12 @@ world:add(entity, Dead) -- Adds the tag print(world:has(entity, Dead)) -- true +-- jecs.is_tag(world, id) returns true if the id is a tag (no data). +print(jecs.is_tag(world, Dead)) -- true + +local Position = world:component() :: jecs.Id +print(jecs.is_tag(world, Position)) -- false + -- Tags are removed using world:remove(entity, component) world:remove(entity, Dead) diff --git a/how_to/012_entity_liveliness.luau b/how_to/012_entity_liveliness.luau new file mode 100644 index 0000000..e69de29 diff --git a/how_to/041_entity_relationships.luau b/how_to/041_entity_relationships.luau index 408e50c..4608181 100755 --- a/how_to/041_entity_relationships.luau +++ b/how_to/041_entity_relationships.luau @@ -2,7 +2,7 @@ Relationships makes it possible to describe entity graphs natively in ECS. Adding/removing relationships is similar to adding/removing regular components, - with as difference that instead of a single component id, a relationship adds + with the difference that instead of a single component id, a relationship adds a pair of two things to an entity. In this pair, the first element represents the relationship (e.g. "Eats"), and the second element represents the relationship target (e.g. "Apples"). @@ -36,9 +36,6 @@ world:add(alice, pair(Likes, bob)) -- Test if entity has a relationship pair print(world:has(bob, pair(Eats, Apples))) -- true --- Test if entity has a relationship wildcard -print(world:has(bob, pair(Eats, jecs.Wildcard))) -- true - --[[ Querying for relationship targets @@ -77,26 +74,6 @@ for child in world:query(pair(ChildOf, parent)) do print(`Entity {child} is a child of parent {parent}`) end ---[[ - Querying with wildcards and getting targets - - When you query with a wildcard, you can use world:target() to get the - actual target entity. This is useful when you want to find all entities - with a relationship, regardless of the target. -]] - --- Find all entities that eat something (any target) -for entity in world:query(pair(Eats, jecs.Wildcard)) do - local food = world:target(entity, Eats) -- Get the actual target - print(`Entity {entity} eats {food}`) -end - --- Find all entities that like someone (any target) -for entity in world:query(pair(Likes, jecs.Wildcard)) do - local target = world:target(entity, Likes) - print(`Entity {entity} likes {target}`) -end - --[[ Combining relationship queries with regular components @@ -118,39 +95,6 @@ for entity, pos, health in world:query(Position, Health, pair(ChildOf, parent)) print(`Child {entity} has position {pos} and health {health}`) end ---[[ - Querying for entities with multiple relationship targets - - An entity can have multiple relationships with the same relationship type - but different targets. For example, bob might like both alice and charlie. - - When querying with a wildcard, you'll get the entity once, but world:target() - will return the first matching target. If you need all targets, you'll need - to use a different approach (see the targets example for advanced usage). -]] - -local charlie = world:entity() -world:add(bob, pair(Likes, charlie)) - --- This query will return bob once, even though bob likes both alice and charlie -for entity in world:query(pair(Likes, jecs.Wildcard)) do - local target = world:target(entity, Likes) - print(`Entity {entity} likes {target}`) -- Will show one target per entity -end - ---[[ - Querying for all relationships with a specific target - - You can also query for all entities that have any relationship with a - specific target using a wildcard for the relationship part. -]] - --- Find all entities that have any relationship with alice as the target -for entity in world:query(pair(jecs.Wildcard, alice)) do - -- Note: This is less common and may have performance implications - print(`Entity {entity} has some relationship with alice`) -end - --[[ Relationship pairs, just like regular component, can be associated with data. ]] @@ -178,30 +122,4 @@ for entity, eats_data in world:query(pair(Eats, Apples)) do print(`Entity {entity} eats apples: amount = {eats_data.amount}`) end ---[[ - When querying for relationship pairs, it is often useful to be able to find - all instances for a given relationship or target. To accomplish this, a game - can use wildcard expressions. - - Wildcards may used for the relationship or target part of a pair: - - pair(Likes, jecs.Wildcard) -- Matches all Likes relationships - pair(jecs.Wildcard, Alice) -- Matches all relationships with Alice as target - - Using world:target() is the recommended way to get the target in a wildcard - query. However, if you're in a very hot path and need maximum performance, - you can access the relationship column directly (see advanced examples). -]] - -for entity in world:query(pair(Eats, jecs.Wildcard)) do - local nth = 0 - local food = world:target(entity, Eats, nth) - while food do - local eats_data = world:get(entity, pair(Eats, food)) - assert(eats_data) -- This coerces the type to be non-nilable for the type checker - print(`Entity {entity} eats {food}: amount = {eats_data.amount}`) - - nth += 1 - food = world:target(entity, Eats, nth) - end -end +-- For wildcard queries and world:target (0-based index), see 042_target.luau and 043_wildcards.luau. diff --git a/how_to/042_target.luau b/how_to/042_target.luau new file mode 100644 index 0000000..50d8b3a --- /dev/null +++ b/how_to/042_target.luau @@ -0,0 +1,37 @@ +--[[ + world:target(entity, relation, index?) returns the target of a relationship + on an entity. The index is 0-based. If no target exists at that index, it + returns nil. + + Use target when you have queried with a wildcard (e.g. pair(Eats, jecs.Wildcard)) + and need the actual target entity for each result. Without an index, the + default is 0 (the first target). +]] + +local jecs = require("@jecs") +local pair = jecs.pair +local world = jecs.world() + +local Eats = world:entity() +local Apples = world:entity() +local Oranges = world:entity() +local bob = world:entity() + +world:add(bob, pair(Eats, Apples)) +world:add(bob, pair(Eats, Oranges)) + +-- First target is at index 0 +local first = world:target(bob, Eats, 0) +print(first == Apples) -- true + +-- Second target is at index 1 +local second = world:target(bob, Eats, 1) +print(second == Oranges) -- true + +-- No third target: index 2 returns nil +local third = world:target(bob, Eats, 2) +print(third == nil) -- true + +-- Omitting the index is the same as index 0 +local default = world:target(bob, Eats) +print(default == Apples) -- true diff --git a/how_to/043_wildcards.luau b/how_to/043_wildcards.luau new file mode 100644 index 0000000..aae01b3 --- /dev/null +++ b/how_to/043_wildcards.luau @@ -0,0 +1,58 @@ +--[[ + Wildcards let you query relationships without specifying the exact target or + relationship. jecs.Wildcard matches any entity in that slot. + + - pair(relation, jecs.Wildcard) matches that relationship with any target. + - pair(jecs.Wildcard, target) matches any relationship with that target. + + Use world:target(entity, relation, index) to get the actual target when + querying with a wildcard. The index is 0-based (see 042_target.luau). +]] + +local jecs = require("@jecs") +local pair = jecs.pair +local world = jecs.world() + +local Eats = world:component() :: jecs.Id<{ amount: number }> +local Likes = world:entity() +local Apples = world:entity() +local alice = world:entity() +local bob = world:entity() + +world:add(bob, pair(Eats, Apples)) +world:set(bob, pair(Eats, Apples), { amount = 1 }) +world:add(bob, pair(Likes, alice)) + +-- world:has with wildcard +print(world:has(bob, pair(Eats, jecs.Wildcard))) -- true + +-- Query with wildcard: all entities that eat something +for entity in world:query(pair(Eats, jecs.Wildcard)) do + local food = world:target(entity, Eats) + print(`Entity {entity} eats {food}`) +end + +-- Query with wildcard: all entities that like someone +for entity in world:query(pair(Likes, jecs.Wildcard)) do + local target = world:target(entity, Likes) + print(`Entity {entity} likes {target}`) +end + +-- Multiple targets: index is 0-based. Iterate until nil. +local charlie = world:entity() +world:add(bob, pair(Likes, charlie)) + +for entity in world:query(pair(Likes, jecs.Wildcard)) do + local nth = 0 + local target = world:target(entity, Likes, nth) + while target do + print(`Entity {entity} likes {target}`) + nth += 1 + target = world:target(entity, Likes, nth) + end +end + +-- pair(jecs.Wildcard, target): all relationships that have this target +for entity in world:query(pair(jecs.Wildcard, alice)) do + print(`Entity {entity} has some relationship with alice`) +end