diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e878b5..6fd615a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,16 @@ The format is based on [Keep a Changelog][kac], and this project adheres to ## [Unreleased] +### Changed + +- Iterator now goes backwards instead to prevent common cases of iterator invalidation + ## [0.2.1] - 2024-07-06 ### Added -* Added `jecs.Component` built-in component which will be added to ids created with `world:component()`. - * used to find every component id with `query(jecs.Component) +- Added `jecs.Component` built-in component which will be added to ids created with `world:component()`. + - Used to find every component id with `query(jecs.Component) ## [0.2.0] - 2024-07-03 diff --git a/src/init.luau b/src/init.luau index 797172e..d89e406 100644 --- a/src/init.luau +++ b/src/init.luau @@ -184,13 +184,9 @@ local function nextEntityId(entityIndex: EntityIndex, index: i24): i53 return id end -local function transitionArchetype( - entityIndex: EntityIndex, - to: Archetype, - destinationRow: i24, - from: Archetype, - sourceRow: i24 -) +local function transitionArchetype(entityIndex: EntityIndex, to: Archetype, + destinationRow: i24, from: Archetype, sourceRow: i24) + local columns = from.columns local sourceEntities = from.entities local destinationEntities = to.entities @@ -690,11 +686,11 @@ local function preparedQuery(compatibleArchetypes: { Archetype }, local queryOutput = {} - local i = 1 + local entities = archetype.entities + local i = #entities local function queryNext(): ...any - local entityId = archetype.entities[i] - + local entityId = entities[i] while entityId == nil do lastArchetype += 1 archetype = compatibleArchetypes[lastArchetype] @@ -703,12 +699,13 @@ local function preparedQuery(compatibleArchetypes: { Archetype }, return end - i = 1 - entityId = archetype.entities[1] + entities = archetype.entities + i = #entities + entityId = entities[i] end local row = i - i+=1 + i-=1 local columns = archetype.columns local tr = indices[lastArchetype] @@ -788,9 +785,10 @@ local function preparedQuery(compatibleArchetypes: { Archetype }, local it = { __iter = function() - i = 1 lastArchetype = 1 archetype = compatibleArchetypes[1] + entities = archetype.entities + i = #entities return queryNext end, diff --git a/tests/world.luau b/tests/world.luau index 04873f6..1aaa04c 100644 --- a/tests/world.luau +++ b/tests/world.luau @@ -25,30 +25,23 @@ local N = 10 type World = jecs.WorldShim TEST("world", function() - do CASE("should be iterable") + do CASE("should find every component id") local world = jecs.World.new() :: World local A = world:component() local B = world:component() - local eA = world:entity() - world:set(eA, A, true) - local eB = world:entity() - world:set(eB, B, true) - local eAB = world:entity() - world:set(eAB, A, true) - world:set(eAB, B, true) + world:entity() + world:entity() + world:entity() - for id, data in world do - if id == eA then - CHECK(data[A] == true) - CHECK(data[B] == nil) - elseif id == eB then - CHECK(data[A] == nil) - CHECK(data[B] == true) - elseif id == eAB then - CHECK(data[A] == true) - CHECK(data[B] == true) + local count = 0 + for componentId in world:query(jecs.Component) do + if componentId ~= A and componentId ~= B then + error("found entity") end + count += 1 end + + CHECK(count == 2) end do CASE("should remove its components") @@ -395,6 +388,70 @@ TEST("world", function() end CHECK(count == 2) end + + do CASE "should be able to add/remove matching entity during iteration" + local world = jecs.World.new() + local Name = world:component() + for i = 1, 5 do + local e = world:entity() + world:set(e, Name, tostring(e)) + end + local count = 0 + for id, name in world:query(Name) do + count += 1 + CHECK(id == tonumber(name)) + + world:remove(id, Name) + local e = world:entity() + world:set(e, Name, tostring(e)) + end + CHECK(count == 5) + end + + do CASE "should allow adding a matching entity during iteration" + local world = jecs.World.new() + local A = world:component() + local B = world:component() + + local e1 = world:entity() + local e2 = world:entity() + world:add(e1, A) + world:add(e2, A) + world:add(e2, B) + + local count = 0 + for id in world:query(A) do + local e = world:entity() + world:add(e, A) + world:add(e, B) + count += 1 + end + + CHECK(count == 3) + end + + + do CASE "should not iterate same entity when adding component" + local world = jecs.World.new() + local A = world:component() + local B = world:component() + + local e1 = world:entity() + local e2 = world:entity() + world:add(e1, A) + world:add(e2, A) + world:add(e2, B) + + local count = 0 + for id in world:query(A) do + world:add(id, B) + + count += 1 + end + + print(count) + CHECK(count == 2) + end end) FINISH()