diff --git a/src/init.luau b/src/init.luau index 3b6523a..0071416 100644 --- a/src/init.luau +++ b/src/init.luau @@ -455,7 +455,10 @@ local function add(world: World, entityId: i53, componentId: i53) local record = entityIndex.sparse[entityId] local from = record.archetype 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) else if #to.types > 0 then @@ -793,6 +796,10 @@ local function preparedQuery(compatibleArchetypes: { Archetype }, end end + if #compatibleArchetypes == 0 then + return EmptyQuery + end + return self end @@ -805,7 +812,7 @@ local function preparedQuery(compatibleArchetypes: { Archetype }, return queryNext end - local function replace(_, fn) + local function replace(_, fn: any) for i, archetype in compatibleArchetypes do local tr = indices[i] local columns = archetype.columns diff --git a/tests/world.luau b/tests/world.luau index bb33fa7..044776b 100644 --- a/tests/world.luau +++ b/tests/world.luau @@ -512,6 +512,218 @@ TEST("world", function() local query = world:query(B) CHECK(query == query:without()) 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 + removedComponents = {}, -- Vec + 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) FINISH()