From b654d01421835fdeb5671ffc92a6d1072adacdff Mon Sep 17 00:00:00 2001 From: PepeElToro41 Date: Sat, 30 Aug 2025 22:00:56 -0600 Subject: [PATCH] improve observers --- addons/ob.luau | 265 +++++++++++++++++++++++++++---------------------- 1 file changed, 144 insertions(+), 121 deletions(-) diff --git a/addons/ob.luau b/addons/ob.luau index efc0630..bfef656 100755 --- a/addons/ob.luau +++ b/addons/ob.luau @@ -3,10 +3,11 @@ local jecs = require("@jecs") type World = jecs.World type Query = jecs.Query +type QueryInner = Query & jecs.QueryInner -type Id = jecs.Id +type Id = jecs.Id -type Entity = jecs.Entity +type Entity = jecs.Entity export type Iter = (Observer) -> () -> (jecs.Entity, T...) @@ -14,24 +15,20 @@ export type Observer = typeof(setmetatable( {} :: { iter: Iter, entities: { Entity }, - disconnect: (Observer) -> () + disconnect: (Observer) -> (), }, {} :: { __iter: Iter, } )) -local function observers_new( - query: Query, - callback: ((Entity, Id, value: any?) -> ())? -): Observer - - query:cached() - - local world = (query :: Query & { world: World }).world - callback = callback - +local function get_matching_archetypes(world: jecs.World, query: QueryInner<...any>) local archetypes = {} + + for _, archetype in query.compatible_archetypes do + archetypes[archetype.id] = true + end + local terms = query.ids local first = terms[1] @@ -40,113 +37,139 @@ local function observers_new( 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 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(query: Query, callback: ((Entity, Id, value: any?) -> ())?): Observer + query:cached() + + local world = (query :: QueryInner).world + callback = callback + + local archetypes, disconnect_query = get_matching_archetypes(world, query :: QueryInner) + + local terms = query.ids + local query_with = query.filter_with or terms + local entity_index = world.entity_index :: any - local i = 0 - local entities = {} - local function emplaced( - entity: jecs.Entity, - id: jecs.Id, - value: a? - ) + local iter_indexes = {} :: { [Entity]: number } + local iter_queue = {} :: { Entity } + + 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(entity: jecs.Entity, id: jecs.Id, value: T?) 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 if archetypes[archetype.id] then - i += 1 - entities[i] = entity + if index == nil then + table.insert(iter_queue, entity) + iter_indexes[entity] = #iter_queue + end if callback ~= nil then - callback(entity, id, value) + callback(entity :: Entity, id, value) 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 term = jecs.ECS_PAIR_FIRST(term) end - world:added(term, emplaced) - world:changed(term, emplaced) - end + table.insert(hooked, world:added(term, emplaced)) + table.insert(hooked, world:changed(term, emplaced)) + table.insert(hooked, world:removed(term, removed)) + end - local function disconnect() - table.remove(observers_on_create, table.find( - observers_on_create, - observer_on_create - )) + local function disconnect() + disconnect_query() + for _, unhook in hooked do + unhook() + end + end - table.remove(observers_on_delete, table.find( - observers_on_delete, - observer_on_delete - )) - end + local function iter() + local row = #iter_queue + return function() + if row == 0 then + table.clear(iter_queue) + table.clear(iter_indexes) + end + local entity = iter_queue[row] + row -= 1 + return entity + end + end - local function iter() - local row = i - return function() - if row == 0 then - i = 0 - table.clear(entities) - end - local entity = entities[row] - row -= 1 - return entity - end - end + local observer = { + disconnect = disconnect, + entities = iter_queue, + __iter = iter, + iter = iter, + } - local observer = { - disconnect = disconnect, - entities = entities, - __iter = iter, - iter = iter - } + setmetatable(observer, observer) - setmetatable(observer, observer) - - return (observer :: any) :: Observer + return (observer :: any) :: Observer end -local function monitors_new( - query: Query, - callback: ((Entity, Id, value: any?) -> ())? -): Observer - +local function monitors_new(query: Query, callback: ((Entity, Id, value: any?) -> ())?): Observer query:cached() - local world = (query :: Query & { world: World }).world + local world = (query :: QueryInner).world + + local archetypes, disconnect_query = get_matching_archetypes(world, query :: QueryInner) - local archetypes = {} 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 i = 0 local entities = {} - local function emplaced( - entity: jecs.Entity, - id: jecs.Id, - value: a? - ) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) :: jecs.Record + local function emplaced(entity: jecs.Entity, id: jecs.Id, value: a?) + local r = jecs.entity_index_try_get_fast(entity_index, entity :: any) :: jecs.Record + if not r or not r.archetype then + if callback then + callback(entity :: Entity, jecs.OnRemove) + end + return + end local archetype = r.archetype @@ -154,14 +177,18 @@ local function monitors_new( i += 1 entities[i] = entity 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 local function removed(entity: jecs.Entity, component: jecs.Id) local r = jecs.record(world, entity) - if not archetypes[r.archetype.id] then + if not r or not archetypes[r.archetype.id] then return end local EcsOnRemove = jecs.OnRemove :: jecs.Id @@ -170,52 +197,48 @@ local function monitors_new( end end - for _, term in terms do + local hooked = {} :: { () -> () } + for _, term in query_with do if jecs.IS_PAIR(term) then term = jecs.ECS_PAIR_FIRST(term) end - world:added(term, emplaced) - world:removed(term, removed) - end + table.insert(hooked, world:added(term, emplaced)) + table.insert(hooked, world:removed(term, removed)) + end - local function disconnect() - table.remove(observers_on_create, table.find( - observers_on_create, - observer_on_create - )) + local function disconnect() + disconnect_query() + for _, unhook in hooked do + unhook() + end + end - table.remove(observers_on_delete, table.find( - observers_on_delete, - observer_on_delete - )) - end - - local function iter() - local row = i - return function() - if row == 0 then - i = 0 - table.clear(entities) - end - local entity = entities[row] - row -= 1 - return entity - end - end + local function iter() + local row = i + return function() + if row == 0 then + i = 0 + table.clear(entities) + end + local entity = entities[row] + row -= 1 + return entity + end + end local observer = { - disconnect = disconnect, - entities = entities, - __iter = iter, - iter = iter - } + disconnect = disconnect, + entities = entities, + __iter = iter, + iter = iter, + } - setmetatable(observer, observer) + setmetatable(observer, observer) - return (observer :: any) :: Observer + return (observer :: any) :: Observer end return { monitor = monitors_new, - observer = observers_new + observer = observers_new, }