Make EcsWildcard a component

This commit is contained in:
Ukendio 2024-09-10 22:31:21 +02:00
parent 8e0a9409f5
commit 8d1a62b71c
2 changed files with 255 additions and 265 deletions

View file

@ -57,7 +57,7 @@ local function flip()
end end
local common = 0 local common = 0
local N = 2^16-2 local N = 5000
local archetypes = {} local archetypes = {}
local hm = 0 local hm = 0
@ -170,13 +170,8 @@ return {
end, end,
Functions = { Functions = {
Matter = function() Mirror = function()
for entityId, firstComponent in newWorld:query(A1, A2, A3, A4) do for entityId, firstComponent in mcs:query(E1, E2, E3, E4) do
end
end,
ECR = function()
for entityId, firstComponent in registry2:view(B1, B2, B3, B3) do
end end
end, end,

View file

@ -942,73 +942,70 @@ local EmptyQuery = {
setmetatable(EmptyQuery, EmptyQuery) setmetatable(EmptyQuery, EmptyQuery)
local function columns_replace_values(row, columns, ...) local function query_init(query)
for i, column in columns do local world_query_iter_next = query.iter_next
column[row] = select(i, ...) if world_query_iter_next then
end return world_query_iter_next
end end
local compatible_archetypes = query.compatible_archetypes
local function world_query(world: World, ...) local ids = query.ids
local compatible_archetypes = {} local A, B, C, D, E, F, G, H, I = unpack(ids)
local length = 0 local a, b, c, d, e, f, g, h
local lastArchetype = 1
local ids = { ... } local archetype = compatible_archetypes[1]
local A, B, C, D, E, F, G, H, I = ... if not archetype then
local a, b, c, d, e, f, g, h
local archetypes = world.archetypes
local idr: ArchetypeMap
local componentIndex = world.componentIndex
for _, id in ids do
local map = componentIndex[id]
if not map then
return EmptyQuery
end
if idr == nil or map.size < idr.size then
idr = map
end
end
for archetype_id in idr.cache do
local compatibleArchetype = archetypes[archetype_id]
if #compatibleArchetype.entities == 0 then
continue
end
local records = compatibleArchetype.records
local skip = false
for i, id in ids do
local tr = records[id]
if not tr then
skip = true
break
end
end
if skip then
continue
end
length += 1
compatible_archetypes[length] = compatibleArchetype
end
if length == 0 then
return EmptyQuery return EmptyQuery
end end
local columns = archetype.columns
local entities = archetype.entities
local i = #entities
local lastArchetype = 1 local records = archetype.records
local archetype if not B then
local columns a = columns[records[A].column]
local entities elseif not C then
local i a = columns[records[A].column]
local queryOutput b = columns[records[B].column]
elseif not D then
local world_query_iter_next a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
elseif not E then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
elseif not F then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
elseif not G then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
elseif not H then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
elseif not I then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
h = columns[records[H].column]
end
if not B then if not B then
function world_query_iter_next(): any function world_query_iter_next(): any
@ -1121,6 +1118,7 @@ local function world_query(world: World, ...)
return entityId, a[row], b[row], c[row], d[row] return entityId, a[row], b[row], c[row], d[row]
end end
else else
local queryOutput = {}
function world_query_iter_next(): any function world_query_iter_next(): any
local entityId = entities[i] local entityId = entities[i]
while entityId == nil do while entityId == nil do
@ -1194,221 +1192,217 @@ local function world_query(world: World, ...)
end end
end end
local init = false query.iter_next = world_query_iter_next
local drain = false return world_query_iter_next
end
local function query_init(query) local function query_iter(query)
if init and drain then return query_init(query)
return true end
end
init = true local function query_drain(query)
lastArchetype = 1 local query_iter_next = query_init(query)
archetype = compatible_archetypes[lastArchetype] query.next = query_iter_next
return query
end
if not archetype then local function query_next(query)
return false error("Did you forget to call drain?")
end end
queryOutput = {}
entities = archetype.entities
i = #entities
columns = archetype.columns
local function query_without(query, ...)
local compatible_archetypes = query.compatible_archetypes
local length: number = query.length
local N = select("#", ...)
for i = #compatible_archetypes, 1, -1 do
local archetype = compatible_archetypes[i]
local records = archetype.records local records = archetype.records
if not B then local shouldRemove = false
a = columns[records[A].column]
elseif not C then
a = columns[records[A].column]
b = columns[records[B].column]
elseif not D then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
elseif not E then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
elseif not F then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
elseif not G then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
elseif not H then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
elseif not I then
a = columns[records[A].column]
b = columns[records[B].column]
c = columns[records[C].column]
d = columns[records[D].column]
e = columns[records[E].column]
f = columns[records[F].column]
g = columns[records[G].column]
h = columns[records[H].column]
end
return true
end
local function world_query_without(query, ...) for j = 1, N do
local N = select("#", ...) local id = select(j, ...)
for i = #compatible_archetypes, 1, -1 do if records[id] then
local archetype = compatible_archetypes[i] shouldRemove = true
local records = archetype.records break
local shouldRemove = false
for j = 1, N do
local id = select(j, ...)
if records[id] then
shouldRemove = true
break
end
end
if shouldRemove then
local last = #compatible_archetypes
if last ~= i then
compatible_archetypes[i] = compatible_archetypes[last]
end
compatible_archetypes[last] = nil
length -= 1
end end
end end
if length == 0 then if shouldRemove then
return EmptyQuery local last = #compatible_archetypes
end if last ~= i then
compatible_archetypes[i] = compatible_archetypes[last]
return query
end
local function world_query_replace(query, fn: (...any) -> ...any)
query_init(query)
for i, archetype in compatible_archetypes do
local columns = archetype.columns
local records = archetype.records
for row in archetype.entities do
if not B then
local va = columns[records[A].column]
local pa = fn(va[row])
va[row] = pa
elseif not C then
local va = columns[records[A].column]
local vb = columns[records[B].column]
va[row], vb[row] = fn(va[row], vb[row])
elseif not D then
local va = columns[records[A].column]
local vb = columns[records[B].column]
local vc = columns[records[C].column]
va[row], vb[row], vc[row] = fn(va[row], vb[row], vc[row])
elseif not E then
local va = columns[records[A].column]
local vb = columns[records[B].column]
local vc = columns[records[C].column]
local vd = columns[records[D].column]
va[row], vb[row], vc[row], vd[row] = fn(va[row], vb[row], vc[row], vd[row])
else
for j, id in ids do
local tr = records[id]
queryOutput[j] = columns[tr.column][row]
end
columns_replace_values(row, columns, fn(unpack(queryOutput)))
end
end end
compatible_archetypes[last] = nil
length -= 1
end end
end end
local function world_query_with(query, ...) query.length = length
local N = select("#", ...)
for i = #compatible_archetypes, 1, -1 do
local archetype = compatible_archetypes[i]
local records = archetype.records
local shouldRemove = false
for j = 1, N do if length == 0 then
local id = select(j, ...)
if not records[id] then
shouldRemove = true
break
end
end
if shouldRemove then
local last = #compatible_archetypes
if last ~= i then
compatible_archetypes[i] = compatible_archetypes[last]
end
compatible_archetypes[last] = nil
length -= 1
end
end
if length == 0 then
return EmptyQuery
end
return query
end
-- Meant for directly iterating over archetypes to minimize
-- function call overhead. Should not be used unless iterating over
-- hundreds of thousands of entities in bulk.
local function world_query_archetypes()
return compatible_archetypes
end
local function world_query_drain(query)
drain = true
if query_init(query) then
return query
end
return EmptyQuery return EmptyQuery
end end
local function world_query_iter(query) return query
query_init(query) end
return world_query_iter_next
end
local function world_query_next(world) local function query_with(query, ...)
if not drain then local compatible_archetypes = query.compatible_archetypes
error("Did you forget to call query:drain()?") local length: number = query.length
local N = select("#", ...)
for i = #compatible_archetypes, 1, -1 do
local archetype = compatible_archetypes[i]
local records = archetype.records
local shouldRemove = false
for j = 1, N do
local id = select(j, ...)
if not records[id] then
shouldRemove = true
break
end
end
if shouldRemove then
local last = #compatible_archetypes
if last ~= i then
compatible_archetypes[i] = compatible_archetypes[last]
end
compatible_archetypes[last] = nil
length -= 1
end
end
query.length = length
if length == 0 then
return EmptyQuery
end
return query
end
local function columns_replace_values(row, columns, ...)
for i, column in columns do
column[row] = select(i, ...)
end
end
local function query_replace(query, fn: (...any) -> ...any)
local compatible_archetypes = query.compatible_archetypes
local ids = query.ids
local A, B, C, D, E = unpack(ids, 1, 5)
local queryOutput = {}
for i, archetype in compatible_archetypes do
local columns = archetype.columns
local records = archetype.records
for row in archetype.entities do
if not B then
local va = columns[records[A].column]
local pa = fn(va[row])
va[row] = pa
elseif not C then
local va = columns[records[A].column]
local vb = columns[records[B].column]
va[row], vb[row] = fn(va[row], vb[row])
elseif not D then
local va = columns[records[A].column]
local vb = columns[records[B].column]
local vc = columns[records[C].column]
va[row], vb[row], vc[row] = fn(va[row], vb[row], vc[row])
elseif not E then
local va = columns[records[A].column]
local vb = columns[records[B].column]
local vc = columns[records[C].column]
local vd = columns[records[D].column]
va[row], vb[row], vc[row], vd[row] = fn(va[row], vb[row], vc[row], vd[row])
else
for j, id in ids do
local tr = records[id]
queryOutput[j] = columns[tr.column][row]
end
columns_replace_values(row, columns, fn(unpack(queryOutput)))
end
end
end
end
-- Meant for directly iterating over archetypes to minimize
-- function call overhead. Should not be used unless iterating over
-- hundreds of thousands of entities in bulk.
local function query_archetypes(query)
return query.compatible_archetypes
end
local Query = {}
Query.__index = Query
Query.__iter = query_iter
Query.without = query_without
Query.with = query_with
Query.archetypes = query_archetypes
Query.drain = query_drain
Query.next = query_next
Query.replace = query_replace
local function world_query(world: World, ...)
local compatible_archetypes = {}
local length = 0
local ids = { ... }
local archetypes = world.archetypes
local idr: ArchetypeMap
local componentIndex = world.componentIndex
for _, id in ids do
local map = componentIndex[id]
if not map then
return EmptyQuery
end
if idr == nil or map.size < idr.size then
idr = map
end end
return world_query_iter_next(world)
end end
local it = { for archetype_id in idr.cache do
__iter = world_query_iter, local compatibleArchetype = archetypes[archetype_id]
iter = world_query_iter, if #compatibleArchetype.entities == 0 then
drain = world_query_drain, continue
next = world_query_next, end
with = world_query_with, local records = compatibleArchetype.records
without = world_query_without,
replace = world_query_replace,
archetypes = world_query_archetypes,
} :: any
setmetatable(it, it) local skip = false
return it for i, id in ids do
local tr = records[id]
if not tr then
skip = true
break
end
end
if skip then
continue
end
length += 1
compatible_archetypes[length] = compatibleArchetype
end
if length == 0 then
return EmptyQuery
end
local q = setmetatable({
compatible_archetypes = compatible_archetypes,
length = length,
ids = ids,
}, Query)
return q
end end
local World = {} local World = {}
@ -1519,6 +1513,7 @@ function World.new()
end end
world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete)) world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
world_add(self, EcsWildcard, EcsComponent)
return self return self
end end
@ -1655,7 +1650,7 @@ return {
Name = EcsName :: Entity<string>, Name = EcsName :: Entity<string>,
Rest = EcsRest :: Entity, Rest = EcsRest :: Entity,
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number, pair = ECS_PAIR,
-- Inwards facing API for testing -- Inwards facing API for testing
ECS_ID = ECS_ENTITY_T_LO, ECS_ID = ECS_ENTITY_T_LO,