Compare commits

...

2 commits

Author SHA1 Message Date
PepeElToro41
c73f484560
Merge b654d01421 into 1eecaac96f 2025-09-09 20:24:51 -03:00
PepeElToro41
b654d01421 improve observers 2025-08-30 22:00:56 -06:00

View file

@ -3,10 +3,11 @@ local jecs = require("@jecs")
type World = jecs.World type World = jecs.World
type Query<T...> = jecs.Query<T...> type Query<T...> = jecs.Query<T...>
type QueryInner<T...> = Query<T...> & jecs.QueryInner
type Id<T=any> = jecs.Id<T> type Id<T = any> = jecs.Id<T>
type Entity<T> = jecs.Entity<T> type Entity<T = any> = jecs.Entity<T>
export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...) export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...)
@ -14,24 +15,20 @@ export type Observer<T...> = typeof(setmetatable(
{} :: { {} :: {
iter: Iter<T...>, iter: Iter<T...>,
entities: { Entity<nil> }, entities: { Entity<nil> },
disconnect: (Observer<T...>) -> () disconnect: (Observer<T...>) -> (),
}, },
{} :: { {} :: {
__iter: Iter<T...>, __iter: Iter<T...>,
} }
)) ))
local function observers_new<T...>( local function get_matching_archetypes(world: jecs.World, query: QueryInner<...any>)
query: Query<T...>,
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
): Observer<T...>
query:cached()
local world = (query :: Query<T...> & { world: World }).world
callback = callback
local archetypes = {} local archetypes = {}
for _, archetype in query.compatible_archetypes do
archetypes[archetype.id] = true
end
local terms = query.ids local terms = query.ids
local first = terms[1] local first = terms[1]
@ -40,113 +37,139 @@ local function observers_new<T...>(
observer_on_create.callback = function(archetype) observer_on_create.callback = function(archetype)
archetypes[archetype.id] = true archetypes[archetype.id] = true
end end
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first] local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
local observer_on_delete = observers_on_delete[#observers_on_delete] local observer_on_delete = observers_on_delete[#observers_on_delete]
observer_on_delete.callback = function(archetype) observer_on_delete.callback = function(archetype)
archetypes[archetype.id] = nil archetypes[archetype.id] = nil
end end
local function disconnect()
table.remove(observers_on_create, table.find(observers_on_create, observer_on_create))
table.remove(observers_on_delete, table.find(observers_on_delete, observer_on_delete))
end
return archetypes, disconnect
end
local function observers_new<T...>(query: Query<T...>, callback: ((Entity<nil>, Id<any>, value: any?) -> ())?): Observer<T...>
query:cached()
local world = (query :: QueryInner<T...>).world
callback = callback
local archetypes, disconnect_query = get_matching_archetypes(world, query :: QueryInner<T...>)
local terms = query.ids
local query_with = query.filter_with or terms
local entity_index = world.entity_index :: any local entity_index = world.entity_index :: any
local i = 0
local entities = {}
local function emplaced<T, a>( local iter_indexes = {} :: { [Entity]: number }
entity: jecs.Entity<T>, local iter_queue = {} :: { Entity }
id: jecs.Id<a>,
value: a? local function remove_queued(entity: jecs.Entity, index: number)
) if index ~= nil then
local last = table.remove(iter_queue)
if last and last ~= entity then
iter_queue[index] = last
iter_indexes[last] = index
end
iter_indexes[entity] = nil
end
end
local function emplaced<T>(entity: jecs.Entity, id: jecs.Id<T>, value: T?)
local r = entity_index.sparse_array[jecs.ECS_ID(entity)] local r = entity_index.sparse_array[jecs.ECS_ID(entity)]
local index = iter_indexes[entity]
if r == nil then
remove_queued(entity, index)
end
local archetype = r.archetype local archetype = r.archetype
if archetypes[archetype.id] then if archetypes[archetype.id] then
i += 1 if index == nil then
entities[i] = entity table.insert(iter_queue, entity)
iter_indexes[entity] = #iter_queue
end
if callback ~= nil then if callback ~= nil then
callback(entity, id, value) callback(entity :: Entity, id, value)
end end
end end
end end
for _, term in terms do local function removed(entity: jecs.Entity)
local index = iter_indexes[entity]
if index ~= nil then
remove_queued(entity, index)
end
end
local hooked = {} :: { () -> () }
for _, term in query_with do
if jecs.IS_PAIR(term) then if jecs.IS_PAIR(term) then
term = jecs.ECS_PAIR_FIRST(term) term = jecs.ECS_PAIR_FIRST(term)
end end
world:added(term, emplaced) table.insert(hooked, world:added(term, emplaced))
world:changed(term, emplaced) table.insert(hooked, world:changed(term, emplaced))
end table.insert(hooked, world:removed(term, removed))
end
local function disconnect() local function disconnect()
table.remove(observers_on_create, table.find( disconnect_query()
observers_on_create, for _, unhook in hooked do
observer_on_create unhook()
)) end
end
table.remove(observers_on_delete, table.find( local function iter()
observers_on_delete, local row = #iter_queue
observer_on_delete return function()
)) if row == 0 then
end table.clear(iter_queue)
table.clear(iter_indexes)
end
local entity = iter_queue[row]
row -= 1
return entity
end
end
local function iter() local observer = {
local row = i disconnect = disconnect,
return function() entities = iter_queue,
if row == 0 then __iter = iter,
i = 0 iter = iter,
table.clear(entities) }
end
local entity = entities[row]
row -= 1
return entity
end
end
local observer = { setmetatable(observer, observer)
disconnect = disconnect,
entities = entities,
__iter = iter,
iter = iter
}
setmetatable(observer, observer) return (observer :: any) :: Observer<T...>
return (observer :: any) :: Observer<T...>
end end
local function monitors_new<T...>( local function monitors_new<T...>(query: Query<T...>, callback: ((Entity<nil>, Id<any>, value: any?) -> ())?): Observer<T...>
query: Query<T...>,
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
): Observer<T...>
query:cached() query:cached()
local world = (query :: Query<T...> & { world: World }).world local world = (query :: QueryInner<T...>).world
local archetypes, disconnect_query = get_matching_archetypes(world, query :: QueryInner<T...>)
local archetypes = {}
local terms = query.ids local terms = query.ids
local first = terms[1] local query_with = query.filter_with or terms
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
local observer_on_create = observers_on_create[#observers_on_create]
observer_on_create.callback = function(archetype)
archetypes[archetype.id] = true
end
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
local observer_on_delete = observers_on_delete[#observers_on_delete]
observer_on_delete.callback = function(archetype)
archetypes[archetype.id] = nil
end
local entity_index = world.entity_index :: any local entity_index = world.entity_index :: any
local i = 0 local i = 0
local entities = {} local entities = {}
local function emplaced<T, a>( local function emplaced<T, a>(entity: jecs.Entity<T>, id: jecs.Id<a>, value: a?)
entity: jecs.Entity<T>, local r = jecs.entity_index_try_get_fast(entity_index, entity :: any) :: jecs.Record
id: jecs.Id<a>, if not r or not r.archetype then
value: a? if callback then
) callback(entity :: Entity, jecs.OnRemove)
local r = jecs.entity_index_try_get_fast( end
entity_index, entity :: any) :: jecs.Record return
end
local archetype = r.archetype local archetype = r.archetype
@ -154,14 +177,18 @@ local function monitors_new<T...>(
i += 1 i += 1
entities[i] = entity entities[i] = entity
if callback ~= nil then if callback ~= nil then
callback(entity, jecs.OnAdd) callback(entity :: Entity, jecs.OnAdd)
end
else
if callback ~= nil then
callback(entity :: Entity, jecs.OnRemove)
end end
end end
end end
local function removed(entity: jecs.Entity, component: jecs.Id) local function removed(entity: jecs.Entity, component: jecs.Id)
local r = jecs.record(world, entity) local r = jecs.record(world, entity)
if not archetypes[r.archetype.id] then if not r or not archetypes[r.archetype.id] then
return return
end end
local EcsOnRemove = jecs.OnRemove :: jecs.Id local EcsOnRemove = jecs.OnRemove :: jecs.Id
@ -170,52 +197,48 @@ local function monitors_new<T...>(
end end
end end
for _, term in terms do local hooked = {} :: { () -> () }
for _, term in query_with do
if jecs.IS_PAIR(term) then if jecs.IS_PAIR(term) then
term = jecs.ECS_PAIR_FIRST(term) term = jecs.ECS_PAIR_FIRST(term)
end end
world:added(term, emplaced) table.insert(hooked, world:added(term, emplaced))
world:removed(term, removed) table.insert(hooked, world:removed(term, removed))
end end
local function disconnect() local function disconnect()
table.remove(observers_on_create, table.find( disconnect_query()
observers_on_create, for _, unhook in hooked do
observer_on_create unhook()
)) end
end
table.remove(observers_on_delete, table.find( local function iter()
observers_on_delete, local row = i
observer_on_delete return function()
)) if row == 0 then
end i = 0
table.clear(entities)
local function iter() end
local row = i local entity = entities[row]
return function() row -= 1
if row == 0 then return entity
i = 0 end
table.clear(entities) end
end
local entity = entities[row]
row -= 1
return entity
end
end
local observer = { local observer = {
disconnect = disconnect, disconnect = disconnect,
entities = entities, entities = entities,
__iter = iter, __iter = iter,
iter = iter iter = iter,
} }
setmetatable(observer, observer) setmetatable(observer, observer)
return (observer :: any) :: Observer<T...> return (observer :: any) :: Observer<T...>
end end
return { return {
monitor = monitors_new, monitor = monitors_new,
observer = observers_new observer = observers_new,
} }