mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Merge 927bee30cd
into 7c025a3782
This commit is contained in:
commit
ee3486f170
2 changed files with 173 additions and 128 deletions
247
jecs.luau
247
jecs.luau
|
@ -89,7 +89,8 @@ local EcsOnDeleteTarget = HI_COMPONENT_ID + 8
|
||||||
local EcsDelete = HI_COMPONENT_ID + 9
|
local EcsDelete = HI_COMPONENT_ID + 9
|
||||||
local EcsRemove = HI_COMPONENT_ID + 10
|
local EcsRemove = HI_COMPONENT_ID + 10
|
||||||
local EcsName = HI_COMPONENT_ID + 11
|
local EcsName = HI_COMPONENT_ID + 11
|
||||||
local EcsRest = HI_COMPONENT_ID + 12
|
local EcsTableCreate = HI_COMPONENT_ID + 12
|
||||||
|
local EcsRest = HI_COMPONENT_ID + 13
|
||||||
|
|
||||||
local ECS_PAIR_FLAG = 0x8
|
local ECS_PAIR_FLAG = 0x8
|
||||||
local ECS_ID_FLAGS_MASK = 0x10
|
local ECS_ID_FLAGS_MASK = 0x10
|
||||||
|
@ -250,6 +251,57 @@ local function ecs_pair_second(world, e)
|
||||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_LO(e))
|
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_LO(e))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function query_match(query, archetype)
|
||||||
|
local records = archetype.records
|
||||||
|
for _, id in query.ids do
|
||||||
|
if not records[id] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local filters = query.filters
|
||||||
|
if filters then
|
||||||
|
local without = filters.without
|
||||||
|
if without then
|
||||||
|
for _, id in filters.without do
|
||||||
|
if records[id] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local with = filters.with
|
||||||
|
if with then
|
||||||
|
for _, id in filters.without do
|
||||||
|
if not records[id] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function observer_invoke(observer, event)
|
||||||
|
table.insert(observer.query.compatible_archetypes, event.archetype)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function emit(world: World, event)
|
||||||
|
local map = world.observerable[event.id]
|
||||||
|
if not map then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local observer_list: {[string]: any} = map[event.component]
|
||||||
|
if not observer_list then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _, observer in observer_list do
|
||||||
|
if query_match(observer.query, event.archetype) then
|
||||||
|
observer_invoke(observer, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row: i24, from: Archetype, src_row: i24)
|
local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row: i24, from: Archetype, src_row: i24)
|
||||||
local src_columns = from.columns
|
local src_columns = from.columns
|
||||||
local dst_columns = to.columns
|
local dst_columns = to.columns
|
||||||
|
@ -535,15 +587,49 @@ local function archetype_append_to_records(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetype_create(world: World, types: { i24 }, ty, prev: i53?): Archetype
|
local function create_observer_uni(world: World, component: number, event)
|
||||||
|
local map = world.observerable[event]
|
||||||
|
if not map then
|
||||||
|
map = {}
|
||||||
|
world.observerable[event] = map
|
||||||
|
end
|
||||||
|
|
||||||
|
local observer_list = map[component]
|
||||||
|
if not observer_list then
|
||||||
|
observer_list = {}
|
||||||
|
map[component] = observer_list
|
||||||
|
end
|
||||||
|
|
||||||
|
local observer = {}
|
||||||
|
|
||||||
|
table.insert(observer_list, observer)
|
||||||
|
|
||||||
|
return observer
|
||||||
|
end
|
||||||
|
|
||||||
|
local function archetype_create(world: World, id_types: { i24 }, ty, prev: i53?): Archetype
|
||||||
local archetype_id = (world.nextArchetypeId :: number) + 1
|
local archetype_id = (world.nextArchetypeId :: number) + 1
|
||||||
world.nextArchetypeId = archetype_id
|
world.nextArchetypeId = archetype_id
|
||||||
|
|
||||||
local length = #types
|
local length = #id_types
|
||||||
local columns = (table.create(length) :: any) :: { Column }
|
local columns = (table.create(length) :: any) :: { Column }
|
||||||
|
|
||||||
local records: { ArchetypeRecord } = {}
|
local records: { ArchetypeRecord } = {}
|
||||||
for i, componentId in types do
|
|
||||||
|
local archetype: Archetype = {
|
||||||
|
columns = columns,
|
||||||
|
entities = {},
|
||||||
|
id = archetype_id,
|
||||||
|
records = records,
|
||||||
|
type = ty,
|
||||||
|
types = id_types,
|
||||||
|
|
||||||
|
add = {},
|
||||||
|
remove = {},
|
||||||
|
refs = {} :: GraphEdge,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, componentId in id_types do
|
||||||
local idr = id_record_ensure(world, componentId)
|
local idr = id_record_ensure(world, componentId)
|
||||||
archetype_append_to_records(idr, archetype_id, records, componentId, i)
|
archetype_append_to_records(idr, archetype_id, records, componentId, i)
|
||||||
|
|
||||||
|
@ -564,20 +650,12 @@ local function archetype_create(world: World, types: { i24 }, ty, prev: i53?): A
|
||||||
else
|
else
|
||||||
columns[i] = NULL_ARRAY
|
columns[i] = NULL_ARRAY
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype: Archetype = {
|
for _, id in id_types do
|
||||||
columns = columns,
|
emit(world, { id = EcsTableCreate, component = id, archetype = archetype})
|
||||||
entities = {},
|
end
|
||||||
id = archetype_id,
|
|
||||||
records = records,
|
|
||||||
type = ty,
|
|
||||||
types = types,
|
|
||||||
|
|
||||||
add = {},
|
|
||||||
remove = {},
|
|
||||||
refs = {} :: GraphEdge,
|
|
||||||
}
|
|
||||||
|
|
||||||
world.archetypeIndex[ty] = archetype
|
world.archetypeIndex[ty] = archetype
|
||||||
world.archetypes[archetype_id] = archetype
|
world.archetypes[archetype_id] = archetype
|
||||||
|
@ -1409,32 +1487,35 @@ local function query_iter(query): () -> (number, ...any)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function query_without(query: { compatible_archetypes: { Archetype } }, ...)
|
local function query_without(query: { compatible_archetypes: { Archetype } }, ...)
|
||||||
|
local filters = query.filters
|
||||||
|
local without = { ... }
|
||||||
|
if not filters then
|
||||||
|
filters = {}
|
||||||
|
query.filters = filters
|
||||||
|
end
|
||||||
|
filters.without = without
|
||||||
local compatible_archetypes = query.compatible_archetypes
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
local N = select("#", ...)
|
|
||||||
for i = #compatible_archetypes, 1, -1 do
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
local archetype = compatible_archetypes[i]
|
local archetype = compatible_archetypes[i]
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
local shouldRemove = false
|
local matches = true
|
||||||
|
|
||||||
for j = 1, N do
|
for _, id in without do
|
||||||
local id = select(j, ...)
|
|
||||||
if records[id] then
|
if records[id] then
|
||||||
shouldRemove = true
|
matches = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if shouldRemove then
|
if matches then
|
||||||
local last = #compatible_archetypes
|
continue
|
||||||
if last ~= i then
|
|
||||||
compatible_archetypes[i] = compatible_archetypes[last]
|
|
||||||
end
|
|
||||||
compatible_archetypes[last] = nil :: any
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if #compatible_archetypes == 0 then
|
local last = #compatible_archetypes
|
||||||
return EMPTY_QUERY
|
if last ~= i then
|
||||||
|
compatible_archetypes[i] = compatible_archetypes[last]
|
||||||
|
end
|
||||||
|
compatible_archetypes[last] = nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
return query :: any
|
return query :: any
|
||||||
|
@ -1442,31 +1523,36 @@ end
|
||||||
|
|
||||||
local function query_with(query: { compatible_archetypes: { Archetype } }, ...)
|
local function query_with(query: { compatible_archetypes: { Archetype } }, ...)
|
||||||
local compatible_archetypes = query.compatible_archetypes
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
local N = select("#", ...)
|
local filters = query.filters
|
||||||
|
local with = { ... }
|
||||||
|
if not filters then
|
||||||
|
filters = {}
|
||||||
|
query.filters = filters
|
||||||
|
end
|
||||||
|
filters.with = with
|
||||||
for i = #compatible_archetypes, 1, -1 do
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
local archetype = compatible_archetypes[i]
|
local archetype = compatible_archetypes[i]
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
local shouldRemove = false
|
local matches = true
|
||||||
|
|
||||||
for j = 1, N do
|
for _, id in with do
|
||||||
local id = select(j, ...)
|
|
||||||
if not records[id] then
|
if not records[id] then
|
||||||
shouldRemove = true
|
matches = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if shouldRemove then
|
if matches then
|
||||||
local last = #compatible_archetypes
|
continue
|
||||||
if last ~= i then
|
|
||||||
compatible_archetypes[i] = compatible_archetypes[last]
|
|
||||||
end
|
|
||||||
compatible_archetypes[last] = nil :: any
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local last = #compatible_archetypes
|
||||||
|
if last ~= i then
|
||||||
|
compatible_archetypes[i] = compatible_archetypes[last]
|
||||||
|
end
|
||||||
|
compatible_archetypes[last] = nil :: any
|
||||||
end
|
end
|
||||||
if #compatible_archetypes == 0 then
|
|
||||||
return EMPTY_QUERY
|
|
||||||
end
|
|
||||||
return query :: any
|
return query :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1477,6 +1563,12 @@ local function query_archetypes(query)
|
||||||
return query.compatible_archetypes
|
return query.compatible_archetypes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function query_cached(query)
|
||||||
|
local observer = create_observer_uni(query.world, query.ids[1], EcsTableCreate)
|
||||||
|
observer.query = query
|
||||||
|
return query
|
||||||
|
end
|
||||||
|
|
||||||
local Query = {}
|
local Query = {}
|
||||||
Query.__index = Query
|
Query.__index = Query
|
||||||
Query.__iter = query_iter
|
Query.__iter = query_iter
|
||||||
|
@ -1484,6 +1576,7 @@ Query.iter = query_iter_init
|
||||||
Query.without = query_without
|
Query.without = query_without
|
||||||
Query.with = query_with
|
Query.with = query_with
|
||||||
Query.archetypes = query_archetypes
|
Query.archetypes = query_archetypes
|
||||||
|
Query.cached = query_cached
|
||||||
|
|
||||||
local function world_query(world: World, ...)
|
local function world_query(world: World, ...)
|
||||||
local compatible_archetypes = {}
|
local compatible_archetypes = {}
|
||||||
|
@ -1496,10 +1589,16 @@ local function world_query(world: World, ...)
|
||||||
local idr: IdRecord?
|
local idr: IdRecord?
|
||||||
local componentIndex = world.componentIndex
|
local componentIndex = world.componentIndex
|
||||||
|
|
||||||
|
local q = setmetatable({
|
||||||
|
ids = ids,
|
||||||
|
compatible_archetypes = compatible_archetypes,
|
||||||
|
world = world,
|
||||||
|
}, Query)
|
||||||
|
|
||||||
for _, id in ids do
|
for _, id in ids do
|
||||||
local map = componentIndex[id]
|
local map = componentIndex[id]
|
||||||
if not map then
|
if not map then
|
||||||
return EMPTY_QUERY
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
if idr == nil or map.size < idr.size then
|
if idr == nil or map.size < idr.size then
|
||||||
|
@ -1508,7 +1607,7 @@ local function world_query(world: World, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not idr then
|
if not idr then
|
||||||
return EMPTY_QUERY
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
for archetype_id in idr.cache do
|
for archetype_id in idr.cache do
|
||||||
|
@ -1536,15 +1635,6 @@ local function world_query(world: World, ...)
|
||||||
compatible_archetypes[length] = compatibleArchetype
|
compatible_archetypes[length] = compatibleArchetype
|
||||||
end
|
end
|
||||||
|
|
||||||
if length == 0 then
|
|
||||||
return EMPTY_QUERY
|
|
||||||
end
|
|
||||||
|
|
||||||
local q = setmetatable({
|
|
||||||
compatible_archetypes = compatible_archetypes,
|
|
||||||
ids = ids,
|
|
||||||
}, Query) :: any
|
|
||||||
|
|
||||||
return q
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1730,6 +1820,7 @@ function World.new()
|
||||||
nextComponentId = 0 :: number,
|
nextComponentId = 0 :: number,
|
||||||
nextEntityId = 0 :: number,
|
nextEntityId = 0 :: number,
|
||||||
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
||||||
|
observerable = {}
|
||||||
}, World) :: any
|
}, World) :: any
|
||||||
|
|
||||||
self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
|
self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
|
||||||
|
@ -1793,13 +1884,14 @@ export type Entity<T = unknown> = number & { __T: T }
|
||||||
|
|
||||||
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
||||||
|
|
||||||
type Query<T...> = typeof(setmetatable({}, {
|
export type Query<T...> = typeof(setmetatable({}, {
|
||||||
__iter = (nil :: any) :: Iter<T...>,
|
__iter = (nil :: any) :: Iter<T...>,
|
||||||
})) & {
|
})) & {
|
||||||
iter: Iter<T...>,
|
iter: Iter<T...>,
|
||||||
with: (self: Query<T...>, ...i53) -> Query<T...>,
|
with: (self: Query<T...>, ...Id) -> Query<T...>,
|
||||||
without: (self: Query<T...>, ...i53) -> Query<T...>,
|
without: (self: Query<T...>, ...Id) -> Query<T...>,
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
|
cached: (self: Query<T...>) -> Query<T...>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type World = {
|
export type World = {
|
||||||
|
@ -1812,6 +1904,8 @@ export type World = {
|
||||||
nextComponentId: number,
|
nextComponentId: number,
|
||||||
nextEntityId: number,
|
nextEntityId: number,
|
||||||
nextArchetypeId: number,
|
nextArchetypeId: number,
|
||||||
|
|
||||||
|
observerable: { [string]: { [Id]: { query: Query<i53> } } }
|
||||||
} & {
|
} & {
|
||||||
--- Creates a new entity
|
--- Creates a new entity
|
||||||
entity: (self: World) -> Entity,
|
entity: (self: World) -> Entity,
|
||||||
|
@ -1854,42 +1948,7 @@ export type World = {
|
||||||
children: (self: World, id: Id) -> () -> Entity,
|
children: (self: World, id: Id) -> () -> Entity,
|
||||||
|
|
||||||
--- Searches the world for entities that match a given query
|
--- Searches the world for entities that match a given query
|
||||||
query: (<A>(self: World, Id<A>) -> Query<A>)
|
query: (<A>(self: World, a: { __T: A }) -> Query<A>)
|
||||||
& (<A, B>(self: World, Id<A>, Id<B>) -> Query<A, B>)
|
|
||||||
& (<A, B, C>(self: World, Id<A>, Id<B>, Id<C>) -> Query<A, B, C>)
|
|
||||||
& (<A, B, C, D>(self: World, Id<A>, Id<B>, Id<C>, Id<D>) -> Query<A, B, C, D>)
|
|
||||||
& (<A, B, C, D, E>(self: World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>) -> Query<A, B, C, D, E>)
|
|
||||||
& (<A, B, C, D, E, F>(
|
|
||||||
self: World,
|
|
||||||
Id<A>,
|
|
||||||
Id<B>,
|
|
||||||
Id<C>,
|
|
||||||
Id<D>,
|
|
||||||
Id<E>,
|
|
||||||
Id<F>
|
|
||||||
) -> Query<A, B, C, D, E, F>)
|
|
||||||
& (<A, B, C, D, E, F, G>(
|
|
||||||
self: World,
|
|
||||||
Id<A>,
|
|
||||||
Id<B>,
|
|
||||||
Id<C>,
|
|
||||||
Id<D>,
|
|
||||||
Id<E>,
|
|
||||||
Id<F>,
|
|
||||||
Id<G>
|
|
||||||
) -> Query<A, B, C, D, E, F, G>)
|
|
||||||
& (<A, B, C, D, E, F, G, H>(
|
|
||||||
self: World,
|
|
||||||
Id<A>,
|
|
||||||
Id<B>,
|
|
||||||
Id<C>,
|
|
||||||
Id<D>,
|
|
||||||
Id<E>,
|
|
||||||
Id<F>,
|
|
||||||
Id<G>,
|
|
||||||
Id<H>,
|
|
||||||
...Id<any>
|
|
||||||
) -> Query<A, B, C, D, E, F, G, H>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -62,6 +62,10 @@ local function debug_world_inspect(world: World)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function name(world, e)
|
||||||
|
return world:get(e, jecs.Name)
|
||||||
|
end
|
||||||
|
|
||||||
TEST("archetype", function()
|
TEST("archetype", function()
|
||||||
local archetype_append_to_records = jecs.archetype_append_to_records
|
local archetype_append_to_records = jecs.archetype_append_to_records
|
||||||
local id_record_ensure = jecs.id_record_ensure
|
local id_record_ensure = jecs.id_record_ensure
|
||||||
|
@ -353,6 +357,22 @@ TEST("world:add()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:query()", function()
|
TEST("world:query()", function()
|
||||||
|
do CASE "cached"
|
||||||
|
local world = world_new()
|
||||||
|
local Foo = world:component()
|
||||||
|
local Bar = world:component()
|
||||||
|
local Baz = world:component()
|
||||||
|
local e = world:entity()
|
||||||
|
local q = world:query(Foo, Bar):without(Baz):cached()
|
||||||
|
world:set(e, Foo, true)
|
||||||
|
world:set(e, Bar, false)
|
||||||
|
world:set(e, Baz, true)
|
||||||
|
for _, e in q do
|
||||||
|
CHECK(true)
|
||||||
|
end
|
||||||
|
CHECK(#q:archetypes() == 1)
|
||||||
|
CHECK(not table.find(q:archetypes(), world.archetypes[table.concat({Foo, Bar, Baz}, "_")]))
|
||||||
|
end
|
||||||
do CASE("multiple iter")
|
do CASE("multiple iter")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
@ -808,40 +828,6 @@ TEST("world:query()", function()
|
||||||
CHECK(withoutCount == 0)
|
CHECK(withoutCount == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
|
||||||
CASE("Empty Query")
|
|
||||||
do
|
|
||||||
local world = jecs.World.new()
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:add(e1, A)
|
|
||||||
|
|
||||||
local query = world:query(B)
|
|
||||||
CHECK(query:without() == query)
|
|
||||||
CHECK(query:with() == query)
|
|
||||||
-- They always return the same EMPTY_LIST
|
|
||||||
CHECK(query:archetypes() == world:query(B):archetypes())
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
local world = jecs.World.new()
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:add(e1, A)
|
|
||||||
|
|
||||||
local count = 0
|
|
||||||
for id in world:query(B) do
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(count == 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
do
|
||||||
CASE("without")
|
CASE("without")
|
||||||
do
|
do
|
||||||
|
|
Loading…
Reference in a new issue