mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-16 05:19:18 +00:00
Change Observers to support cleanups and :with/without
Some checks are pending
Some checks are pending
This commit is contained in:
parent
3dacb2af80
commit
456713c2d5
2 changed files with 142 additions and 88 deletions
164
addons/ob.luau
164
addons/ob.luau
|
@ -10,22 +10,16 @@ type Entity<T> = jecs.Entity<T>
|
||||||
|
|
||||||
export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...)
|
export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...)
|
||||||
|
|
||||||
export type Observer<T...> = typeof(setmetatable(
|
export type Observer<T...> = {
|
||||||
{} :: {
|
disconnect: (Observer<T...>) -> (),
|
||||||
iter: Iter<T...>,
|
added: ((jecs.Entity) -> ()) -> (),
|
||||||
entities: { Entity<nil> },
|
removed: ((jecs.Entity) -> ()) -> ()
|
||||||
disconnect: (Observer<T...>) -> ()
|
|
||||||
},
|
|
||||||
{} :: {
|
|
||||||
__iter: Iter<T...>,
|
|
||||||
}
|
}
|
||||||
))
|
|
||||||
|
|
||||||
local function observers_new<T...>(
|
local function observers_new<T...>(
|
||||||
query: Query<T...>,
|
query: Query<T...>,
|
||||||
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
|
callback: ((Entity<nil>) -> ())
|
||||||
): Observer<T...>
|
): Observer<T...>
|
||||||
|
|
||||||
query:cached()
|
query:cached()
|
||||||
|
|
||||||
local world = (query :: Query<T...> & { world: World }).world
|
local world = (query :: Query<T...> & { world: World }).world
|
||||||
|
@ -47,8 +41,6 @@ local function observers_new<T...>(
|
||||||
end
|
end
|
||||||
|
|
||||||
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 function emplaced<T, a>(
|
||||||
entity: jecs.Entity<T>,
|
entity: jecs.Entity<T>,
|
||||||
|
@ -60,20 +52,40 @@ local function observers_new<T...>(
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
if archetypes[archetype.id] then
|
||||||
i += 1
|
callback(entity)
|
||||||
entities[i] = entity
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local cleanup = {}
|
||||||
|
|
||||||
for _, term in terms do
|
for _, term in terms 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)
|
local onadded = world:added(term, emplaced)
|
||||||
world:changed(term, emplaced)
|
local onchanged = world:changed(term, emplaced)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onchanged)
|
||||||
|
end
|
||||||
|
|
||||||
|
local without = query.filter_without
|
||||||
|
if without then
|
||||||
|
for _, term in without do
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
end
|
||||||
|
local onremoved = world:removed(term, function(entity, id)
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function disconnect()
|
local function disconnect()
|
||||||
|
@ -86,44 +98,28 @@ local function observers_new<T...>(
|
||||||
observers_on_delete,
|
observers_on_delete,
|
||||||
observer_on_delete
|
observer_on_delete
|
||||||
))
|
))
|
||||||
end
|
|
||||||
|
|
||||||
local function iter()
|
table.clear(archetypes)
|
||||||
local row = i
|
|
||||||
return function()
|
for _, disconnect in cleanup do
|
||||||
if row == 0 then
|
disconnect()
|
||||||
i = 0
|
|
||||||
table.clear(entities)
|
|
||||||
end
|
|
||||||
local entity = entities[row]
|
|
||||||
row -= 1
|
|
||||||
return entity
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local observer = {
|
local observer = {
|
||||||
disconnect = disconnect,
|
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...>): 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 :: Query<T...> & { world: World }).world
|
||||||
|
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
local terms = query.ids
|
local terms = query.filter_with :: { jecs.Id<any> }
|
||||||
local first = terms[1]
|
local first = terms[1]
|
||||||
|
|
||||||
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
@ -137,45 +133,75 @@ local function monitors_new<T...>(
|
||||||
archetypes[archetype.id] = nil
|
archetypes[archetype.id] = nil
|
||||||
end
|
end
|
||||||
local entity_index = world.entity_index :: any
|
local entity_index = world.entity_index :: any
|
||||||
local i = 0
|
|
||||||
local entities = {}
|
local callback_added: ((jecs.Entity) -> ())?
|
||||||
|
local callback_removed: ((jecs.Entity) -> ())?
|
||||||
|
|
||||||
local function emplaced<T, a>(
|
local function emplaced<T, a>(
|
||||||
entity: jecs.Entity<T>,
|
entity: jecs.Entity<T>,
|
||||||
id: jecs.Id<a>,
|
id: jecs.Id<a>,
|
||||||
value: a?
|
value: a?
|
||||||
)
|
)
|
||||||
|
if callback_added == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local r = jecs.entity_index_try_get_fast(
|
local r = jecs.entity_index_try_get_fast(
|
||||||
entity_index, entity :: any) :: jecs.Record
|
entity_index, entity :: any) :: jecs.Record
|
||||||
|
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
if archetypes[archetype.id] then
|
||||||
i += 1
|
callback_added(entity)
|
||||||
entities[i] = entity
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, jecs.OnAdd)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function removed(entity: jecs.Entity, component: jecs.Id)
|
local function removed(entity: jecs.Entity, component: jecs.Id)
|
||||||
|
if callback_removed == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
if not archetypes[r.archetype.id] then
|
if not archetypes[r.archetype.id] then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local EcsOnRemove = jecs.OnRemove :: jecs.Id
|
callback_removed(entity)
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, EcsOnRemove)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local cleanup = {}
|
||||||
|
|
||||||
for _, term in terms do
|
for _, term in terms 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)
|
local onadded = world:added(term, emplaced)
|
||||||
world:removed(term, removed)
|
local onremoved = world:removed(term, removed)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
end
|
||||||
|
|
||||||
|
local without = query.filter_without
|
||||||
|
if without then
|
||||||
|
for _, term in without do
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
end
|
||||||
|
local onadded = world:added(term, removed)
|
||||||
|
local onremoved = world:removed(term, function(entity, id)
|
||||||
|
if callback_added == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_added(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function disconnect()
|
local function disconnect()
|
||||||
|
@ -188,30 +214,28 @@ local function monitors_new<T...>(
|
||||||
observers_on_delete,
|
observers_on_delete,
|
||||||
observer_on_delete
|
observer_on_delete
|
||||||
))
|
))
|
||||||
|
|
||||||
|
table.clear(archetypes)
|
||||||
|
|
||||||
|
for _, disconnect in cleanup do
|
||||||
|
disconnect()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function iter()
|
local function monitor_added(callback)
|
||||||
local row = i
|
callback_added = callback
|
||||||
return function()
|
|
||||||
if row == 0 then
|
|
||||||
i = 0
|
|
||||||
table.clear(entities)
|
|
||||||
end
|
|
||||||
local entity = entities[row]
|
|
||||||
row -= 1
|
|
||||||
return entity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function monitor_removed(callback)
|
||||||
|
callback_removed = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
local observer = {
|
local observer = {
|
||||||
disconnect = disconnect,
|
disconnect = disconnect,
|
||||||
entities = entities,
|
added = monitor_added,
|
||||||
__iter = iter,
|
removed = monitor_removed
|
||||||
iter = iter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(observer, observer)
|
|
||||||
|
|
||||||
return (observer :: any) :: Observer<T...>
|
return (observer :: any) :: Observer<T...>
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,42 @@ local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
||||||
local FOCUS = test.FOCUS
|
local FOCUS = test.FOCUS
|
||||||
local ob = require("@addons/ob")
|
local ob = require("@addons/ob")
|
||||||
|
|
||||||
TEST("addons/observers", function()
|
TEST("addons/ob", function()
|
||||||
|
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
do CASE "Should support query:without()"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
local monitor = ob.monitor(world:query(A):without(B))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, B)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, B)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:remove(child, A)
|
||||||
|
CHECK(c==3)
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "monitors should accept pairs"
|
do CASE "monitors should accept pairs"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
|
||||||
local c = 1
|
local c = 1
|
||||||
ob.monitor(world:query(jecs.pair(A, B)), function (_, event)
|
local monitor = ob.monitor(world:query(jecs.pair(A, B)))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
c += 1
|
c += 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -24,6 +51,7 @@ TEST("addons/observers", function()
|
||||||
world:remove(child, jecs.pair(A, B))
|
world:remove(child, jecs.pair(A, B))
|
||||||
CHECK(c == 3)
|
CHECK(c == 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Ensure ordering between signals and observers"
|
do CASE "Ensure ordering between signals and observers"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
@ -78,7 +106,9 @@ TEST("addons/observers", function()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
ob.monitor(world:query(A), counter)
|
local monitor = ob.monitor(world:query(A))
|
||||||
|
monitor.added(counter)
|
||||||
|
monitor.removed(counter)
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, false)
|
world:set(e, A, false)
|
Loading…
Reference in a new issue