Add backwards iteration (#61)

* Iterate backwards

* Should test for removing as well

* Add test

* Add faíling test

* Add to changelog
This commit is contained in:
Marcus 2024-07-06 16:36:00 +02:00 committed by GitHub
parent 6559c56d47
commit c0e73273d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 93 additions and 34 deletions

View file

@ -10,12 +10,16 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
## [Unreleased] ## [Unreleased]
### Changed
- Iterator now goes backwards instead to prevent common cases of iterator invalidation
## [0.2.1] - 2024-07-06 ## [0.2.1] - 2024-07-06
### Added ### Added
* Added `jecs.Component` built-in component which will be added to ids created with `world: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) - Used to find every component id with `query(jecs.Component)
## [0.2.0] - 2024-07-03 ## [0.2.0] - 2024-07-03

View file

@ -184,13 +184,9 @@ local function nextEntityId(entityIndex: EntityIndex, index: i24): i53
return id return id
end end
local function transitionArchetype( local function transitionArchetype(entityIndex: EntityIndex, to: Archetype,
entityIndex: EntityIndex, destinationRow: i24, from: Archetype, sourceRow: i24)
to: Archetype,
destinationRow: i24,
from: Archetype,
sourceRow: i24
)
local columns = from.columns local columns = from.columns
local sourceEntities = from.entities local sourceEntities = from.entities
local destinationEntities = to.entities local destinationEntities = to.entities
@ -690,11 +686,11 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
local queryOutput = {} local queryOutput = {}
local i = 1 local entities = archetype.entities
local i = #entities
local function queryNext(): ...any local function queryNext(): ...any
local entityId = archetype.entities[i] local entityId = entities[i]
while entityId == nil do while entityId == nil do
lastArchetype += 1 lastArchetype += 1
archetype = compatibleArchetypes[lastArchetype] archetype = compatibleArchetypes[lastArchetype]
@ -703,12 +699,13 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
return return
end end
i = 1 entities = archetype.entities
entityId = archetype.entities[1] i = #entities
entityId = entities[i]
end end
local row = i local row = i
i+=1 i-=1
local columns = archetype.columns local columns = archetype.columns
local tr = indices[lastArchetype] local tr = indices[lastArchetype]
@ -788,9 +785,10 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
local it = { local it = {
__iter = function() __iter = function()
i = 1
lastArchetype = 1 lastArchetype = 1
archetype = compatibleArchetypes[1] archetype = compatibleArchetypes[1]
entities = archetype.entities
i = #entities
return queryNext return queryNext
end, end,

View file

@ -25,30 +25,23 @@ local N = 10
type World = jecs.WorldShim type World = jecs.WorldShim
TEST("world", function() TEST("world", function()
do CASE("should be iterable") do CASE("should find every component id")
local world = jecs.World.new() :: World local world = jecs.World.new() :: World
local A = world:component() local A = world:component()
local B = world:component() local B = world:component()
local eA = world:entity() world:entity()
world:set(eA, A, true) world:entity()
local eB = world:entity() world:entity()
world:set(eB, B, true)
local eAB = world:entity()
world:set(eAB, A, true)
world:set(eAB, B, true)
for id, data in world do local count = 0
if id == eA then for componentId in world:query(jecs.Component) do
CHECK(data[A] == true) if componentId ~= A and componentId ~= B then
CHECK(data[B] == nil) error("found entity")
elseif id == eB then
CHECK(data[A] == nil)
CHECK(data[B] == true)
elseif id == eAB then
CHECK(data[A] == true)
CHECK(data[B] == true)
end end
count += 1
end end
CHECK(count == 2)
end end
do CASE("should remove its components") do CASE("should remove its components")
@ -395,6 +388,70 @@ TEST("world", function()
end end
CHECK(count == 2) CHECK(count == 2)
end 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) end)
FINISH() FINISH()