diff --git a/.gitignore b/.gitignore
index c3a366f..2f97ec9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,7 +53,7 @@ WallyPatches
# Misc
roblox.toml
sourcemap.json
-drafts/*.lua
+drafts/
# Cached Vitepress (docs)
@@ -61,4 +61,4 @@ drafts/*.lua
/docs/.vitepress/dist
.vitepress/cache
-.vitepress/dist
\ No newline at end of file
+.vitepress/dist
diff --git a/src/init.luau b/src/init.luau
index 521a551..303dcde 100644
--- a/src/init.luau
+++ b/src/init.luau
@@ -1,4 +1,3 @@
-
--!optimize 2
--!native
--!strict
@@ -17,6 +16,7 @@ type ArchetypeEdge = {
remove: Archetype,
}
+
type Archetype = {
id: number,
edges: { [i53]: ArchetypeEdge },
@@ -669,7 +669,8 @@ do
end
end
-local world_has: () -> boolean
+local world_has: (world: World, entityId: number, ...i53) -> boolean
+
do
function world_has(world, entity_id, ...)
local id = entity_id
@@ -704,8 +705,8 @@ export type Query = typeof({
end,
}) & {
next: Item,
- replace: (Query, ...any) -> (),
- without: (Query) -> Query
+ without: (Query) -> Query,
+ replace: (Query, (...any) -> (...any)) -> ()
}
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
@@ -714,135 +715,145 @@ local world_query: (World, ...i53) -> Query
do
local noop: Item = function()
- return nil :: any
+ return nil :: any
end
local EmptyQuery: Query = {
- __iter = function(): Item
- return noop
- end,
- next = noop :: Item,
- replace = noop :: (Query, ...any) -> (),
- without = function(self: Query, ...)
- return self
- end
+ __iter = function(): Item
+ return noop
+ end,
+ next = noop :: Item,
+ replace = noop :: (Query, ...any) -> (),
+ without = function(self: Query, ...)
+ return self
+ end
}
setmetatable(EmptyQuery, EmptyQuery)
- local indices: { { number } }
- local compatibleArchetypes: { Archetype }
- local length
- local components: { number }
- local queryLength: number
local lastArchetype: number
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 i: number
-
- local function world_query_next()
+ local function world_query_next(): any
local entityId = entities[i]
- while entityId == nil do
+ while entityId == nil do
lastArchetype += 1
- archetype = compatibleArchetypes[lastArchetype]
-
+ archetype = compatible_archetypes[lastArchetype]
if not archetype then
- return
- end
-
- entities = archetype.entities
+ return nil
+ end
+ entities = archetype.entities
i = #entities
- entityId = entities[i]
- end
+ entityId = entities[i]
+ end
- local row = i
- i-=1
+ local row = i
+ i-=1
- local columns = archetype.columns
- local tr = indices[lastArchetype]
+ local columns = archetype.columns
+ local tr = column_indices[lastArchetype]
- if queryLength == 1 then
- return entityId, columns[tr[1]][row]
- elseif queryLength == 2 then
- return entityId, columns[tr[1]][row], columns[tr[2]][row]
- elseif queryLength == 3 then
- return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
- elseif queryLength == 4 then
- return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
- elseif queryLength == 5 then
- return entityId,columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row],
- columns[tr[5]][row]
- elseif queryLength == 6 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]
- elseif queryLength == 7 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]
- 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
+ if queryLength == 1 then
+ return entityId, columns[tr[1]][row]
+ elseif queryLength == 2 then
+ return entityId, columns[tr[1]][row], columns[tr[2]][row]
+ elseif queryLength == 3 then
+ return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
+ elseif queryLength == 4 then
+ return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
+ elseif queryLength == 5 then
+ return entityId,
+ columns[tr[1]][row],
+ columns[tr[2]][row],
+ columns[tr[3]][row],
+ columns[tr[4]][row],
+ columns[tr[5]][row]
+ elseif queryLength == 6 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]
+ elseif queryLength == 7 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]
+ 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
- queryOutput[i] = columns[tr[i]][row]
- end
+ for j in ids do
+ queryOutput[j] = columns[tr[j]][row]
+ end
- return entityId, unpack(queryOutput, 1, queryLength)
+ return entityId, unpack(queryOutput, 1, queryLength)
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 = { ... }
- for i = #compatibleArchetypes, 1, -1 do
- local archetype = compatibleArchetypes[i]
+ for i = #compatible_archetypes, 1, -1 do
+ local archetype = compatible_archetypes[i]
local records = archetype.records
local shouldRemove = false
for _, componentId in withoutComponents do
- if records[componentId] then
- shouldRemove = true
- break
- end
+ if records[componentId] then
+ shouldRemove = true
+ break
+ end
end
if shouldRemove then
- table.remove(compatibleArchetypes, i)
+ table.remove(compatible_archetypes, i)
end
- end
+ end
- if #compatibleArchetypes == 0 then
- return EmptyQuery
- end
-
- return self
- end
-
- local function world_query_iter()
lastArchetype = 1
- archetype = compatibleArchetypes[1]
- entities = archetype.entities
- i = #entities
+ archetype = compatible_archetypes[lastArchetype]
- return world_query_next
+ if not archetype then
+ return EmptyQuery
+ end
+
+ return self
end
local function world_query_replace_values(row, columns, ...)
- for i, column in columns do
- column[row] = select(i, ...)
- end
+ for i, column in columns do
+ column[row] = select(i, ...)
+ end
end
- local function world_query_replace(_, fn: any)
- for i, archetype in compatibleArchetypes do
- local tr = indices[i]
+ local function world_query_replace(_, fn: (...any) -> (...any))
+ for i, archetype in compatible_archetypes do
+ local tr = column_indices[i]
local columns = archetype.columns
for row in archetype.entities do
@@ -881,80 +892,88 @@ do
end
end
- function world_query(world: World, ...: number): Query
- -- breaking?
- if (...) == nil then
- error("Missing components")
- end
- indices = {}
- compatibleArchetypes = {}
- length = 0
- components = { ... }
+ function world_query(world: World, ...: any): Query
+ -- breaking?
+ if (...) == nil then
+ error("Missing components")
+ end
- local archetypes: { Archetype } = world.archetypes :: any
- local firstArchetypeMap: ArchetypeMap
- local componentIndex = world.componentIndex
+ local indices = {}
+ local compatibleArchetypes = {}
+ local length = 0
- for _, componentId in components do
- local map: ArchetypeMap = componentIndex[componentId] :: any
- if not map then
- return EmptyQuery
- end
+ local components = { ... } :: any
+ local archetypes = world.archetypes
- if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size > map.size then
- firstArchetypeMap = map
- end
- end
+ local firstArchetypeMap: ArchetypeMap
+ local componentIndex = world.componentIndex
- for id in firstArchetypeMap.cache do
- local compatibleArchetype = archetypes[id]
- local archetypeRecords = compatibleArchetype.records
+ for _, componentId in components do
+ local map = componentIndex[componentId]
+ if not map then
+ return EmptyQuery
+ end
- local records: { number } = {}
- local skip = false
+ if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then
+ firstArchetypeMap = map
+ end
+ end
- for i, componentId in components do
- local index = archetypeRecords[componentId]
- if not index then
- skip = true
- break
- end
- -- index should be index.offset
- records[i] = index
- end
+ for id in firstArchetypeMap.cache do
+ local compatibleArchetype = archetypes[id]
+ local archetypeRecords = compatibleArchetype.records
- if skip then
- continue
- end
+ local records = {}
+ local skip = false
- length += 1
- compatibleArchetypes[length] = compatibleArchetype
- indices[length] = records
- end
+ for i, componentId in components do
+ local index = archetypeRecords[componentId]
+ if not index then
+ skip = true
+ break
+ end
+ -- index should be index.offset
+ records[i] = index
+ end
- lastArchetype = 1
- archetype = compatibleArchetypes[lastArchetype]
+ if skip then
+ continue
+ end
- if not archetype then
- return EmptyQuery
- end
+ length += 1
+ compatibleArchetypes[length] = compatibleArchetype
+ indices[length] = records
+ end
- queryOutput = {}
- queryLength = #components
+ compatible_archetypes = compatibleArchetypes
+ column_indices = indices
+ ids = components
- entities = archetype.entities
- i = #entities
+ lastArchetype = 1
+ archetype = compatible_archetypes[lastArchetype]
- local it = {
- __iter = world_query_iter,
- next = world_query_next,
- without = world_query_without,
- replace = world_query_replace
- }
+ if not archetype then
+ return EmptyQuery
+ end
- return setmetatable(it, it) :: any
- end
+ queryOutput = {}
+ queryLength = #ids
+
+ entities = archetype.entities
+ i = #entities
+
+ local it = {
+ __iter = world_query_iter,
+ next = world_query_next,
+ without = world_query_without,
+ replace = world_query_replace
+ } :: any
+
+ setmetatable(it, it)
+
+ return it
+ end
end
type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53)
@@ -997,11 +1016,13 @@ export type WorldShim = typeof(setmetatable(
--- Removes a component from the given entity
remove: (WorldShim, id: Entity, component: Entity) -> (),
--- Retrieves the value of up to 4 components. These values may be nil.
- get: ((WorldShim, id: any, Entity) -> A)
+ get: ((WorldShim, id: Entity, Entity) -> A)
& ((WorldShim, id: Entity, Entity, Entity) -> (A, B))
& ((WorldShim, id: Entity, Entity, Entity, Entity) -> (A, B, C))
& (WorldShim, id: Entity, Entity, Entity, Entity, Entity) -> (A, B, C, D),
+ has: (WorldShim, Entity, ...Entity) -> boolean,
+
--- Searches the world for entities that match a given query
query: ((WorldShim, Entity) -> QueryShim)
& ((WorldShim, Entity, Entity) -> QueryShim)
diff --git a/test/tests.luau b/test/tests.luau
index 508648c..6110be0 100644
--- a/test/tests.luau
+++ b/test/tests.luau
@@ -60,10 +60,9 @@ TEST("world", function()
world:clear(e)
CHECK(world:get(e, A) == nil)
CHECK(world:get(e, B) == nil)
-
end
- do CASE("iterator should not drain the query")
+ do CASE("should drain query while iterating")
local world = jecs.World.new() :: World
local A = world:component()
local B = world:component()
@@ -85,7 +84,8 @@ TEST("world", function()
for _ in q do
j+=1
end
- CHECK(i == j)
+ CHECK(i == 2)
+ CHECK(j == 0)
end
do CASE("should be able to get next results")