mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Merge branch 'main' into Add-queries-with-tags
This commit is contained in:
commit
cc33c4d038
8 changed files with 460 additions and 237 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -53,7 +53,7 @@ WallyPatches
|
||||||
# Misc
|
# Misc
|
||||||
roblox.toml
|
roblox.toml
|
||||||
sourcemap.json
|
sourcemap.json
|
||||||
drafts/*.lua
|
drafts/
|
||||||
|
|
||||||
# Cached Vitepress (docs)
|
# Cached Vitepress (docs)
|
||||||
|
|
||||||
|
@ -61,4 +61,4 @@ drafts/*.lua
|
||||||
/docs/.vitepress/dist
|
/docs/.vitepress/dist
|
||||||
|
|
||||||
.vitepress/cache
|
.vitepress/cache
|
||||||
.vitepress/dist
|
.vitepress/dist
|
||||||
|
|
59
docs/api.md
59
docs/api.md
|
@ -1,59 +0,0 @@
|
||||||
# API
|
|
||||||
|
|
||||||
## World
|
|
||||||
|
|
||||||
### World.new() -> `World`
|
|
||||||
Creates a new world.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
::: code-group
|
|
||||||
|
|
||||||
```luau [luau]
|
|
||||||
local world = jecs.World.new()
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts [typescript]
|
|
||||||
import { World } from "@rbxts/jecs";
|
|
||||||
|
|
||||||
const world = new World();
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
### world:entity() -> `Entity<T>`
|
|
||||||
Creates a new entity.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
::: code-group
|
|
||||||
|
|
||||||
```luau [luau]
|
|
||||||
local entity = world:entity()
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts [typescript]
|
|
||||||
const entity = world.entity();
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
### world:component() -> `Entity<T>`
|
|
||||||
Creates a new static component. Keep in mind that components are also entities.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
::: code-group
|
|
||||||
|
|
||||||
```luau [luau]
|
|
||||||
local Health = world:component()
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts [typescript]
|
|
||||||
const Health = world.component<number>();
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
::: info
|
|
||||||
You should use this when creating static components.
|
|
||||||
|
|
||||||
For example, a generic Health entity should be created using this.
|
|
||||||
:::
|
|
132
docs/api/query.md
Normal file
132
docs/api/query.md
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
# Query
|
||||||
|
|
||||||
|
A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### new()
|
||||||
|
```luau
|
||||||
|
function World.new(): World
|
||||||
|
```
|
||||||
|
Creates a new world.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local world = jecs.World.new()
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
import { World } from "@rbxts/jecs";
|
||||||
|
|
||||||
|
const world = new World();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## entity()
|
||||||
|
```luau
|
||||||
|
function World:entity(): Entity -- The new entit.
|
||||||
|
```
|
||||||
|
Creates a new entity.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local entity = world:entity()
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
const entity = world.entity();
|
||||||
|
```
|
||||||
|
|
||||||
|
::
|
||||||
|
:
|
||||||
|
|
||||||
|
### component()
|
||||||
|
```luau
|
||||||
|
function World:component<T>(): Entity<T> -- The new componen.
|
||||||
|
```
|
||||||
|
Creates a new component.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local Health = world:component() :: jecs.Entity<number>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
const Health = world.component<number>();
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: info
|
||||||
|
You should use this when creating components.
|
||||||
|
|
||||||
|
For example, a Health type should be created using this.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### get()
|
||||||
|
```luau
|
||||||
|
function World:get(
|
||||||
|
entity: Entity, -- The entity
|
||||||
|
...: Entity<T> -- The types to fetch
|
||||||
|
): ... -- Returns the component data in the same order they were passed in
|
||||||
|
```
|
||||||
|
Returns the data for each provided type for the corresponding entity.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### add()
|
||||||
|
```luau
|
||||||
|
function World:add(
|
||||||
|
entity: Entity, -- The entity
|
||||||
|
id: Entity<T> -- The component ID to add
|
||||||
|
): ()
|
||||||
|
```
|
||||||
|
Adds a component ID to the entity.
|
||||||
|
|
||||||
|
This operation adds a single (component) id to an entity.
|
||||||
|
|
||||||
|
::: info
|
||||||
|
This function is idempotent, meaning if the entity already has the id, this operation will have no side effects.
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
### set()
|
||||||
|
```luau
|
||||||
|
function World:set(
|
||||||
|
entity: Entity, -- The entity
|
||||||
|
id: Entity<T>, -- The component ID to set
|
||||||
|
data: T -- The data of the component's type
|
||||||
|
): ()
|
||||||
|
```
|
||||||
|
Adds or changes the entity's component.
|
||||||
|
|
||||||
|
### query()
|
||||||
|
```luau
|
||||||
|
function World:query(
|
||||||
|
...: Entity<T> -- The component IDs to query with. Entities that satifies the conditions will be returned
|
||||||
|
): Query<...Entity<T>> -- Returns the Query which gets the entity and their corresponding data when iterated
|
||||||
|
```
|
||||||
|
Creates a [`query`](query) with the given component IDs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
for id, position, velocity in world:query(Position, Velocity) do
|
||||||
|
-- Do something
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
for (const [id, position, velocity] of world.query(Position, Velocity) {
|
||||||
|
// Do something
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
70
docs/api/world.md
Normal file
70
docs/api/world.md
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# World
|
||||||
|
|
||||||
|
A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### new()
|
||||||
|
```lua
|
||||||
|
function World.new(): World
|
||||||
|
```
|
||||||
|
Creates a new world.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local world = jecs.World.new()
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
import { World } from "@rbxts/jecs";
|
||||||
|
|
||||||
|
const world = new World();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## entity()
|
||||||
|
```luau
|
||||||
|
function World:entity(): Entity
|
||||||
|
```
|
||||||
|
Creates a new entity.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local entity = world:entity()
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
const entity = world.entity();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### component()`
|
||||||
|
```luau
|
||||||
|
function World:component<T>(): Entity<T>
|
||||||
|
```
|
||||||
|
Creates a new component.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [luau]
|
||||||
|
local Health = world:component() :: jecs.Entity<number>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [typescript]
|
||||||
|
const Health = world.component<number>();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: info
|
||||||
|
You should use this when creating components.
|
||||||
|
|
||||||
|
For example, a Health type should be created using this.
|
||||||
|
:::
|
|
@ -1,3 +1 @@
|
||||||
## TODO
|
|
||||||
|
|
||||||
This is a TODO stub.
|
|
|
@ -13,8 +13,8 @@ hero:
|
||||||
text: Get Started
|
text: Get Started
|
||||||
link: /overview/get-started.md
|
link: /overview/get-started.md
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: API Examples
|
text: API References
|
||||||
link: /api.md
|
link: /api/
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: Stupidly Fast
|
- title: Stupidly Fast
|
||||||
|
@ -26,4 +26,4 @@ features:
|
||||||
- title: Zero-Dependencies
|
- title: Zero-Dependencies
|
||||||
icon: 📦
|
icon: 📦
|
||||||
details: Jecs doesn't rely on anything other than itself.
|
details: Jecs doesn't rely on anything other than itself.
|
||||||
---
|
---
|
||||||
|
|
358
src/init.luau
358
src/init.luau
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
--!optimize 2
|
--!optimize 2
|
||||||
--!native
|
--!native
|
||||||
--!strict
|
--!strict
|
||||||
|
@ -17,6 +16,7 @@ type ArchetypeEdge = {
|
||||||
remove: Archetype,
|
remove: Archetype,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Archetype = {
|
type Archetype = {
|
||||||
id: number,
|
id: number,
|
||||||
edges: { [i53]: ArchetypeEdge },
|
edges: { [i53]: ArchetypeEdge },
|
||||||
|
@ -533,6 +533,9 @@ local function world_remove(world: World, entityId: i53, componentId: i53)
|
||||||
local entityIndex = world.entityIndex
|
local entityIndex = world.entityIndex
|
||||||
local record = entityIndex.sparse[entityId]
|
local record = entityIndex.sparse[entityId]
|
||||||
local sourceArchetype = record.archetype
|
local sourceArchetype = record.archetype
|
||||||
|
if not sourceArchetype then
|
||||||
|
return
|
||||||
|
end
|
||||||
local destinationArchetype = archetype_traverse_remove(world, componentId, sourceArchetype)
|
local destinationArchetype = archetype_traverse_remove(world, componentId, sourceArchetype)
|
||||||
|
|
||||||
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
||||||
|
@ -669,6 +672,32 @@ do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local world_has: (world: World, entityId: number, ...i53) -> boolean
|
||||||
|
do
|
||||||
|
function world_has(world, entity_id, ...)
|
||||||
|
local id = entity_id
|
||||||
|
local record = world.entityIndex.sparse[id]
|
||||||
|
if not record then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local archetype = record.archetype
|
||||||
|
if not archetype then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local tr = archetype.records
|
||||||
|
|
||||||
|
for i = 1, select("#", ...) do
|
||||||
|
if not tr[select(i, ...)] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
type Item = () -> (number, ...any)
|
type Item = () -> (number, ...any)
|
||||||
export type Query = typeof({
|
export type Query = typeof({
|
||||||
__iter = function(): Item
|
__iter = function(): Item
|
||||||
|
@ -678,8 +707,8 @@ export type Query = typeof({
|
||||||
end,
|
end,
|
||||||
}) & {
|
}) & {
|
||||||
next: Item,
|
next: Item,
|
||||||
replace: (Query, ...any) -> (),
|
without: (Query) -> Query,
|
||||||
without: (Query) -> Query
|
replace: (Query, (...any) -> (...any)) -> ()
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
||||||
|
@ -688,135 +717,146 @@ local world_query: (World, ...i53) -> Query
|
||||||
do
|
do
|
||||||
|
|
||||||
local noop: Item = function()
|
local noop: Item = function()
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
local EmptyQuery: Query = {
|
local EmptyQuery: Query = {
|
||||||
__iter = function(): Item
|
__iter = function(): Item
|
||||||
return noop
|
return noop
|
||||||
end,
|
end,
|
||||||
next = noop :: Item,
|
next = noop :: Item,
|
||||||
replace = noop :: (Query, ...any) -> (),
|
replace = noop :: (Query, ...any) -> (),
|
||||||
without = function(self: Query, ...)
|
without = function(self: Query, ...)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(EmptyQuery, EmptyQuery)
|
setmetatable(EmptyQuery, EmptyQuery)
|
||||||
|
|
||||||
local indices: { { number } }
|
|
||||||
local compatibleArchetypes: { Archetype }
|
|
||||||
local length
|
|
||||||
local components: { number }
|
|
||||||
local queryLength: number
|
|
||||||
local lastArchetype: number
|
local lastArchetype: number
|
||||||
local archetype: Archetype
|
local archetype: Archetype
|
||||||
|
local queryOutput: { any }
|
||||||
|
local queryLength: number
|
||||||
|
local entities: { number }
|
||||||
|
local i: number
|
||||||
|
|
||||||
local queryOutput: { any }
|
local compatible_archetypes: { Archetype }
|
||||||
|
local column_indices: { { number} }
|
||||||
|
local ids: { number }
|
||||||
|
|
||||||
local entities: {}
|
local function world_query_next(): any
|
||||||
local i: number
|
|
||||||
|
|
||||||
local function world_query_next()
|
|
||||||
local entityId = entities[i]
|
local entityId = entities[i]
|
||||||
while entityId == nil do
|
while entityId == nil do
|
||||||
lastArchetype += 1
|
lastArchetype += 1
|
||||||
archetype = compatibleArchetypes[lastArchetype]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return
|
return nil
|
||||||
end
|
end
|
||||||
|
entities = archetype.entities
|
||||||
entities = archetype.entities
|
|
||||||
i = #entities
|
i = #entities
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = i
|
local row = i
|
||||||
i-=1
|
i-=1
|
||||||
|
|
||||||
local columns = archetype.columns
|
local columns = archetype.columns
|
||||||
local tr = indices[lastArchetype]
|
local tr = column_indices[lastArchetype]
|
||||||
|
|
||||||
if queryLength == 1 then
|
if queryLength == 1 then
|
||||||
return entityId, columns[tr[1]][row]
|
return entityId, columns[tr[1]][row]
|
||||||
elseif queryLength == 2 then
|
elseif queryLength == 2 then
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
||||||
elseif queryLength == 3 then
|
elseif queryLength == 3 then
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
|
||||||
elseif queryLength == 4 then
|
elseif queryLength == 4 then
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
|
||||||
elseif queryLength == 5 then
|
elseif queryLength == 5 then
|
||||||
return entityId,columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
|
return entityId,
|
||||||
columns[tr[5]][row]
|
columns[tr[1]][row],
|
||||||
elseif queryLength == 6 then
|
columns[tr[2]][row],
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
|
columns[tr[3]][row],
|
||||||
columns[tr[5]][row],
|
columns[tr[4]][row],
|
||||||
columns[tr[6]][row]
|
columns[tr[5]][row]
|
||||||
elseif queryLength == 7 then
|
elseif queryLength == 6 then
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
|
return entityId,
|
||||||
columns[tr[5]][row],
|
columns[tr[1]][row],
|
||||||
columns[tr[6]][row],
|
columns[tr[2]][row],
|
||||||
columns[tr[7]][row]
|
columns[tr[3]][row],
|
||||||
elseif queryLength == 8 then
|
columns[tr[4]][row],
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
|
columns[tr[5]][row],
|
||||||
columns[tr[5]][row],
|
columns[tr[6]][row]
|
||||||
columns[tr[6]][row],
|
elseif queryLength == 7 then
|
||||||
columns[tr[7]][row],
|
return entityId,
|
||||||
columns[tr[8]][row]
|
columns[tr[1]][row],
|
||||||
end
|
columns[tr[2]][row],
|
||||||
|
columns[tr[3]][row],
|
||||||
|
columns[tr[4]][row],
|
||||||
|
columns[tr[5]][row],
|
||||||
|
columns[tr[6]][row],
|
||||||
|
columns[tr[7]][row]
|
||||||
|
elseif queryLength == 8 then
|
||||||
|
return entityId,
|
||||||
|
columns[tr[1]][row],
|
||||||
|
columns[tr[2]][row],
|
||||||
|
columns[tr[3]][row],
|
||||||
|
columns[tr[4]][row],
|
||||||
|
columns[tr[5]][row],
|
||||||
|
columns[tr[6]][row],
|
||||||
|
columns[tr[7]][row],
|
||||||
|
columns[tr[8]][row]
|
||||||
|
end
|
||||||
|
|
||||||
for i in components do
|
for j in ids do
|
||||||
queryOutput[i] = columns[tr[i]][row]
|
queryOutput[j] = columns[tr[j]][row]
|
||||||
end
|
end
|
||||||
|
|
||||||
return entityId, unpack(queryOutput, 1, queryLength)
|
return entityId, unpack(queryOutput, 1, queryLength)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_query_without(self, ...): Query
|
local function world_query_iter()
|
||||||
|
return world_query_next
|
||||||
|
end
|
||||||
|
|
||||||
|
local function world_query_without(self, ...)
|
||||||
local withoutComponents = { ... }
|
local withoutComponents = { ... }
|
||||||
for i = #compatibleArchetypes, 1, -1 do
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
local archetype = compatibleArchetypes[i]
|
local archetype = compatible_archetypes[i]
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
local shouldRemove = false
|
local shouldRemove = false
|
||||||
|
|
||||||
for _, componentId in withoutComponents do
|
for _, componentId in withoutComponents do
|
||||||
if records[componentId] then
|
if records[componentId] then
|
||||||
shouldRemove = true
|
shouldRemove = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if shouldRemove then
|
if shouldRemove then
|
||||||
table.remove(compatibleArchetypes, i)
|
table.remove(compatible_archetypes, i)
|
||||||
|
table.remove(column_indices, i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #compatibleArchetypes == 0 then
|
|
||||||
return EmptyQuery
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_iter()
|
|
||||||
lastArchetype = 1
|
lastArchetype = 1
|
||||||
archetype = compatibleArchetypes[1]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
entities = archetype.entities
|
|
||||||
i = #entities
|
|
||||||
|
|
||||||
return world_query_next
|
if not archetype then
|
||||||
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_query_replace_values(row, columns, ...)
|
local function world_query_replace_values(row, columns, ...)
|
||||||
for i, column in columns do
|
for i, column in columns do
|
||||||
column[row] = select(i, ...)
|
column[row] = select(i, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_query_replace(_, fn: any)
|
local function world_query_replace(_, fn: (...any) -> (...any))
|
||||||
for i, archetype in compatibleArchetypes do
|
for i, archetype in compatible_archetypes do
|
||||||
local tr = indices[i]
|
local tr = column_indices[i]
|
||||||
local columns = archetype.columns
|
local columns = archetype.columns
|
||||||
|
|
||||||
for row in archetype.entities do
|
for row in archetype.entities do
|
||||||
|
@ -881,81 +921,88 @@ do
|
||||||
return query
|
return query
|
||||||
end
|
end
|
||||||
|
|
||||||
function world_query(world: World, ...: number): Query
|
function world_query(world: World, ...: any): Query
|
||||||
-- breaking?
|
-- breaking?
|
||||||
if (...) == nil then
|
if (...) == nil then
|
||||||
error("Missing components")
|
error("Missing components")
|
||||||
end
|
end
|
||||||
|
|
||||||
indices = {}
|
local indices = {}
|
||||||
compatibleArchetypes = {}
|
local compatibleArchetypes = {}
|
||||||
length = 0
|
local length = 0
|
||||||
components = { ... }
|
|
||||||
|
|
||||||
local archetypes: { Archetype } = world.archetypes :: any
|
local components = { ... } :: any
|
||||||
local firstArchetypeMap: ArchetypeMap
|
local archetypes = world.archetypes
|
||||||
local componentIndex = world.componentIndex
|
|
||||||
|
|
||||||
for _, componentId in components do
|
local firstArchetypeMap: ArchetypeMap
|
||||||
local map: ArchetypeMap = componentIndex[componentId] :: any
|
local componentIndex = world.componentIndex
|
||||||
if not map then
|
|
||||||
return EmptyQuery
|
|
||||||
end
|
|
||||||
|
|
||||||
if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size > map.size then
|
for _, componentId in components do
|
||||||
firstArchetypeMap = map
|
local map = componentIndex[componentId]
|
||||||
end
|
if not map then
|
||||||
end
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
for id in firstArchetypeMap.cache do
|
if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then
|
||||||
local compatibleArchetype = archetypes[id]
|
firstArchetypeMap = map
|
||||||
local archetypeRecords = compatibleArchetype.records
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local records: { number } = {}
|
for id in firstArchetypeMap.cache do
|
||||||
local skip = false
|
local compatibleArchetype = archetypes[id]
|
||||||
|
local archetypeRecords = compatibleArchetype.records
|
||||||
|
|
||||||
for i, componentId in components do
|
local records = {}
|
||||||
local index = archetypeRecords[componentId]
|
local skip = false
|
||||||
if not index then
|
|
||||||
skip = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
-- index should be index.offset
|
|
||||||
records[i] = index
|
|
||||||
end
|
|
||||||
|
|
||||||
if skip then
|
for i, componentId in components do
|
||||||
continue
|
local index = archetypeRecords[componentId]
|
||||||
end
|
if not index then
|
||||||
|
skip = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- index should be index.offset
|
||||||
|
records[i] = index
|
||||||
|
end
|
||||||
|
|
||||||
length += 1
|
if skip then
|
||||||
compatibleArchetypes[length] = compatibleArchetype
|
continue
|
||||||
indices[length] = records
|
end
|
||||||
end
|
|
||||||
|
|
||||||
lastArchetype = 1
|
length += 1
|
||||||
archetype = compatibleArchetypes[lastArchetype]
|
compatibleArchetypes[length] = compatibleArchetype
|
||||||
|
indices[length] = records
|
||||||
|
end
|
||||||
|
|
||||||
if not archetype then
|
compatible_archetypes = compatibleArchetypes
|
||||||
return EmptyQuery
|
column_indices = indices
|
||||||
end
|
ids = components
|
||||||
|
|
||||||
queryOutput = {}
|
lastArchetype = 1
|
||||||
queryLength = #components
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
|
||||||
entities = archetype.entities
|
if not archetype then
|
||||||
i = #entities
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
local it = {
|
queryOutput = {}
|
||||||
__iter = world_query_iter,
|
queryLength = #ids
|
||||||
next = world_query_next,
|
|
||||||
without = world_query_without,
|
|
||||||
with = world_query_with,
|
|
||||||
replace = world_query_replace,
|
|
||||||
}
|
|
||||||
|
|
||||||
return setmetatable(it, it) :: any
|
entities = archetype.entities
|
||||||
end
|
i = #entities
|
||||||
|
|
||||||
|
local it = {
|
||||||
|
__iter = world_query_iter,
|
||||||
|
next = world_query_next,
|
||||||
|
with = world_query_with,
|
||||||
|
without = world_query_without,
|
||||||
|
replace = world_query_replace,
|
||||||
|
} :: any
|
||||||
|
|
||||||
|
setmetatable(it, it)
|
||||||
|
|
||||||
|
return it
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53)
|
type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53)
|
||||||
|
@ -998,11 +1045,13 @@ export type WorldShim = typeof(setmetatable(
|
||||||
--- Removes a component from the given entity
|
--- Removes a component from the given entity
|
||||||
remove: (WorldShim, id: Entity, component: Entity) -> (),
|
remove: (WorldShim, id: Entity, component: Entity) -> (),
|
||||||
--- Retrieves the value of up to 4 components. These values may be nil.
|
--- Retrieves the value of up to 4 components. These values may be nil.
|
||||||
get: (<A>(WorldShim, id: any, Entity<A>) -> A)
|
get: (<A>(WorldShim, id: Entity, Entity<A>) -> A)
|
||||||
& (<A, B>(WorldShim, id: Entity, Entity<A>, Entity<B>) -> (A, B))
|
& (<A, B>(WorldShim, id: Entity, Entity<A>, Entity<B>) -> (A, B))
|
||||||
& (<A, B, C>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>) -> (A, B, C))
|
& (<A, B, C>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>) -> (A, B, C))
|
||||||
& <A, B, C, D>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>, Entity<D>) -> (A, B, C, D),
|
& <A, B, C, D>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>, Entity<D>) -> (A, B, C, D),
|
||||||
|
|
||||||
|
has: (WorldShim, Entity, ...Entity) -> boolean,
|
||||||
|
|
||||||
--- Searches the world for entities that match a given query
|
--- Searches the world for entities that match a given query
|
||||||
query: (<A>(WorldShim, Entity<A>) -> QueryShim<A>)
|
query: (<A>(WorldShim, Entity<A>) -> QueryShim<A>)
|
||||||
& (<A, B>(WorldShim, Entity<A>, Entity<B>) -> QueryShim<A, B>)
|
& (<A, B>(WorldShim, Entity<A>, Entity<B>) -> QueryShim<A, B>)
|
||||||
|
@ -1104,6 +1153,7 @@ World.component = world_component
|
||||||
World.add = world_add
|
World.add = world_add
|
||||||
World.set = world_set
|
World.set = world_set
|
||||||
World.get = world_get
|
World.get = world_get
|
||||||
|
World.has = world_has
|
||||||
World.target = world_target
|
World.target = world_target
|
||||||
World.parent = world_parent
|
World.parent = world_parent
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,24 @@ local N = 10
|
||||||
type World = jecs.WorldShim
|
type World = jecs.WorldShim
|
||||||
|
|
||||||
TEST("world", function()
|
TEST("world", function()
|
||||||
|
do CASE "should allow remove a component that doesn't exist on entity"
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local Health = world:entity()
|
||||||
|
local Poison = world:component()
|
||||||
|
|
||||||
|
local id = world:entity()
|
||||||
|
do
|
||||||
|
world:remove(id, Poison)
|
||||||
|
CHECK(true) -- Didn't error
|
||||||
|
end
|
||||||
|
|
||||||
|
world:set(id, Health, 50)
|
||||||
|
world:remove(id, Poison)
|
||||||
|
|
||||||
|
CHECK(world:get(id, Poison) == nil)
|
||||||
|
CHECK(world:get(id, Health) == 50)
|
||||||
|
end
|
||||||
do CASE("should find every component id")
|
do CASE("should find every component id")
|
||||||
local world = jecs.World.new() :: World
|
local world = jecs.World.new() :: World
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
@ -60,10 +78,9 @@ TEST("world", function()
|
||||||
world:clear(e)
|
world:clear(e)
|
||||||
CHECK(world:get(e, A) == nil)
|
CHECK(world:get(e, A) == nil)
|
||||||
CHECK(world:get(e, B) == nil)
|
CHECK(world:get(e, B) == nil)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE("iterator should not drain the query")
|
do CASE("should drain query while iterating")
|
||||||
local world = jecs.World.new() :: World
|
local world = jecs.World.new() :: World
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
@ -85,7 +102,8 @@ TEST("world", function()
|
||||||
for _ in q do
|
for _ in q do
|
||||||
j+=1
|
j+=1
|
||||||
end
|
end
|
||||||
CHECK(i == j)
|
CHECK(i == 2)
|
||||||
|
CHECK(j == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE("should be able to get next results")
|
do CASE("should be able to get next results")
|
||||||
|
@ -224,20 +242,6 @@ TEST("world", function()
|
||||||
CHECK(world:get(id1, Health) == 50)
|
CHECK(world:get(id1, Health) == 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE("should allow remove that doesn't exist on entity")
|
|
||||||
local world = jecs.World.new()
|
|
||||||
|
|
||||||
local Health = world:entity()
|
|
||||||
local Poison = world:component()
|
|
||||||
|
|
||||||
local id = world:entity()
|
|
||||||
world:set(id, Health, 50)
|
|
||||||
world:remove(id, Poison)
|
|
||||||
|
|
||||||
CHECK(world:get(id, Poison) == nil)
|
|
||||||
CHECK(world:get(id, Health) == 50)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE("should increment generation")
|
do CASE("should increment generation")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
|
@ -530,6 +534,34 @@ TEST("world", function()
|
||||||
|
|
||||||
CHECK(withoutCount == 0)
|
CHECK(withoutCount == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "should find Tag on entity"
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local Tag = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, Tag)
|
||||||
|
|
||||||
|
CHECK(world:has(e, Tag))
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "should return false when missing one tag"
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
world:add(e, C)
|
||||||
|
world:add(e, D)
|
||||||
|
|
||||||
|
CHECK(world:has(e, A, B, C, D) == false)
|
||||||
|
end
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue