mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Add clear (#59)
* 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
This commit is contained in:
parent
0f67cb1c86
commit
5ff6a43750
2 changed files with 112 additions and 42 deletions
|
@ -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
|
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function STRIP_GENERATION(e: i53): i24
|
||||||
|
return ECS_ENTITY_T_LO(e)
|
||||||
|
end
|
||||||
|
|
||||||
local function ECS_PAIR(pred: i53, obj: i53): i53
|
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
|
return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) :: i53
|
||||||
end
|
end
|
||||||
|
@ -442,6 +446,24 @@ function World.delete(world: World, entityId: i53)
|
||||||
|
|
||||||
sparse[entityId] = nil :: any
|
sparse[entityId] = nil :: any
|
||||||
dense[#dense] = 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
|
end
|
||||||
|
|
||||||
local function ensureArchetype(world: World, types, prev): Archetype
|
local function ensureArchetype(world: World, types, prev): Archetype
|
||||||
|
@ -823,7 +845,6 @@ type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -
|
||||||
|
|
||||||
function World.__iter(world: World): WorldIterator
|
function World.__iter(world: World): WorldIterator
|
||||||
local entityIndex = world.entityIndex
|
local entityIndex = world.entityIndex
|
||||||
local dense = entityIndex.dense
|
|
||||||
local sparse = entityIndex.sparse
|
local sparse = entityIndex.sparse
|
||||||
local last
|
local last
|
||||||
|
|
||||||
|
@ -832,14 +853,13 @@ function World.__iter(world: World): WorldIterator
|
||||||
local i = 0
|
local i = 0
|
||||||
local function iterator()
|
local function iterator()
|
||||||
i+=1
|
i+=1
|
||||||
local entityId = dense[i]
|
local entityId, record = next(sparse, last)
|
||||||
if not entityId then
|
if not entityId then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
last = lastEntity
|
last = entityId
|
||||||
|
|
||||||
local record = sparse[entityId]
|
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
if not archetype then
|
if not archetype then
|
||||||
-- Returns only the entity id as an entity without data should not return
|
-- Returns only the entity id as an entity without data should not return
|
||||||
|
|
126
tests/world.luau
126
tests/world.luau
|
@ -22,10 +22,11 @@ local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
|
||||||
end
|
end
|
||||||
local N = 10
|
local N = 10
|
||||||
|
|
||||||
|
type World = jecs.WorldShim
|
||||||
|
|
||||||
TEST("world", function()
|
TEST("world", function()
|
||||||
do
|
do CASE("should be iterable")
|
||||||
CASE("should be iterable")
|
local world = jecs.World.new() :: World
|
||||||
local world = jecs.World.new()
|
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
local eA = world:entity()
|
local eA = world:entity()
|
||||||
|
@ -36,9 +37,7 @@ TEST("world", function()
|
||||||
world:set(eAB, A, true)
|
world:set(eAB, A, true)
|
||||||
world:set(eAB, B, true)
|
world:set(eAB, B, true)
|
||||||
|
|
||||||
local count = 0
|
|
||||||
for id, data in world do
|
for id, data in world do
|
||||||
count += 1
|
|
||||||
if id == eA then
|
if id == eA then
|
||||||
CHECK(data[A] == true)
|
CHECK(data[A] == true)
|
||||||
CHECK(data[B] == nil)
|
CHECK(data[B] == nil)
|
||||||
|
@ -50,14 +49,79 @@ TEST("world", function()
|
||||||
CHECK(data[B] == true)
|
CHECK(data[B] == true)
|
||||||
end
|
end
|
||||||
end
|
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should remove its components")
|
||||||
CASE("should query all matching entities")
|
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
|
||||||
|
|
||||||
|
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")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
@ -80,8 +144,7 @@ TEST("world", function()
|
||||||
CHECK(#entities == 0)
|
CHECK(#entities == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should query all matching entities when irrelevant component is removed")
|
||||||
CASE("should query all matching entities when irrelevant component is removed")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
@ -110,8 +173,7 @@ TEST("world", function()
|
||||||
CHECK(added == N)
|
CHECK(added == N)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should query all entities without B")
|
||||||
CASE("should query all entities without B")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
@ -135,8 +197,7 @@ TEST("world", function()
|
||||||
CHECK(#entities == 0)
|
CHECK(#entities == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should allow setting components in arbitrary order")
|
||||||
CASE("should allow setting components in arbitrary order")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Health = world:entity()
|
local Health = world:entity()
|
||||||
|
@ -149,8 +210,7 @@ TEST("world", function()
|
||||||
CHECK(world:get(id, Poison) == 5)
|
CHECK(world:get(id, Poison) == 5)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should allow deleting components")
|
||||||
CASE("should allow deleting components")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Health = world:entity()
|
local Health = world:entity()
|
||||||
|
@ -171,8 +231,7 @@ TEST("world", function()
|
||||||
CHECK(world:get(id1, Health) == 50)
|
CHECK(world:get(id1, Health) == 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should allow remove that doesn't exist on entity")
|
||||||
CASE("should allow remove that doesn't exist on entity")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Health = world:entity()
|
local Health = world:entity()
|
||||||
|
@ -186,8 +245,7 @@ TEST("world", function()
|
||||||
CHECK(world:get(id, Health) == 50)
|
CHECK(world:get(id, Health) == 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should increment generation")
|
||||||
CASE("should increment generation")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
CHECK(ECS_ID(e) == 1 + jecs.Rest)
|
CHECK(ECS_ID(e) == 1 + jecs.Rest)
|
||||||
|
@ -197,8 +255,7 @@ TEST("world", function()
|
||||||
CHECK(ECS_GENERATION(e) == 1) -- 1
|
CHECK(ECS_GENERATION(e) == 1) -- 1
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should get alive from index in the dense array")
|
||||||
CASE("should get alive from index in the dense array")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local _e = world:entity()
|
local _e = world:entity()
|
||||||
local e2 = world:entity()
|
local e2 = world:entity()
|
||||||
|
@ -213,8 +270,7 @@ TEST("world", function()
|
||||||
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3)
|
CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should allow querying for relations")
|
||||||
CASE("should allow querying for relations")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:entity()
|
||||||
local Apples = world:entity()
|
local Apples = world:entity()
|
||||||
|
@ -227,8 +283,7 @@ TEST("world", function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should allow wildcards in queries")
|
||||||
CASE("should allow wildcards in queries")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:entity()
|
||||||
local Apples = world:entity()
|
local Apples = world:entity()
|
||||||
|
@ -247,8 +302,7 @@ TEST("world", function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should match against multiple pairs")
|
||||||
CASE("should match against multiple pairs")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:entity()
|
||||||
local Apples = world:entity()
|
local Apples = world:entity()
|
||||||
|
@ -280,8 +334,7 @@ TEST("world", function()
|
||||||
CHECK(count == 1)
|
CHECK(count == 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should only relate alive entities")
|
||||||
CASE("should only relate alive entities")
|
|
||||||
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:entity()
|
||||||
|
@ -308,8 +361,7 @@ TEST("world", function()
|
||||||
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil)
|
CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should error when setting invalid pair")
|
||||||
CASE("should error when setting invalid pair")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:entity()
|
||||||
local Apples = world:entity()
|
local Apples = world:entity()
|
||||||
|
@ -320,8 +372,7 @@ TEST("world", function()
|
||||||
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
|
world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples")
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do CASE("should find target for ChildOf")
|
||||||
CASE("should find target for ChildOf")
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local ChildOf = world:component()
|
local ChildOf = world:component()
|
||||||
|
@ -343,7 +394,6 @@ TEST("world", function()
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do
|
for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do
|
||||||
print(name)
|
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
CHECK(count == 2)
|
CHECK(count == 2)
|
||||||
|
|
Loading…
Reference in a new issue