Skip to content

Commit

Permalink
Add clear (#59)
Browse files Browse the repository at this point in the history
* Rename files to luau

* Rename remaining files

* Update bench.project.json for luau files

* Stress test insertion

* Add clear

* Add a few guards

* Use next() in World.__iter

* Add tests
  • Loading branch information
Ukendio authored Jul 2, 2024
1 parent 0f67cb1 commit 5ff6a43
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 41 deletions.
28 changes: 24 additions & 4 deletions lib/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ local function ECS_ENTITY_T_LO(e: i53): i24
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
end

local function STRIP_GENERATION(e: i53): i24
return ECS_ENTITY_T_LO(e)
end

local function ECS_PAIR(pred: i53, obj: i53): i53
return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) :: i53
end
Expand Down Expand Up @@ -442,6 +446,24 @@ function World.delete(world: World, entityId: i53)

sparse[entityId] = nil :: any
dense[#dense] = nil :: any

end

function World.clear(world: World, entityId: i53)
--TODO: use sparse_get (stashed)
local record = world.entityIndex.sparse[entityId]
if not record then
return
end

local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
local archetype = record.archetype

if archetype == nil or archetype == ROOT_ARCHETYPE then
return
end

moveEntity(world.entityIndex, entityId, record, ROOT_ARCHETYPE)
end

local function ensureArchetype(world: World, types, prev): Archetype
Expand Down Expand Up @@ -823,7 +845,6 @@ type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -

function World.__iter(world: World): WorldIterator
local entityIndex = world.entityIndex
local dense = entityIndex.dense
local sparse = entityIndex.sparse
local last

Expand All @@ -832,14 +853,13 @@ function World.__iter(world: World): WorldIterator
local i = 0
local function iterator()
i+=1
local entityId = dense[i]
local entityId, record = next(sparse, last)
if not entityId then
return
end

last = lastEntity
last = entityId

local record = sparse[entityId]
local archetype = record.archetype
if not archetype then
-- Returns only the entity id as an entity without data should not return
Expand Down
124 changes: 87 additions & 37 deletions tests/world.luau
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
end
local N = 10

type World = jecs.WorldShim

TEST("world", function()
do
CASE("should be iterable")
local world = jecs.World.new()
do CASE("should be iterable")
local world = jecs.World.new() :: World
local A = world:component()
local B = world:component()
local eA = world:entity()
Expand All @@ -36,9 +37,7 @@ TEST("world", function()
world:set(eAB, A, true)
world:set(eAB, B, true)

local count = 0
for id, data in world do
count += 1
if id == eA then
CHECK(data[A] == true)
CHECK(data[B] == nil)
Expand All @@ -50,14 +49,79 @@ TEST("world", function()
CHECK(data[B] == true)
end
end
end

do CASE("should remove its components")
local world = jecs.World.new() :: World
local A = world:component()
local B = world:component()

local e = world:entity()

world:set(e, A, true)
world:set(e, B, true)

CHECK(world:get(e, A))
CHECK(world:get(e, B))

world:clear(e)
CHECK(world:get(e, A) == nil)
CHECK(world:get(e, B) == nil)

end

do CASE("iterator should not drain the query")
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)

local q = world:query(A)

local i = 0
local j = 0
for _ in q do
i+=1
end
for _ in q do
j+=1
end
CHECK(i == j)
end

-- components are registered in the entity index as well
-- so this test has to add 2 to account for them
CHECK(count == 3 + 2)
do CASE("should be able to get next results")
local world = jecs.World.new() :: World
world:component()
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)

local q = world:query(A)

local e, data = q:next()
while e do
CHECK(
if e == eA then data == true
elseif e == eAB then data == true
else false
)
e, data = q:next()
end
end

do
CASE("should query all matching entities")
do CASE("should query all matching entities")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
Expand All @@ -80,8 +144,7 @@ TEST("world", function()
CHECK(#entities == 0)
end

do
CASE("should query all matching entities when irrelevant component is removed")
do CASE("should query all matching entities when irrelevant component is removed")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
Expand Down Expand Up @@ -110,8 +173,7 @@ TEST("world", function()
CHECK(added == N)
end

do
CASE("should query all entities without B")
do CASE("should query all entities without B")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
Expand All @@ -135,8 +197,7 @@ TEST("world", function()
CHECK(#entities == 0)
end

do
CASE("should allow setting components in arbitrary order")
do CASE("should allow setting components in arbitrary order")
local world = jecs.World.new()

local Health = world:entity()
Expand All @@ -149,8 +210,7 @@ TEST("world", function()
CHECK(world:get(id, Poison) == 5)
end

do
CASE("should allow deleting components")
do CASE("should allow deleting components")
local world = jecs.World.new()

local Health = world:entity()
Expand All @@ -171,8 +231,7 @@ TEST("world", function()
CHECK(world:get(id1, Health) == 50)
end

do
CASE("should allow remove that doesn't exist on entity")
do CASE("should allow remove that doesn't exist on entity")
local world = jecs.World.new()

local Health = world:entity()
Expand All @@ -186,8 +245,7 @@ TEST("world", function()
CHECK(world:get(id, Health) == 50)
end

do
CASE("should increment generation")
do CASE("should increment generation")
local world = jecs.World.new()
local e = world:entity()
CHECK(ECS_ID(e) == 1 + jecs.Rest)
Expand All @@ -197,8 +255,7 @@ TEST("world", function()
CHECK(ECS_GENERATION(e) == 1) -- 1
end

do
CASE("should get alive from index in the dense array")
do CASE("should get alive from index in the dense array")
local world = jecs.World.new()
local _e = world:entity()
local e2 = world:entity()
Expand All @@ -213,8 +270,7 @@ TEST("world", function()
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3)
end

do
CASE("should allow querying for relations")
do CASE("should allow querying for relations")
local world = jecs.World.new()
local Eats = world:entity()
local Apples = world:entity()
Expand All @@ -227,8 +283,7 @@ TEST("world", function()
end
end

do
CASE("should allow wildcards in queries")
do CASE("should allow wildcards in queries")
local world = jecs.World.new()
local Eats = world:entity()
local Apples = world:entity()
Expand All @@ -247,8 +302,7 @@ TEST("world", function()
end
end

do
CASE("should match against multiple pairs")
do CASE("should match against multiple pairs")
local world = jecs.World.new()
local Eats = world:entity()
local Apples = world:entity()
Expand Down Expand Up @@ -280,8 +334,7 @@ TEST("world", function()
CHECK(count == 1)
end

do
CASE("should only relate alive entities")
do CASE("should only relate alive entities")

local world = jecs.World.new()
local Eats = world:entity()
Expand All @@ -308,8 +361,7 @@ TEST("world", function()
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil)
end

do
CASE("should error when setting invalid pair")
do CASE("should error when setting invalid pair")
local world = jecs.World.new()
local Eats = world:entity()
local Apples = world:entity()
Expand All @@ -320,8 +372,7 @@ TEST("world", function()
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
end

do
CASE("should find target for ChildOf")
do CASE("should find target for ChildOf")
local world = jecs.World.new()

local ChildOf = world:component()
Expand All @@ -343,7 +394,6 @@ TEST("world", function()

local count = 0
for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do
print(name)
count += 1
end
CHECK(count == 2)
Expand Down

0 comments on commit 5ff6a43

Please sign in to comment.