mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Initial commit
This commit is contained in:
parent
c3874486a0
commit
7d09aca97d
2 changed files with 167 additions and 77 deletions
230
src/init.luau
230
src/init.luau
|
@ -44,11 +44,6 @@ type Record = {
|
|||
dense: i24,
|
||||
}
|
||||
|
||||
type EntityIndex = {
|
||||
dense: Map<i24, i53>,
|
||||
sparse: Map<i53, Record>,
|
||||
}
|
||||
|
||||
type ArchetypeRecord = {
|
||||
count: number,
|
||||
column: number,
|
||||
|
@ -74,6 +69,14 @@ type ArchetypeDiff = {
|
|||
removed: Ty,
|
||||
}
|
||||
|
||||
type EntityIndex = {
|
||||
dense_array: Map<i24, i53>,
|
||||
sparse_array: Map<i53, Record>,
|
||||
sparse_count: number,
|
||||
alive_count: number,
|
||||
max_id: number
|
||||
}
|
||||
|
||||
local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256
|
||||
-- stylua: ignore start
|
||||
local EcsOnAdd = HI_COMPONENT_ID + 1
|
||||
|
@ -141,7 +144,12 @@ local function ECS_GENERATION_INC(e: i53)
|
|||
local id = flags // ECS_ENTITY_MASK
|
||||
local generation = flags % ECS_GENERATION_MASK
|
||||
|
||||
return ECS_COMBINE(id, generation + 1) + flags
|
||||
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
|
||||
|
@ -167,46 +175,104 @@ end
|
|||
local ERROR_ENTITY_NOT_ALIVE = "Entity is not alive"
|
||||
local ERROR_GENERATION_INVALID = "INVALID GENERATION"
|
||||
|
||||
local function entity_index_get_alive(index: EntityIndex, e: i24): i53
|
||||
local denseArray = index.dense
|
||||
local id = denseArray[ECS_ENTITY_T_LO(e)]
|
||||
|
||||
if id then
|
||||
local currentGeneration = ECS_GENERATION(id)
|
||||
local gen = ECS_GENERATION(e)
|
||||
if gen == currentGeneration then
|
||||
return id
|
||||
end
|
||||
|
||||
error(ERROR_GENERATION_INVALID)
|
||||
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
|
||||
|
||||
error(ERROR_ENTITY_NOT_ALIVE)
|
||||
if not r or r.dense == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return r
|
||||
end
|
||||
|
||||
local function _entity_index_sparse_get(entityIndex, id)
|
||||
return entityIndex.sparse[entity_index_get_alive(entityIndex, id)]
|
||||
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(index: EntityIndex, e: i24): i53
|
||||
local r = entity_index_try_get_any(index, e)
|
||||
if r then
|
||||
return index.dense_array[r.dense]
|
||||
end
|
||||
error("Invalid entity")
|
||||
end
|
||||
|
||||
|
||||
local function entity_index_is_alive(entity_index: EntityIndex, entity: number)
|
||||
return entity_index_try_get(entity_index, entity) ~= nil
|
||||
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 record_create(entity_index: EntityIndex, id: number, count: number )
|
||||
entity_index.sparse_array[id] = { dense = count } :: Record
|
||||
entity_index.sparse_count += 1
|
||||
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,
|
||||
} :: Record
|
||||
|
||||
entity_index.sparse_count += 1
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits
|
||||
local function ecs_pair_first(world, e)
|
||||
return entity_index_get_alive(world.entityIndex, ECS_ENTITY_T_HI(e))
|
||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_HI(e))
|
||||
end
|
||||
|
||||
-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits
|
||||
local function ecs_pair_second(world, e)
|
||||
return entity_index_get_alive(world.entityIndex, ECS_ENTITY_T_LO(e))
|
||||
end
|
||||
|
||||
local function entity_index_new_id(entityIndex: EntityIndex, index: i24): i53
|
||||
--local id = ECS_COMBINE(index, 0)
|
||||
local id = index
|
||||
entityIndex.sparse[id] = {
|
||||
dense = index,
|
||||
} :: Record
|
||||
entityIndex.dense[index] = id
|
||||
|
||||
return id
|
||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_LO(e))
|
||||
end
|
||||
|
||||
local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row: i24, from: Archetype, src_row: i24)
|
||||
|
@ -239,7 +305,7 @@ local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row:
|
|||
column[last] = nil
|
||||
end
|
||||
|
||||
local sparse = entity_index.sparse
|
||||
local sparse_array = entity_index.sparse_array
|
||||
local moved = #src_entities
|
||||
|
||||
-- Move the entity from the source to the destination archetype.
|
||||
|
@ -255,8 +321,8 @@ local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row:
|
|||
src_entities[moved] = nil :: any
|
||||
dst_entities[dst_row] = e1
|
||||
|
||||
local record1 = sparse[e1]
|
||||
local record2 = sparse[e2]
|
||||
local record1 = entity_index_try_get_any(entity_index, e1)
|
||||
local record2 = entity_index_try_get_any(entity_index, e2)
|
||||
|
||||
record1.row = dst_row
|
||||
record2.row = src_row
|
||||
|
@ -307,7 +373,7 @@ do
|
|||
end
|
||||
|
||||
function world_get(world: World, entity: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
|
||||
local record = world.entityIndex.sparse[entity]
|
||||
local record = entity_index_try_get(world.entity_index, entity)
|
||||
if not record then
|
||||
return nil
|
||||
end
|
||||
|
@ -338,7 +404,7 @@ do
|
|||
end
|
||||
|
||||
local function world_get_one_inline(world: World, entity: i53, id: i53): any
|
||||
local record = world.entityIndex.sparse[entity]
|
||||
local record = entity_index_try_get(world.entity_index, entity)
|
||||
if not record then
|
||||
return nil
|
||||
end
|
||||
|
@ -355,8 +421,9 @@ local function world_get_one_inline(world: World, entity: i53, id: i53): any
|
|||
return archetype.columns[tr.column][record.row]
|
||||
end
|
||||
|
||||
|
||||
local function world_has_one_inline(world: World, entity: number, id: i53): boolean
|
||||
local record = world.entityIndex.sparse[entity]
|
||||
local record = entity_index_try_get(world.entity_index, entity)
|
||||
if not record then
|
||||
return false
|
||||
end
|
||||
|
@ -372,7 +439,7 @@ local function world_has_one_inline(world: World, entity: number, id: i53): bool
|
|||
end
|
||||
|
||||
local function world_has(world: World, entity: number, ...: i53): boolean
|
||||
local record = world.entityIndex.sparse[entity]
|
||||
local record = entity_index_try_get(world.entity_index, entity)
|
||||
if not record then
|
||||
return false
|
||||
end
|
||||
|
@ -395,7 +462,10 @@ end
|
|||
|
||||
local function world_target(world: World, entity: i53, relation: i24, index: number?): i24?
|
||||
local nth = index or 0
|
||||
local record = world.entityIndex.sparse[entity]
|
||||
local record = entity_index_try_get(world.entity_index, entity)
|
||||
if not record then
|
||||
return nil
|
||||
end
|
||||
local archetype = record.archetype
|
||||
if not archetype then
|
||||
return nil
|
||||
|
@ -543,9 +613,7 @@ local function archetype_create(world: World, types: { i24 }, ty, prev: i53?): A
|
|||
end
|
||||
|
||||
local function world_entity(world: World): i53
|
||||
local entityId = (world.nextEntityId :: number) + 1
|
||||
world.nextEntityId = entityId
|
||||
return entity_index_new_id(world.entityIndex, entityId + EcsRest)
|
||||
return entity_index_new_id(world.entity_index)
|
||||
end
|
||||
|
||||
local function world_parent(world: World, entity: i53)
|
||||
|
@ -701,15 +769,19 @@ local function invoke_hook(action, entity, data)
|
|||
end
|
||||
|
||||
local function world_add(world: World, entity: i53, id: i53): ()
|
||||
local entityIndex = world.entityIndex
|
||||
local record = entityIndex.sparse[entity]
|
||||
local entity_index = world.entity_index
|
||||
local record = entity_index_try_get(entity_index, entity)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
||||
local from = record.archetype
|
||||
local to = archetype_traverse_add(world, id, from)
|
||||
if from == to then
|
||||
return
|
||||
end
|
||||
if from then
|
||||
entity_move(entityIndex, entity, record, to)
|
||||
entity_move(entity_index, entity, record, to)
|
||||
else
|
||||
if #to.types > 0 then
|
||||
new_entity(entity, record, to)
|
||||
|
@ -725,8 +797,12 @@ local function world_add(world: World, entity: i53, id: i53): ()
|
|||
end
|
||||
|
||||
local function world_set(world: World, entity: i53, id: i53, data: unknown): ()
|
||||
local entityIndex = world.entityIndex
|
||||
local record = entityIndex.sparse[entity]
|
||||
local entity_index = world.entity_index
|
||||
local record = entity_index_try_get(entity_index, entity)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
||||
local from: Archetype = record.archetype
|
||||
local to: Archetype = archetype_traverse_add(world, id, from)
|
||||
local idr = world.componentIndex[id]
|
||||
|
@ -741,7 +817,8 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown): ()
|
|||
-- If the archetypes are the same it can avoid moving the entity
|
||||
-- and just set the data directly.
|
||||
local tr = to.records[id]
|
||||
from.columns[tr.column][record.row] = data
|
||||
local column = from.columns[tr.column]
|
||||
column[record.row] = data
|
||||
local on_set = idr_hooks.on_set
|
||||
if on_set then
|
||||
on_set(entity, data)
|
||||
|
@ -752,7 +829,7 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown): ()
|
|||
|
||||
if from then
|
||||
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||
entity_move(entityIndex, entity, record, to)
|
||||
entity_move(entity_index, entity, record, to)
|
||||
else
|
||||
if #to.types > 0 then
|
||||
-- When there is no previous archetype it should create the archetype
|
||||
|
@ -793,8 +870,11 @@ local function world_component(world: World): i53
|
|||
end
|
||||
|
||||
local function world_remove(world: World, entity: i53, id: i53)
|
||||
local entity_index = world.entityIndex
|
||||
local record = entity_index.sparse[entity]
|
||||
local entity_index = world.entity_index
|
||||
local record = entity_index_try_get(entity_index, entity)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
local from = record.archetype
|
||||
|
||||
if not from then
|
||||
|
@ -831,7 +911,7 @@ local function archetype_fast_delete(columns: { Column }, column_count: number,
|
|||
end
|
||||
|
||||
local function archetype_delete(world: World, archetype: Archetype, row: number, destruct: boolean?)
|
||||
local entityIndex = world.entityIndex
|
||||
local entityIndex = world.entity_index
|
||||
local columns = archetype.columns
|
||||
local types = archetype.types
|
||||
local entities = archetype.entities
|
||||
|
@ -844,7 +924,7 @@ local function archetype_delete(world: World, archetype: Archetype, row: number,
|
|||
|
||||
if row ~= last then
|
||||
-- TODO: should be "entity_index_sparse_get(entityIndex, move)"
|
||||
local record_to_move = entityIndex.sparse[move]
|
||||
local record_to_move = entity_index_try_get_any(entityIndex, move)
|
||||
if record_to_move then
|
||||
record_to_move.row = row
|
||||
end
|
||||
|
@ -868,7 +948,7 @@ end
|
|||
|
||||
local function world_clear(world: World, entity: i53)
|
||||
--TODO: use sparse_get (stashed)
|
||||
local record = world.entityIndex.sparse[entity]
|
||||
local record = entity_index_try_get(world.entity_index, entity)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
@ -959,6 +1039,7 @@ local function archetype_destroy(world: World, archetype: Archetype)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
local function world_cleanup(world: World)
|
||||
local archetypes = world.archetypes
|
||||
|
||||
|
@ -983,10 +1064,8 @@ end
|
|||
local world_delete: (world: World, entity: i53, destruct: boolean?) -> ()
|
||||
do
|
||||
function world_delete(world: World, entity: i53, destruct: boolean?)
|
||||
local entityIndex = world.entityIndex
|
||||
local sparse_array = entityIndex.sparse
|
||||
|
||||
local record = sparse_array[entity]
|
||||
local entity_index = world.entity_index
|
||||
local record = entity_index_try_get(entity_index, entity)
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
@ -1044,7 +1123,7 @@ do
|
|||
if not ECS_IS_PAIR(id) then
|
||||
continue
|
||||
end
|
||||
local object = ECS_ENTITY_T_LO(id)
|
||||
local object = ecs_pair_second(world, id)
|
||||
if object == delete then
|
||||
local id_record = component_index[id]
|
||||
local flags = id_record.flags
|
||||
|
@ -1067,15 +1146,17 @@ do
|
|||
end
|
||||
end
|
||||
|
||||
record.archetype = nil :: any
|
||||
sparse_array[entity] = nil :: any
|
||||
entity_index_remove(entity_index, delete)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function world_contains(world: World, entity): boolean
|
||||
return world.entityIndex.sparse[entity] ~= nil
|
||||
return entity_index_is_alive(world.entity_index, entity)
|
||||
end
|
||||
|
||||
|
||||
local function NOOP() end
|
||||
|
||||
local function ARM(query, ...)
|
||||
|
@ -1610,14 +1691,17 @@ end
|
|||
|
||||
function World.new()
|
||||
local entity_index: EntityIndex = {
|
||||
dense = {} :: { [i24]: i53 },
|
||||
sparse = {} :: { [i53]: Record },
|
||||
dense_array = {} :: { [i24]: i53 },
|
||||
sparse_array = {} :: { [i53]: Record },
|
||||
sparse_count = 0,
|
||||
alive_count = 0,
|
||||
max_id = 0,
|
||||
}
|
||||
local self = setmetatable({
|
||||
archetypeIndex = {} :: { [string]: Archetype },
|
||||
archetypes = {} :: Archetypes,
|
||||
componentIndex = {} :: ComponentIndex,
|
||||
entityIndex = entity_index,
|
||||
entity_index = entity_index,
|
||||
nextArchetypeId = 0 :: number,
|
||||
nextComponentId = 0 :: number,
|
||||
nextEntityId = 0 :: number,
|
||||
|
@ -1627,13 +1711,13 @@ function World.new()
|
|||
self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
|
||||
|
||||
for i = 1, HI_COMPONENT_ID do
|
||||
local e = entity_index_new_id(self.entityIndex, i)
|
||||
local e = entity_index_new_id(entity_index, i)
|
||||
world_add(self, e, EcsComponent)
|
||||
end
|
||||
|
||||
for i = HI_COMPONENT_ID + 1, EcsRest do
|
||||
-- Initialize built-in components
|
||||
entity_index_new_id(self.entityIndex, i)
|
||||
entity_index_new_id(entity_index, i)
|
||||
end
|
||||
|
||||
world_add(self, EcsName, EcsComponent)
|
||||
|
@ -1698,7 +1782,7 @@ export type World = {
|
|||
archetypeIndex: { [string]: Archetype },
|
||||
archetypes: Archetypes,
|
||||
componentIndex: ComponentIndex,
|
||||
entityIndex: EntityIndex,
|
||||
entity_index: EntityIndex,
|
||||
ROOT_ARCHETYPE: Archetype,
|
||||
|
||||
nextComponentId: number,
|
||||
|
@ -1825,4 +1909,8 @@ return {
|
|||
create_edge_for_remove = create_edge_for_remove,
|
||||
archetype_traverse_add = archetype_traverse_add,
|
||||
archetype_traverse_remove = archetype_traverse_remove,
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ local ECS_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION
|
|||
local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC
|
||||
local IS_PAIR = jecs.IS_PAIR
|
||||
local pair = jecs.pair
|
||||
local getAlive = jecs.entity_index_get_alive
|
||||
local ecs_pair_first = jecs.pair_first
|
||||
local ecs_pair_second = jecs.pair_second
|
||||
local entity_index_try_get_any = jecs.entity_index_try_get_any
|
||||
local entity_index_get_alive = jecs.entity_index_get_alive
|
||||
local entity_index_is_alive = jecs.entity_index_is_alive
|
||||
local world_new = jecs.World.new
|
||||
|
||||
local TEST, CASE, CHECK, FINISH, SKIP, FOCUS = testkit.test()
|
||||
|
@ -29,7 +31,7 @@ type World = jecs.WorldShim
|
|||
|
||||
local function debug_world_inspect(world)
|
||||
local function record(e)
|
||||
return world.entityIndex.sparse[e]
|
||||
return entity_index_try_get_any(world.entity_index, e)
|
||||
end
|
||||
local function tbl(e)
|
||||
return record(e).archetype
|
||||
|
@ -168,7 +170,6 @@ TEST("world:entity()", function()
|
|||
local world = jecs.World.new()
|
||||
local e = world:entity()
|
||||
CHECK(ECS_ID(e) == 1 + jecs.Rest)
|
||||
CHECK(getAlive(world.entityIndex, ECS_ID(e)) == e)
|
||||
CHECK(ECS_GENERATION(e) == 0) -- 0
|
||||
e = ECS_GENERATION_INC(e)
|
||||
CHECK(ECS_GENERATION(e) == 1) -- 1
|
||||
|
@ -878,9 +879,10 @@ TEST("world:clear()", function()
|
|||
CHECK(archetype_entities[1] == _e)
|
||||
CHECK(archetype_entities[2] == _e1)
|
||||
|
||||
local sparse_array = world.entityIndex.sparse
|
||||
local e_record = sparse_array[e]
|
||||
local e1_record = sparse_array[e1]
|
||||
local e_record = entity_index_try_get_any(
|
||||
world.entity_index, e)
|
||||
local e1_record = entity_index_try_get_any(
|
||||
world.entity_index, e1)
|
||||
CHECK(e_record.archetype == archetype)
|
||||
CHECK(e1_record.archetype == archetype)
|
||||
CHECK(e1_record.row == 2)
|
||||
|
|
Loading…
Reference in a new issue