mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
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:
parent
e486c11dc2
commit
45adef066d
2 changed files with 221 additions and 2 deletions
|
@ -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
|
||||||
|
|
212
tests/world.luau
212
tests/world.luau
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue