mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
Track bulk operation pattern
This commit is contained in:
parent
4db046b925
commit
7bc6935965
3 changed files with 319 additions and 16 deletions
|
|
@ -29,7 +29,7 @@ local function observers_new<T...>(
|
|||
callback = callback
|
||||
|
||||
local archetypes = cachedquery.archetypes_map
|
||||
local terms = query.filter_with :: { jecs.Id<any, any> }
|
||||
local terms = query.filter_with :: { jecs.Id<any> }
|
||||
|
||||
local entity_index = world.entity_index
|
||||
|
||||
|
|
@ -73,13 +73,13 @@ local function observers_new<T...>(
|
|||
end
|
||||
|
||||
local without = query.filter_without
|
||||
if without then
|
||||
if without then
|
||||
for _, term in without do
|
||||
if jecs.IS_PAIR(term) then
|
||||
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||
local wc = tgt == jecs.w
|
||||
local onremoved = world:removed(rel, function(entity, id)
|
||||
local onremoved = world:removed(rel, function(entity, id, delete: boolean?)
|
||||
if not wc and id ~= term then
|
||||
return
|
||||
end
|
||||
|
|
@ -130,28 +130,49 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
local world = (cachedquery :: Query<...any> & { world: World }).world :: jecs.World
|
||||
|
||||
local archetypes = cachedquery.archetypes_map
|
||||
local terms = cachedquery.filter_with :: { jecs.Id<any, any> }
|
||||
local terms = cachedquery.filter_with :: { jecs.Id<any> }
|
||||
|
||||
local entity_index = world.entity_index :: any
|
||||
|
||||
local terms_lookup: { [jecs.Id<any>]: boolean } = {}
|
||||
for _, term in terms do
|
||||
terms_lookup[term] = true
|
||||
end
|
||||
|
||||
local callback_added: ((jecs.Entity) -> ())?
|
||||
local callback_removed: ((jecs.Entity) -> ())?
|
||||
|
||||
-- NOTE(marcus): Track the last old archetype we processed to detect bulk operations.
|
||||
-- We can detect this pattern by checking if we've seen this old archetype
|
||||
-- before with a component in the terms list.
|
||||
local last_old_archetype: jecs.Archetype? = nil
|
||||
|
||||
local function emplaced<a>(
|
||||
entity: jecs.Entity,
|
||||
id: jecs.Id<a>,
|
||||
value: a,
|
||||
oldarchetype: jecs.Archetype
|
||||
)
|
||||
if callback_added == nil then
|
||||
value: a,
|
||||
oldarchetype: jecs.Archetype
|
||||
)
|
||||
if callback_added == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- NOTE(marcus): Skip if we've seen this old archetype before AND
|
||||
-- this component is in the query's terms. The component-in-terms
|
||||
-- check ensures we don't skip legitimate separate operations.
|
||||
if last_old_archetype == oldarchetype and terms_lookup[id] then
|
||||
return
|
||||
end
|
||||
|
||||
local r = jecs.entity_index_try_get_fast(
|
||||
entity_index, entity :: any) :: jecs.Record
|
||||
if not archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||
|
||||
if archetypes[r.archetype.id] then
|
||||
last_old_archetype = oldarchetype
|
||||
callback_added(entity)
|
||||
else
|
||||
-- NOTE(marcus): Clear tracking when we see a different transition pattern
|
||||
last_old_archetype = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -168,6 +189,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
local dst = jecs.archetype_traverse_remove(world, component, src)
|
||||
|
||||
if not archetypes[dst.id] then
|
||||
last_old_archetype = nil
|
||||
callback_removed(entity)
|
||||
end
|
||||
end
|
||||
|
|
@ -180,10 +202,13 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||
local wc = tgt == jecs.w
|
||||
|
||||
local onadded = world:added(rel, function(entity, id, _, oldarchetype)
|
||||
local onadded = world:added(rel, function(entity, id, _, oldarchetype: jecs.Archetype)
|
||||
if callback_added == nil then
|
||||
return
|
||||
end
|
||||
if last_old_archetype == oldarchetype and terms_lookup[id] then
|
||||
return
|
||||
end
|
||||
|
||||
if not wc and id ~= term then
|
||||
return
|
||||
|
|
@ -192,10 +217,8 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
local r = jecs.entity_index_try_get_fast(
|
||||
entity_index, entity :: any) :: jecs.Record
|
||||
|
||||
if archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||
print("???")
|
||||
end
|
||||
if not archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||
last_old_archetype = oldarchetype
|
||||
callback_added(entity)
|
||||
end
|
||||
end)
|
||||
|
|
@ -209,6 +232,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
|
||||
local r = jecs.record(world, entity)
|
||||
if archetypes[r.archetype.id] then
|
||||
last_old_archetype = nil
|
||||
callback_removed(entity)
|
||||
end
|
||||
end)
|
||||
|
|
@ -230,7 +254,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||
local wc = tgt == jecs.w
|
||||
local onadded = world:added(rel, function(entity, id, _, oldarchetype)
|
||||
local onadded = world:added(rel, function(entity, id, _, oldarchetype: jecs.Archetype)
|
||||
if callback_removed == nil then
|
||||
return
|
||||
end
|
||||
|
|
@ -249,6 +273,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
-- removed a component even though the entity is not
|
||||
-- apart of the monitor
|
||||
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||
last_old_archetype = nil
|
||||
callback_removed(entity)
|
||||
end
|
||||
end)
|
||||
|
|
@ -262,15 +287,20 @@ local function monitors_new(query: Query<...any>): Monitor
|
|||
if not wc and id ~= term then
|
||||
return
|
||||
end
|
||||
|
||||
local r = jecs.record(world, entity)
|
||||
local archetype = r.archetype
|
||||
if not archetype then
|
||||
return
|
||||
end
|
||||
if last_old_archetype == archetype and terms_lookup[id] then
|
||||
return
|
||||
end
|
||||
|
||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||
|
||||
if archetypes[dst.id] then
|
||||
last_old_archetype = archetype
|
||||
callback_added(entity)
|
||||
end
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ type function ecs_id_t(first: type, second: type)
|
|||
return p
|
||||
end
|
||||
|
||||
export type Entity<T = nil> = { __T: T }
|
||||
export type Entity<T = any> = { __T: T }
|
||||
export type Id<T = any> = { __T: T }
|
||||
export type Pair<First=any, Second=any> = ecs_pair_t<Entity<First>, Entity<Second>>
|
||||
export type Component<T=any> = { __T: T }
|
||||
|
|
|
|||
|
|
@ -222,10 +222,28 @@ TEST("addons/ob::observer", function()
|
|||
end
|
||||
end)
|
||||
|
||||
FOCUS()
|
||||
TEST("addons/ob::monitor", function()
|
||||
local world = jecs.world()
|
||||
|
||||
|
||||
do CASE [[should not invoke monitor.added callback multiple times in a bulk_move
|
||||
]]
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { A, B, C }, { 1, 2, 3 })
|
||||
CHECK(c==1)
|
||||
print(c)
|
||||
end
|
||||
|
||||
do CASE [[should not invoke monitor.added callback unless it wasn't apart
|
||||
of the monitor]]
|
||||
local A = world:component()
|
||||
|
|
@ -490,6 +508,261 @@ TEST("addons/ob::monitor", function()
|
|||
world:set(e, A, false)
|
||||
CHECK(count == 4)
|
||||
end
|
||||
|
||||
do CASE "should not invoke monitor.added callback multiple times in bulk_insert with pairs"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1), jecs.pair(A, e2)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { jecs.pair(A, e1), jecs.pair(A, e2) }, { true, true })
|
||||
CHECK(c == 1)
|
||||
end
|
||||
|
||||
do CASE "should not invoke monitor.added callback multiple times in bulk_insert with mixed components and pairs"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
local e1 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, jecs.pair(C, e1)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { A, B, jecs.pair(C, e1) }, { 1, 2, true })
|
||||
CHECK(c == 1)
|
||||
end
|
||||
|
||||
do CASE "monitor with wildcard pair should handle bulk_insert"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
local e3 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, jecs.pair(B, jecs.w)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(c == 0)
|
||||
world:add(e, jecs.pair(B, e1))
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(B, e2))
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(B, e3))
|
||||
CHECK(c == 1)
|
||||
end
|
||||
|
||||
do CASE "monitor with multiple pairs should handle separate operations correctly"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1), jecs.pair(A, e2)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
local r = 0
|
||||
monitor.removed(function()
|
||||
r += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 0)
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 1)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(r == 1)
|
||||
world:remove(e, jecs.pair(A, e2))
|
||||
CHECK(r == 1)
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 2)
|
||||
end
|
||||
|
||||
if true then return end
|
||||
|
||||
do CASE "monitor with pair should handle remove and re-add correctly"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
local r = 0
|
||||
monitor.removed(function()
|
||||
r += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 1)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(r == 1)
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 2)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(r == 2)
|
||||
end
|
||||
|
||||
do CASE "monitor with pair and without clause should handle bulk operations"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, e1)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
local r = 0
|
||||
monitor.removed(function()
|
||||
r += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(B, e2))
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(B, e1))
|
||||
CHECK(c == 1)
|
||||
CHECK(r == 1)
|
||||
world:remove(e, jecs.pair(B, e1))
|
||||
CHECK(c == 2)
|
||||
CHECK(r == 1)
|
||||
end
|
||||
|
||||
do CASE "monitor with wildcard pair in without should handle operations correctly"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, jecs.w)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
local r = 0
|
||||
monitor.removed(function()
|
||||
r += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(B, e1))
|
||||
CHECK(c == 1)
|
||||
CHECK(r == 1)
|
||||
world:add(e, jecs.pair(B, e2))
|
||||
CHECK(c == 1)
|
||||
CHECK(r == 1)
|
||||
world:remove(e, jecs.pair(B, e1))
|
||||
CHECK(c == 1)
|
||||
CHECK(r == 1)
|
||||
world:remove(e, jecs.pair(B, e2))
|
||||
CHECK(c == 2)
|
||||
CHECK(r == 1)
|
||||
end
|
||||
|
||||
do CASE "monitor with multiple pairs should not skip legitimate transitions"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
local e3 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1), jecs.pair(A, e2)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 1)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 2)
|
||||
world:remove(e, jecs.pair(A, e2))
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 3)
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 4)
|
||||
end
|
||||
|
||||
do CASE "monitor with pair should handle set operations correctly"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
local r = 0
|
||||
monitor.removed(function()
|
||||
r += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:set(e, jecs.pair(A, e1), true)
|
||||
CHECK(c == 1)
|
||||
world:set(e, jecs.pair(A, e1), false)
|
||||
CHECK(c == 1)
|
||||
world:set(e, jecs.pair(A, e1), true)
|
||||
CHECK(c == 2)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(r == 1)
|
||||
world:set(e, jecs.pair(A, e1), true)
|
||||
CHECK(c == 3)
|
||||
end
|
||||
|
||||
do CASE "monitor with pair query should handle non-matching pairs"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1)))
|
||||
local c = 0
|
||||
monitor.added(function()
|
||||
c += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 1)
|
||||
world:add(e, jecs.pair(B, e1))
|
||||
CHECK(c == 1)
|
||||
end
|
||||
end)
|
||||
|
||||
return FINISH()
|
||||
|
|
|
|||
Loading…
Reference in a new issue