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
|
callback = callback
|
||||||
|
|
||||||
local archetypes = cachedquery.archetypes_map
|
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
|
local entity_index = world.entity_index
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ local function observers_new<T...>(
|
||||||
local rel = jecs.ECS_PAIR_FIRST(term)
|
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
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
|
if not wc and id ~= term then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -130,13 +130,23 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local world = (cachedquery :: Query<...any> & { world: World }).world :: jecs.World
|
local world = (cachedquery :: Query<...any> & { world: World }).world :: jecs.World
|
||||||
|
|
||||||
local archetypes = cachedquery.archetypes_map
|
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 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_added: ((jecs.Entity) -> ())?
|
||||||
local callback_removed: ((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>(
|
local function emplaced<a>(
|
||||||
entity: jecs.Entity,
|
entity: jecs.Entity,
|
||||||
id: jecs.Id<a>,
|
id: jecs.Id<a>,
|
||||||
|
|
@ -147,11 +157,22 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
return
|
return
|
||||||
end
|
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(
|
local r = jecs.entity_index_try_get_fast(
|
||||||
entity_index, entity :: any) :: jecs.Record
|
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)
|
callback_added(entity)
|
||||||
|
else
|
||||||
|
-- NOTE(marcus): Clear tracking when we see a different transition pattern
|
||||||
|
last_old_archetype = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -168,6 +189,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local dst = jecs.archetype_traverse_remove(world, component, src)
|
local dst = jecs.archetype_traverse_remove(world, component, src)
|
||||||
|
|
||||||
if not archetypes[dst.id] then
|
if not archetypes[dst.id] then
|
||||||
|
last_old_archetype = nil
|
||||||
callback_removed(entity)
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -180,10 +202,13 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
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
|
if callback_added == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if last_old_archetype == oldarchetype and terms_lookup[id] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if not wc and id ~= term then
|
if not wc and id ~= term then
|
||||||
return
|
return
|
||||||
|
|
@ -192,10 +217,8 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
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
|
||||||
|
|
||||||
if archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
|
||||||
print("???")
|
|
||||||
end
|
|
||||||
if not archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
if not archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||||
|
last_old_archetype = oldarchetype
|
||||||
callback_added(entity)
|
callback_added(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -209,6 +232,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
|
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
if archetypes[r.archetype.id] then
|
if archetypes[r.archetype.id] then
|
||||||
|
last_old_archetype = nil
|
||||||
callback_removed(entity)
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -230,7 +254,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local rel = jecs.ECS_PAIR_FIRST(term)
|
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
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
|
if callback_removed == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -249,6 +273,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
-- removed a component even though the entity is not
|
-- removed a component even though the entity is not
|
||||||
-- apart of the monitor
|
-- apart of the monitor
|
||||||
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||||
|
last_old_archetype = nil
|
||||||
callback_removed(entity)
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -262,15 +287,20 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
if not wc and id ~= term then
|
if not wc and id ~= term then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if last_old_archetype == archetype and terms_lookup[id] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
|
||||||
if archetypes[dst.id] then
|
if archetypes[dst.id] then
|
||||||
|
last_old_archetype = archetype
|
||||||
callback_added(entity)
|
callback_added(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ type function ecs_id_t(first: type, second: type)
|
||||||
return p
|
return p
|
||||||
end
|
end
|
||||||
|
|
||||||
export type Entity<T = nil> = { __T: T }
|
export type Entity<T = any> = { __T: T }
|
||||||
export type Id<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 Pair<First=any, Second=any> = ecs_pair_t<Entity<First>, Entity<Second>>
|
||||||
export type Component<T=any> = { __T: T }
|
export type Component<T=any> = { __T: T }
|
||||||
|
|
|
||||||
|
|
@ -222,10 +222,28 @@ TEST("addons/ob::observer", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
FOCUS()
|
|
||||||
TEST("addons/ob::monitor", function()
|
TEST("addons/ob::monitor", function()
|
||||||
local world = jecs.world()
|
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
|
do CASE [[should not invoke monitor.added callback unless it wasn't apart
|
||||||
of the monitor]]
|
of the monitor]]
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
|
@ -490,6 +508,261 @@ TEST("addons/ob::monitor", function()
|
||||||
world:set(e, A, false)
|
world:set(e, A, false)
|
||||||
CHECK(count == 4)
|
CHECK(count == 4)
|
||||||
end
|
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)
|
end)
|
||||||
|
|
||||||
return FINISH()
|
return FINISH()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue