mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Compare commits
14 commits
47cd4f09b3
...
d2d63bf97b
Author | SHA1 | Date | |
---|---|---|---|
|
d2d63bf97b | ||
|
9d83c3bc13 | ||
|
e073a570b7 | ||
|
79a4a6a0c4 | ||
|
db1b29fa04 | ||
|
b26fc39fce | ||
|
0e4f40ced7 | ||
|
de8e263828 | ||
|
d15266b6d5 | ||
|
18019679d5 | ||
|
925864dd2b | ||
|
ac79638599 | ||
|
f7573e8824 | ||
|
918231a1ad |
15 changed files with 3077 additions and 646 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -65,3 +65,7 @@ drafts/
|
||||||
|
|
||||||
# Luau tools
|
# Luau tools
|
||||||
profile.*
|
profile.*
|
||||||
|
|
||||||
|
# Patch files
|
||||||
|
|
||||||
|
*.patch
|
||||||
|
|
5
.luaurc
5
.luaurc
|
@ -1,8 +1,9 @@
|
||||||
{
|
{
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"jecs": "jecs",
|
"jecs": "jecs",
|
||||||
"testkit": "test/testkit",
|
"testkit": "tools/testkit",
|
||||||
"mirror": "mirror"
|
"mirror": "mirror",
|
||||||
|
"tools": "tools",
|
||||||
},
|
},
|
||||||
"languageMode": "strict"
|
"languageMode": "strict"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,41 +5,60 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
||||||
local jecs = require(ReplicatedStorage.Lib)
|
local jecs = require(ReplicatedStorage.Lib)
|
||||||
|
local pair = jecs.pair
|
||||||
local newWorld = Matter.World.new()
|
local newWorld = Matter.World.new()
|
||||||
local ecs = jecs.World.new()
|
local ecs = jecs.World.new()
|
||||||
|
local mirror = require(ReplicatedStorage.mirror)
|
||||||
|
local mcs = mirror.World.new()
|
||||||
|
|
||||||
local A, B = Matter.component(), Matter.component()
|
local C1 = ecs:component()
|
||||||
local C, D = ecs:component(), ecs:component()
|
local C2 = ecs:entity()
|
||||||
|
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local C3 = ecs:entity()
|
||||||
|
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local C4 = ecs:entity()
|
||||||
|
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local E1 = mcs:component()
|
||||||
|
local E2 = mcs:entity()
|
||||||
|
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local E3 = mcs:entity()
|
||||||
|
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local E4 = mcs:entity()
|
||||||
|
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
|
||||||
|
local registry2 = ecr.registry()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ParameterGenerator = function()
|
ParameterGenerator = function()
|
||||||
local matter_entities = {}
|
local j = ecs:entity()
|
||||||
local jecs_entities = {}
|
ecs:set(j, C1, true)
|
||||||
local entities = {
|
local m = mcs:entity()
|
||||||
matter = matter_entities,
|
mcs:set(m, E1, true)
|
||||||
jecs = jecs_entities,
|
|
||||||
}
|
|
||||||
for i = 1, 1000 do
|
for i = 1, 1000 do
|
||||||
table.insert(matter_entities, newWorld:spawn(A(), B()))
|
local friend1 = ecs:entity()
|
||||||
local e = ecs:entity()
|
local friend2 = mcs:entity()
|
||||||
ecs:set(e, C, {})
|
|
||||||
ecs:set(e, D, {})
|
ecs:add(friend1, pair(C2, j))
|
||||||
table.insert(jecs_entities, e)
|
ecs:add(friend1, pair(C3, j))
|
||||||
|
ecs:add(friend1, pair(C4, j))
|
||||||
|
|
||||||
|
mcs:add(friend2, pair(E2, m))
|
||||||
|
mcs:add(friend2, pair(E3, m))
|
||||||
|
mcs:add(friend2, pair(E4, m))
|
||||||
end
|
end
|
||||||
return entities
|
return {
|
||||||
|
m = m,
|
||||||
|
j = j,
|
||||||
|
}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Functions = {
|
Functions = {
|
||||||
Matter = function(_, entities)
|
Mirror = function(_, a)
|
||||||
for _, entity in entities.matter do
|
mcs:delete(a.m)
|
||||||
newWorld:despawn(entity)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Jecs = function(_, entities)
|
Jecs = function(_, a)
|
||||||
for _, entity in entities.jecs do
|
ecs:delete(a.j)
|
||||||
ecs:delete(entity)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,22 +28,22 @@ local B6 = ecr.component()
|
||||||
local B7 = ecr.component()
|
local B7 = ecr.component()
|
||||||
local B8 = ecr.component()
|
local B8 = ecr.component()
|
||||||
|
|
||||||
local C1 = ecs:entity()
|
local C1 = ecs:component()
|
||||||
local C2 = ecs:entity()
|
local C2 = ecs:component()
|
||||||
local C3 = ecs:entity()
|
local C3 = ecs:component()
|
||||||
local C4 = ecs:entity()
|
local C4 = ecs:component()
|
||||||
local C5 = ecs:entity()
|
local C5 = ecs:component()
|
||||||
local C6 = ecs:entity()
|
local C6 = ecs:component()
|
||||||
local C7 = ecs:entity()
|
local C7 = ecs:component()
|
||||||
local C8 = ecs:entity()
|
local C8 = ecs:component()
|
||||||
local E1 = mcs:entity()
|
local E1 = mcs:component()
|
||||||
local E2 = mcs:entity()
|
local E2 = mcs:component()
|
||||||
local E3 = mcs:entity()
|
local E3 = mcs:component()
|
||||||
local E4 = mcs:entity()
|
local E4 = mcs:component()
|
||||||
local E5 = mcs:entity()
|
local E5 = mcs:component()
|
||||||
local E6 = mcs:entity()
|
local E6 = mcs:component()
|
||||||
local E7 = mcs:entity()
|
local E7 = mcs:component()
|
||||||
local E8 = mcs:entity()
|
local E8 = mcs:component()
|
||||||
|
|
||||||
local registry2 = ecr.registry()
|
local registry2 = ecr.registry()
|
||||||
return {
|
return {
|
||||||
|
@ -52,48 +52,30 @@ return {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Functions = {
|
Functions = {
|
||||||
Matter = function()
|
Mirror = function()
|
||||||
local e = newWorld:spawn()
|
local e = mcs:entity()
|
||||||
for i = 1, 5000 do
|
for i = 1, 5000 do
|
||||||
newWorld:insert(
|
mcs:set(e, E1, false)
|
||||||
e,
|
mcs:set(e, E2, false)
|
||||||
A1({ value = true }),
|
mcs:set(e, E3, false)
|
||||||
A2({ value = true }),
|
mcs:set(e, E4, false)
|
||||||
A3({ value = true }),
|
mcs:set(e, E5, false)
|
||||||
A4({ value = true }),
|
mcs:set(e, E6, false)
|
||||||
A5({ value = true }),
|
mcs:set(e, E7, false)
|
||||||
A6({ value = true }),
|
mcs:set(e, E8, false)
|
||||||
A7({ value = true }),
|
|
||||||
A8({ value = true })
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
ECR = function()
|
|
||||||
local e = registry2.create()
|
|
||||||
for i = 1, 5000 do
|
|
||||||
registry2:set(e, B1, { value = false })
|
|
||||||
registry2:set(e, B2, { value = false })
|
|
||||||
registry2:set(e, B3, { value = false })
|
|
||||||
registry2:set(e, B4, { value = false })
|
|
||||||
registry2:set(e, B5, { value = false })
|
|
||||||
registry2:set(e, B6, { value = false })
|
|
||||||
registry2:set(e, B7, { value = false })
|
|
||||||
registry2:set(e, B8, { value = false })
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
Jecs = function()
|
Jecs = function()
|
||||||
local e = ecs:entity()
|
local e = ecs:entity()
|
||||||
for i = 1, 5000 do
|
for i = 1, 5000 do
|
||||||
ecs:set(e, C1, { value = false })
|
ecs:set(e, C1, false)
|
||||||
ecs:set(e, C2, { value = false })
|
ecs:set(e, C2, false)
|
||||||
ecs:set(e, C3, { value = false })
|
ecs:set(e, C3, false)
|
||||||
ecs:set(e, C4, { value = false })
|
ecs:set(e, C4, false)
|
||||||
ecs:set(e, C5, { value = false })
|
ecs:set(e, C5, false)
|
||||||
ecs:set(e, C6, { value = false })
|
ecs:set(e, C6, false)
|
||||||
ecs:set(e, C7, { value = false })
|
ecs:set(e, C7, false)
|
||||||
ecs:set(e, C8, { value = false })
|
ecs:set(e, C8, false)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
49
benches/visual/remove.bench.luau
Normal file
49
benches/visual/remove.bench.luau
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
--!optimize 2
|
||||||
|
--!native
|
||||||
|
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
||||||
|
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
||||||
|
local jecs = require(ReplicatedStorage.Lib)
|
||||||
|
local pair = jecs.pair
|
||||||
|
local ecs = jecs.World.new()
|
||||||
|
local mirror = require(ReplicatedStorage.mirror)
|
||||||
|
local mcs = mirror.World.new()
|
||||||
|
|
||||||
|
local C1 = ecs:component()
|
||||||
|
local C2 = ecs:entity()
|
||||||
|
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local C3 = ecs:entity()
|
||||||
|
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local C4 = ecs:entity()
|
||||||
|
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local E1 = mcs:component()
|
||||||
|
local E2 = mcs:entity()
|
||||||
|
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local E3 = mcs:entity()
|
||||||
|
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
local E4 = mcs:entity()
|
||||||
|
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
|
||||||
|
return {
|
||||||
|
ParameterGenerator = function()
|
||||||
|
end,
|
||||||
|
|
||||||
|
Functions = {
|
||||||
|
Mirror = function()
|
||||||
|
local m = mcs:entity()
|
||||||
|
for i = 1, 100 do
|
||||||
|
mcs:add(m, E3)
|
||||||
|
mcs:remove(m, E3)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Jecs = function()
|
||||||
|
local j = ecs:entity()
|
||||||
|
for i = 1, 100 do
|
||||||
|
ecs:add(j, C3)
|
||||||
|
ecs:remove(j, C3)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
48
demo/src/ReplicatedStorage/track.luau
Normal file
48
demo/src/ReplicatedStorage/track.luau
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
local events = {}
|
||||||
|
|
||||||
|
local function trackers_invoke(event, component, entity, ...)
|
||||||
|
local trackers = events[event][component]
|
||||||
|
if not trackers then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tracker in trackers do
|
||||||
|
tracker(entity, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trackers_init(event, component, fn)
|
||||||
|
local ob = events[event]
|
||||||
|
|
||||||
|
return {
|
||||||
|
connect = function(component, fn)
|
||||||
|
local trackers = ob[component]
|
||||||
|
if not trackers then
|
||||||
|
trackers = {}
|
||||||
|
ob[component] = trackers
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(trackers, fn)
|
||||||
|
end,
|
||||||
|
invoke = function(component, ...)
|
||||||
|
trackers_invoke(event, component, ...)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
return function(component, fn)
|
||||||
|
local trackers = ob[component]
|
||||||
|
if not trackers then
|
||||||
|
trackers = {}
|
||||||
|
ob[component] = trackers
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(trackers, fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local trackers = {
|
||||||
|
emplace = trackers_init("emplace"),
|
||||||
|
add = trackers_init("added"),
|
||||||
|
remove = trackers_init("removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackers
|
|
@ -5,4 +5,4 @@ registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
jabby = "alicesaidhi/jabby@0.2.0-rc.9"
|
jabby = "alicesaidhi/jabby@0.2.2"
|
||||||
|
|
238
jecs.luau
238
jecs.luau
|
@ -46,7 +46,7 @@ export type Record = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IdRecord = {
|
type IdRecord = {
|
||||||
columns: { number },
|
cache: { number },
|
||||||
counts: { number },
|
counts: { number },
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
@ -90,60 +90,35 @@ local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
|
||||||
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
|
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
|
||||||
local EcsRest = HI_COMPONENT_ID + 14
|
local EcsRest = HI_COMPONENT_ID + 14
|
||||||
|
|
||||||
local ECS_PAIR_FLAG = 0x8
|
|
||||||
local ECS_ID_FLAGS_MASK = 0x10
|
|
||||||
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
|
||||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
|
||||||
|
|
||||||
local ECS_ID_DELETE = 0b0000_0001
|
local ECS_ID_DELETE = 0b0000_0001
|
||||||
local ECS_ID_IS_TAG = 0b0000_0010
|
local ECS_ID_IS_TAG = 0b0000_0010
|
||||||
local ECS_ID_HAS_ON_ADD = 0b0000_0100
|
local ECS_ID_HAS_ON_ADD = 0b0000_0100
|
||||||
local ECS_ID_HAS_ON_SET = 0b0000_1000
|
local ECS_ID_HAS_ON_SET = 0b0000_1000
|
||||||
local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
|
local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
|
||||||
local ECS_ID_MASK = 0b0000_0000
|
local ECS_ID_MASK = 0b0000_0000
|
||||||
-- stylua: ignore end
|
|
||||||
local NULL_ARRAY = table.freeze({}) :: Column
|
|
||||||
|
|
||||||
local function FLAGS_ADD(is_pair: boolean): number
|
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
||||||
local flags = 0x0
|
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
||||||
|
|
||||||
if is_pair then
|
local NULL_ARRAY = table.freeze({})
|
||||||
flags = bit32.bor(flags, ECS_PAIR_FLAG) -- HIGHEST bit in the ID.
|
|
||||||
end
|
|
||||||
if false then
|
|
||||||
flags = bit32.bor(flags, 0x4) -- Set the second flag to true
|
|
||||||
end
|
|
||||||
if false then
|
|
||||||
flags = bit32.bor(flags, 0x2) -- Set the third flag to true
|
|
||||||
end
|
|
||||||
if false then
|
|
||||||
flags = bit32.bor(flags, 0x1) -- LAST BIT in the ID.
|
|
||||||
end
|
|
||||||
|
|
||||||
return flags
|
|
||||||
end
|
local function ECS_COMBINE(id: number, generation: number): i53
|
||||||
|
return id + (generation * ECS_ENTITY_MASK)
|
||||||
local function ECS_COMBINE(source: number, target: number): i53
|
|
||||||
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
|
|
||||||
end
|
end
|
||||||
|
local ECS_PAIR_OFFSET = 2^48
|
||||||
|
|
||||||
local function ECS_IS_PAIR(e: number): boolean
|
local function ECS_IS_PAIR(e: number): boolean
|
||||||
return if e > ECS_ENTITY_MASK then (e % ECS_ID_FLAGS_MASK) // ECS_PAIR_FLAG ~= 0 else false
|
return e > ECS_PAIR_OFFSET
|
||||||
end
|
|
||||||
|
|
||||||
-- HIGH 24 bits LOW 24 bits
|
|
||||||
local function ECS_GENERATION(e: i53): i24
|
|
||||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ECS_GENERATION_INC(e: i53)
|
local function ECS_GENERATION_INC(e: i53)
|
||||||
if e > ECS_ENTITY_MASK then
|
if e > ECS_ENTITY_MASK then
|
||||||
local flags = e // ECS_ID_FLAGS_MASK
|
local id = e % ECS_ENTITY_MASK
|
||||||
local id = flags // ECS_ENTITY_MASK
|
local generation = e // ECS_ENTITY_MASK
|
||||||
local generation = flags % ECS_GENERATION_MASK
|
|
||||||
|
|
||||||
local next_gen = generation + 1
|
local next_gen = generation + 1
|
||||||
if next_gen > ECS_GENERATION_MASK then
|
if next_gen >= ECS_GENERATION_MASK then
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -152,22 +127,23 @@ local function ECS_GENERATION_INC(e: i53)
|
||||||
return ECS_COMBINE(e, 1)
|
return ECS_COMBINE(e, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- FIRST gets the high ID
|
|
||||||
local function ECS_ENTITY_T_HI(e: i53): i24
|
|
||||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_ENTITY_MASK else e
|
|
||||||
end
|
|
||||||
|
|
||||||
-- SECOND
|
|
||||||
local function ECS_ENTITY_T_LO(e: i53): i24
|
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 e % ECS_ENTITY_MASK
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _STRIP_GENERATION(e: i53): i24
|
local function ECS_GENERATION(e: i53)
|
||||||
return ECS_ENTITY_T_LO(e)
|
return e // ECS_ENTITY_MASK
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ECS_ENTITY_T_HI(e: i53): i24
|
||||||
|
return e // ECS_ENTITY_MASK
|
||||||
end
|
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(pred), ECS_ENTITY_T_LO(obj)) + FLAGS_ADD(--[[isPair]] true) :: i53
|
pred %= ECS_ENTITY_MASK
|
||||||
|
obj %= ECS_ENTITY_MASK
|
||||||
|
|
||||||
|
return obj + (pred * 2^24) + ECS_PAIR_OFFSET
|
||||||
end
|
end
|
||||||
|
|
||||||
local function entity_index_try_get_any(entity_index: EntityIndex, entity: number): Record?
|
local function entity_index_try_get_any(entity_index: EntityIndex, entity: number): Record?
|
||||||
|
@ -236,14 +212,14 @@ local function entity_index_new_id(entity_index: EntityIndex): i53
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits
|
|
||||||
local function ecs_pair_first(world, e)
|
local function ecs_pair_first(world, e)
|
||||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_LO(e))
|
local pred = (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK
|
||||||
|
return entity_index_get_alive(world.entity_index, pred)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits
|
|
||||||
local function ecs_pair_second(world, e)
|
local function ecs_pair_second(world, e)
|
||||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_HI(e))
|
local obj = (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK
|
||||||
|
return entity_index_get_alive(world.entity_index, obj)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function query_match(query, archetype: Archetype)
|
local function query_match(query, archetype: Archetype)
|
||||||
|
@ -476,11 +452,11 @@ local function world_target(world: World, entity: i53, relation: i24, index: num
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if nth >= count then
|
if nth > count then
|
||||||
nth = nth + count + 1
|
nth = nth + count
|
||||||
end
|
end
|
||||||
|
|
||||||
local tr = idr.columns[archetype_id]
|
local tr = idr.cache[archetype_id]
|
||||||
|
|
||||||
nth = archetype.types[nth + tr]
|
nth = archetype.types[nth + tr]
|
||||||
|
|
||||||
|
@ -537,7 +513,7 @@ local function id_record_ensure(world: World, id: number): IdRecord
|
||||||
|
|
||||||
idr = {
|
idr = {
|
||||||
size = 0,
|
size = 0,
|
||||||
columns = {},
|
cache = {},
|
||||||
counts = {},
|
counts = {},
|
||||||
flags = flags,
|
flags = flags,
|
||||||
hooks = {
|
hooks = {
|
||||||
|
@ -562,7 +538,7 @@ local function archetype_append_to_records(
|
||||||
local archetype_id = archetype.id
|
local archetype_id = archetype.id
|
||||||
local archetype_records = archetype.records
|
local archetype_records = archetype.records
|
||||||
local archetype_counts = archetype.counts
|
local archetype_counts = archetype.counts
|
||||||
local idr_columns = idr.columns
|
local idr_columns = idr.cache
|
||||||
local idr_counts = idr.counts
|
local idr_counts = idr.counts
|
||||||
local tr = idr_columns[archetype_id]
|
local tr = idr_columns[archetype_id]
|
||||||
if not tr then
|
if not tr then
|
||||||
|
@ -860,16 +836,16 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown): ()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local on_add = idr_hooks.on_add
|
|
||||||
if on_add then
|
|
||||||
on_add(entity)
|
|
||||||
end
|
|
||||||
|
|
||||||
local tr = to.records[id]
|
local tr = to.records[id]
|
||||||
local column = to.columns[tr]
|
local column = to.columns[tr]
|
||||||
|
|
||||||
column[record.row] = data
|
column[record.row] = data
|
||||||
|
|
||||||
|
local on_add = idr_hooks.on_add
|
||||||
|
if on_add then
|
||||||
|
on_add(entity)
|
||||||
|
end
|
||||||
|
|
||||||
local on_set = idr_hooks.on_set
|
local on_set = idr_hooks.on_set
|
||||||
if on_set then
|
if on_set then
|
||||||
on_set(entity, data)
|
on_set(entity, data)
|
||||||
|
@ -899,15 +875,16 @@ local function world_remove(world: World, entity: i53, id: i53)
|
||||||
if not from then
|
if not from then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local to = archetype_traverse_remove(world, id, from)
|
|
||||||
|
|
||||||
if from ~= to then
|
if from.records[id] then
|
||||||
local idr = world.component_index[id]
|
local idr = world.component_index[id]
|
||||||
local on_remove = idr.hooks.on_remove
|
local on_remove = idr.hooks.on_remove
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity)
|
on_remove(entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local to = archetype_traverse_remove(world, id, record.archetype)
|
||||||
|
|
||||||
entity_move(entity_index, entity, record, to)
|
entity_move(entity_index, entity, record, to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1063,7 +1040,7 @@ local function archetype_destroy(world: World, archetype: Archetype)
|
||||||
|
|
||||||
for id in records do
|
for id in records do
|
||||||
local idr = component_index[id]
|
local idr = component_index[id]
|
||||||
idr.columns[archetype_id] = nil :: any
|
idr.cache[archetype_id] = nil :: any
|
||||||
idr.counts[archetype_id] = nil
|
idr.counts[archetype_id] = nil
|
||||||
idr.size -= 1
|
idr.size -= 1
|
||||||
records[id] = nil :: any
|
records[id] = nil :: any
|
||||||
|
@ -1122,7 +1099,7 @@ do
|
||||||
if idr then
|
if idr then
|
||||||
local flags = idr.flags
|
local flags = idr.flags
|
||||||
if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
|
if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
|
||||||
for archetype_id in idr.columns do
|
for archetype_id in idr.cache do
|
||||||
local idr_archetype = archetypes[archetype_id]
|
local idr_archetype = archetypes[archetype_id]
|
||||||
|
|
||||||
local entities = idr_archetype.entities
|
local entities = idr_archetype.entities
|
||||||
|
@ -1134,7 +1111,7 @@ do
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for archetype_id in idr.columns do
|
for archetype_id in idr.cache do
|
||||||
local idr_archetype = archetypes[archetype_id]
|
local idr_archetype = archetypes[archetype_id]
|
||||||
local entities = idr_archetype.entities
|
local entities = idr_archetype.entities
|
||||||
local n = #entities
|
local n = #entities
|
||||||
|
@ -1147,55 +1124,66 @@ do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local sparse_array = entity_index.sparse_array
|
|
||||||
local dense_array = entity_index.dense_array
|
local dense_array = entity_index.dense_array
|
||||||
|
|
||||||
if idr_t then
|
if idr_t then
|
||||||
for archetype_id in idr_t.columns do
|
local children
|
||||||
local children = {}
|
local ids
|
||||||
|
local count = 0
|
||||||
|
local archetype_ids = idr_t.cache
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
|
||||||
local idr_t_types = idr_t_archetype.types
|
local idr_t_types = idr_t_archetype.types
|
||||||
|
local entities = idr_t_archetype.entities
|
||||||
for _, child in idr_t_archetype.entities do
|
local removal_queued = false
|
||||||
table.insert(children, child)
|
|
||||||
end
|
|
||||||
|
|
||||||
local n = #children
|
|
||||||
|
|
||||||
for _, id in idr_t_types do
|
for _, id in idr_t_types do
|
||||||
if not ECS_IS_PAIR(id) then
|
if not ECS_IS_PAIR(id) then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
local object = ecs_pair_second(world, id)
|
local object = ecs_pair_second(world, id)
|
||||||
if object == delete then
|
if object ~= delete then
|
||||||
local id_record = component_index[id]
|
continue
|
||||||
local flags = id_record.flags
|
end
|
||||||
local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE)
|
local id_record = component_index[id]
|
||||||
if flags_delete_mask ~= 0 then
|
local flags = id_record.flags
|
||||||
for i = n, 1, -1 do
|
local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE)
|
||||||
world_delete(world, children[i])
|
if flags_delete_mask ~= 0 then
|
||||||
end
|
for i = #entities, 1, -1 do
|
||||||
break
|
local child = entities[i]
|
||||||
else
|
world_delete(world, child)
|
||||||
local on_remove = id_record.hooks.on_remove
|
|
||||||
local to = archetype_traverse_remove(world, id, idr_t_archetype)
|
|
||||||
local empty = #to.types == 0
|
|
||||||
for i = n, 1, -1 do
|
|
||||||
local child = children[i]
|
|
||||||
if on_remove then
|
|
||||||
on_remove(child)
|
|
||||||
end
|
|
||||||
local r = sparse_array[ECS_ENTITY_T_LO(child)]
|
|
||||||
if not empty then
|
|
||||||
entity_move(entity_index, child, r, to)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
break
|
||||||
|
else
|
||||||
|
if not ids then
|
||||||
|
ids = {}
|
||||||
|
end
|
||||||
|
ids[id] = true
|
||||||
|
removal_queued = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_t_archetype)
|
if not removal_queued then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
if not children then
|
||||||
|
children = {}
|
||||||
|
end
|
||||||
|
local n = #entities
|
||||||
|
table.move(entities, 1, n, count + 1, children)
|
||||||
|
count += n
|
||||||
|
end
|
||||||
|
|
||||||
|
if ids then
|
||||||
|
for id in ids do
|
||||||
|
for _, child in children do
|
||||||
|
world_remove(world, child, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
|
archetype_destroy(world, archetypes[archetype_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2096,7 +2084,7 @@ local function world_query(world: World, ...)
|
||||||
return q
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
for archetype_id in idr.columns do
|
for archetype_id in idr.cache do
|
||||||
local compatibleArchetype = archetypes[archetype_id]
|
local compatibleArchetype = archetypes[archetype_id]
|
||||||
if #compatibleArchetype.entities == 0 then
|
if #compatibleArchetype.entities == 0 then
|
||||||
continue
|
continue
|
||||||
|
@ -2130,9 +2118,9 @@ local function world_each(world: World, id): () -> ()
|
||||||
return NOOP
|
return NOOP
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr_columns = idr.columns
|
local idr_cache = idr.cache
|
||||||
local archetypes = world.archetypes
|
local archetypes = world.archetypes
|
||||||
local archetype_id = next(idr_columns, nil) :: number
|
local archetype_id = next(idr_cache, nil) :: number
|
||||||
local archetype = archetypes[archetype_id]
|
local archetype = archetypes[archetype_id]
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return NOOP
|
return NOOP
|
||||||
|
@ -2144,7 +2132,7 @@ local function world_each(world: World, id): () -> ()
|
||||||
return function(): any
|
return function(): any
|
||||||
local entity = entities[row]
|
local entity = entities[row]
|
||||||
while not entity do
|
while not entity do
|
||||||
archetype_id = next(idr_columns, archetype_id) :: number
|
archetype_id = next(idr_cache, archetype_id) :: number
|
||||||
if not archetype_id then
|
if not archetype_id then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -2402,51 +2390,44 @@ export type World = {
|
||||||
observable: any,
|
observable: any,
|
||||||
|
|
||||||
--- Creates a new entity
|
--- Creates a new entity
|
||||||
entity: (self: World) -> Entity,
|
entity: (self: World, id: Entity?) -> Entity,
|
||||||
--- Creates a new entity located in the first 256 ids.
|
--- Creates a new entity located in the first 256 ids.
|
||||||
--- These should be used for static components for fast access.
|
--- These should be used for static components for fast access.
|
||||||
component: <T>(self: World) -> Entity<T>,
|
component: <T>(self: World) -> Entity<T>,
|
||||||
--- Gets the target of an relationship. For example, when a user calls
|
--- Gets the target of an relationship. For example, when a user calls
|
||||||
--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
|
--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
|
||||||
target: <T, U>(self: World, id: Entity<T>, relation: Entity<U>, index: number?) -> Entity?,
|
target: (self: World, id: Entity, relation: Id, index: number?) -> Entity?,
|
||||||
--- Deletes an entity and all it's related components and relationships.
|
--- Deletes an entity and all it's related components and relationships.
|
||||||
delete: <T>(self: World, id: Entity<T>) -> (),
|
delete: (self: World, id: Entity) -> (),
|
||||||
|
|
||||||
--- Adds a component to the entity with no value
|
--- Adds a component to the entity with no value
|
||||||
add: <T, U>(self: World, id: Entity<T>, component: Id<U>) -> (),
|
add: <T>(self: World, id: Entity, component: Id) -> (),
|
||||||
--- Assigns a value to a component on the given entity
|
--- Assigns a value to a component on the given entity
|
||||||
set: <T, U>(self: World, id: Entity<T>, component: Id<U>, data: U) -> (),
|
set: <T>(self: World, id: Entity, component: Id<T>, data: T) -> (),
|
||||||
|
|
||||||
cleanup: (self: World) -> (),
|
cleanup: (self: World) -> (),
|
||||||
-- Clears an entity from the world
|
-- Clears an entity from the world
|
||||||
clear: <T>(self: World, id: Entity<T>) -> (),
|
clear: (self: World, id: Entity) -> (),
|
||||||
--- Removes a component from the given entity
|
--- Removes a component from the given entity
|
||||||
remove: <T, U>(self: World, id: Entity<T>, component: Id<U>) -> (),
|
remove: (self: World, id: Entity, component: Id) -> (),
|
||||||
--- Retrieves the value of up to 4 components. These values may be nil.
|
--- Retrieves the value of up to 4 components. These values may be nil.
|
||||||
get: (<T, A>(self: World, id: Entity<T>, Id<A>) -> A?)
|
get: (<A>(self: World, id: Entity, Id<A>) -> A?)
|
||||||
& (<T, A, B>(self: World, id: Entity<T>, Id<A>, Id<B>) -> (A?, B?))
|
& (<A, B>(self: World, id: Entity, Id<A>, Id<B>) -> (A?, B?))
|
||||||
& (<T, A, B, C>(self: World, id: Entity<T>, Id<A>, Id<B>, Id<C>) -> (A?, B?, C?))
|
& (<A, B, C>(self: World, id: Entity, Id<A>, Id<B>, Id<C>) -> (A?, B?, C?))
|
||||||
& <T, A, B, C, D>(self: World, id: Entity<T>, Id<A>, Id<B>, Id<C>, Id<D>) -> (A?, B?, C?, D?),
|
& <A, B, C, D>(self: World, id: Entity, Id<A>, Id<B>, Id<C>, Id<D>) -> (A?, B?, C?, D?),
|
||||||
|
|
||||||
--- Returns whether the entity has the ID.
|
--- Returns whether the entity has the ID.
|
||||||
has: (<T, U>(self: World, entity: Entity<T>, ...Id<U>) -> boolean)
|
has: (self: World, entity: Entity, ...Id) -> boolean,
|
||||||
& (<T, U, V>(self: World, entity: Entity<T>, Id<U>, Id<V>) -> boolean)
|
|
||||||
& (<T, U, V, W>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>) -> boolean)
|
|
||||||
& (<T, U, V, W, X>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>) -> boolean)
|
|
||||||
& (<T, U, V, W, X, Y>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>) -> boolean)
|
|
||||||
& (<T, U, V, W, X, Y, Z>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>, Id<Z>) -> boolean)
|
|
||||||
& (<T, U, V, W, X, Y, Z, A>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>, Id<Z>, Id<A>) -> boolean)
|
|
||||||
& (<T, U, V, W, X, Y, Z, A>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>, Id<Z>, Id<A>, ...unknown) -> boolean),
|
|
||||||
|
|
||||||
--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
|
--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
|
||||||
parent: <T>(self: World, entity: Entity<T>) -> Entity,
|
parent:(self: World, entity: Entity) -> Entity,
|
||||||
|
|
||||||
--- Checks if the world contains the given entity
|
--- Checks if the world contains the given entity
|
||||||
contains: <T>(self: World, entity: Entity<T>) -> boolean,
|
contains:(self: World, entity: Entity) -> boolean,
|
||||||
|
|
||||||
each: <T>(self: World, id: Id<T>) -> () -> Entity,
|
each: (self: World, id: Id) -> () -> Entity,
|
||||||
|
|
||||||
children: <T>(self: World, id: Id<T>) -> () -> Entity,
|
children: (self: World, id: Id) -> () -> Entity,
|
||||||
|
|
||||||
--- Searches the world for entities that match a given query
|
--- Searches the world for entities that match a given query
|
||||||
query: (<A>(World, Id<A>) -> Query<A>)
|
query: (<A>(World, Id<A>) -> Query<A>)
|
||||||
|
@ -2477,6 +2458,7 @@ export type World = {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
World = World :: { new: () -> World },
|
World = World :: { new: () -> World },
|
||||||
|
world = World.new :: () -> World,
|
||||||
|
|
||||||
OnAdd = EcsOnAdd :: Entity<(entity: Entity) -> ()>,
|
OnAdd = EcsOnAdd :: Entity<(entity: Entity) -> ()>,
|
||||||
OnRemove = EcsOnRemove :: Entity<(entity: Entity) -> ()>,
|
OnRemove = EcsOnRemove :: Entity<(entity: Entity) -> ()>,
|
||||||
|
@ -2500,6 +2482,8 @@ return {
|
||||||
ECS_GENERATION = ECS_GENERATION,
|
ECS_GENERATION = ECS_GENERATION,
|
||||||
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
|
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
|
||||||
|
|
||||||
|
ECS_ID_DELETE = ECS_ID_DELETE,
|
||||||
|
|
||||||
IS_PAIR = ECS_IS_PAIR,
|
IS_PAIR = ECS_IS_PAIR,
|
||||||
pair_first = ecs_pair_first,
|
pair_first = ecs_pair_first,
|
||||||
pair_second = ecs_pair_second,
|
pair_second = ecs_pair_second,
|
||||||
|
|
2765
mirror.luau
2765
mirror.luau
File diff suppressed because it is too large
Load diff
25
test/devtools_test.luau
Normal file
25
test/devtools_test.luau
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local pair = jecs.pair
|
||||||
|
local ChildOf = jecs.ChildOf
|
||||||
|
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
||||||
|
local pe = require("@tools/entity_visualiser").prettify
|
||||||
|
local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false})
|
||||||
|
local FriendsWith = world:component()
|
||||||
|
local _1 = world:print_snapshot()
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:delete(e2)
|
||||||
|
|
||||||
|
local _2 = world:print_snapshot()
|
||||||
|
local e3 = world:entity()
|
||||||
|
world:add(e3, pair(ChildOf, e1))
|
||||||
|
local e4 = world:entity()
|
||||||
|
world:add(e4, pair(FriendsWith, e3))
|
||||||
|
local _3 = world:print_snapshot()
|
||||||
|
world:delete(e1)
|
||||||
|
world:delete(e3)
|
||||||
|
local _4 = world:print_snapshot()
|
||||||
|
world:print_entity_index()
|
||||||
|
world:entity()
|
||||||
|
world:entity()
|
||||||
|
local _5 = world:print_snapshot()
|
147
test/tests.luau
147
test/tests.luau
|
@ -26,6 +26,50 @@ local N = 2 ^ 8
|
||||||
type World = jecs.World
|
type World = jecs.World
|
||||||
type Entity<T=nil> = jecs.Entity<T>
|
type Entity<T=nil> = jecs.Entity<T>
|
||||||
|
|
||||||
|
local c = {
|
||||||
|
white_underline = function(s: any)
|
||||||
|
return `\27[1;4m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
white = function(s: any)
|
||||||
|
return `\27[37;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
green = function(s: any)
|
||||||
|
return `\27[32;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
red = function(s: any)
|
||||||
|
return `\27[31;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
yellow = function(s: any)
|
||||||
|
return `\27[33;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
red_highlight = function(s: any)
|
||||||
|
return `\27[41;1;30m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
green_highlight = function(s: any)
|
||||||
|
return `\27[42;1;30m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
gray = function(s: any)
|
||||||
|
return `\27[30;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function pe(e)
|
||||||
|
local gen = ECS_GENERATION(e)
|
||||||
|
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pp(e)
|
||||||
|
local gen = ECS_GENERATION(e)
|
||||||
|
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{jecs.ECS_ENTITY_T_HI(e)}`)
|
||||||
|
end
|
||||||
|
|
||||||
local function debug_world_inspect(world: World)
|
local function debug_world_inspect(world: World)
|
||||||
local function record(e): jecs.Record
|
local function record(e): jecs.Record
|
||||||
return entity_index_try_get_any(world.entity_index, e) :: any
|
return entity_index_try_get_any(world.entity_index, e) :: any
|
||||||
|
@ -67,10 +111,63 @@ local function debug_world_inspect(world: World)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local dwi = debug_world_inspect
|
||||||
|
|
||||||
local function name(world, e)
|
local function name(world, e)
|
||||||
return world:get(e, jecs.Name)
|
return world:get(e, jecs.Name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
TEST("#repro", function()
|
||||||
|
local world = world_new()
|
||||||
|
|
||||||
|
local function getTargets(relation)
|
||||||
|
local tgts = {}
|
||||||
|
local pairwildcard = pair(relation, jecs.Wildcard)
|
||||||
|
for _, archetype in world:query(pairwildcard):archetypes() do
|
||||||
|
local tr = archetype.records[pairwildcard]
|
||||||
|
local count = archetype.counts[pairwildcard]
|
||||||
|
local types = archetype.types
|
||||||
|
for _, entity in archetype.entities do
|
||||||
|
for i = 0, count - 1 do
|
||||||
|
local tgt = jecs.pair_second(world, types[i + tr])
|
||||||
|
table.insert(tgts, tgt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return tgts
|
||||||
|
end
|
||||||
|
|
||||||
|
local Attacks = world:component()
|
||||||
|
local Eats = world:component()
|
||||||
|
|
||||||
|
local function setAttacksAndEats(entity1, entity2)
|
||||||
|
world:add(entity1, pair(Attacks, entity2))
|
||||||
|
world:add(entity1, pair(Eats, entity2))
|
||||||
|
end
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
local e3 = world:entity()
|
||||||
|
setAttacksAndEats(e3, e1)
|
||||||
|
setAttacksAndEats(e3, e2)
|
||||||
|
setAttacksAndEats(e1, e2)
|
||||||
|
local d = dwi(world)
|
||||||
|
world:delete(e2)
|
||||||
|
local types1 = { pair(Attacks, e1), pair(Eats, e1) }
|
||||||
|
table.sort(types1)
|
||||||
|
|
||||||
|
|
||||||
|
CHECK(d.tbl(e1).type == "")
|
||||||
|
CHECK(d.tbl(e3).type == table.concat(types1, "_"))
|
||||||
|
|
||||||
|
for _, entity in getTargets(Attacks) do
|
||||||
|
CHECK(entity == e1)
|
||||||
|
end
|
||||||
|
for _, entity in getTargets(Eats) do
|
||||||
|
CHECK(entity == e1)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
TEST("archetype", function()
|
TEST("archetype", function()
|
||||||
local archetype_traverse_add = jecs.archetype_traverse_add
|
local archetype_traverse_add = jecs.archetype_traverse_add
|
||||||
local archetype_traverse_remove = jecs.archetype_traverse_remove
|
local archetype_traverse_remove = jecs.archetype_traverse_remove
|
||||||
|
@ -140,6 +237,9 @@ TEST("world:cleanup()", function()
|
||||||
CHECK(#archetype_index["1_2_3"].entities == 1)
|
CHECK(#archetype_index["1_2_3"].entities == 1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
local pe = require("@tools/entity_visualiser").prettify
|
||||||
|
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
||||||
|
|
||||||
TEST("world:entity()", function()
|
TEST("world:entity()", function()
|
||||||
do
|
do
|
||||||
CASE("unique IDs")
|
CASE("unique IDs")
|
||||||
|
@ -1199,6 +1299,7 @@ TEST("world:delete", function()
|
||||||
world:delete(e)
|
world:delete(e)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
local d = debug_world_inspect(world)
|
||||||
for i, friend in friends do
|
for i, friend in friends do
|
||||||
CHECK(not world:has(friend, pair(FriendsWith, e)))
|
CHECK(not world:has(friend, pair(FriendsWith, e)))
|
||||||
CHECK(world:has(friend, Health))
|
CHECK(world:has(friend, Health))
|
||||||
|
@ -1274,10 +1375,15 @@ TEST("world:target", function()
|
||||||
world:add(e, pair(C, D))
|
world:add(e, pair(C, D))
|
||||||
|
|
||||||
CHECK(pair(A, B) < pair(A, C))
|
CHECK(pair(A, B) < pair(A, C))
|
||||||
CHECK(pair(A, E) < pair(B, C))
|
CHECK(pair(A, C) < pair(A, D))
|
||||||
|
CHECK(pair(C, A) < pair(C, D))
|
||||||
|
|
||||||
local records = debug_world_inspect(world).records(e)
|
local records = debug_world_inspect(world).records(e)
|
||||||
CHECK(jecs.pair_first(world, pair(B, C)) == B)
|
CHECK(jecs.pair_first(world, pair(B, C)) == B)
|
||||||
|
local r = jecs.entity_index_try_get(world.entity_index, e)
|
||||||
|
local archetype = r.archetype
|
||||||
|
local counts = archetype.counts
|
||||||
|
CHECK(counts[pair(A, __)] == 4)
|
||||||
CHECK(records[pair(B, C)] > records[pair(A, E)])
|
CHECK(records[pair(B, C)] > records[pair(A, E)])
|
||||||
CHECK(world:target(e, A, 0) == B)
|
CHECK(world:target(e, A, 0) == B)
|
||||||
CHECK(world:target(e, A, 1) == C)
|
CHECK(world:target(e, A, 1) == C)
|
||||||
|
@ -1287,6 +1393,28 @@ TEST("world:target", function()
|
||||||
CHECK(world:target(e, B, 1) == D)
|
CHECK(world:target(e, B, 1) == D)
|
||||||
CHECK(world:target(e, C, 0) == D)
|
CHECK(world:target(e, C, 0) == D)
|
||||||
CHECK(world:target(e, C, 1) == nil)
|
CHECK(world:target(e, C, 1) == nil)
|
||||||
|
|
||||||
|
-- for id in archetype.records do
|
||||||
|
-- local f = world:get(ecs_pair_first(world, id), jecs.Name)
|
||||||
|
-- local s = world:get(ecs_pair_second(world, id), jecs.Name)
|
||||||
|
-- print(`({f}, {s})`)
|
||||||
|
-- end
|
||||||
|
--
|
||||||
|
|
||||||
|
CHECK(archetype.records[pair(A, B)] == 1)
|
||||||
|
CHECK(archetype.records[pair(A, C)] == 2)
|
||||||
|
CHECK(archetype.records[pair(A, D)] == 3)
|
||||||
|
CHECK(archetype.records[pair(A, E)] == 4)
|
||||||
|
-- print("(A, B)", archetype.records[pair(A, B)])
|
||||||
|
-- print("(A, C)", archetype.records[pair(A, C)])
|
||||||
|
-- print("(A, D)", archetype.records[pair(A, D)])
|
||||||
|
-- print("(A, E)", archetype.records[pair(A, E)])
|
||||||
|
|
||||||
|
-- print(pair(A, D), pair(B, C))
|
||||||
|
-- print("(B, C)", archetype.records[pair(B, C)])
|
||||||
|
|
||||||
|
CHECK(world:target(e, C, 0) == D)
|
||||||
|
CHECK(world:target(e, C, 1) == nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
|
@ -1377,11 +1505,12 @@ TEST("Hooks", function()
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component() :: Entity<boolean>
|
local A = world:component() :: Entity<boolean>
|
||||||
local e1 = world:entity()
|
local e1 = world:entity()
|
||||||
world:add(e1, A)
|
|
||||||
world:set(A, jecs.OnRemove, function(entity)
|
world:set(A, jecs.OnRemove, function(entity)
|
||||||
CHECK(e1 == entity)
|
CHECK(e1 == entity)
|
||||||
CHECK(not world:has(e1, A))
|
CHECK(world:has(e1, A))
|
||||||
end)
|
end)
|
||||||
|
world:add(e1, A)
|
||||||
|
|
||||||
world:remove(e1, A)
|
world:remove(e1, A)
|
||||||
CHECK(not world:has(e1, A))
|
CHECK(not world:has(e1, A))
|
||||||
end
|
end
|
||||||
|
@ -1401,7 +1530,7 @@ TEST("Hooks", function()
|
||||||
world:set(e, A, true)
|
world:set(e, A, true)
|
||||||
world:remove(e, A)
|
world:remove(e, A)
|
||||||
CHECK(not world:get(e, A))
|
CHECK(not world:get(e, A))
|
||||||
CHECK(not world:get(e, B))
|
CHECK(world:get(e, B))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
@ -1469,18 +1598,18 @@ TEST("repro", function()
|
||||||
do CASE "#1"
|
do CASE "#1"
|
||||||
local world = world_new()
|
local world = world_new()
|
||||||
local reproEntity = world:component()
|
local reproEntity = world:component()
|
||||||
local components = { Cooldown = world:component() :: jecs.Id<number> }
|
local components = { Cooldown = world:component() :: jecs.Entity<number> }
|
||||||
world:set(reproEntity, components.Cooldown, 2)
|
world:set(reproEntity, components.Cooldown, 2)
|
||||||
|
|
||||||
local function updateCooldowns(dt: number)
|
local function updateCooldowns(dt: number)
|
||||||
local toRemove = {}
|
local toRemove = {}
|
||||||
|
|
||||||
for id, cooldown in world:query(components.Cooldown):iter() do
|
local it = world:query(components.Cooldown):iter()
|
||||||
|
for id, cooldown in it do
|
||||||
cooldown -= dt
|
cooldown -= dt
|
||||||
|
|
||||||
if cooldown <= 0 then
|
if cooldown <= 0 then
|
||||||
table.insert(toRemove, id)
|
table.insert(toRemove, id)
|
||||||
print("removing")
|
|
||||||
-- world:remove(id, components.Cooldown)
|
-- world:remove(id, components.Cooldown)
|
||||||
else
|
else
|
||||||
world:set(id, components.Cooldown, cooldown)
|
world:set(id, components.Cooldown, cooldown)
|
||||||
|
@ -1529,6 +1658,10 @@ TEST("wildcard query", function()
|
||||||
|
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
|
|
||||||
|
local p = pair(Relation, A)
|
||||||
|
CHECK(jecs.pair_first(world, p) == Relation)
|
||||||
|
CHECK(jecs.pair_second(world, p) == A)
|
||||||
|
local w = dwi(world)
|
||||||
world:add(entity, pair(Relation, A))
|
world:add(entity, pair(Relation, A))
|
||||||
|
|
||||||
local counter = 0
|
local counter = 0
|
||||||
|
|
33
tools/ansi.luau
Normal file
33
tools/ansi.luau
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
return {
|
||||||
|
white_underline = function(s: any)
|
||||||
|
return `\27[1;4m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
white = function(s: any)
|
||||||
|
return `\27[37;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
green = function(s: any)
|
||||||
|
return `\27[32;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
red = function(s: any)
|
||||||
|
return `\27[31;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
yellow = function(s: any)
|
||||||
|
return `\27[33;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
red_highlight = function(s: any)
|
||||||
|
return `\27[41;1;30m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
green_highlight = function(s: any)
|
||||||
|
return `\27[42;1;30m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
|
||||||
|
gray = function(s: any)
|
||||||
|
return `\27[30;1m{s}\27[0m`
|
||||||
|
end,
|
||||||
|
}
|
43
tools/entity_visualiser.luau
Normal file
43
tools/entity_visualiser.luau
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local ECS_GENERATION = jecs.ECS_GENERATION
|
||||||
|
local ECS_ID = jecs.ECS_ID
|
||||||
|
local ansi = require("@tools/ansi")
|
||||||
|
|
||||||
|
local function pe(e: any)
|
||||||
|
local gen = ECS_GENERATION(e)
|
||||||
|
return ansi.green(`e{ECS_ID(e)}`)..ansi.yellow(`v{gen}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function name(world: jecs.World, id: any)
|
||||||
|
return world:get(id, jecs.Name) or `${id}`
|
||||||
|
end
|
||||||
|
|
||||||
|
local function components(world: jecs.World, entity: any)
|
||||||
|
local r = jecs.entity_index_try_get(world.entity_index, entity)
|
||||||
|
if not r then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local archetype = r.archetype
|
||||||
|
local row = r.row
|
||||||
|
print(`Entity {pe(entity)}`)
|
||||||
|
print("-----------------------------------------------------")
|
||||||
|
for i, column in archetype.columns do
|
||||||
|
local component = archetype.types[i]
|
||||||
|
local n
|
||||||
|
if jecs.IS_PAIR(component) then
|
||||||
|
n = `({name(world, jecs.pair_first(world, component))}, {name(world, jecs.pair_second(world, component))})`
|
||||||
|
else
|
||||||
|
n = name(world, component)
|
||||||
|
end
|
||||||
|
local data = column[row] or "TAG"
|
||||||
|
print(`| {n} | {data} |`)
|
||||||
|
end
|
||||||
|
print("-----------------------------------------------------")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
components = components,
|
||||||
|
prettify = pe,
|
||||||
|
}
|
215
tools/lifetime_tracker.luau
Normal file
215
tools/lifetime_tracker.luau
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local ECS_GENERATION = jecs.ECS_GENERATION
|
||||||
|
local ECS_ID = jecs.ECS_ID
|
||||||
|
local __ = jecs.Wildcard
|
||||||
|
local pair = jecs.pair
|
||||||
|
|
||||||
|
local prettify = require("@tools/entity_visualiser").prettify
|
||||||
|
|
||||||
|
local pe = prettify
|
||||||
|
local ansi = require("@tools/ansi")
|
||||||
|
|
||||||
|
function print_centered_entity(entity, width: number)
|
||||||
|
local entity_str = tostring(entity)
|
||||||
|
local entity_length = #entity_str
|
||||||
|
|
||||||
|
local padding_total = width - 2 - entity_length
|
||||||
|
|
||||||
|
local padding_left = math.floor(padding_total / 2)
|
||||||
|
local padding_right = padding_total - padding_left
|
||||||
|
|
||||||
|
local centered_str = string.rep(" ", padding_left) .. entity_str .. string.rep(" ", padding_right)
|
||||||
|
|
||||||
|
print("|" .. centered_str .. "|")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function name(world, e)
|
||||||
|
return world:get(world, e, jecs.Name) or pe(e)
|
||||||
|
end
|
||||||
|
local padding_enabled = false
|
||||||
|
local function pad()
|
||||||
|
if padding_enabled then
|
||||||
|
print("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lifetime_tracker_add(world: jecs.World, opt)
|
||||||
|
local entity_index = world.entity_index
|
||||||
|
local dense_array = entity_index.dense_array
|
||||||
|
local component_index = world.component_index
|
||||||
|
|
||||||
|
local ENTITY_RANGE = (jecs.Rest :: any) + 1
|
||||||
|
|
||||||
|
local w = setmetatable({}, { __index = world })
|
||||||
|
|
||||||
|
padding_enabled = opt.padding_enabled
|
||||||
|
|
||||||
|
local world_entity = world.entity
|
||||||
|
w.entity = function(self, entity)
|
||||||
|
if entity then
|
||||||
|
return world_entity(world, entity)
|
||||||
|
end
|
||||||
|
local will_recycle = entity_index.max_id ~= entity_index.alive_count
|
||||||
|
local e = world_entity(world)
|
||||||
|
if will_recycle then
|
||||||
|
print(`*recycled {pe(e)}`)
|
||||||
|
else
|
||||||
|
print(`*created {pe(e)}`)
|
||||||
|
end
|
||||||
|
pad()
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
w.print_entity_index = function(self)
|
||||||
|
local max_id = entity_index.max_id
|
||||||
|
local alive_count = entity_index.alive_count
|
||||||
|
local alive = table.move(dense_array, 1+jecs.Rest::any, alive_count, 1, {})
|
||||||
|
local dead = table.move(dense_array, alive_count + 1, max_id, 1, {})
|
||||||
|
|
||||||
|
local sep = "|--------|"
|
||||||
|
if #alive > 0 then
|
||||||
|
print("|-alive--|")
|
||||||
|
for i = 1, #alive do
|
||||||
|
local e = pe(alive[i])
|
||||||
|
print_centered_entity(e, 32)
|
||||||
|
print(sep)
|
||||||
|
end
|
||||||
|
print("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
if #dead > 0 then
|
||||||
|
print("|--dead--|")
|
||||||
|
for i = 1, #dead do
|
||||||
|
print_centered_entity(pe(dead[i]), 32)
|
||||||
|
print(sep)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pad()
|
||||||
|
end
|
||||||
|
local timelines = {}
|
||||||
|
w.print_snapshot = function(self)
|
||||||
|
local timeline = #timelines + 1
|
||||||
|
local entity_column_width = 10
|
||||||
|
local status_column_width = 8
|
||||||
|
|
||||||
|
local header = string.format("| %-" .. entity_column_width .. "s |", "Entity")
|
||||||
|
for i = 1, timeline do
|
||||||
|
header = header .. string.format(" %-" .. status_column_width .. "s |", string.format("T%d", i))
|
||||||
|
end
|
||||||
|
|
||||||
|
local max_id = entity_index.max_id
|
||||||
|
local alive_count = entity_index.alive_count
|
||||||
|
local alive = table.move(dense_array, 1+jecs.Rest::any, alive_count, 1, {})
|
||||||
|
local dead = table.move(dense_array, alive_count + 1, max_id, 1, {})
|
||||||
|
|
||||||
|
local data = {}
|
||||||
|
print("-------------------------------------------------------------------")
|
||||||
|
print(header)
|
||||||
|
|
||||||
|
-- Store the snapshot data for this timeline
|
||||||
|
for i = ENTITY_RANGE, max_id do
|
||||||
|
if dense_array[i] then
|
||||||
|
local entity = dense_array[i]
|
||||||
|
local id = ECS_ID(entity)
|
||||||
|
local status = "alive"
|
||||||
|
if not world:contains(entity) then
|
||||||
|
status = "dead"
|
||||||
|
end
|
||||||
|
data[id] = status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(timelines, data)
|
||||||
|
|
||||||
|
-- Create a table to hold entity data for sorting
|
||||||
|
local entities = {}
|
||||||
|
for i = ENTITY_RANGE, max_id do
|
||||||
|
if dense_array[i] then
|
||||||
|
local entity = dense_array[i]
|
||||||
|
local id = ECS_ID(entity)
|
||||||
|
-- Push entity and id into the new `entities` table
|
||||||
|
table.insert(entities, {entity = entity, id = id})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Sort the entities by ECS_ID
|
||||||
|
table.sort(entities, function(a, b)
|
||||||
|
return a.id < b.id
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Print the sorted rows
|
||||||
|
for _, entity_data in ipairs(entities) do
|
||||||
|
local entity = entity_data.entity
|
||||||
|
local id = entity_data.id
|
||||||
|
local status = "alive"
|
||||||
|
if id > alive_count then
|
||||||
|
status = "dead"
|
||||||
|
end
|
||||||
|
local row = string.format("| %-" .. entity_column_width .. "s |", pe(entity))
|
||||||
|
for j = 1, timeline do
|
||||||
|
local timeline_data = timelines[j]
|
||||||
|
local entity_data = timeline_data[id]
|
||||||
|
if entity_data then
|
||||||
|
row = row .. string.format(" %-" .. status_column_width .. "s |", entity_data)
|
||||||
|
else
|
||||||
|
row = row .. string.format(" %-" .. status_column_width .. "s |", "-")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print(row)
|
||||||
|
end
|
||||||
|
print("-------------------------------------------------------------------")
|
||||||
|
pad()
|
||||||
|
end
|
||||||
|
local world_add = world.add
|
||||||
|
local relations = {}
|
||||||
|
w.add = function(self, entity: any, component: any)
|
||||||
|
world_add(world, entity, component)
|
||||||
|
if jecs.IS_PAIR(component) then
|
||||||
|
local relation = jecs.pair_first(world, component)
|
||||||
|
local target = jecs.pair_second(world, component)
|
||||||
|
print(`*added ({pe(relation)}, {pe(target)}) to {pe(entity)}`)
|
||||||
|
pad()
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local world_delete = world.delete
|
||||||
|
w.delete = function(self, e)
|
||||||
|
world_delete(world, e)
|
||||||
|
|
||||||
|
local idr_t = component_index[pair(__, e)]
|
||||||
|
if idr_t then
|
||||||
|
for archetype_id in idr_t.cache do
|
||||||
|
local archetype = world.archetypes[archetype_id]
|
||||||
|
for _, id in archetype.types do
|
||||||
|
if not jecs.IS_PAIR(id) then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local object = jecs.pair_second(world, id)
|
||||||
|
if object ~= e then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local id_record = component_index[id]
|
||||||
|
local flags = id_record.flags
|
||||||
|
local flags_delete_mask: number = bit32.band(flags, jecs.ECS_ID_DELETE)
|
||||||
|
if flags_delete_mask ~= 0 then
|
||||||
|
for _, entity in archetype.entities do
|
||||||
|
print(`*deleted dependant {pe(entity)} of {pe(e)}`)
|
||||||
|
pad()
|
||||||
|
end
|
||||||
|
break
|
||||||
|
else
|
||||||
|
for _, entity in archetype.entities do
|
||||||
|
print(`*removed dependency ({pe(jecs.pair_first(world, id))}, {pe(object)}) from {pe(entity)}`)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(`*deleted {pe(e)}`)
|
||||||
|
pad()
|
||||||
|
end
|
||||||
|
return w
|
||||||
|
end
|
||||||
|
|
||||||
|
return lifetime_tracker_add
|
Loading…
Reference in a new issue