diff --git a/src/init.luau b/src/init.luau index 1fde9ee..575a9f2 100644 --- a/src/init.luau +++ b/src/init.luau @@ -611,13 +611,7 @@ end local function archetype_ensure_edge(world, edges, id): GraphEdge local edge = edges[id] if not edge then - edge = { - from = nil :: any, - to = nil :: any, - id = id, - prev = nil, - next = nil, - } :: GraphEdge + edge = {} :: GraphEdge edges[id] = edge end @@ -1095,9 +1089,6 @@ local EMPTY_QUERY = { iter = function() return NOOP end, - drain = ARM, - next = NOOP, - replace = NOOP, with = ARM, without = ARM, archetypes = function() @@ -1107,16 +1098,9 @@ local EMPTY_QUERY = { setmetatable(EMPTY_QUERY, EMPTY_QUERY) -local function query_iter(query) +local function query_iter_init(query) local world_query_iter_next - if query.should_drain then - world_query_iter_next = query.iter_next - if world_query_iter_next then - return world_query_iter_next - end - end - local compatible_archetypes = query.compatible_archetypes local lastArchetype = 1 local archetype = compatible_archetypes[1] @@ -1363,19 +1347,16 @@ local function query_iter(query) end end - query.iter_next = world_query_iter_next + query.next = world_query_iter_next return world_query_iter_next end -local function query_drain(query) - local query_iter_next = query_iter(query) - query.next = query_iter_next - query.should_drain = true - return query -end - -local function query_next(query) - error("Did you forget to call drain?") +local function query_iter(query) + local query_next = query.next + if not query_next then + query_next = query_iter_init(query) + end + return query_next end local function query_without(query, ...) @@ -1440,55 +1421,6 @@ local function query_with(query, ...) return query end -local function columns_replace_values(row, columns, ...) - for i, column in columns do - column[row] = select(i, ...) - end -end - -local function query_replace(query, fn: (...any) -> ...any) - local compatible_archetypes = query.compatible_archetypes - local ids = query.ids - local A, B, C, D, E = unpack(ids, 1, 5) - local queryOutput = {} - for i, archetype in compatible_archetypes do - local columns = archetype.columns - local records = archetype.records - for row in archetype.entities do - if not B then - local va = columns[records[A].column] - local pa = fn(va[row]) - - va[row] = pa - elseif not C then - local va = columns[records[A].column] - local vb = columns[records[B].column] - - va[row], vb[row] = fn(va[row], vb[row]) - elseif not D then - local va = columns[records[A].column] - local vb = columns[records[B].column] - local vc = columns[records[C].column] - - va[row], vb[row], vc[row] = fn(va[row], vb[row], vc[row]) - elseif not E then - local va = columns[records[A].column] - local vb = columns[records[B].column] - local vc = columns[records[C].column] - local vd = columns[records[D].column] - - va[row], vb[row], vc[row], vd[row] = fn(va[row], vb[row], vc[row], vd[row]) - else - for j, id in ids do - local tr = records[id] - queryOutput[j] = columns[tr.column][row] - end - columns_replace_values(row, columns, fn(unpack(queryOutput))) - end - end - end -end - -- Meant for directly iterating over archetypes to minimize -- function call overhead. Should not be used unless iterating over -- hundreds of thousands of entities in bulk. @@ -1499,13 +1431,10 @@ end local Query = {} Query.__index = Query Query.__iter = query_iter -Query.iter = query_iter +Query.iter = query_iter_init Query.without = query_without Query.with = query_with Query.archetypes = query_archetypes -Query.drain = query_drain -Query.next = query_next -Query.replace = query_replace local function world_query(world: World, ...) local compatible_archetypes = {} diff --git a/test/tests.luau b/test/tests.luau index c708a6e..88bdcf4 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -328,7 +328,7 @@ TEST("world:query()", function() world:set(eAB, B, true) -- Should drain the iterator - local q = world:query(A):drain() + local q = world:query(A) local i = 0 local j = 0 @@ -403,9 +403,9 @@ TEST("world:query()", function() world:set(eAB, A, true) world:set(eAB, B, true) - local q = world:query(A):drain() + local it = world:query(A):iter() - local e, data = q.next() + local e, data = it() while e do if e == eA then CHECK(data) @@ -415,7 +415,7 @@ TEST("world:query()", function() CHECK(false) end - e, data = q.next() + e, data = it() end CHECK(true) end @@ -689,9 +689,10 @@ TEST("world:query()", function() world:add(e1, A) local query = world:query(B) - CHECK(query:next() == nil) - CHECK(query:replace() == nil) CHECK(query:without() == query) + CHECK(query:with() == query) + -- They always return the same EMPTY_LIST + CHECK(query:archetypes() == world:query(B):archetypes()) end do @@ -711,26 +712,6 @@ TEST("world:query()", function() end end - do CASE "replace" - local world = jecs.World.new() - local A = world:component() - local B = world:component() - local C = world:component() - - local e = world:entity() - world:set(e, A, 1) - world:set(e, B, true) - world:set(e, C, "hello ") - - world:query(A, B, C):replace(function(a, b, c) - return a * 2, not b, c.."world" - end) - - CHECK(world:get(e, A) == 2) - CHECK(world:get(e, B) == false) - CHECK(world:get(e, C) == "hello world") - end - do CASE "without" do -- REGRESSION TEST @@ -1104,9 +1085,9 @@ local function ChangeTracker(world, T: Entity): Tracker local function changes_added() added = true - local q = world:query(T):without(PreviousT):drain() + local it = world:query(T):without(PreviousT):iter() return function() - local id, data = q.next() + local id, data = it() if not id then return nil end @@ -1120,10 +1101,10 @@ local function ChangeTracker(world, T: Entity): Tracker end local function changes_changed() - local q = world:query(T, PreviousT):drain() + local it = world:query(T, PreviousT):iter() return function() - local id, new, old = q.next() + local id, new, old = it() while true do if not id then return nil @@ -1137,7 +1118,7 @@ local function ChangeTracker(world, T: Entity): Tracker break end - id, new, old = q.next() + id, new, old = it() end add[id] = new @@ -1149,9 +1130,9 @@ local function ChangeTracker(world, T: Entity): Tracker local function changes_removed() removed = true - local q = world:query(PreviousT):without(T):drain() + local it = world:query(PreviousT):without(T):iter() return function() - local id = q.next() + local id = it() if id then world:remove(id, PreviousT) end @@ -1705,4 +1686,37 @@ TEST("scheduler", function() CHECK(systems[1].callback == camera) end end) + +TEST("repro", function() + do CASE "" + local world = world_new() + local reproEntity = world:component() + local components = { Cooldown = world:component() } + world:set(reproEntity, components.Cooldown, 2) + + local function updateCooldowns(dt: number) + local toRemove = {} + + for id, cooldown in world:query(components.Cooldown):iter() do + cooldown -= dt + + if cooldown <= 0 then + table.insert(toRemove, id) + print('removing') + -- world:remove(id, components.Cooldown) + else + world:set(id, components.Cooldown, cooldown) + end + end + + for _, id in toRemove do + world:remove(id, components.Cooldown) + CHECK(not world:get(id, components.Cooldown)) + end + end + + updateCooldowns(1.5) + updateCooldowns(1.5) + end +end) FINISH()