mirror of
https://github.com/Ukendio/jecs.git
synced 2026-03-18 00:44:32 +00:00
Add query:fini and query:archetypes(override) and changes to OB
This commit is contained in:
parent
d4a7f1d86c
commit
30597ed389
6 changed files with 530 additions and 235 deletions
|
|
@ -47,6 +47,8 @@ end
|
|||
-- local tgt = tonumber(tokens[2]) :: jecs.Entity
|
||||
|
||||
-- rel = ecs_ensure_entity(world, rel)
|
||||
--
|
||||
-- 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
|
||||
-- tgt = ecs_ensure_entity(world, tgt)
|
||||
|
||||
-- return jecs.pair(rel, tgt)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,17 @@ local jecs = require("@jecs")
|
|||
|
||||
type World = jecs.World
|
||||
|
||||
type Id<T=any> = jecs.Id<T>
|
||||
type Id<T=any> = jecs.Id<any>
|
||||
|
||||
local function duplicate(query: jecs.Query<...any>): jecs.CachedQuery<...any>
|
||||
local world = (query :: jecs.Query<any> & { world: World }).world
|
||||
local dup = world:query()
|
||||
dup.filter_with = table.clone(query.filter_with)
|
||||
if query.filter_without then
|
||||
dup.filter_without = query.filter_without
|
||||
end
|
||||
return dup:cached()
|
||||
end
|
||||
|
||||
export type Observer = {
|
||||
disconnect: () -> (),
|
||||
|
|
@ -20,7 +29,7 @@ local function observers_new(
|
|||
query: jecs.Query<...any>,
|
||||
callback: (jecs.Entity) -> ()
|
||||
): Observer
|
||||
local cachedquery = query:cached()
|
||||
local cachedquery = duplicate(query)
|
||||
|
||||
local world = (cachedquery :: jecs.Query<any> & { world: World }).world
|
||||
callback = callback
|
||||
|
|
@ -134,7 +143,7 @@ local function observers_new(
|
|||
end
|
||||
|
||||
local function monitors_new(query: jecs.Query<...any>): Monitor
|
||||
local cachedquery = query:cached()
|
||||
local cachedquery = duplicate(query)
|
||||
|
||||
local world = (cachedquery :: jecs.Query<...any> & { world: World }).world :: jecs.World
|
||||
|
||||
|
|
@ -151,12 +160,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
local callback_added: ((jecs.Entity) -> ())?
|
||||
local callback_removed: ((jecs.Entity) -> ())?
|
||||
|
||||
-- NOTE(marcus): Track the last (entity, old archetype) pair we processed to detect bulk operations.
|
||||
-- During bulk_insert from ROOT_ARCHETYPE, the entity is moved to the target archetype first,
|
||||
-- then all on_add callbacks fire sequentially with the same oldarchetype for the same entity.
|
||||
-- We track both entity and old archetype to distinguish between:
|
||||
-- 1. Same entity, same old archetype (bulk operation - skip)
|
||||
-- 2. Different entity, same old archetype (separate operation - don't skip)
|
||||
local last_old_archetype: jecs.Archetype? = nil
|
||||
local last_entity: jecs.Entity? = nil
|
||||
|
||||
|
|
@ -173,10 +176,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
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
|
||||
-- NOTE(marcus): Skip if we've seen this exact (entity, old archetype) combination before
|
||||
-- AND this component is in the query's terms. This detects bulk operations where
|
||||
-- the same entity transitions with multiple components, while allowing different
|
||||
-- entities to trigger even if they share the same old archetype.
|
||||
if last_old_archetype == oldarchetype and last_entity == entity and terms_lookup[id] then
|
||||
return
|
||||
end
|
||||
|
|
@ -185,52 +184,31 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
last_entity = entity
|
||||
callback_added(entity)
|
||||
else
|
||||
-- NOTE(marcus): Clear tracking when we see a different transition pattern
|
||||
last_old_archetype = nil
|
||||
last_entity = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Track which entity we've already processed for deletion to avoid duplicate callbacks
|
||||
-- during bulk deletion where multiple components are removed with delete=true
|
||||
local last_deleted_entity: jecs.Entity? = nil
|
||||
|
||||
local function removed(entity: jecs.Entity, component: jecs.Component, delete:boolean?)
|
||||
if callback_removed == nil then
|
||||
return
|
||||
end
|
||||
|
||||
if delete then
|
||||
-- Deletion is a bulk removal - all components are removed with delete=true
|
||||
-- We should only trigger the callback once per entity, not once per component
|
||||
if last_deleted_entity == entity then
|
||||
return
|
||||
end
|
||||
|
||||
local r = jecs.record(world, entity)
|
||||
if r and r.archetype and archetypes[r.archetype.id] then
|
||||
-- Entity was in the monitor before deletion
|
||||
last_deleted_entity = entity
|
||||
-- Clear tracking when entity is deleted
|
||||
last_old_archetype = nil
|
||||
last_entity = nil
|
||||
callback_removed(entity)
|
||||
end
|
||||
return
|
||||
end
|
||||
if not r then return end
|
||||
|
||||
local r = jecs.record(world, entity)
|
||||
local src = r.archetype
|
||||
if not src then return end
|
||||
|
||||
local dst = jecs.archetype_traverse_remove(world, component, src)
|
||||
if not archetypes[src.id] then return end
|
||||
|
||||
if not archetypes[dst.id] then
|
||||
-- Clear tracking when entity leaves the monitor to allow re-entry
|
||||
last_old_archetype = nil
|
||||
last_entity = nil
|
||||
last_deleted_entity = nil
|
||||
callback_removed(entity)
|
||||
if last_entity == entity and last_old_archetype == src then
|
||||
return
|
||||
end
|
||||
|
||||
last_entity = entity
|
||||
last_old_archetype = src
|
||||
callback_removed(entity)
|
||||
end
|
||||
|
||||
local cleanup = {}
|
||||
|
|
@ -254,8 +232,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
entity_index, entity :: any) :: jecs.Record
|
||||
|
||||
if not archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||
-- NOTE(marcus): Skip if we've seen this exact (entity, old archetype) combination before
|
||||
-- AND this component is in the query's terms.
|
||||
if last_old_archetype == oldarchetype and last_entity == entity and terms_lookup[id] then
|
||||
return
|
||||
end
|
||||
|
|
@ -264,7 +240,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
last_entity = entity
|
||||
callback_added(entity)
|
||||
else
|
||||
-- Clear tracking when we see a different transition pattern
|
||||
last_old_archetype = nil
|
||||
last_entity = nil
|
||||
end
|
||||
|
|
@ -314,11 +289,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
return
|
||||
end
|
||||
|
||||
-- NOTE(marcus): This check that it was presently in
|
||||
-- the query but distinctively leaves is important as
|
||||
-- sometimes it could be too eager to report that it
|
||||
-- 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)
|
||||
|
|
@ -364,10 +334,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
return
|
||||
end
|
||||
|
||||
-- NOTE(marcus): Sometimes OnAdd listeners for excluded
|
||||
-- terms are too eager to report that it is leaving the
|
||||
-- monitor even though the entity is not apart of it
|
||||
-- already.
|
||||
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||
callback_removed(entity)
|
||||
end
|
||||
|
|
@ -386,11 +352,6 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
|||
end
|
||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||
|
||||
-- NOTE(marcus): Inversely with the opposite operation, you
|
||||
-- only need to check if it is going to enter the query once
|
||||
-- because world:remove already stipulates that it is
|
||||
-- idempotent so that this hook won't be invoked if it is
|
||||
-- was already removed.
|
||||
if archetypes[dst.id] then
|
||||
callback_added(entity)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -80,15 +80,17 @@ export type Id2<First, Second=nil> = ecs_id_t<First, Second>
|
|||
|
||||
export type Item<T...> = (self: Query<T...>) -> (Entity, T...)
|
||||
export type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
||||
export type CachedIter<T...> = (query: CachedQuery<T...>) -> () -> (Entity, T...)
|
||||
export type Cached_Query_Iter<T...> = (query: Cached_Query<T...>) -> () -> (Entity, T...)
|
||||
|
||||
type TypePack<T...> = (T...) -> never
|
||||
|
||||
export type CachedQuery<T...> = typeof(setmetatable(
|
||||
export type Cached_Query<T...> = typeof(setmetatable(
|
||||
{} :: {
|
||||
iter: CachedIter<T...>,
|
||||
archetypes: (CachedQuery<T...>) -> { Archetype },
|
||||
has: (CachedQuery<T...>, Entity) -> boolean,
|
||||
iter: Cached_Query_Iter<T...>,
|
||||
archetypes: (Cached_Query<T...>) -> { Archetype },
|
||||
has: (Cached_Query<T...>, Entity) -> boolean,
|
||||
fini: (Cached_Query<T...>) -> (),
|
||||
|
||||
ids: { Id<any> },
|
||||
filter_with: { Id<any> }?,
|
||||
filter_without: { Id<any> }?,
|
||||
|
|
@ -96,7 +98,7 @@ export type CachedQuery<T...> = typeof(setmetatable(
|
|||
-- world: World
|
||||
},
|
||||
{} :: {
|
||||
__iter: CachedIter<T...>,
|
||||
__iter: Cached_Query_Iter<T...>,
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -106,7 +108,7 @@ export type Query<T...> = typeof(setmetatable(
|
|||
with: ((Query<T...>, ...Component) -> Query<T...>),
|
||||
without: ((Query<T...>, ...Component) -> Query<T...>),
|
||||
archetypes: (Query<T...>) -> { Archetype },
|
||||
cached: (Query<T...>) -> CachedQuery<T...>,
|
||||
cached: (Query<T...>) -> Cached_Query<T...>,
|
||||
has: (Query<T...>, Entity) -> boolean,
|
||||
ids: { Id<any> },
|
||||
filter_with: { Id<any> }?,
|
||||
|
|
@ -1240,9 +1242,9 @@ end
|
|||
|
||||
local function NOOP() end
|
||||
|
||||
local function query_archetypes(query: query)
|
||||
local function query_archetypes(query: query, override: boolean?)
|
||||
local compatible_archetypes = query.compatible_archetypes
|
||||
if not compatible_archetypes then
|
||||
if not compatible_archetypes or override then
|
||||
compatible_archetypes = {}
|
||||
query.compatible_archetypes = compatible_archetypes
|
||||
|
||||
|
|
@ -1755,7 +1757,7 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], col6[row], col7[row]
|
||||
end
|
||||
else
|
||||
else
|
||||
local output = {}
|
||||
local ids_len = #ids_u
|
||||
function world_query_iter_next(): any
|
||||
|
|
@ -1817,10 +1819,10 @@ else
|
|||
|
||||
return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], col6[row], col7[row], unpack(output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
query.next = world_query_iter_next
|
||||
return world_query_iter_next
|
||||
query.next = world_query_iter_next
|
||||
return world_query_iter_next
|
||||
end
|
||||
|
||||
local function query_iter(query): () -> (number, ...any)
|
||||
|
|
@ -2416,7 +2418,7 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
local eindex = world.entity_index :: entityindex
|
||||
|
||||
local function cached_query_has(entity): boolean
|
||||
local function cached_query_has(_, entity): boolean
|
||||
local r = entity_index_try_get_fast(eindex, entity)
|
||||
if not r then
|
||||
return false
|
||||
|
|
@ -2430,11 +2432,30 @@ local function query_cached(query: QueryInner)
|
|||
return archetypes_map[entityarchetype.id] ~= nil
|
||||
end
|
||||
|
||||
local function cached_query_fini()
|
||||
local create_pos = table.find(query_cache_on_create, observer_for_create)
|
||||
if create_pos then
|
||||
table.remove(query_cache_on_create, create_pos)
|
||||
end
|
||||
|
||||
local delete_pos = table.find(query_cache_on_delete, observer_for_delete)
|
||||
if delete_pos then
|
||||
table.remove(query_cache_on_delete, delete_pos)
|
||||
end
|
||||
|
||||
compatible_archetypes_u = nil
|
||||
-- NOTE(marcus): Maybe we have to be even more aggressive with cleaning
|
||||
-- things up to ensure it the memory is free`d. But since most of it are
|
||||
-- references we cannot be sure that someone is holding onto them making
|
||||
-- it implausible to free the memory anyways
|
||||
end
|
||||
|
||||
local cached_query = query :: any
|
||||
cached_query.archetypes = query_archetypes
|
||||
cached_query.__iter = cached_query_iter
|
||||
cached_query.iter = cached_query_iter
|
||||
cached_query.has = cached_query_has
|
||||
cached_query.fini = cached_query_fini
|
||||
setmetatable(cached_query, cached_query)
|
||||
return cached_query
|
||||
end
|
||||
|
|
|
|||
315
test/ob.luau
315
test/ob.luau
|
|
@ -3,7 +3,7 @@ local testkit = require("@modules/testkit")
|
|||
local test = testkit.test()
|
||||
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
||||
local FOCUS = test.FOCUS
|
||||
local ob = require("@modules/ob")
|
||||
local ob = require("@modules/OB/module")
|
||||
|
||||
TEST("modules/ob::observer", function()
|
||||
local world = jecs.world()
|
||||
|
|
@ -292,6 +292,36 @@ end)
|
|||
TEST("modules/ob::monitor", function()
|
||||
local world = jecs.world()
|
||||
|
||||
do CASE "same query can be used for multiple monitors without error (monitors use own cached proxy)"
|
||||
local A = world:component()
|
||||
local q = world:query(A)
|
||||
local c1, c2 = 0, 0
|
||||
ob.monitor(q).added(function() c1 += 1 end)
|
||||
ob.monitor(q).added(function() c2 += 1 end)
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(c1 == 1)
|
||||
CHECK(c2 == 1)
|
||||
end
|
||||
|
||||
do CASE [[Monitor should only report removed entities if it was previously
|
||||
apart of it]]
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local count = 0
|
||||
ob.monitor(world:query(A, C)).removed(function()
|
||||
count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, {A, B}, {0,0})
|
||||
CHECK(count==0)
|
||||
world:remove(e, A)
|
||||
CHECK(count==0)
|
||||
end
|
||||
|
||||
do CASE [[should not invoke monitor.added callback multiple times in a bulk_move
|
||||
]]
|
||||
local A = world:component()
|
||||
|
|
@ -609,6 +639,74 @@ TEST("modules/ob::monitor", function()
|
|||
CHECK(r==2)
|
||||
end
|
||||
|
||||
-- Without-clause pair onremoved path: guards must stay or these fail.
|
||||
-- Removing "if archetypes[dst.id]" would make this fail: we must only report added when dst matches the query.
|
||||
do CASE "without(pair): removing one excluded pair only fires added when entity actually enters (dst matches query)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local added_count = 0
|
||||
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, e1), jecs.pair(B, e2)))
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(B, e1))
|
||||
world:add(e, jecs.pair(B, e2))
|
||||
world:add(e, A)
|
||||
CHECK(added_count == 0)
|
||||
|
||||
world:remove(e, jecs.pair(B, e1))
|
||||
CHECK(added_count == 0)
|
||||
|
||||
world:remove(e, jecs.pair(B, e2))
|
||||
CHECK(added_count == 1)
|
||||
end
|
||||
|
||||
-- Removing "if delete then return" would make this fail: must not report added when removal is due to delete.
|
||||
do CASE "without(pair): must not report added when pair is removed due to entity delete"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
|
||||
local added_count = 0
|
||||
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, e1)))
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(added_count == 1)
|
||||
world:add(e, jecs.pair(B, e1))
|
||||
world:delete(e)
|
||||
CHECK(added_count == 1)
|
||||
end
|
||||
|
||||
-- Removing "if not wc and id ~= term then return" would make this fail: must only react to removal of the excluded term, not other pairs.
|
||||
do CASE "without(pair): must not report added when a different pair (same relation) is removed"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local added_count = 0
|
||||
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, e1)))
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, A)
|
||||
CHECK(added_count == 1)
|
||||
world:add(e, jecs.pair(B, e2))
|
||||
world:remove(e, jecs.pair(B, e2))
|
||||
CHECK(added_count == 1)
|
||||
end
|
||||
|
||||
do CASE "Should enter monitor at query:without(pair(R, *)) when adding pair(R, t1)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
|
@ -792,7 +890,206 @@ TEST("modules/ob::monitor", function()
|
|||
CHECK(c == 2)
|
||||
end
|
||||
|
||||
if true then return end
|
||||
do CASE "entity about to leave (remove queried pair) reports to removed exactly once"
|
||||
local A = world:component()
|
||||
local e1 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1)))
|
||||
local removed_count = 0
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(removed_count == 1)
|
||||
end
|
||||
|
||||
do CASE "removing non-queried pair (same relation, other target) must not report to removed"
|
||||
local A = world:component()
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1)))
|
||||
local removed_count = 0
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
world:remove(e, jecs.pair(A, e2))
|
||||
CHECK(removed_count == 0)
|
||||
end
|
||||
|
||||
do CASE "about-to-leave reports to removed exactly once per exit; second removal (already out) must not report"
|
||||
local A = 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 removed_count = 0
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(removed_count == 1)
|
||||
world:remove(e, jecs.pair(A, e2))
|
||||
CHECK(removed_count == 1)
|
||||
end
|
||||
|
||||
do CASE "pair term leave then re-enter gives one removed (about to leave) then one added (has entered)"
|
||||
local A = world:component()
|
||||
local e1 = world:entity()
|
||||
|
||||
local monitor = ob.monitor(world:query(jecs.pair(A, e1)))
|
||||
local added_count = 0
|
||||
local removed_count = 0
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(added_count == 2)
|
||||
CHECK(removed_count == 1)
|
||||
end
|
||||
|
||||
do CASE "bulk_insert causes added exactly once (entity has entered monitor)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local added_count = 0
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { A, B, C }, { 1, 2, 3 })
|
||||
CHECK(added_count == 1)
|
||||
end
|
||||
|
||||
do CASE "bulk_insert of multiple entities each reports to added once (each has entered)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local added_count = 0
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
local e3 = world:entity()
|
||||
jecs.bulk_insert(world, e1, { A, B, C }, { 1, 2, 3 })
|
||||
jecs.bulk_insert(world, e2, { A, B, C }, { 4, 5, 6 })
|
||||
jecs.bulk_insert(world, e3, { A, B, C }, { 7, 8, 9 })
|
||||
CHECK(added_count == 3)
|
||||
end
|
||||
|
||||
do CASE "bulk_remove causes removed exactly once (entity about to leave monitor)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local added_count = 0
|
||||
local removed_count = 0
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { A, B, C }, { 1, 2, 3 })
|
||||
CHECK(added_count == 1)
|
||||
CHECK(removed_count == 0)
|
||||
|
||||
jecs.bulk_remove(world, e, { A, B, C })
|
||||
CHECK(removed_count == 1)
|
||||
CHECK(not world:has(e, A))
|
||||
CHECK(not world:has(e, B))
|
||||
CHECK(not world:has(e, C))
|
||||
end
|
||||
|
||||
do CASE "bulk_remove of multiple entities each reports to removed once (each about to leave)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local removed_count = 0
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
jecs.bulk_insert(world, e1, { A, B, C }, { 1, 2, 3 })
|
||||
jecs.bulk_insert(world, e2, { A, B, C }, { 4, 5, 6 })
|
||||
|
||||
jecs.bulk_remove(world, e1, { A, B, C })
|
||||
CHECK(removed_count == 1)
|
||||
jecs.bulk_remove(world, e2, { A, B, C })
|
||||
CHECK(removed_count == 2)
|
||||
end
|
||||
|
||||
do CASE "bulk_remove only of query terms still reports to removed exactly once (entity about to leave)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
local D = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local removed_count = 0
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { A, B, C, D }, { 1, 2, 3, 4 })
|
||||
jecs.bulk_remove(world, e, { A, B, C })
|
||||
CHECK(removed_count == 1)
|
||||
CHECK(world:has(e, D))
|
||||
end
|
||||
|
||||
do CASE "bulk_insert then bulk_remove gives added once (entered) and removed once (about to leave)"
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local monitor = ob.monitor(world:query(A, B, C))
|
||||
local added_count = 0
|
||||
local removed_count = 0
|
||||
monitor.added(function()
|
||||
added_count += 1
|
||||
end)
|
||||
monitor.removed(function()
|
||||
removed_count += 1
|
||||
end)
|
||||
|
||||
local e = world:entity()
|
||||
jecs.bulk_insert(world, e, { A, B, C }, { 1, 2, 3 })
|
||||
jecs.bulk_remove(world, e, { A, B, C })
|
||||
CHECK(added_count == 1)
|
||||
CHECK(removed_count == 1)
|
||||
end
|
||||
|
||||
do CASE "monitor with pair should handle remove and re-add correctly"
|
||||
local A = world:component()
|
||||
|
|
@ -888,7 +1185,6 @@ TEST("modules/ob::monitor", function()
|
|||
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
|
||||
|
|
@ -898,15 +1194,15 @@ TEST("modules/ob::monitor", function()
|
|||
|
||||
local e = world:entity()
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 1)
|
||||
CHECK(c == 0)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 2)
|
||||
CHECK(c == 0)
|
||||
world:remove(e, jecs.pair(A, e2))
|
||||
world:add(e, jecs.pair(A, e1))
|
||||
CHECK(c == 3)
|
||||
CHECK(c == 0)
|
||||
world:add(e, jecs.pair(A, e2))
|
||||
CHECK(c == 4)
|
||||
CHECK(c == 1)
|
||||
end
|
||||
|
||||
do CASE "monitor with pair should handle set operations correctly"
|
||||
|
|
@ -929,12 +1225,13 @@ TEST("modules/ob::monitor", function()
|
|||
CHECK(c == 1)
|
||||
world:set(e, jecs.pair(A, e1), false)
|
||||
CHECK(c == 1)
|
||||
CHECK(r == 0)
|
||||
world:set(e, jecs.pair(A, e1), true)
|
||||
CHECK(c == 2)
|
||||
CHECK(c == 1)
|
||||
world:remove(e, jecs.pair(A, e1))
|
||||
CHECK(r == 1)
|
||||
world:set(e, jecs.pair(A, e1), true)
|
||||
CHECK(c == 3)
|
||||
CHECK(c == 2)
|
||||
end
|
||||
|
||||
do CASE "monitor with pair query should handle non-matching pairs"
|
||||
|
|
|
|||
|
|
@ -2147,6 +2147,20 @@ TEST("world:query()", function()
|
|||
end
|
||||
end
|
||||
|
||||
do CASE "query:archetypes(override) should create new archetypes list"
|
||||
local world = jecs.world()
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
|
||||
local q = world:query(A, B)
|
||||
local e = world:entity()
|
||||
world:set(e, A, false)
|
||||
world:set(e, B, true)
|
||||
|
||||
CHECK(q:archetypes() == q:archetypes())
|
||||
CHECK(q:archetypes() ~= q:archetypes(true))
|
||||
end
|
||||
|
||||
do CASE "cached"
|
||||
local world = jecs.world()
|
||||
local Foo = world:component()
|
||||
|
|
|
|||
Loading…
Reference in a new issue