Fixed regression in Without (#72)

* Return empty query when Without removes all archetypes

* Type replace callback as Any

* Add test with ChangeTracker
This commit is contained in:
Marcus 2024-07-13 04:32:31 +02:00 committed by GitHub
parent e486c11dc2
commit 45adef066d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 221 additions and 2 deletions

View file

@ -455,7 +455,10 @@ local function add(world: World, entityId: i53, componentId: i53)
local record = entityIndex.sparse[entityId] local record = entityIndex.sparse[entityId]
local from = record.archetype local from = record.archetype
local to = archetypeTraverseAdd(world, componentId, from) local to = archetypeTraverseAdd(world, componentId, from)
if from and not (from == world.ROOT_ARCHETYPE) then if from == to then
return
end
if from then
moveEntity(entityIndex, entityId, record, to) moveEntity(entityIndex, entityId, record, to)
else else
if #to.types > 0 then if #to.types > 0 then
@ -793,6 +796,10 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
end end
end end
if #compatibleArchetypes == 0 then
return EmptyQuery
end
return self return self
end end
@ -805,7 +812,7 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
return queryNext return queryNext
end end
local function replace(_, fn) local function replace(_, fn: any)
for i, archetype in compatibleArchetypes do for i, archetype in compatibleArchetypes do
local tr = indices[i] local tr = indices[i]
local columns = archetype.columns local columns = archetype.columns

View file

@ -512,6 +512,218 @@ TEST("world", function()
local query = world:query(B) local query = world:query(B)
CHECK(query == query:without()) CHECK(query == query:without())
end end
do CASE "should not find any entities"
local world = jecs.World.new()
local Hello = world:component()
local Bob = world:component()
local helloBob = world:entity()
world:add(helloBob, jecs.pair(Hello, Bob))
world:add(helloBob, Bob)
local withoutCount = 0
for _ in world
:query(jecs.pair(Hello, Bob))
:without(Bob)
do
withoutCount += 1
end
CHECK(withoutCount == 0)
end
do CASE "should allow change tracking"
local world = jecs.World.new()
local Previous = world:component()
local ChangeTracker = {}
ChangeTracker.__index = ChangeTracker
function ChangeTracker.new(component)
return setmetatable({
addedComponents = {}, -- Map<Entity, T>
removedComponents = {}, -- Vec<Entity>
component = component,
previous = jecs.pair(Previous, component),
isTrivial = nil,
}, ChangeTracker)
end
local function shallowEq(a, b)
for k, v in a do
if b[k] ~= v then
return false
end
end
return true
end
function ChangeTracker.track(tracker, world, fn)
local added = false
local removed = false
local addedComponents = tracker.addedComponents
local removedComponents = tracker.removedComponents
local component = tracker.component
local previous = tracker.previous
local isTrivial = tracker.isTrivial
local changes = {}
function changes:added()
added = true
local q = world:query(component):without(previous)
return function()
local id, data = q:next()
if not id then
return nil
end
if isTrivial == nil then
isTrivial = typeof(data) ~= "table"
tracker.isTrivial = isTrivial
end
if not isTrivial then
data = table.clone(data)
end
addedComponents[id] = data
return id, data
end
end
function changes:changed()
local q = world:query(component, previous)
return function()
local id, new, old = q:next()
while true do
if not id then
return nil
end
if not isTrivial then
if not shallowEq(new, old) then
break
end
elseif new ~= old then
break
end
id, new, old = q:next()
end
print("nil?", id)
addedComponents[id] = new
return id, old, new
end
end
function changes:removed()
removed = true
local q = world:query(tracker.previous):without(tracker.component)
return function()
local id = q:next()
if id then
table.insert(removedComponents, id)
end
return id
end
end
fn(changes)
if not added then
for _ in changes:added() do
end
end
if not removed then
for _ in changes:removed() do
end
end
for e, data in addedComponents do
world:set(e, previous, if isTrivial then data else table.clone(data))
end
for _, e in removedComponents do
world:remove(e, previous)
end
end
local Test = world:component()
local TestTracker = ChangeTracker.new(Test)
local e = world:entity()
world:set(e, Test, { foo = 11 })
for e, test in world:query(Test) do
test.foo = test.foo + 1
end
TestTracker:track(world, function(changes)
local added = 0
local changed = 0
local removed = 0
for e, data in changes.added() do
added+=1
end
for e, old, new in changes.changed() do
changed+=1
end
for e in changes.removed() do
removed+=1
end
CHECK(added == 1)
CHECK(changed == 0)
CHECK(removed == 0)
end)
for e, test in world:query(Test) do
test.foo = test.foo + 1
end
TestTracker:track(world, function(changes)
local added = 0
local changed = 0
local removed = 0
for e, data in changes.added() do
added+=1
end
for e, old, new in changes.changed() do
changed+=1
end
for e in changes.removed() do
removed+=1
end
CHECK(added == 0)
CHECK(changed == 1)
CHECK(removed == 0)
end)
world:remove(e, Test)
TestTracker:track(world, function(changes)
local added = 0
local changed = 0
local removed = 0
for e, data in changes.added() do
added+=1
end
for e, old, new in changes.changed() do
changed+=1
end
for e in changes.removed() do
removed+=1
end
CHECK(added == 0)
CHECK(changed == 0)
CHECK(removed == 1)
end)
end
end) end)
FINISH() FINISH()