mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Fix generation increment overflowing id
This commit is contained in:
parent
ceb7ea9866
commit
b40af9fe9d
4 changed files with 384 additions and 3 deletions
|
@ -148,7 +148,7 @@ local function ECS_GENERATION_INC(e: i53)
|
|||
return id
|
||||
end
|
||||
|
||||
return ECS_COMBINE(id, next_gen) + flags
|
||||
return ECS_COMBINE(id, next_gen)
|
||||
end
|
||||
return ECS_COMBINE(e, 1)
|
||||
end
|
||||
|
@ -1683,13 +1683,13 @@ function World.new()
|
|||
self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
|
||||
|
||||
for i = 1, HI_COMPONENT_ID do
|
||||
local e = entity_index_new_id(entity_index, i)
|
||||
local e = entity_index_new_id(entity_index)
|
||||
world_add(self, e, EcsComponent)
|
||||
end
|
||||
|
||||
for i = HI_COMPONENT_ID + 1, EcsRest do
|
||||
-- Initialize built-in components
|
||||
entity_index_new_id(entity_index, i)
|
||||
entity_index_new_id(entity_index)
|
||||
end
|
||||
|
||||
world_add(self, EcsName, EcsComponent)
|
||||
|
@ -1885,4 +1885,6 @@ return {
|
|||
entity_index_try_get = entity_index_try_get,
|
||||
entity_index_try_get_any = entity_index_try_get_any,
|
||||
entity_index_is_alive = entity_index_is_alive,
|
||||
entity_index_remove = entity_index_remove,
|
||||
entity_index_new_id = entity_index_new_id
|
||||
}
|
||||
|
|
192
test/gen.luau
Normal file
192
test/gen.luau
Normal file
|
@ -0,0 +1,192 @@
|
|||
type i53 = number
|
||||
type i24 = number
|
||||
|
||||
type Ty = { i53 }
|
||||
type ArchetypeId = number
|
||||
|
||||
type Column = { any }
|
||||
|
||||
type Map<K, V> = { [K]: V }
|
||||
|
||||
type GraphEdge = {
|
||||
from: Archetype,
|
||||
to: Archetype?,
|
||||
prev: GraphEdge?,
|
||||
next: GraphEdge?,
|
||||
id: number,
|
||||
}
|
||||
|
||||
type GraphEdges = Map<i53, GraphEdge>
|
||||
|
||||
type GraphNode = {
|
||||
add: GraphEdges,
|
||||
remove: GraphEdges,
|
||||
refs: GraphEdge,
|
||||
}
|
||||
|
||||
type ArchetypeRecord = {
|
||||
count: number,
|
||||
column: number,
|
||||
}
|
||||
|
||||
export type Archetype = {
|
||||
id: number,
|
||||
node: GraphNode,
|
||||
types: Ty,
|
||||
type: string,
|
||||
entities: { number },
|
||||
columns: { Column },
|
||||
records: { ArchetypeRecord },
|
||||
}
|
||||
type Record = {
|
||||
archetype: Archetype,
|
||||
row: number,
|
||||
dense: i24,
|
||||
}
|
||||
|
||||
type EntityIndex = {
|
||||
dense_array: Map<i24, i53>,
|
||||
sparse_array: Map<i53, Record>,
|
||||
sparse_count: number,
|
||||
alive_count: number,
|
||||
max_id: number
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
|
||||
-- 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
|
||||
|
||||
local function ECS_COMBINE(source: number, target: number): i53
|
||||
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
|
||||
end
|
||||
|
||||
local function ECS_GENERATION_INC(e: i53)
|
||||
if e > ECS_ENTITY_MASK then
|
||||
local flags = e // ECS_ID_FLAGS_MASK
|
||||
local id = flags // ECS_ENTITY_MASK
|
||||
local generation = flags % ECS_GENERATION_MASK
|
||||
|
||||
local next_gen = generation + 1
|
||||
if next_gen > ECS_GENERATION_MASK then
|
||||
return id
|
||||
end
|
||||
|
||||
return ECS_COMBINE(id, next_gen) + flags
|
||||
end
|
||||
return ECS_COMBINE(e, 1)
|
||||
end
|
||||
local function ECS_ENTITY_T_LO(e: i53): i24
|
||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
|
||||
end
|
||||
|
||||
|
||||
local function entity_index_try_get_any(entity_index: EntityIndex, entity: number): Record?
|
||||
local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)]
|
||||
if not r then
|
||||
return nil
|
||||
end
|
||||
|
||||
if not r or r.dense == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return r
|
||||
end
|
||||
|
||||
local function entity_index_try_get(entity_index: EntityIndex, entity: number): Record?
|
||||
local r = entity_index_try_get_any(entity_index, entity)
|
||||
if r then
|
||||
local r_dense = r.dense
|
||||
if r_dense > entity_index.alive_count then
|
||||
return nil
|
||||
end
|
||||
if entity_index.dense_array[r_dense] ~= entity then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
local function entity_index_get_alive(entity_index: EntityIndex,
|
||||
entity: number): number
|
||||
|
||||
local r = entity_index_try_get_any(entity_index, entity)
|
||||
if r then
|
||||
return entity_index.dense_array[r.dense]
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function entity_index_remove(entity_index: EntityIndex, entity: number)
|
||||
local r = entity_index_try_get(entity_index, entity)
|
||||
if not r then
|
||||
return
|
||||
end
|
||||
local dense_array = entity_index.dense_array
|
||||
local index_of_deleted_entity = r.dense
|
||||
local last_entity_alive_at_index = entity_index.alive_count
|
||||
entity_index.alive_count -= 1
|
||||
|
||||
local last_alive_entity = dense_array[last_entity_alive_at_index]
|
||||
local r_swap = entity_index_try_get_any(
|
||||
entity_index, last_alive_entity) :: Record
|
||||
r_swap.dense = index_of_deleted_entity
|
||||
r.archetype = nil :: any
|
||||
r.row = nil :: any
|
||||
r.dense = last_entity_alive_at_index
|
||||
|
||||
dense_array[index_of_deleted_entity] = last_alive_entity
|
||||
dense_array[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
|
||||
end
|
||||
|
||||
local function entity_index_new_id(entity_index: EntityIndex, data): i53
|
||||
local dense_array = entity_index.dense_array
|
||||
if entity_index.alive_count ~= #dense_array then
|
||||
entity_index.alive_count += 1
|
||||
local id = dense_array[entity_index.alive_count]
|
||||
return id
|
||||
end
|
||||
entity_index.max_id +=1
|
||||
local id = entity_index.max_id
|
||||
entity_index.alive_count += 1
|
||||
|
||||
dense_array[entity_index.alive_count] = id
|
||||
entity_index.sparse_array[id] = {
|
||||
dense = entity_index.alive_count,
|
||||
archetype = data
|
||||
} :: Record
|
||||
|
||||
entity_index.sparse_count += 1
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
local function entity_index_is_alive(entity_index: EntityIndex, entity: number)
|
||||
return entity_index_try_get(entity_index, entity) ~= nil
|
||||
end
|
||||
|
||||
local eidx = {
|
||||
alive_count = 0,
|
||||
max_id = 0,
|
||||
sparse_array = {} :: { Record },
|
||||
sparse_count = 0,
|
||||
dense_array = {} :: { i53 }
|
||||
}
|
||||
local e1v0 = entity_index_new_id(eidx, "e1v0")
|
||||
local e2v0 = entity_index_new_id(eidx, "e2v0")
|
||||
local e3v0 = entity_index_new_id(eidx, "e3v0")
|
||||
local e4v0 = entity_index_new_id(eidx, "e4v0")
|
||||
local e5v0 = entity_index_new_id(eidx, "e5v0")
|
||||
local t = require("@testkit")
|
||||
local tprint = t.print
|
||||
entity_index_remove(eidx, e5v0)
|
||||
local e5v1 = entity_index_new_id(eidx, "e5v1")
|
||||
entity_index_remove(eidx, e2v0)
|
||||
tprint(eidx)
|
157
test/lol.luau
Normal file
157
test/lol.luau
Normal file
|
@ -0,0 +1,157 @@
|
|||
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 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)
|
||||
|
||||
type i53 = number
|
||||
type i24 = number
|
||||
|
||||
local function ECS_ENTITY_T_LO(e: i53): i24
|
||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
|
||||
end
|
||||
|
||||
local function ECS_GENERATION(e: i53): i24
|
||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
|
||||
end
|
||||
|
||||
local ECS_ID = ECS_ENTITY_T_LO
|
||||
|
||||
local function ECS_COMBINE(source: number, target: number): i53
|
||||
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
|
||||
end
|
||||
|
||||
local function ECS_GENERATION_INC(e: i53)
|
||||
if e > ECS_ENTITY_MASK then
|
||||
local flags = e // ECS_ID_FLAGS_MASK
|
||||
local id = flags // ECS_ENTITY_MASK
|
||||
local generation = flags % ECS_GENERATION_MASK
|
||||
|
||||
local next_gen = generation + 1
|
||||
if next_gen > ECS_GENERATION_MASK then
|
||||
return id
|
||||
end
|
||||
|
||||
return ECS_COMBINE(id, next_gen) + flags
|
||||
end
|
||||
return ECS_COMBINE(e, 1)
|
||||
end
|
||||
|
||||
local function bl()
|
||||
print("")
|
||||
end
|
||||
|
||||
local function pe(e)
|
||||
local gen = ECS_GENERATION(e)
|
||||
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
|
||||
end
|
||||
|
||||
local function dprint(tbl: { [number]: number })
|
||||
bl()
|
||||
print("--------")
|
||||
for i, e in tbl do
|
||||
print("| "..pe(e).." |")
|
||||
print("--------")
|
||||
end
|
||||
bl()
|
||||
end
|
||||
|
||||
local max_id = 0
|
||||
local alive_count = 0
|
||||
local dense = {}
|
||||
local sparse = {}
|
||||
local function alloc()
|
||||
if alive_count ~= #dense then
|
||||
alive_count += 1
|
||||
print("*recycled", pe(dense[alive_count]))
|
||||
return dense[alive_count]
|
||||
end
|
||||
max_id += 1
|
||||
local id = max_id
|
||||
alive_count += 1
|
||||
dense[alive_count] = id
|
||||
sparse[id] = {
|
||||
dense = alive_count
|
||||
}
|
||||
print("*allocated", pe(id))
|
||||
return id
|
||||
end
|
||||
|
||||
local function remove(entity)
|
||||
local id = ECS_ID(entity)
|
||||
local r = sparse[id]
|
||||
local index_of_deleted_entity = r.dense
|
||||
local last_entity_alive_at_index = alive_count -- last entity alive
|
||||
alive_count -= 1
|
||||
local last_alive_entity = dense[last_entity_alive_at_index]
|
||||
local r_swap = sparse[ECS_ID(last_alive_entity)]
|
||||
r_swap.dense = r.dense
|
||||
r.dense = last_entity_alive_at_index
|
||||
dense[index_of_deleted_entity] = last_alive_entity
|
||||
dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
|
||||
end
|
||||
|
||||
local function alive(e)
|
||||
local r = sparse[ECS_ID(e)]
|
||||
|
||||
return dense[r.dense] == e
|
||||
end
|
||||
|
||||
local function pa(e)
|
||||
print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`)
|
||||
end
|
||||
|
||||
local tprint = require("@testkit").print
|
||||
local e1v0 = alloc()
|
||||
local e2v0 = alloc()
|
||||
local e3v0 = alloc()
|
||||
local e4v0 = alloc()
|
||||
local e5v0 = alloc()
|
||||
pa(e1v0)
|
||||
pa(e4v0)
|
||||
remove(e5v0)
|
||||
pa(e5v0)
|
||||
|
||||
local e5v1 = alloc()
|
||||
pa(e5v0)
|
||||
pa(e5v1)
|
||||
pa(e2v0)
|
||||
print(ECS_ID(e2v0))
|
||||
|
||||
dprint(dense)
|
||||
remove(e2v0)
|
||||
dprint(dense)
|
|
@ -191,6 +191,36 @@ TEST("world:entity()", function()
|
|||
CHECK(ecs_pair_first(world, pair) == e2)
|
||||
CHECK(ecs_pair_second(world, pair) == e3)
|
||||
end
|
||||
|
||||
do CASE "Recycling"
|
||||
local world = world_new()
|
||||
local e = world:entity()
|
||||
world:delete(e)
|
||||
local e1 = world:entity()
|
||||
world:delete(e1)
|
||||
local e2 = world:entity()
|
||||
CHECK(ECS_ID(e2) == e)
|
||||
CHECK(ECS_GENERATION(e2) == 2)
|
||||
CHECK(world:contains(e2))
|
||||
CHECK(not world:contains(e1))
|
||||
CHECK(not world:contains(e))
|
||||
end
|
||||
|
||||
do CASE "Recycling max generation"
|
||||
local world = world_new()
|
||||
local pin = jecs.Rest + 1
|
||||
for i = 1, 2^16-1 do
|
||||
local e = world:entity()
|
||||
world:delete(e)
|
||||
end
|
||||
local e = world:entity()
|
||||
CHECK(ECS_ID(e) == pin)
|
||||
CHECK(ECS_GENERATION(e) == 2^16-1)
|
||||
world:delete(e)
|
||||
e = world:entity()
|
||||
CHECK(ECS_ID(e) == pin)
|
||||
CHECK(ECS_GENERATION(e) == 0)
|
||||
end
|
||||
end)
|
||||
|
||||
TEST("world:set()", function()
|
||||
|
|
Loading…
Reference in a new issue