mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
Merge branch 'main' into docs/studio
This commit is contained in:
commit
fb178980a9
12 changed files with 1426 additions and 812 deletions
|
|
@ -16,8 +16,8 @@ local jecs = require("@jecs")
|
||||||
--[[
|
--[[
|
||||||
There are two ways to create tags:
|
There are two ways to create tags:
|
||||||
|
|
||||||
1. Using jecs.tag() - preregister a tag entity which will be allocated when you create the world.
|
1. Using jecs.tag() to preregister a tag entity which will be allocated when you create the world.
|
||||||
2. Using world:entity() - creates a regular entity id
|
2. Using world:entity() to create a regular entity id
|
||||||
|
|
||||||
The first method is the "proper" way to create tags but it hinges upon that
|
The first method is the "proper" way to create tags but it hinges upon that
|
||||||
you do remember to create the world after declaring all of your
|
you do remember to create the world after declaring all of your
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,6 @@ end
|
||||||
-- Cached query (faster for repeated use)
|
-- Cached query (faster for repeated use)
|
||||||
local cached_query = world:query(Position, Velocity):cached()
|
local cached_query = world:query(Position, Velocity):cached()
|
||||||
for entity, pos, vel in cached_query do
|
for entity, pos, vel in cached_query do
|
||||||
-- Process entities - this is faster for repeated iterations
|
-- Process entities. This is faster for repeated iterations.
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ world:set(Transform, jecs.OnAdd, function(entity, id, data)
|
||||||
print(`Transform added to entity {entity}`)
|
print(`Transform added to entity {entity}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
world:set(Transform, jecs.OnRemove, function(entity, id)
|
world:set(Transform, jecs.OnRemove, function(entity, id, delete)
|
||||||
-- A transform component id has been removed from entity
|
-- A transform component id has been removed from entity
|
||||||
|
-- delete is true if the entity is being deleted, false/nil otherwise
|
||||||
print(`Transform removed from entity {entity}`)
|
print(`Transform removed from entity {entity}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -38,3 +39,66 @@ end)
|
||||||
When an entity graph contains cycles, order is undefined. This includes cycles
|
When an entity graph contains cycles, order is undefined. This includes cycles
|
||||||
that can be formed using different relationships.
|
that can be formed using different relationships.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Structural changes in OnRemove hooks
|
||||||
|
|
||||||
|
You can call world:add, world:remove, and world:set inside OnRemove hooks.
|
||||||
|
That's fine. But there's a catch.
|
||||||
|
|
||||||
|
When an entity is being deleted, all of its components get removed. Each
|
||||||
|
removal triggers the OnRemove hook. If you try to make structural changes
|
||||||
|
to the entity during deletion, like removing more components or adding new
|
||||||
|
ones, you're fighting against the deletion process itself. The entity is
|
||||||
|
going to lose all its components anyway, so what's the point?
|
||||||
|
|
||||||
|
This creates a conflict. On one hand, you might want to clean up related
|
||||||
|
components when a specific component is removed. On the other hand, during
|
||||||
|
deletion, you don't want to do that because the entity is already being
|
||||||
|
torn down. So you need a way to tell the difference.
|
||||||
|
|
||||||
|
The solution is the delete boolean. Every OnRemove hook receives it as the
|
||||||
|
third parameter. It's true when the entity is being deleted, and false
|
||||||
|
(or nil) when you're just removing a single component normally.
|
||||||
|
|
||||||
|
So you check it. If delete is true, you bail out early. If it's false,
|
||||||
|
you do your cleanup. Simple.
|
||||||
|
|
||||||
|
Here's what it looks like in practice:
|
||||||
|
]]
|
||||||
|
|
||||||
|
local Health = world:component()
|
||||||
|
local Dead = world:component()
|
||||||
|
|
||||||
|
world:set(Health, jecs.OnRemove, function(entity, id, delete)
|
||||||
|
if delete then
|
||||||
|
-- Entity is being deleted, don't try to clean up
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Normal removal, do cleanup
|
||||||
|
world:remove(entity, Dead)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The ob.luau module uses this pattern extensively. When you're building
|
||||||
|
observers or monitors that track component removals, you need to distinguish
|
||||||
|
between "component removed" and "entity deleted" because they mean different
|
||||||
|
things for your tracking logic.
|
||||||
|
|
||||||
|
Now, about the DEBUG flag. If you create a world with DEBUG enabled:
|
||||||
|
|
||||||
|
local world = jecs.world(true)
|
||||||
|
|
||||||
|
Then the world will actively prevent you from calling world:add, world:remove,
|
||||||
|
or world:set inside OnRemove hooks when delete is true. It throws an error
|
||||||
|
that tells you exactly what went wrong. This is useful during development
|
||||||
|
to catch cases where you forgot to check the delete flag.
|
||||||
|
|
||||||
|
But here's the important part: even with DEBUG enabled, you're still allowed
|
||||||
|
to call these functions when delete is false. The DEBUG mode only prevents
|
||||||
|
structural changes during deletion, not during normal component removal.
|
||||||
|
|
||||||
|
So the pattern is always the same: check delete, bail if true, proceed if false.
|
||||||
|
The DEBUG flag just makes sure you don't forget to do the check.
|
||||||
|
]]
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ local function observers_new(
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
local wc = tgt == jecs.w
|
||||||
local onremoved = world:removed(rel, function(entity, id, delete: boolean?)
|
local onremoved = world:removed(rel, function(entity, id, delete: boolean?)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
if not wc and id ~= term then
|
if not wc and id ~= term then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -98,7 +101,10 @@ local function observers_new(
|
||||||
|
|
||||||
table.insert(cleanup, onremoved)
|
table.insert(cleanup, onremoved)
|
||||||
else
|
else
|
||||||
local onremoved = world:removed(term, function(entity, id)
|
local onremoved = world:removed(term, function(entity, id, delete: boolean?)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if archetype then
|
if archetype then
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[tools]
|
[tools]
|
||||||
wally = "upliftgames/wally@0.3.2"
|
wally = "upliftgames/wally@0.3.2"
|
||||||
rojo = "rojo-rbx/rojo@7.4.4"
|
rojo = "rojo-rbx/rojo@7.7.0-rc.1"
|
||||||
luau = "luau-lang/luau@0.701"
|
luau = "luau-lang/luau@0.703.0"
|
||||||
zune = "scythe-technology/zune@0.5.1"
|
zune = "scythe-technology/zune@0.5.1"
|
||||||
8
src/jecs.d.ts
vendored
8
src/jecs.d.ts
vendored
|
|
@ -121,7 +121,7 @@ export class World {
|
||||||
/**
|
/**
|
||||||
* Creates a new World.
|
* Creates a new World.
|
||||||
*/
|
*/
|
||||||
private constructor();
|
private constructor(DEBUG?: boolean);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforces a check for entities to be created within a desired range.
|
* Enforces a check for entities to be created within a desired range.
|
||||||
|
|
@ -354,9 +354,9 @@ export type ComponentRecord = {
|
||||||
export function component_record(world: World, id: Id): ComponentRecord;
|
export function component_record(world: World, id: Id): ComponentRecord;
|
||||||
|
|
||||||
type TagToUndefined<T> = T extends TagDiscriminator ? undefined : T
|
type TagToUndefined<T> = T extends TagDiscriminator ? undefined : T
|
||||||
type TrimOptional<T extends unknown[]> = T extends [...infer L, infer R]
|
type TrimOptional<T extends unknown[]> = T extends [...infer L, infer R]
|
||||||
? unknown extends R
|
? unknown extends R
|
||||||
? L | T | TrimOptional<L>
|
? L | T | TrimOptional<L>
|
||||||
: R extends undefined
|
: R extends undefined
|
||||||
? L | T | TrimOptional<L>
|
? L | T | TrimOptional<L>
|
||||||
: T
|
: T
|
||||||
|
|
|
||||||
1472
src/jecs.luau
1472
src/jecs.luau
File diff suppressed because it is too large
Load diff
BIN
test.rbxl
Executable file
BIN
test.rbxl
Executable file
Binary file not shown.
|
|
@ -12,16 +12,16 @@
|
||||||
"ReplicatedStorage": {
|
"ReplicatedStorage": {
|
||||||
"$className": "ReplicatedStorage",
|
"$className": "ReplicatedStorage",
|
||||||
"Lib": {
|
"Lib": {
|
||||||
"$path": "../src/jecs.luau"
|
"$path": "../../src/jecs.luau"
|
||||||
},
|
},
|
||||||
"benches": {
|
"benches": {
|
||||||
"$path": "benches"
|
"$path": "visual"
|
||||||
},
|
},
|
||||||
"mirror": {
|
"mirror": {
|
||||||
"$path": "mirror.luau"
|
"$path": "../../src/mirror.luau"
|
||||||
},
|
},
|
||||||
"DevPackages": {
|
"DevPackages": {
|
||||||
"$path": "benches/visual/DevPackages"
|
"$path": "visual/DevPackages"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,48 +27,11 @@ do
|
||||||
G: jecs.Id,
|
G: jecs.Id,
|
||||||
H: jecs.Id
|
H: jecs.Id
|
||||||
)
|
)
|
||||||
BENCH("1 component", function()
|
|
||||||
for _ in world:query(A) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("2 component", function()
|
|
||||||
for _ in world:query(B, A) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("4 component", function()
|
BENCH("4 component", function()
|
||||||
for _ in world:query(D, C, B, A) do
|
for _ in world:query(D, C, B, A) do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("8 component", function()
|
|
||||||
for _ in world:query(H, G, F, E, D, C, B, A) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
world:set(e, A, true)
|
|
||||||
world:set(e, B, true)
|
|
||||||
world:set(e, C, true)
|
|
||||||
world:set(e, D, true)
|
|
||||||
world:set(e, E, true)
|
|
||||||
world:set(e, F, true)
|
|
||||||
world:set(e, G, true)
|
|
||||||
world:set(e, H, true)
|
|
||||||
|
|
||||||
BENCH("Update Data", function()
|
|
||||||
for _ = 1, 100 do
|
|
||||||
world:set(e, A, false)
|
|
||||||
world:set(e, B, false)
|
|
||||||
world:set(e, C, false)
|
|
||||||
world:set(e, D, false)
|
|
||||||
world:set(e, E, false)
|
|
||||||
world:set(e, F, false)
|
|
||||||
world:set(e, G, false)
|
|
||||||
world:set(e, H, false)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local D1 = ecs:component()
|
local D1 = ecs:component()
|
||||||
|
|
@ -86,8 +49,9 @@ do
|
||||||
|
|
||||||
local added = 0
|
local added = 0
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
for i = 1, 2 ^ 16 - 2 do
|
for i = 1, 2 ^ 12 - 2 do
|
||||||
local entity = ecs:entity()
|
local entity = ecs:entity()
|
||||||
|
ecs:add(entity, entity)
|
||||||
|
|
||||||
local combination = ""
|
local combination = ""
|
||||||
|
|
||||||
|
|
@ -153,48 +117,10 @@ do
|
||||||
G: jecs.Id,
|
G: jecs.Id,
|
||||||
H: jecs.Id
|
H: jecs.Id
|
||||||
)
|
)
|
||||||
BENCH("1 component", function()
|
|
||||||
for _ in world:query(A) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("2 component", function()
|
|
||||||
for _ in world:query(B, A) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("4 component", function()
|
BENCH("4 component", function()
|
||||||
for _ in world:query(D, C, B, A) do
|
for _ in world:query(D, C, B, A) do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("8 component", function()
|
|
||||||
for _ in world:query(H, G, F, E, D, C, B, A) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
world:set(e, A, true)
|
|
||||||
world:set(e, B, true)
|
|
||||||
world:set(e, C, true)
|
|
||||||
world:set(e, D, true)
|
|
||||||
world:set(e, E, true)
|
|
||||||
world:set(e, F, true)
|
|
||||||
world:set(e, G, true)
|
|
||||||
world:set(e, H, true)
|
|
||||||
|
|
||||||
BENCH("Update Data", function()
|
|
||||||
for _ = 1, 100 do
|
|
||||||
world:set(e, A, false)
|
|
||||||
world:set(e, B, false)
|
|
||||||
world:set(e, C, false)
|
|
||||||
world:set(e, D, false)
|
|
||||||
world:set(e, E, false)
|
|
||||||
world:set(e, F, false)
|
|
||||||
world:set(e, G, false)
|
|
||||||
world:set(e, H, false)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local D1 = ecs:component()
|
local D1 = ecs:component()
|
||||||
|
|
@ -207,13 +133,14 @@ do
|
||||||
local D8 = ecs:component()
|
local D8 = ecs:component()
|
||||||
|
|
||||||
local function flip()
|
local function flip()
|
||||||
return math.random() >= 0.15
|
return math.random() >= 0.5
|
||||||
end
|
end
|
||||||
|
|
||||||
local added = 0
|
local added = 0
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
for i = 1, 2 ^ 16 - 2 do
|
for i = 1, 2 ^ 12 - 2 do
|
||||||
local entity = ecs:entity()
|
local entity = ecs:entity()
|
||||||
|
ecs:add(entity, entity)
|
||||||
|
|
||||||
local combination = ""
|
local combination = ""
|
||||||
|
|
||||||
|
|
@ -246,11 +173,9 @@ do
|
||||||
ecs:set(entity, D8, { value = true })
|
ecs:set(entity, D8, { value = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
if #combination == 7 then
|
if flip() then
|
||||||
added += 1
|
|
||||||
ecs:set(entity, D1, { value = true })
|
ecs:set(entity, D1, { value = true })
|
||||||
end
|
end
|
||||||
archetypes[combination] = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local a = 0
|
local a = 0
|
||||||
|
|
|
||||||
|
|
@ -50,85 +50,54 @@ local E8 = mcs:component()
|
||||||
local registry2 = ecr.registry()
|
local registry2 = ecr.registry()
|
||||||
|
|
||||||
local function flip()
|
local function flip()
|
||||||
return math.random() >= 0.25
|
return math.random() >= 0.5
|
||||||
end
|
end
|
||||||
|
|
||||||
local N = 2 ^ 16 - 2
|
local N = 2 ^ 12- 2
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
|
|
||||||
local hm = 0
|
local hm = 0
|
||||||
for i = 1, N do
|
for i = 1, N do
|
||||||
local id = registry2.create()
|
|
||||||
local combination = ""
|
|
||||||
local n = newWorld:spawn()
|
|
||||||
local entity = ecs:entity()
|
local entity = ecs:entity()
|
||||||
local m = mcs:entity()
|
local m = mcs:entity()
|
||||||
|
if flip() then
|
||||||
|
ecs:add(entity, entity)
|
||||||
|
mcs:add(m, m)
|
||||||
|
end
|
||||||
|
|
||||||
if flip() then
|
if flip() then
|
||||||
registry2:set(id, B1, { value = true })
|
|
||||||
ecs:set(entity, D1, { value = true })
|
|
||||||
newWorld:insert(n, A1({ value = true }))
|
|
||||||
mcs:set(m, E1, { value = 2 })
|
mcs:set(m, E1, { value = 2 })
|
||||||
|
ecs:set(entity, D1, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "B"
|
|
||||||
registry2:set(id, B2, { value = true })
|
|
||||||
ecs:set(entity, D2, { value = true })
|
ecs:set(entity, D2, { value = true })
|
||||||
mcs:set(m, E2, { value = 2 })
|
mcs:set(m, E2, { value = 2 })
|
||||||
newWorld:insert(n, A2({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "C"
|
|
||||||
registry2:set(id, B3, { value = true })
|
|
||||||
ecs:set(entity, D3, { value = true })
|
ecs:set(entity, D3, { value = true })
|
||||||
mcs:set(m, E3, { value = 2 })
|
mcs:set(m, E3, { value = 2 })
|
||||||
newWorld:insert(n, A3({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "D"
|
|
||||||
registry2:set(id, B4, { value = true })
|
|
||||||
ecs:set(entity, D4, { value = true })
|
ecs:set(entity, D4, { value = true })
|
||||||
mcs:set(m, E4, { value = 2 })
|
mcs:set(m, E4, { value = 2 })
|
||||||
|
|
||||||
newWorld:insert(n, A4({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "E"
|
|
||||||
registry2:set(id, B5, { value = true })
|
|
||||||
ecs:set(entity, D5, { value = true })
|
ecs:set(entity, D5, { value = true })
|
||||||
mcs:set(m, E5, { value = 2 })
|
mcs:set(m, E5, { value = 2 })
|
||||||
|
|
||||||
newWorld:insert(n, A5({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "F"
|
|
||||||
registry2:set(id, B6, { value = true })
|
|
||||||
ecs:set(entity, D6, { value = true })
|
ecs:set(entity, D6, { value = true })
|
||||||
mcs:set(m, E6, { value = 2 })
|
mcs:set(m, E6, { value = 2 })
|
||||||
newWorld:insert(n, A6({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "G"
|
|
||||||
registry2:set(id, B7, { value = true })
|
|
||||||
ecs:set(entity, D7, { value = true })
|
ecs:set(entity, D7, { value = true })
|
||||||
mcs:set(m, E7, { value = 2 })
|
mcs:set(m, E7, { value = 2 })
|
||||||
newWorld:insert(n, A7({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "H"
|
|
||||||
registry2:set(id, B8, { value = true })
|
|
||||||
newWorld:insert(n, A8({ value = true }))
|
|
||||||
ecs:set(entity, D8, { value = true })
|
ecs:set(entity, D8, { value = true })
|
||||||
mcs:set(m, E8, { value = 2 })
|
mcs:set(m, E8, { value = 2 })
|
||||||
end
|
end
|
||||||
|
|
||||||
if combination:find("BCDF") then
|
|
||||||
if not archetypes[combination] then
|
|
||||||
print(combination)
|
|
||||||
end
|
|
||||||
hm += 1
|
|
||||||
end
|
|
||||||
archetypes[combination] = true
|
|
||||||
end
|
end
|
||||||
print("TEST", hm)
|
print("TEST", hm)
|
||||||
|
|
||||||
|
|
@ -140,30 +109,38 @@ end
|
||||||
|
|
||||||
print(count)
|
print(count)
|
||||||
|
|
||||||
|
local mq = mcs:query(E1, E2, E3, E4)
|
||||||
|
local jq = ecs:query(D1, D2, D3, D4)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ParameterGenerator = function()
|
ParameterGenerator = function()
|
||||||
return
|
return
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Functions = {
|
Functions = {
|
||||||
Matter = function()
|
-- Matter = function()
|
||||||
for entityId, firstComponent in newWorld:query(A2, A4, A6, A8) do
|
-- for entityId, firstComponent in newWorld:query(A2, A4, A6, A8) do
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
ECR = function()
|
|
||||||
for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- Mirror = function()
|
|
||||||
-- for entityId, firstComponent in mcs:query(E2, E4, E6, E8) do
|
|
||||||
-- end
|
-- end
|
||||||
-- end,
|
-- end,
|
||||||
|
|
||||||
Jecs = function()
|
-- ECR = function()
|
||||||
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
|
-- for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
|
||||||
|
-- end
|
||||||
|
-- end,
|
||||||
|
|
||||||
|
Mirror = function()
|
||||||
|
for i = 1, 10 do
|
||||||
|
for entityId, firstComponent in mq:iter() do
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
Jecs = function()
|
||||||
|
for i = 1, 10 do
|
||||||
|
for entityId, firstComponent in jq:iter() do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
496
test/tests.luau
496
test/tests.luau
|
|
@ -24,83 +24,8 @@ type Id<T=unknown> = jecs.Id<T>
|
||||||
local entity_visualiser = require("@modules/entity_visualiser")
|
local entity_visualiser = require("@modules/entity_visualiser")
|
||||||
local dwi = entity_visualiser.stringify
|
local dwi = entity_visualiser.stringify
|
||||||
|
|
||||||
TEST("optimize idr_r removal", function()
|
|
||||||
|
|
||||||
local pair = jecs.pair
|
|
||||||
local world = jecs.world()
|
|
||||||
local rel = world:component()
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local t1 = world:entity()
|
|
||||||
local t2 = world:entity()
|
|
||||||
|
|
||||||
local entities = {} :: { jecs.Entity }
|
|
||||||
|
|
||||||
for i = 1, 10 do
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
local e2 = world:entity()
|
|
||||||
|
|
||||||
world:set(e1, A, true)
|
|
||||||
world:set(e2, A, true)
|
|
||||||
world:add(e1, pair(B, t1))
|
|
||||||
world:add(e1, pair(B, t2))
|
|
||||||
world:add(e2, pair(B, t1))
|
|
||||||
world:add(e2, pair(B, t2))
|
|
||||||
|
|
||||||
table.insert(entities, e1)
|
|
||||||
table.insert(entities, e2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
local e2 = world:entity()
|
|
||||||
|
|
||||||
table.insert(entities, e1)
|
|
||||||
table.insert(entities, e2)
|
|
||||||
|
|
||||||
world:set(e1, A, true)
|
|
||||||
world:set(e2, A, true)
|
|
||||||
world:add(e1, pair(B, t1))
|
|
||||||
world:add(e1, pair(B, t2))
|
|
||||||
world:add(e2, pair(B, t1))
|
|
||||||
world:add(e2, pair(B, t2))
|
|
||||||
|
|
||||||
BENCH("delete B", function()
|
|
||||||
world:delete(B)
|
|
||||||
end)
|
|
||||||
|
|
||||||
for _, e in entities do
|
|
||||||
CHECK(world:has(e, A))
|
|
||||||
CHECK(not world:target(e, B))
|
|
||||||
CHECK(not world:target(e, B))
|
|
||||||
end
|
|
||||||
|
|
||||||
end)
|
|
||||||
TEST("deleting t1's archetype before invoking its onremove hooks", function()
|
|
||||||
local pair = jecs.pair
|
|
||||||
local world = jecs.world()
|
|
||||||
local rel = world:component()
|
|
||||||
|
|
||||||
local t1 = world:entity()
|
|
||||||
local t2 = world:entity()
|
|
||||||
|
|
||||||
--[[
|
|
||||||
weirdly enough if i do this (only when adding childof relation after adding (rel, t2) to t1) it does not error. Probably a red herring
|
|
||||||
|
|
||||||
world:add(t2, pair(rel, t1))
|
|
||||||
world:add(t1, pair(rel, t2))
|
|
||||||
world:add(t2, pair(jecs.ChildOf, t1))
|
|
||||||
--]]
|
|
||||||
|
|
||||||
-- this causes world:delete to error
|
|
||||||
world:add(t2, pair(jecs.ChildOf, t1))
|
|
||||||
world:add(t1, pair(rel, t2))
|
|
||||||
|
|
||||||
world:delete(t1)
|
|
||||||
end)
|
|
||||||
TEST("reproduce idr_t nil archetype bug", function()
|
TEST("reproduce idr_t nil archetype bug", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world(true)
|
||||||
|
|
||||||
local cts = {
|
local cts = {
|
||||||
Humanoid = world:component(),
|
Humanoid = world:component(),
|
||||||
|
|
@ -121,46 +46,58 @@ TEST("reproduce idr_t nil archetype bug", function()
|
||||||
local src = r.archetype
|
local src = r.archetype
|
||||||
|
|
||||||
--REMOVING THIS jecs.archetype_traverse_remove CALL STOPS IT FROM HAPPENING
|
--REMOVING THIS jecs.archetype_traverse_remove CALL STOPS IT FROM HAPPENING
|
||||||
local dst = src and jecs.archetype_traverse_remove(world, id, src)
|
CHECK_EXPECT_ERR(function()
|
||||||
|
world:remove(entity, cts.Humanoid)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local batches = 10
|
local batchSize = 200
|
||||||
local batchSize = 20
|
|
||||||
|
|
||||||
local trackedEntities: { [number]: { parentId: number? } } = {}
|
local trackedEntities: { [number]: { parentId: number? } } = {}
|
||||||
|
|
||||||
for batch = 1, batches do
|
for i = 1, batchSize do
|
||||||
for i = 1, batchSize do
|
|
||||||
local root = world:entity()
|
|
||||||
world:add(root, jecs.pair(jecs.ChildOf, char))
|
|
||||||
|
|
||||||
-- Removing animator from trackEntity1 causes it to stop happening
|
local root = world:entity()
|
||||||
local trackEntity1 = world:entity()
|
-- world:add(root, jecs.pair(jecs.ChildOf, char))
|
||||||
world:set(trackEntity1, cts.Animator, 0)
|
|
||||||
world:add(trackEntity1, jecs.pair(jecs.ChildOf, root))
|
|
||||||
trackedEntities[trackEntity1] = { parentId = root }
|
|
||||||
|
|
||||||
-- Removing animator from trackEntity2 causes it to happen less frequently
|
-- Removing animator from trackEntity1 causes it to stop happening
|
||||||
local trackEntity2 = world:entity()
|
local trackEntity1 = world:entity()
|
||||||
world:set(trackEntity2, cts.Animator, 0)
|
world:set(trackEntity1, cts.Animator, 0)
|
||||||
world:add(trackEntity2, jecs.pair(jecs.ChildOf, root))
|
world:add(trackEntity1, jecs.pair(jecs.ChildOf, root))
|
||||||
trackedEntities[trackEntity2] = { parentId = root }
|
world:set(trackEntity1, jecs.Name, "trackEntity1v"..i)
|
||||||
|
trackedEntities[trackEntity1] = { parentId = root }
|
||||||
|
|
||||||
-- Removing this, but keeping Animator on the other 2 causes it to stop happening
|
-- Removing animator from trackEntity2 causes it to happen less frequently
|
||||||
world:set(trackEntity1, cts.VelocitizeAnimationWeight, 0)
|
local trackEntity2 = world:entity()
|
||||||
|
world:set(trackEntity2, cts.Animator, 0)
|
||||||
|
world:add(trackEntity2, jecs.pair(jecs.ChildOf, root))
|
||||||
|
world:set(trackEntity2, jecs.Name, "trackEntity2v"..i)
|
||||||
|
trackedEntities[trackEntity2] = { parentId = root }
|
||||||
|
|
||||||
for entityId, info in trackedEntities do
|
-- Removing this, but keeping Animator on the other 2 causes it to stop happening
|
||||||
if world:contains(entityId) and not world:parent(entityId :: any) then
|
world:set(trackEntity1, cts.VelocitizeAnimationWeight, 0)
|
||||||
print(`bugged entity found: {entityId}`)
|
|
||||||
print(`original parent: {info.parentId}`)
|
local q = world:query(jecs.pair(jecs.ChildOf, __)):cached()
|
||||||
print(`batch = {batch}, i = {i}`)
|
|
||||||
print("==========================================")
|
|
||||||
trackedEntities[entityId] = nil
|
world:delete(root)
|
||||||
world:delete(entityId)
|
|
||||||
end
|
|
||||||
end
|
for entityId, info in trackedEntities do
|
||||||
end
|
if world:contains(entityId) and not world:parent(entityId :: any) then
|
||||||
end
|
print(`bugged entity found: {entityId}`)
|
||||||
|
print(`original parent: {info.parentId}`)
|
||||||
|
print(`current parent: {i}`)
|
||||||
|
print(`batch = {batchSize}, i = {i}`)
|
||||||
|
print("==========================================")
|
||||||
|
trackedEntities[entityId] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for entity in q do
|
||||||
|
local parent = world:parent(entity) :: jecs.Entity
|
||||||
|
CHECK(world:parent(entity) == nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("Ensure archetype edges get cleaned", function()
|
TEST("Ensure archetype edges get cleaned", function()
|
||||||
|
|
@ -980,9 +917,6 @@ TEST("world:delete()", function()
|
||||||
CHECK(destroyed)
|
CHECK(destroyed)
|
||||||
CHECK(not world:contains(child))
|
CHECK(not world:contains(child))
|
||||||
end
|
end
|
||||||
if true then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Should delete children in different archetypes if they have the same parent"
|
do CASE "Should delete children in different archetypes if they have the same parent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
@ -1599,12 +1533,10 @@ TEST("world:range()", function()
|
||||||
do CASE "spawn entity under min range"
|
do CASE "spawn entity under min range"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
world:range(400, 1000)
|
world:range(400, 1000)
|
||||||
CHECK(world.entity_index.alive_count == 399)
|
|
||||||
local e = world:entity(300)
|
local e = world:entity(300)
|
||||||
CHECK(world.entity_index.alive_count == 400)
|
|
||||||
local e1 = world:entity(300)
|
local e1 = world:entity(300)
|
||||||
CHECK(world.entity_index.alive_count == 400)
|
CHECK(world:contains(e))
|
||||||
CHECK(e)
|
CHECK(world:contains(e1))
|
||||||
end
|
end
|
||||||
do CASE "entity ID reuse works correctly across different world ranges"
|
do CASE "entity ID reuse works correctly across different world ranges"
|
||||||
local base = jecs.world()
|
local base = jecs.world()
|
||||||
|
|
@ -1651,6 +1583,7 @@ TEST("world:range()", function()
|
||||||
CHECK(bar == bar_mirror)
|
CHECK(bar == bar_mirror)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
print("<do CASE \"delete outside partitioned range\">")
|
||||||
do CASE "delete outside partitioned range"
|
do CASE "delete outside partitioned range"
|
||||||
local server = jecs.world()
|
local server = jecs.world()
|
||||||
local client = jecs.world()
|
local client = jecs.world()
|
||||||
|
|
@ -1668,8 +1601,25 @@ TEST("world:range()", function()
|
||||||
CHECK(client:get(e2, A))
|
CHECK(client:get(e2, A))
|
||||||
|
|
||||||
client:delete(e2)
|
client:delete(e2)
|
||||||
|
|
||||||
|
|
||||||
|
print("<client.entity_index.max_id>")
|
||||||
|
print(client.entity_index.max_id)
|
||||||
|
print("</client.entity_index.max_id>")
|
||||||
|
|
||||||
|
print("<client.entity_index.alive_count>")
|
||||||
|
print(client.entity_index.alive_count)
|
||||||
|
print("</client.entity_index.alive_count>")
|
||||||
|
|
||||||
|
print("</client.entity_index.dense_array[client.entity_index.alive_count]>")
|
||||||
|
print(client.entity_index.dense_array[client.entity_index.alive_count])
|
||||||
|
print("</client.entity_index.dense_array[client.entity_index.alive_count]>")
|
||||||
|
print("<client:entity()>")
|
||||||
local e3 = client:entity()
|
local e3 = client:entity()
|
||||||
CHECK(ECS_ID(e3) == 1000)
|
print("</client:entity()>")
|
||||||
|
print("<ECS_ID(e3) == 1001>")
|
||||||
|
CHECK(ECS_ID(e3) == 1001)
|
||||||
|
print("</ECS_ID(e3) == 1001>")
|
||||||
|
|
||||||
local e1v1 = server:entity()
|
local e1v1 = server:entity()
|
||||||
local e4 = client:entity(e1v1)
|
local e4 = client:entity(e1v1)
|
||||||
|
|
@ -1679,6 +1629,281 @@ TEST("world:range()", function()
|
||||||
CHECK(client:contains(e4))
|
CHECK(client:contains(e4))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
print("</do CASE \"delete outside partitioned range\">")
|
||||||
|
|
||||||
|
do CASE "desired id within range does not overwrite existing entities"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(1000, 2000)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
local e1 = world:entity(1000)
|
||||||
|
local e2 = world:entity(1001)
|
||||||
|
local e3 = world:entity(1002)
|
||||||
|
|
||||||
|
world:set(e1, ctype, "entity1")
|
||||||
|
world:set(e2, ctype, "entity2")
|
||||||
|
world:set(e3, ctype, "entity3")
|
||||||
|
|
||||||
|
local e4 = world:entity(1500)
|
||||||
|
world:set(e4, ctype, "entity4")
|
||||||
|
|
||||||
|
CHECK(world:contains(e1))
|
||||||
|
CHECK(world:contains(e2))
|
||||||
|
CHECK(world:contains(e3))
|
||||||
|
CHECK(world:contains(e4))
|
||||||
|
CHECK(world:get(e1, ctype) == "entity1")
|
||||||
|
CHECK(world:get(e2, ctype) == "entity2")
|
||||||
|
CHECK(world:get(e3, ctype) == "entity3")
|
||||||
|
CHECK(world:get(e4, ctype) == "entity4")
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating entity with range+offset does not conflict with existing"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(500, 1000)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
local base = world:entity(500)
|
||||||
|
world:set(base, ctype, "base")
|
||||||
|
|
||||||
|
local offset1 = world:entity(501)
|
||||||
|
world:set(offset1, ctype, "offset1")
|
||||||
|
|
||||||
|
local offset2 = world:entity(750)
|
||||||
|
world:set(offset2, ctype, "offset2")
|
||||||
|
|
||||||
|
CHECK(world:contains(base))
|
||||||
|
CHECK(world:contains(offset1))
|
||||||
|
CHECK(world:contains(offset2))
|
||||||
|
CHECK(world:get(base, ctype) == "base")
|
||||||
|
CHECK(world:get(offset1, ctype) == "offset1")
|
||||||
|
CHECK(world:get(offset2, ctype) == "offset2")
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating entities in reverse order within range"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(100, 200)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
local e3 = world:entity(150)
|
||||||
|
local e2 = world:entity(120)
|
||||||
|
local e1 = world:entity(110)
|
||||||
|
|
||||||
|
world:set(e1, ctype, 1)
|
||||||
|
world:set(e2, ctype, 2)
|
||||||
|
world:set(e3, ctype, 3)
|
||||||
|
|
||||||
|
-- All should exist independently
|
||||||
|
CHECK(world:contains(e1))
|
||||||
|
CHECK(world:contains(e2))
|
||||||
|
CHECK(world:contains(e3))
|
||||||
|
CHECK(world:get(e1, ctype) == 1)
|
||||||
|
CHECK(world:get(e2, ctype) == 2)
|
||||||
|
CHECK(world:get(e3, ctype) == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating entity with desired ID after range pre-population"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(400, 1000)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
-- Range pre-populates 1-399 with dense=0
|
||||||
|
-- Create an entity at a pre-populated position
|
||||||
|
local e1 = world:entity(300)
|
||||||
|
world:set(e1, ctype, "prepop")
|
||||||
|
|
||||||
|
-- Create another entity at a different pre-populated position
|
||||||
|
local e2 = world:entity(250)
|
||||||
|
world:set(e2, ctype, "prepop2")
|
||||||
|
|
||||||
|
-- Create entity within the range
|
||||||
|
local e3 = world:entity(500)
|
||||||
|
world:set(e3, ctype, "inrange")
|
||||||
|
|
||||||
|
-- All should work correctly
|
||||||
|
CHECK(world:contains(e1))
|
||||||
|
CHECK(world:contains(e2))
|
||||||
|
CHECK(world:contains(e3))
|
||||||
|
CHECK(world:get(e1, ctype) == "prepop")
|
||||||
|
CHECK(world:get(e2, ctype) == "prepop2")
|
||||||
|
CHECK(world:get(e3, ctype) == "inrange")
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating same entity twice returns existing entity"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(1000, 2000)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
-- Create entity
|
||||||
|
local e1 = world:entity(1500)
|
||||||
|
world:set(e1, ctype, "original")
|
||||||
|
|
||||||
|
-- Try to create it again
|
||||||
|
local e2 = world:entity(1500)
|
||||||
|
|
||||||
|
-- Should return the same entity
|
||||||
|
CHECK(e1 == e2)
|
||||||
|
CHECK(world:get(e1, ctype) == "original")
|
||||||
|
CHECK(world:get(e2, ctype) == "original")
|
||||||
|
|
||||||
|
-- Should only be one entity
|
||||||
|
local count = 0
|
||||||
|
for _ in world:query(ctype) do
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
CHECK(count == 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating entities with gaps in range does not affect existing"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(200, 300)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
-- Create entities with gaps
|
||||||
|
local e1 = world:entity(200)
|
||||||
|
local e2 = world:entity(250)
|
||||||
|
local e3 = world:entity(299)
|
||||||
|
|
||||||
|
world:set(e1, ctype, 1)
|
||||||
|
world:set(e2, ctype, 2)
|
||||||
|
world:set(e3, ctype, 3)
|
||||||
|
|
||||||
|
-- Create entity in the gap
|
||||||
|
local e4 = world:entity(225)
|
||||||
|
world:set(e4, ctype, 4)
|
||||||
|
|
||||||
|
-- All should still exist
|
||||||
|
CHECK(world:contains(e1))
|
||||||
|
CHECK(world:contains(e2))
|
||||||
|
CHECK(world:contains(e3))
|
||||||
|
CHECK(world:contains(e4))
|
||||||
|
CHECK(world:get(e1, ctype) == 1)
|
||||||
|
CHECK(world:get(e2, ctype) == 2)
|
||||||
|
CHECK(world:get(e3, ctype) == 3)
|
||||||
|
CHECK(world:get(e4, ctype) == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating many entities with desired IDs in range"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(1000, 2000)
|
||||||
|
|
||||||
|
local ctype = world:component()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
-- Create 50 entities with specific IDs
|
||||||
|
for i = 1, 50 do
|
||||||
|
local id = 1000 + (i * 10)
|
||||||
|
local e = world:entity(id)
|
||||||
|
world:set(e, ctype, i)
|
||||||
|
entities[i] = e
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Verify all exist and have correct values
|
||||||
|
for i = 1, 50 do
|
||||||
|
CHECK(world:contains(entities[i]))
|
||||||
|
CHECK(world:get(entities[i], ctype) == i)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create more entities in between
|
||||||
|
for i = 1, 49 do
|
||||||
|
local id = 1000 + (i * 10) + 5
|
||||||
|
local e = world:entity(id)
|
||||||
|
world:set(e, ctype, i + 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Original entities should still be intact
|
||||||
|
for i = 1, 50 do
|
||||||
|
CHECK(world:contains(entities[i]))
|
||||||
|
CHECK(world:get(entities[i], ctype) == i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "sparse_array remains optimized with non-contiguous entity IDs"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
-- Create entities with large gaps to test sparse_array optimization
|
||||||
|
-- Start with entity 100, then jump to 1000 (gap: 101-999)
|
||||||
|
local e1 = world:entity(100)
|
||||||
|
local e2 = world:entity(1000)
|
||||||
|
-- Then jump to 5000 (gap: 1001-4999)
|
||||||
|
local e3 = world:entity(5000)
|
||||||
|
-- Then jump to 10000 (gap: 5001-9999)
|
||||||
|
local e4 = world:entity(10000)
|
||||||
|
|
||||||
|
world:set(e1, ctype, 1)
|
||||||
|
world:set(e2, ctype, 2)
|
||||||
|
world:set(e3, ctype, 3)
|
||||||
|
world:set(e4, ctype, 4)
|
||||||
|
|
||||||
|
-- Verify all entities exist and have correct values
|
||||||
|
CHECK(world:contains(e1))
|
||||||
|
CHECK(world:contains(e2))
|
||||||
|
CHECK(world:contains(e3))
|
||||||
|
CHECK(world:contains(e4))
|
||||||
|
CHECK(world:get(e1, ctype) == 1)
|
||||||
|
CHECK(world:get(e2, ctype) == 2)
|
||||||
|
CHECK(world:get(e3, ctype) == 3)
|
||||||
|
CHECK(world:get(e4, ctype) == 4)
|
||||||
|
|
||||||
|
-- Verify intermediate IDs are pre-populated (they should exist but not be alive)
|
||||||
|
-- These are in the gaps between created entities
|
||||||
|
local sparse_array = world.entity_index.sparse_array
|
||||||
|
CHECK(sparse_array[500] ~= nil) -- Between 100 and 1000, should be pre-populated
|
||||||
|
CHECK(sparse_array[500].dense == 0) -- But not alive (dense=0)
|
||||||
|
CHECK(sparse_array[2500] ~= nil) -- Between 1000 and 5000, should be pre-populated
|
||||||
|
CHECK(sparse_array[2500].dense == 0)
|
||||||
|
CHECK(sparse_array[7500] ~= nil) -- Between 5000 and 10000, should be pre-populated
|
||||||
|
CHECK(sparse_array[7500].dense == 0)
|
||||||
|
|
||||||
|
-- Verify max_id was updated correctly
|
||||||
|
CHECK(world.entity_index.max_id == 10000)
|
||||||
|
end
|
||||||
|
do CASE "desired id does not overwrite old entity id"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ctype = world:component()
|
||||||
|
local id = world:entity()
|
||||||
|
|
||||||
|
print("<local e = world:entity(id + 1)>")
|
||||||
|
local e = world:entity((id::any) + 1)
|
||||||
|
print("</local e = world:entity(id + 1)>")
|
||||||
|
|
||||||
|
CHECK(world:contains(id))
|
||||||
|
CHECK(world:contains(e))
|
||||||
|
|
||||||
|
-- also make sure that they don't share the same record
|
||||||
|
|
||||||
|
world:set(id, ctype, 1)
|
||||||
|
world:set(e, ctype, 2)
|
||||||
|
|
||||||
|
CHECK(world:get(id, ctype) == 1)
|
||||||
|
CHECK(world:get(e, ctype) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating ids with a higher key first"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
local e = world:entity(1000)
|
||||||
|
local id = world:entity(999)
|
||||||
|
|
||||||
|
CHECK(world:contains(id))
|
||||||
|
CHECK(world:contains(e))
|
||||||
|
|
||||||
|
-- also make sure that they don't share the same record
|
||||||
|
|
||||||
|
world:set(id, ctype, 1)
|
||||||
|
world:set(e, ctype, 2)
|
||||||
|
|
||||||
|
CHECK(world:get(id, ctype) == 1)
|
||||||
|
CHECK(world:get(e, ctype) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "under range start"
|
do CASE "under range start"
|
||||||
|
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
@ -1746,6 +1971,45 @@ TEST("world:entity()", function()
|
||||||
local e2 = world:entity(399)
|
local e2 = world:entity(399)
|
||||||
CHECK(world:contains(e2))
|
CHECK(world:contains(e2))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "desired id does not overwrite old entity id"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ctype = world:component()
|
||||||
|
local id = world:entity()
|
||||||
|
|
||||||
|
local e = world:entity((id::any) + 1)
|
||||||
|
|
||||||
|
CHECK(world:contains(id))
|
||||||
|
CHECK(world:contains(e))
|
||||||
|
|
||||||
|
-- also make sure that they don't share the same record
|
||||||
|
|
||||||
|
world:set(id, ctype, 1)
|
||||||
|
world:set(e, ctype, 2)
|
||||||
|
|
||||||
|
CHECK(world:get(id, ctype) == 1)
|
||||||
|
CHECK(world:get(e, ctype) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "creating ids with a higher key first"
|
||||||
|
local world = jecs.world()
|
||||||
|
local ctype = world:component()
|
||||||
|
|
||||||
|
local e = world:entity(1000)
|
||||||
|
local id = world:entity(999)
|
||||||
|
|
||||||
|
CHECK(world:contains(id))
|
||||||
|
CHECK(world:contains(e))
|
||||||
|
|
||||||
|
-- also make sure that they don't share the same record
|
||||||
|
|
||||||
|
world:set(id, ctype, 1)
|
||||||
|
world:set(e, ctype, 2)
|
||||||
|
|
||||||
|
CHECK(world:get(id, ctype) == 1)
|
||||||
|
CHECK(world:get(e, ctype) == 2)
|
||||||
|
end
|
||||||
|
|
||||||
local N = 2^8
|
local N = 2^8
|
||||||
|
|
||||||
do CASE "unique IDs"
|
do CASE "unique IDs"
|
||||||
|
|
@ -1817,6 +2081,8 @@ TEST("world:entity()", function()
|
||||||
CHECK(ECS_ID(e) == pin)
|
CHECK(ECS_ID(e) == pin)
|
||||||
CHECK(ECS_GENERATION(e) == 0)
|
CHECK(ECS_GENERATION(e) == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:has()", function()
|
TEST("world:has()", function()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue