query:archetypes is a method

This commit is contained in:
Ukendio 2024-10-12 22:00:51 +02:00
parent 6359701a69
commit 76ab6838f2
5 changed files with 225 additions and 267 deletions

View file

@ -4,18 +4,6 @@ A World contains entities which have components. The World is queryable and can
# Methods
## drain
This method will impede it from being reset when the query is being iterated.
```luau
function query:drain(): Query
```
## next
Get the next result in the query. Drain must have been called beforehand or otherwise it will error.
```luau
function query:next(): Query
```
## with
Adds components (IDs) to query with, but will not use their data. This is useful for Tags or generally just data you do not care for.
@ -71,41 +59,16 @@ for (const [entity, position] of world.query(Position).without(Velocity)) {
```
:::
## replace
This function takes a callback which is given the current queried data of each matching entity. The values returned by the callback will be set as the new data for each given ID on the entity.
```luau
function query:replace(
fn: (entity: Entity, ...: T...) -> U... -- ): () -- The callback that will transform the entities' data
```
Example:
::: code-group
```luau [luau]
world:query(Position, Velocity):replace(function(e, position, velocity)
return position + velocity, velocity * 0.9
end
```
```ts [typescript]
world
.query(Position, Velocity)
.replace((e, position, velocity) =>
$tuple(position.add(velocity), velocity.mul(0.9)),
);
```
:::
## archetypes
Returns the matching archetypes of the query.
```luau
function query.archetypes(): { Archetype }
function query:archetypes(): { Archetype }
```
Example:
```luau [luau]
for i, archetype in world:query(Position, Velocity).archetypes() do
for i, archetype in world:query(Position, Velocity):archetypes() do
local columns = archetype.columns
local field = archetype.records
@ -121,5 +84,5 @@ end
```
:::info
This function is meant for internal usage. Use this if you want to maximize performance by inlining the iterator.
This function is meant for people who wants to really customize their query behaviour at the archetype-level
:::

View file

@ -1,5 +1,5 @@
# Addons
A collection of third-party jecs addons made by the community. If you would like to share what you're working on, [submit a pull request](https://github.com/Ukendio/jecs)!
# Assets
A collection of third-party jecs assets made by the community. If you would like to share what you're working on, [submit a pull request](https://github.com/Ukendio/jecs)!
# Debuggers
## [jabby](https://github.com/alicesaidhi/jabby)
@ -14,5 +14,3 @@ A batteries-included [sapphire](https://github.com/mark-marks/sapphire) schedule
## [jam](https://github.com/revvy02/Jam)
Provides hooks and a scheduler that implements jabby and a topographical runtime
This page takes wording and terminology directly from Bevy's [assets page](https://bevyengine.org/assets/)

View file

@ -1,115 +1,113 @@
# Entities and Components
## Entities
Entities represent things in a game. In a game there may be entities of characters, buildings, projectiles, particle effects etc.
By itself, an entity is just an unique identifier without any data
### Creation
## Components
A component is something that is added to an entity. Components can simply tag an entity ("this entity is an `Npc`"), attach data to an entity ("this entity is at `Position` `Vector3.new(10, 20, 30)`") and create relationships between entities ("bob `Likes` alice") that may also contain data ("bob `Eats` `10` apples").
### Operations
Operation | Description
----------|------------
`get` | Get a specific component or set of components from an entity.
`add` | Adds component to an entity. If entity already has the component, `add` does nothing.
`set` | Sets the value of a component for an entity. `set` behaves as a combination of `add` and `get`
`remove` | Removes component from entity. If entity doesn't have the component, `remove` does nothing.
`clear` | Remove all components from an entity. Clearing is more efficient than removing one by one.
### Components are entities
In an ECS, components need to be uniquely identified. In Jecs this is done by making each component is its own unique entity. If a game has a component Position and Velocity, there will be two entities, one for each component. Component entities can be distinguished from "regular" entities as they have a `Component` component. An example:
::: code-group
```luau [luau]
local Position = world:component() :: jecs.Entity<Vector3>
print(world:has(Position, Jecs.Component))
```
```typescript [typescript]
const Position = world.component<Vector3>();
print(world.has(Position, Jecs.Component))
```
:::
All of the APIs that apply to regular entities also apply to component entities. This means it is possible to contexualize components with logic by adding traits to components
::: code-group
```luau [luau]
local Networked = world:component()
local Type = world:component()
local Name = world:component()
local Position = world:component() :: jecs.Entity<Vector3>
world:add(Position, Networked)
world:set(Position, Name, "Position")
world:set(Position, Type, { size = 12, type = "Vector3" } ) -- 12 bytes to represent a Vector3
for id, ty, name in world:query(Type, Name):with(Networked) do
local batch = {}
for entity, data in world:query(id) do
table.insert(batch, { entity = entity, data = data })
end
-- entities are sized f64
local packet = buffer.create(#batch * (8 + ty.size))
local offset = 0
for _, entityData in batch do
offset+=8
buffer.writef64(packet, offset, entityData.entity)
if ty.type == "Vector3" then
local vec3 = entity.data :: Vector3
offset += 4
buffer.writei32(packet, offset, vec3.X)
offset += 4
buffer.writei32(packet, offset, vec3.Y)
offset += 4
buffer.writei32(packet, offset, vec3.Z)
end
end
updatePositions:FireServer(packet)
end
```
```typescript [typescript]
const Networked = world.component()
const Type = world.component()
const Name = world.component()
const Position = world.component<Vector3>();
world.add(Position, Networked)
world.set(Position, Name, "Position")
world.set(Position, Type, { size: 12, type: "Vector3" } ) // 12 bytes to represent a Vector3
for (const [id, ty, name] of world.query(Type, Name).with(Networked)) {
const batch = new Array<{ entity: Entity, data: unknown}>()
for (const [entity, data] of world.query(id)) {
batch.push({ entity, data })
}
// entities are sized f64
const packet = buffer.create(batch.size() * (8 + ty.size))
const offset = 0
for (const [_, entityData] of batch) {
offset+=8
buffer.writef64(packet, offset, entityData.entity)
if (ty.type == "Vector3") {
const vec3 = entity.data as Vector3
offset += 4
buffer.writei32(packet, offsetm, vec3.X)
offset += 4
buffer.writei32(packet, offset, vec3.Y)
offset += 4
buffer.writei32(packet, offset, vec3.Z)
}
}
updatePositions.FireServer(packet)
}
```
:::
# Entities and Components
## Entities
Entities represent things in a game. In a game there may be entities of characters, buildings, projectiles, particle effects etc.
By itself, an entity is just an unique identifier without any data
## Components
A component is something that is added to an entity. Components can simply tag an entity ("this entity is an `Npc`"), attach data to an entity ("this entity is at `Position` `Vector3.new(10, 20, 30)`") and create relationships between entities ("bob `Likes` alice") that may also contain data ("bob `Eats` `10` apples").
### Operations
Operation | Description
----------|------------
`get` | Get a specific component or set of components from an entity.
`add` | Adds component to an entity. If entity already has the component, `add` does nothing.
`set` | Sets the value of a component for an entity. `set` behaves as a combination of `add` and `get`
`remove` | Removes component from entity. If entity doesn't have the component, `remove` does nothing.
`clear` | Remove all components from an entity. Clearing is more efficient than removing one by one.
### Components are entities
In an ECS, components need to be uniquely identified. In Jecs this is done by making each component its own unique entity. If a game has a component Position and Velocity, there will be two entities, one for each component. Component entities can be distinguished from "regular" entities as they have a `Component` component. An example:
::: code-group
```luau [luau]
local Position = world:component() :: jecs.Entity<Vector3>
print(world:has(Position, Jecs.Component))
```
```typescript [typescript]
const Position = world.component<Vector3>();
print(world.has(Position, Jecs.Component))
```
:::
All of the APIs that apply to regular entities also apply to component entities. This means it is possible to contexualize components with logic by adding traits to components
::: code-group
```luau [luau]
local Networked = world:component()
local Type = world:component()
local Name = world:component()
local Position = world:component() :: jecs.Entity<Vector3>
world:add(Position, Networked)
world:set(Position, Name, "Position")
world:set(Position, Type, { size = 12, type = "Vector3" } ) -- 12 bytes to represent a Vector3
for id, ty, name in world:query(Type, Name):with(Networked) do
local batch = {}
for entity, data in world:query(id) do
table.insert(batch, { entity = entity, data = data })
end
-- entities are sized f64
local packet = buffer.create(#batch * (8 + ty.size))
local offset = 0
for _, entityData in batch do
offset+=8
buffer.writef64(packet, offset, entityData.entity)
if ty.type == "Vector3" then
local vec3 = entity.data :: Vector3
offset += 4
buffer.writei32(packet, offset, vec3.X)
offset += 4
buffer.writei32(packet, offset, vec3.Y)
offset += 4
buffer.writei32(packet, offset, vec3.Z)
end
end
updatePositions:FireServer(packet)
end
```
```typescript [typescript]
const Networked = world.component()
const Type = world.component()
const Name = world.component()
const Position = world.component<Vector3>();
world.add(Position, Networked)
world.set(Position, Name, "Position")
world.set(Position, Type, { size: 12, type: "Vector3" } ) // 12 bytes to represent a Vector3
for (const [id, ty, name] of world.query(Type, Name).with(Networked)) {
const batch = new Array<{ entity: Entity, data: unknown}>()
for (const [entity, data] of world.query(id)) {
batch.push({ entity, data })
}
// entities are sized f64
const packet = buffer.create(batch.size() * (8 + ty.size))
const offset = 0
for (const [_, entityData] of batch) {
offset+=8
buffer.writef64(packet, offset, entityData.entity)
if (ty.type == "Vector3") {
const vec3 = entity.data as Vector3
offset += 4
buffer.writei32(packet, offsetm, vec3.X)
offset += 4
buffer.writei32(packet, offset, vec3.Y)
offset += 4
buffer.writei32(packet, offset, vec3.Z)
}
}
updatePositions.FireServer(packet)
}
```
:::

View file

@ -1,104 +1,106 @@
# Queries
## Introductiuon
Queries enable games to quickly find entities that satifies provided conditions.
Jecs queries can do anything from returning entities that match a simple list of components, to matching against entity graphs.
This manual contains a full overview of the query features available in Jecs. Some of the features of Jecs queries are:
- Queries have support for relationships pairs which allow for matching against entity graphs without having to build complex data structures for it.
- Queries support filters such as `query:with(...)` if entities are required to have the components but you dont actually care about components value. And `query:without(...)` which selects entities without the components.
- Queries can be drained or reset on when called, which lets you choose iterator behaviour.
- Queries can be called with any ID, including entities created dynamically, this is useful for pairs.
- Queries are already fast but can be futher inlined via `query:archetypes()` for maximum performance to eliminate function call overhead which is roughly 70-80% of the cost for iteration.
## Creating Queries
This section explains how to create queries in the different language bindings.
:::code-group
```luau [luau]
for _ in world:query(Position, Velocity) do end
```
```typescript [typescript]
for (const [_] of world.query(Position, Velocity)) {}
```
:::
### Components
A component is any single ID that can be added to an entity. This includes tags and regular entities, which are IDs that do not have the builtin `Component` component. To match a query, an entity must have all the requested components. An example:
```luau
local e1 = world:entity()
world:add(e1, Position)
local e2 = world:entity()
world:add(e2, Position)
world:add(e2, Velocity)
local e3 = world:entity()
world:add(e3, Position)
world:add(e3, Velocity)
world:add(e3, Mass)
```
Only entities `e2` and `e3` match the query Position, Velocity.
### Wildcards
Jecs currently only supports the `Any` type of wildcards which a single result for the first component that it matches.
When using the `Any` type wildcard it is undefined which component will be matched, as this can be influenced by other parts of the query. It is guaranteed that iterating the same query twice on the same dataset will produce the same result.
Wildcards are particularly useful when used in combination with pairs (next section).
### Pairs
A pair is an ID that encodes two elements. Pairs, like components, can be added to entities and are the foundation for [Relationships](relationships.md).
The elements of a pair are allowed to be wildcards. When a query pair returns an `Any` type wildcard, the query returns at most a single matching pair on an entity.
The following sections describe how to create queries for pairs in the different language bindings.
:::code-group
```luau [luau]
local Likes = world:entity()
local bob = world:entity()
for _ in world:query(pair(Likes, bob)) do end
```
```typescript [typescript]
const Likes = world.entity()
const bob = world.entity()
for (const [_] of world.query(pair(Likes, bob))) {}
```
:::
When a query pair contains a wildcard, the `world:target()` function can be used to determine the target of the pair element that matched the query:
:::code-group
```luau [luau]
for id in world:query(pair(Likes, jecs.Wildcard)) do
print(`entity {getName(id)} likes {getName(world, world:target(id, Likes))}`)
end
```
```typescript [typescript]
const Likes = world.entity()
const bob = world.entity()
for (const [_] of world.query(pair(Likes, jecs.Wildcard))) {
print(`entity ${getName(id)} likes ${getName(world.target(id, Likes))}`)
}
```
:::
### Filters
Filters are extensions to queries which allow you to select entities from a more complex pattern but you don't actually care about the component values.
The following filters are supported by queries:
Identifier | Description
---------- | -----------
With | Must match with all terms.
Without | Must not match with provided terms.
This page takes wording and terminology directly from Flecs [documentation](https://www.flecs.dev/flecs/md_docs_2Queries.html)
# Queries
## Introductiuon
Queries enable games to quickly find entities that satifies provided conditions.
Jecs queries can do anything from returning entities that match a simple list of components, to matching against entity graphs.
This manual contains a full overview of the query features available in Jecs. Some of the features of Jecs queries are:
- Queries have support for relationships pairs which allow for matching against entity graphs without having to build complex data structures for it.
- Queries support filters such as `query:with(...)` if entities are required to have the components but you dont actually care about components value. And `query:without(...)` which selects entities without the components.
- Queries can be drained or reset on when called, which lets you choose iterator behaviour.
- Queries can be called with any ID, including entities created dynamically, this is useful for pairs.
- Queries are already fast but can be futher inlined via `query:archetypes()` for maximum performance to eliminate function call overhead which is roughly 70-80% of the cost for iteration.
## Creating Queries
This section explains how to create queries in the different language bindings.
:::code-group
```luau [luau]
for _ in world:query(Position, Velocity) do end
```
```typescript [typescript]
for (const [_] of world.query(Position, Velocity)) {}
```
:::
### Components
A component is any single ID that can be added to an entity. This includes tags and regular entities, which are IDs that do not have the builtin `Component` component. To match a query, an entity must have all the requested components. An example:
```luau
local e1 = world:entity()
world:add(e1, Position)
local e2 = world:entity()
world:add(e2, Position)
world:add(e2, Velocity)
local e3 = world:entity()
world:add(e3, Position)
world:add(e3, Velocity)
world:add(e3, Mass)
```
Only entities `e2` and `e3` match the query Position, Velocity.
### Wildcards
Jecs currently only supports the `Any` type of wildcards which a single result for the first component that it matches.
When using the `Any` type wildcard it is undefined which component will be matched, as this can be influenced by other parts of the query. It is guaranteed that iterating the same query twice on the same dataset will produce the same result.
If you want to iterate multiple targets for the same relation on a pair, then use [`world:target`](world.md#target)
Wildcards are particularly useful when used in combination with pairs (next section).
### Pairs
A pair is an ID that encodes two elements. Pairs, like components, can be added to entities and are the foundation for [Relationships](relationships.md).
The elements of a pair are allowed to be wildcards. When a query pair returns an `Any` type wildcard, the query returns at most a single matching pair on an entity.
The following sections describe how to create queries for pairs in the different language bindings.
:::code-group
```luau [luau]
local Likes = world:entity()
local bob = world:entity()
for _ in world:query(pair(Likes, bob)) do end
```
```typescript [typescript]
const Likes = world.entity()
const bob = world.entity()
for (const [_] of world.query(pair(Likes, bob))) {}
```
:::
When a query pair contains a wildcard, the `world:target()` function can be used to determine the target of the pair element that matched the query:
:::code-group
```luau [luau]
for id in world:query(pair(Likes, jecs.Wildcard)) do
print(`entity {getName(id)} likes {getName(world, world:target(id, Likes))}`)
end
```
```typescript [typescript]
const Likes = world.entity()
const bob = world.entity()
for (const [_] of world.query(pair(Likes, jecs.Wildcard))) {
print(`entity ${getName(id)} likes ${getName(world.target(id, Likes))}`)
}
```
:::
### Filters
Filters are extensions to queries which allow you to select entities from a more complex pattern but you don't actually care about the component values.
The following filters are supported by queries:
Identifier | Description
---------- | -----------
With | Must match with all terms.
Without | Must not match with provided terms.
This page takes wording and terminology directly from Flecs [documentation](https://www.flecs.dev/flecs/md_docs_2Queries.html)

View file

@ -1676,12 +1676,9 @@ type Query<T...> = typeof(setmetatable({}, {
__iter = (nil :: any) :: Iter<T...>,
})) & {
iter: Iter<T...>,
next: Item<T...>,
drain: (self: Query<T...>) -> Query<T...>,
with: (self: Query<T...>, ...i53) -> Query<T...>,
without: (self: Query<T...>, ...i53) -> Query<T...>,
replace: (self: Query<T...>, <U...>(T...) -> U...) -> (),
archetypes: () -> { Archetype },
archetypes: (self: Query<T...>) -> { Archetype },
}
export type World = {