2024-04-23 15:10:49 +00:00
|
|
|
--!optimize 2
|
|
|
|
--!native
|
|
|
|
--!strict
|
|
|
|
--draft 4
|
|
|
|
|
|
|
|
type i53 = number
|
|
|
|
type i24 = number
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
type Ty = { i53 }
|
2024-04-23 15:10:49 +00:00
|
|
|
type ArchetypeId = number
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
type Column = { any }
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
type ArchetypeEdge = {
|
|
|
|
add: Archetype,
|
|
|
|
remove: Archetype,
|
|
|
|
}
|
|
|
|
|
2024-04-23 15:10:49 +00:00
|
|
|
type Archetype = {
|
|
|
|
id: number,
|
2024-06-24 01:01:55 +00:00
|
|
|
edges: { [i53]: ArchetypeEdge },
|
2024-04-23 15:10:49 +00:00
|
|
|
types: Ty,
|
2024-05-04 23:52:01 +00:00
|
|
|
type: string | number,
|
2024-05-27 01:39:20 +00:00
|
|
|
entities: { number },
|
|
|
|
columns: { Column },
|
2024-08-01 00:16:09 +00:00
|
|
|
records: { ArchetypeRecord },
|
2024-04-23 15:10:49 +00:00
|
|
|
}
|
|
|
|
type Record = {
|
|
|
|
archetype: Archetype,
|
|
|
|
row: number,
|
2024-05-10 15:59:57 +00:00
|
|
|
dense: i24,
|
2024-05-27 01:39:20 +00:00
|
|
|
componentRecord: ArchetypeMap,
|
2024-04-23 15:10:49 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
type EntityIndex = { dense: { [i24]: i53 }, sparse: { [i53]: Record } }
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-08-01 00:16:09 +00:00
|
|
|
type ArchetypeRecord = {
|
2024-05-16 22:17:53 +00:00
|
|
|
count: number,
|
2024-08-04 21:07:32 +00:00
|
|
|
column: number,
|
2024-07-07 02:53:17 +00:00
|
|
|
}
|
2024-05-16 22:17:53 +00:00
|
|
|
|
|
|
|
type ArchetypeMap = {
|
2024-06-10 23:39:43 +00:00
|
|
|
cache: { ArchetypeRecord },
|
2024-08-14 15:18:05 +00:00
|
|
|
flags: number,
|
2024-05-16 22:17:53 +00:00
|
|
|
first: ArchetypeMap,
|
|
|
|
second: ArchetypeMap,
|
|
|
|
parent: ArchetypeMap,
|
2024-05-27 01:39:20 +00:00
|
|
|
size: number,
|
2024-05-16 22:17:53 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
type ComponentIndex = { [i24]: ArchetypeMap }
|
2024-05-16 22:17:53 +00:00
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
type Archetypes = { [ArchetypeId]: Archetype }
|
2024-05-04 23:52:01 +00:00
|
|
|
|
2024-04-28 14:46:40 +00:00
|
|
|
type ArchetypeDiff = {
|
|
|
|
added: Ty,
|
|
|
|
removed: Ty,
|
|
|
|
}
|
|
|
|
|
2024-07-03 00:10:11 +00:00
|
|
|
local HI_COMPONENT_ID = 256
|
|
|
|
|
|
|
|
local EcsOnAdd = HI_COMPONENT_ID + 1
|
|
|
|
local EcsOnRemove = HI_COMPONENT_ID + 2
|
|
|
|
local EcsOnSet = HI_COMPONENT_ID + 3
|
|
|
|
local EcsWildcard = HI_COMPONENT_ID + 4
|
|
|
|
local EcsChildOf = HI_COMPONENT_ID + 5
|
2024-07-03 15:48:32 +00:00
|
|
|
local EcsComponent = HI_COMPONENT_ID + 6
|
2024-08-13 18:08:58 +00:00
|
|
|
local EcsDelete = HI_COMPONENT_ID + 7
|
|
|
|
local EcsRest = HI_COMPONENT_ID + 8
|
2024-07-03 00:10:11 +00:00
|
|
|
|
|
|
|
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)
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
local ECS_ID_HAS_DELETE = 0b0001
|
|
|
|
local ECS_ID_HAS_HOOKS = 0b0010
|
|
|
|
--local EcsIdExclusive = 0b0100
|
|
|
|
|
2024-07-14 05:16:24 +00:00
|
|
|
local function FLAGS_ADD(is_pair: boolean): number
|
|
|
|
local flags = 0x0
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-07-14 05:16:24 +00:00
|
|
|
if is_pair then
|
|
|
|
flags = bit32.bor(flags, ECS_PAIR_FLAG) -- HIGHEST bit in the ID.
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
if false then
|
2024-07-14 05:16:24 +00:00
|
|
|
flags = bit32.bor(flags, 0x4) -- Set the second flag to true
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
2024-05-12 22:53:51 +00:00
|
|
|
if false then
|
2024-07-14 05:16:24 +00:00
|
|
|
flags = bit32.bor(flags, 0x2) -- Set the third flag to true
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
if false then
|
2024-07-14 05:16:24 +00:00
|
|
|
flags = bit32.bor(flags, 0x1) -- LAST BIT in the ID.
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-07-14 05:16:24 +00:00
|
|
|
return flags
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-05-14 15:52:41 +00:00
|
|
|
local function ECS_COMBINE(source: number, target: number): i53
|
2024-06-24 01:01:55 +00:00
|
|
|
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function ECS_IS_PAIR(e: number): boolean
|
2024-07-03 00:10:11 +00:00
|
|
|
return if e > ECS_ENTITY_MASK then (e % ECS_ID_FLAGS_MASK) // ECS_PAIR_FLAG ~= 0 else false
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- HIGH 24 bits LOW 24 bits
|
2024-06-24 01:01:55 +00:00
|
|
|
local function ECS_GENERATION(e: i53): i24
|
|
|
|
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
local function ECS_GENERATION_INC(e: i53)
|
2024-06-05 22:38:27 +00:00
|
|
|
if e > ECS_ENTITY_MASK then
|
2024-06-24 01:01:55 +00:00
|
|
|
local flags = e // ECS_ID_FLAGS_MASK
|
2024-06-05 22:38:27 +00:00
|
|
|
local id = flags // ECS_ENTITY_MASK
|
|
|
|
local generation = flags % ECS_GENERATION_MASK
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-06-05 22:38:27 +00:00
|
|
|
return ECS_COMBINE(id, generation + 1) + flags
|
|
|
|
end
|
|
|
|
return ECS_COMBINE(e, 1)
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
-- FIRST gets the high ID
|
2024-05-27 01:39:20 +00:00
|
|
|
local function ECS_ENTITY_T_HI(e: i53): i24
|
2024-06-24 01:01:55 +00:00
|
|
|
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_ENTITY_MASK else e
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- SECOND
|
2024-06-10 23:39:43 +00:00
|
|
|
local function ECS_ENTITY_T_LO(e: i53): i24
|
2024-06-24 01:01:55 +00:00
|
|
|
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-07-07 02:53:17 +00:00
|
|
|
local function STRIP_GENERATION(e: i53): i24
|
2024-07-02 23:24:17 +00:00
|
|
|
return ECS_ENTITY_T_LO(e)
|
|
|
|
end
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
local function ECS_PAIR(pred: i53, obj: i53): i53
|
2024-07-28 00:39:19 +00:00
|
|
|
return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + FLAGS_ADD(--[[isPair]] true) :: i53
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-07-03 15:48:32 +00:00
|
|
|
local ERROR_ENTITY_NOT_ALIVE = "Entity is not alive"
|
|
|
|
local ERROR_GENERATION_INVALID = "INVALID GENERATION"
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
local function entity_index_get_alive(index: EntityIndex, e: i24): i53
|
2024-07-03 15:48:32 +00:00
|
|
|
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)
|
2024-07-07 02:53:17 +00:00
|
|
|
if gen == currentGeneration then
|
2024-07-03 15:48:32 +00:00
|
|
|
return id
|
|
|
|
end
|
|
|
|
|
|
|
|
error(ERROR_GENERATION_INVALID)
|
|
|
|
end
|
|
|
|
|
|
|
|
error(ERROR_ENTITY_NOT_ALIVE)
|
|
|
|
end
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
local function entity_index_sparse_get(entityIndex, id)
|
|
|
|
return entityIndex.sparse[entity_index_get_alive(entityIndex, id)]
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-05-14 15:52:41 +00:00
|
|
|
-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits
|
2024-08-10 02:55:04 +00:00
|
|
|
local function ecs_pair_first(world, e)
|
2024-08-02 20:24:05 +00:00
|
|
|
return entity_index_get_alive(world.entityIndex, ECS_ENTITY_T_HI(e))
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
2024-05-14 15:52:41 +00:00
|
|
|
|
|
|
|
-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits
|
2024-08-10 02:55:04 +00:00
|
|
|
local function ecs_pair_second(world, e)
|
2024-08-02 20:24:05 +00:00
|
|
|
return entity_index_get_alive(world.entityIndex, ECS_ENTITY_T_LO(e))
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function entity_index_new_id(entityIndex: EntityIndex, index: i24): i53
|
2024-06-05 22:38:27 +00:00
|
|
|
--local id = ECS_COMBINE(index, 0)
|
|
|
|
local id = index
|
2024-05-12 22:53:51 +00:00
|
|
|
entityIndex.sparse[id] = {
|
2024-05-27 01:39:20 +00:00
|
|
|
dense = index,
|
|
|
|
} :: Record
|
2024-05-12 22:53:51 +00:00
|
|
|
entityIndex.dense[index] = id
|
|
|
|
|
|
|
|
return id
|
|
|
|
end
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
local function archetype_move(entity_index: EntityIndex, to: Archetype,
|
|
|
|
dst_row: i24, from: Archetype, src_row: i24)
|
2024-07-06 14:36:00 +00:00
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
local src_columns = from.columns
|
|
|
|
local dst_columns = to.columns
|
|
|
|
local dst_entities = to.entities
|
|
|
|
local src_entities = from.entities
|
|
|
|
|
|
|
|
local last = #src_entities
|
2024-05-03 00:39:59 +00:00
|
|
|
local types = from.types
|
2024-08-12 20:43:46 +00:00
|
|
|
local records = to.records
|
2024-05-03 00:39:59 +00:00
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
for i, column in src_columns do
|
2024-05-04 23:52:01 +00:00
|
|
|
-- Retrieves the new column index from the source archetype's record from each component
|
2024-05-03 00:39:59 +00:00
|
|
|
-- We have to do this because the columns are tightly packed and indexes may not correspond to each other.
|
2024-08-01 00:16:09 +00:00
|
|
|
local tr = records[types[i]]
|
2024-05-03 00:39:59 +00:00
|
|
|
|
|
|
|
-- Sometimes target column may not exist, e.g. when you remove a component.
|
2024-08-01 00:16:09 +00:00
|
|
|
if tr then
|
2024-08-12 20:43:46 +00:00
|
|
|
dst_columns[tr.column][dst_row] = column[src_row]
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
2024-05-03 00:39:59 +00:00
|
|
|
-- If the entity is the last row in the archetype then swapping it would be meaningless.
|
2024-08-12 20:43:46 +00:00
|
|
|
if src_row ~= last then
|
2024-05-03 00:39:59 +00:00
|
|
|
-- Swap rempves columns to ensure there are no holes in the archetype.
|
2024-08-12 20:43:46 +00:00
|
|
|
column[src_row] = column[last]
|
2024-05-03 00:39:59 +00:00
|
|
|
end
|
|
|
|
column[last] = nil
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
local sparse = entity_index.sparse
|
|
|
|
local moved = #src_entities
|
2024-04-30 14:05:31 +00:00
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
-- Move the entity from the source to the destination archetype.
|
2024-05-03 00:39:59 +00:00
|
|
|
-- Because we have swapped columns we now have to update the records
|
|
|
|
-- corresponding to the entities' rows that were swapped.
|
2024-08-12 20:43:46 +00:00
|
|
|
local e1 = src_entities[src_row]
|
|
|
|
local e2 = src_entities[moved]
|
2024-05-10 15:59:57 +00:00
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
if src_row ~= moved then
|
|
|
|
src_entities[src_row] = e2
|
2024-05-03 00:39:59 +00:00
|
|
|
end
|
2024-05-04 23:52:01 +00:00
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
src_entities[moved] = nil :: any
|
|
|
|
dst_entities[dst_row] = e1
|
2024-05-10 15:59:57 +00:00
|
|
|
|
|
|
|
local record1 = sparse[e1]
|
|
|
|
local record2 = sparse[e2]
|
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
record1.row = dst_row
|
|
|
|
record2.row = src_row
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function archetype_append(entity: number, archetype: Archetype): number
|
2024-04-23 15:10:49 +00:00
|
|
|
local entities = archetype.entities
|
2024-05-04 23:52:01 +00:00
|
|
|
local length = #entities + 1
|
|
|
|
entities[length] = entity
|
|
|
|
return length
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function new_entity(entityId: i53, record: Record, archetype: Archetype): Record
|
|
|
|
local row = archetype_append(entityId, archetype)
|
2024-04-23 15:10:49 +00:00
|
|
|
record.archetype = archetype
|
|
|
|
record.row = row
|
|
|
|
return record
|
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function entity_move(entity_index: EntityIndex, entityId: i53, record: Record, to: Archetype)
|
2024-04-23 15:10:49 +00:00
|
|
|
local sourceRow = record.row
|
|
|
|
local from = record.archetype
|
2024-07-14 04:35:13 +00:00
|
|
|
local dst_row = archetype_append(entityId, to)
|
|
|
|
archetype_move(entity_index, to, dst_row, from, sourceRow)
|
2024-04-23 15:10:49 +00:00
|
|
|
record.archetype = to
|
2024-07-14 04:35:13 +00:00
|
|
|
record.row = dst_row
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function hash(arr: { number }): string
|
2024-04-28 14:46:40 +00:00
|
|
|
return table.concat(arr, "_")
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-08-11 13:03:05 +00:00
|
|
|
local world_get: (world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) -> (...any)
|
|
|
|
do
|
|
|
|
-- Keeping the function as small as possible to enable inlining
|
|
|
|
local records
|
|
|
|
local columns
|
|
|
|
local row
|
|
|
|
|
|
|
|
local function fetch(id)
|
|
|
|
local tr = records[id]
|
|
|
|
|
|
|
|
if not tr then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
return columns[tr.column][row]
|
|
|
|
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]
|
|
|
|
if not record then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype = record.archetype
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
records = archetype.records
|
|
|
|
columns = archetype.columns
|
|
|
|
row = record.row
|
|
|
|
|
|
|
|
local va = fetch(a)
|
|
|
|
|
|
|
|
if not b then
|
|
|
|
return va
|
|
|
|
elseif not c then
|
|
|
|
return va, fetch(b)
|
|
|
|
elseif not d then
|
|
|
|
return va, fetch(b), fetch(c)
|
|
|
|
elseif not e then
|
|
|
|
return va, fetch(b), fetch(c), fetch(d)
|
|
|
|
else
|
|
|
|
error("args exceeded")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function world_get_one_inline(world: World, entity: i53, id: i53)
|
2024-08-11 01:27:29 +00:00
|
|
|
local record = world.entityIndex.sparse[entity]
|
|
|
|
if not record then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype = record.archetype
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local tr = archetype.records[id]
|
|
|
|
if not tr then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
return archetype.columns[tr.column][record.row]
|
|
|
|
end
|
|
|
|
|
2024-08-11 13:03:05 +00:00
|
|
|
local function world_has_one_inline(world: World, entity: i53, id: i53): boolean
|
2024-08-11 01:27:29 +00:00
|
|
|
local record = world.entityIndex.sparse[entity]
|
|
|
|
if not record then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype = record.archetype
|
|
|
|
if not archetype then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
return archetype.records[id] ~= nil
|
|
|
|
end
|
|
|
|
|
2024-08-11 13:03:05 +00:00
|
|
|
local function world_has(world: World, entity: number, ...: i53): boolean
|
|
|
|
local record = world.entityIndex.sparse[entity]
|
|
|
|
if not record then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype = record.archetype
|
|
|
|
if not archetype then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local records = archetype.records
|
|
|
|
|
|
|
|
for i = 1, select("#", ...) do
|
|
|
|
if not records[select(i, ...)] then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
local function world_has_any(world: World, entity: number, ...: i53): boolean
|
|
|
|
local record = world.entityIndex.sparse[entity]
|
|
|
|
if not record then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype = record.archetype
|
|
|
|
if not archetype then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local records = archetype.records
|
|
|
|
|
|
|
|
for i = 1, select("#", ...) do
|
|
|
|
if not records[select(i, ...)] then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function id_record_ensure(
|
|
|
|
world,
|
|
|
|
id: number
|
|
|
|
): ArchetypeMap
|
|
|
|
local componentIndex = world.componentIndex
|
|
|
|
local idr = componentIndex[id]
|
|
|
|
|
|
|
|
if not idr then
|
|
|
|
local flags = 0b0000
|
|
|
|
local relation = ECS_ENTITY_T_HI(id)
|
|
|
|
if world_has_one_inline(world, relation, EcsDelete) then
|
|
|
|
flags = bit32.bor(flags, ECS_ID_HAS_DELETE)
|
|
|
|
end
|
|
|
|
|
|
|
|
if world_has_any(world, relation,
|
|
|
|
EcsOnAdd, EcsOnSet, EcsOnRemove)
|
|
|
|
then
|
|
|
|
flags = bit32.bor(flags, ECS_ID_HAS_HOOKS)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- local FLAG2 = 0b0010
|
|
|
|
-- local FLAG3 = 0b0100
|
|
|
|
-- local FLAG4 = 0b1000
|
|
|
|
|
|
|
|
idr = {
|
|
|
|
size = 0,
|
|
|
|
cache = {},
|
|
|
|
flags = flags
|
|
|
|
} :: ArchetypeMap
|
|
|
|
componentIndex[id] = idr
|
|
|
|
end
|
|
|
|
|
|
|
|
return idr
|
|
|
|
end
|
|
|
|
|
|
|
|
local function ECS_ID_IS_WILDCARD(e: i53): boolean
|
|
|
|
assert(ECS_IS_PAIR(e))
|
|
|
|
local first = ECS_ENTITY_T_HI(e)
|
|
|
|
local second = ECS_ENTITY_T_LO(e)
|
|
|
|
return first == EcsWildcard or second == EcsWildcard
|
|
|
|
end
|
|
|
|
|
|
|
|
local function archetype_create(world: any, types: { i24 }, prev: Archetype?): Archetype
|
|
|
|
local ty = hash(types)
|
|
|
|
|
|
|
|
local id = world.nextArchetypeId + 1
|
|
|
|
world.nextArchetypeId = id
|
|
|
|
|
|
|
|
local length = #types
|
|
|
|
local columns = (table.create(length) :: any) :: { Column }
|
|
|
|
|
|
|
|
local records: { ArchetypeRecord } = {}
|
|
|
|
for i, componentId in types do
|
|
|
|
local tr = { column = i, count = 1 }
|
|
|
|
local idr = id_record_ensure(world, componentId)
|
|
|
|
idr.cache[id] = tr
|
|
|
|
idr.size += 1
|
|
|
|
records[componentId] = tr
|
|
|
|
if ECS_IS_PAIR(componentId) then
|
|
|
|
local relation = ecs_pair_first(world, componentId)
|
|
|
|
local object = ecs_pair_second(world, componentId)
|
|
|
|
|
|
|
|
local r = ECS_PAIR(relation, EcsWildcard)
|
|
|
|
local idr_r = id_record_ensure(world, r)
|
|
|
|
|
|
|
|
local o = ECS_PAIR(EcsWildcard, object)
|
|
|
|
local idr_o = id_record_ensure(world, o)
|
|
|
|
|
|
|
|
records[r] = tr
|
|
|
|
records[o] = tr
|
|
|
|
|
|
|
|
idr_r.cache[id] = tr
|
|
|
|
idr_o.cache[id] = tr
|
|
|
|
|
|
|
|
idr_r.size += 1
|
|
|
|
idr_o.size += 1
|
|
|
|
end
|
|
|
|
columns[i] = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype: Archetype = {
|
|
|
|
columns = columns,
|
|
|
|
edges = {},
|
|
|
|
entities = {},
|
|
|
|
id = id,
|
|
|
|
records = records,
|
|
|
|
type = ty,
|
|
|
|
types = types,
|
|
|
|
}
|
|
|
|
|
|
|
|
world.archetypeIndex[ty] = archetype
|
|
|
|
world.archetypes[id] = archetype
|
|
|
|
|
|
|
|
return archetype
|
|
|
|
end
|
|
|
|
|
|
|
|
local function world_entity(world: World): i53
|
|
|
|
local entityId = world.nextEntityId + 1
|
|
|
|
world.nextEntityId = entityId
|
|
|
|
return entity_index_new_id(world.entityIndex, entityId + EcsRest)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO:
|
|
|
|
-- should have an additional `nth` parameter which selects the nth target
|
|
|
|
-- this is important when an entity can have multiple relationships with the same target
|
|
|
|
local function world_target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
|
|
|
|
local record = world.entityIndex.sparse[entity]
|
|
|
|
local archetype = record.archetype
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local idr = world.componentIndex[ECS_PAIR(relation, EcsWildcard)]
|
|
|
|
if not idr then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local tr = idr.cache[archetype.id]
|
|
|
|
if not tr then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
return ecs_pair_second(world, archetype.types[tr.column])
|
|
|
|
end
|
|
|
|
|
|
|
|
local function world_parent(world: World, entity: i53)
|
|
|
|
return world_target(world, entity, EcsChildOf)
|
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function archetype_ensure(world: World, types, prev): Archetype
|
2024-04-23 15:10:49 +00:00
|
|
|
if #types < 1 then
|
2024-04-30 14:36:38 +00:00
|
|
|
return world.ROOT_ARCHETYPE
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
2024-05-04 23:52:01 +00:00
|
|
|
|
2024-04-23 15:10:49 +00:00
|
|
|
local ty = hash(types)
|
|
|
|
local archetype = world.archetypeIndex[ty]
|
|
|
|
if archetype then
|
|
|
|
return archetype
|
|
|
|
end
|
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
return archetype_create(world, types, prev)
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function find_insert(types: { i53 }, toAdd: i53): number
|
2024-05-04 23:52:01 +00:00
|
|
|
for i, id in types do
|
2024-04-23 15:10:49 +00:00
|
|
|
if id == toAdd then
|
|
|
|
return -1
|
|
|
|
end
|
|
|
|
if id > toAdd then
|
|
|
|
return i
|
|
|
|
end
|
|
|
|
end
|
2024-05-04 23:52:01 +00:00
|
|
|
return #types + 1
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
local function find_archetype_with(world: World, node: Archetype, id: i53): Archetype
|
2024-04-23 15:10:49 +00:00
|
|
|
local types = node.types
|
2024-05-03 00:39:59 +00:00
|
|
|
-- Component IDs are added incrementally, so inserting and sorting
|
2024-05-04 23:52:01 +00:00
|
|
|
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
2024-05-03 00:39:59 +00:00
|
|
|
-- point in the types array.
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local dst = table.clone(node.types) :: { i53 }
|
2024-07-15 18:29:06 +00:00
|
|
|
local at = find_insert(types, id)
|
2024-04-23 15:10:49 +00:00
|
|
|
if at == -1 then
|
2024-05-03 00:39:59 +00:00
|
|
|
-- If it finds a duplicate, it just means it is the same archetype so it can return it
|
|
|
|
-- directly instead of needing to hash types for a lookup to the archetype.
|
2024-04-23 15:10:49 +00:00
|
|
|
return node
|
|
|
|
end
|
2024-08-02 20:24:05 +00:00
|
|
|
table.insert(dst, at, id)
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
return archetype_ensure(world, dst, node)
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local function edge_ensure(archetype: Archetype, id: i53): ArchetypeEdge
|
2024-05-04 23:52:01 +00:00
|
|
|
local edges = archetype.edges
|
2024-08-02 20:24:05 +00:00
|
|
|
local edge = edges[id]
|
2024-05-04 23:52:01 +00:00
|
|
|
if not edge then
|
|
|
|
edge = {} :: any
|
2024-08-02 20:24:05 +00:00
|
|
|
edges[id] = edge
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
2024-05-04 23:52:01 +00:00
|
|
|
return edge
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local function archetype_traverse_add(world: World, id: i53, from: Archetype): Archetype
|
2024-05-12 22:53:51 +00:00
|
|
|
from = from or world.ROOT_ARCHETYPE
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local edge = edge_ensure(from, id)
|
2024-05-04 23:52:01 +00:00
|
|
|
local add = edge.add
|
|
|
|
if not add then
|
|
|
|
-- Save an edge using the component ID to the archetype to allow
|
2024-05-03 00:39:59 +00:00
|
|
|
-- faster traversals to adjacent archetypes.
|
2024-08-02 20:24:05 +00:00
|
|
|
add = find_archetype_with(world, from, id)
|
2024-05-04 23:52:01 +00:00
|
|
|
edge.add = add :: never
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
return add
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-08-11 01:30:58 +00:00
|
|
|
local function invoke_hook(world: World, hook_id: number, id: i53, entity: i53, data: any?)
|
2024-08-11 13:03:05 +00:00
|
|
|
local hook = world_get_one_inline(world, id, hook_id)
|
2024-08-11 01:30:58 +00:00
|
|
|
if hook then
|
|
|
|
hook(entity, data)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local function world_add(world: World, entity: i53, id: i53)
|
2024-05-10 15:59:57 +00:00
|
|
|
local entityIndex = world.entityIndex
|
2024-08-02 20:24:05 +00:00
|
|
|
local record = entityIndex.sparse[entity]
|
2024-05-07 19:32:56 +00:00
|
|
|
local from = record.archetype
|
2024-08-02 20:24:05 +00:00
|
|
|
local to = archetype_traverse_add(world, id, from)
|
2024-07-13 02:32:31 +00:00
|
|
|
if from == to then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if from then
|
2024-08-02 20:24:05 +00:00
|
|
|
entity_move(entityIndex, entity, record, to)
|
2024-05-07 19:32:56 +00:00
|
|
|
else
|
|
|
|
if #to.types > 0 then
|
2024-08-02 20:24:05 +00:00
|
|
|
new_entity(entity, record, to)
|
2024-05-07 19:32:56 +00:00
|
|
|
end
|
|
|
|
end
|
2024-08-11 01:30:58 +00:00
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
local idr = world.componentIndex[id]
|
|
|
|
local has_hooks = bit32.band(idr.flags, ECS_ID_HAS_HOOKS) ~= 0
|
|
|
|
|
|
|
|
if has_hooks then
|
|
|
|
invoke_hook(world, EcsOnAdd, id, entity)
|
|
|
|
end
|
2024-05-07 19:32:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Symmetric like `World.add` but idempotent
|
2024-08-02 20:24:05 +00:00
|
|
|
local function world_set(world: World, entity: i53, id: i53, data: unknown)
|
|
|
|
local record = world.entityIndex.sparse[entity]
|
2024-05-03 00:39:59 +00:00
|
|
|
local from = record.archetype
|
2024-08-02 20:24:05 +00:00
|
|
|
local to = archetype_traverse_add(world, id, from)
|
2024-08-14 15:18:05 +00:00
|
|
|
local idr = world.componentIndex[id]
|
|
|
|
local has_hooks = bit32.band(idr.flags, ECS_ID_HAS_HOOKS) ~= 0
|
2024-05-03 00:39:59 +00:00
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
if from == to then
|
2024-05-04 23:52:01 +00:00
|
|
|
-- If the archetypes are the same it can avoid moving the entity
|
|
|
|
-- and just set the data directly.
|
2024-08-02 20:24:05 +00:00
|
|
|
local tr = to.records[id]
|
|
|
|
from.columns[tr.column][record.row] = data
|
2024-08-14 15:18:05 +00:00
|
|
|
if has_hooks then
|
|
|
|
invoke_hook(world, EcsOnSet, id, entity, data)
|
|
|
|
end
|
|
|
|
|
2024-04-30 15:52:44 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-05-03 00:39:59 +00:00
|
|
|
if from then
|
2024-05-04 23:52:01 +00:00
|
|
|
-- If there was a previous archetype, then the entity needs to move the archetype
|
2024-08-02 20:24:05 +00:00
|
|
|
entity_move(world.entityIndex, entity, record, to)
|
2024-04-23 15:10:49 +00:00
|
|
|
else
|
2024-05-03 00:39:59 +00:00
|
|
|
if #to.types > 0 then
|
|
|
|
-- When there is no previous archetype it should create the archetype
|
2024-08-02 20:24:05 +00:00
|
|
|
new_entity(entity, record, to)
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
end
|
2024-05-10 15:59:57 +00:00
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
local tr = to.records[id]
|
2024-08-02 20:24:05 +00:00
|
|
|
to.columns[tr.column][record.row] = data
|
2024-08-11 01:30:58 +00:00
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
if has_hooks then
|
|
|
|
invoke_hook(world, EcsOnSet, id, entity, data)
|
|
|
|
end
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
local function world_component(world: World): i53
|
2024-07-03 15:48:32 +00:00
|
|
|
local componentId = world.nextComponentId + 1
|
|
|
|
if componentId > HI_COMPONENT_ID then
|
|
|
|
-- IDs are partitioned into ranges because component IDs are not nominal,
|
|
|
|
-- so it needs to error when IDs intersect into the entity range.
|
|
|
|
error("Too many components, consider using world:entity() instead to create components.")
|
|
|
|
end
|
|
|
|
world.nextComponentId = componentId
|
2024-07-14 04:35:13 +00:00
|
|
|
local id = entity_index_new_id(world.entityIndex, componentId)
|
|
|
|
world_add(world, id, EcsComponent)
|
2024-07-03 15:48:32 +00:00
|
|
|
return id
|
|
|
|
end
|
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local function archetype_traverse_remove(world: World, id: i53, from: Archetype): Archetype
|
|
|
|
local edge = edge_ensure(from, id)
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
local remove = edge.remove
|
|
|
|
if not remove then
|
2024-06-15 18:03:12 +00:00
|
|
|
local to = table.clone(from.types) :: { i53 }
|
2024-08-02 20:24:05 +00:00
|
|
|
local at = table.find(to, id)
|
2024-05-27 01:39:20 +00:00
|
|
|
if not at then
|
2024-05-07 22:57:22 +00:00
|
|
|
return from
|
|
|
|
end
|
|
|
|
table.remove(to, at)
|
2024-07-14 04:35:13 +00:00
|
|
|
remove = archetype_ensure(world, to, from)
|
2024-08-02 20:24:05 +00:00
|
|
|
edge.remove = remove :: any
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
return remove
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
local function world_remove(world: World, entity: i53, id: i53)
|
|
|
|
local entity_index = world.entityIndex
|
|
|
|
local record = entity_index.sparse[entity]
|
|
|
|
local from = record.archetype
|
|
|
|
if not from then
|
2024-07-26 14:15:12 +00:00
|
|
|
return
|
|
|
|
end
|
2024-08-02 20:24:05 +00:00
|
|
|
local to = archetype_traverse_remove(world, id, from)
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-08-02 20:24:05 +00:00
|
|
|
if from and not (from == to) then
|
2024-08-11 02:03:18 +00:00
|
|
|
invoke_hook(world, EcsOnRemove, id, entity)
|
2024-08-02 20:24:05 +00:00
|
|
|
entity_move(entity_index, entity, record, to)
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
local function world_clear(world: World, entity: i53)
|
|
|
|
--TODO: use sparse_get (stashed)
|
|
|
|
local record = world.entityIndex.sparse[entity]
|
|
|
|
if not record then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
|
|
|
local archetype = record.archetype
|
|
|
|
|
|
|
|
if archetype == nil or archetype == ROOT_ARCHETYPE then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
entity_move(world.entityIndex, entity, record, ROOT_ARCHETYPE)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function archetype_fast_delete_last(world, columns,
|
|
|
|
column_count, types, entity)
|
2024-07-03 15:48:32 +00:00
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
for i, column in columns do
|
|
|
|
column[column_count] = nil
|
|
|
|
end
|
2024-07-03 15:48:32 +00:00
|
|
|
end
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
local function archetype_fast_delete(world, columns,
|
|
|
|
column_count, row, types, entity)
|
|
|
|
for i, column in columns do
|
|
|
|
column[row] = column[column_count]
|
|
|
|
column[column_count] = nil
|
|
|
|
end
|
|
|
|
end
|
2024-07-03 15:48:32 +00:00
|
|
|
|
|
|
|
|
2024-08-13 18:58:07 +00:00
|
|
|
local function archetype_delete(world: World, archetype,
|
|
|
|
row, track)
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
local entityIndex = world.entityIndex
|
|
|
|
local columns = archetype.columns
|
|
|
|
local types = archetype.types
|
|
|
|
local entities = archetype.entities
|
|
|
|
local column_count = #entities
|
|
|
|
local last = #entities
|
|
|
|
local move = entities[last]
|
|
|
|
local delete = entities[row]
|
|
|
|
entities[row] = move
|
|
|
|
entities[last] = nil
|
|
|
|
|
|
|
|
if row ~= last then
|
|
|
|
-- TODO: should be "entity_index_sparse_get(entityIndex, move)"
|
|
|
|
local record_to_move = entityIndex.sparse[move]
|
|
|
|
if record_to_move then
|
|
|
|
record_to_move.row = row
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: if last == 0 then deactivate table
|
2024-07-03 15:48:32 +00:00
|
|
|
|
2024-08-13 18:58:07 +00:00
|
|
|
for _, id in types do
|
|
|
|
invoke_hook(world, EcsOnRemove, id, delete)
|
|
|
|
end
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
if row == last then
|
|
|
|
archetype_fast_delete_last(world, columns,
|
|
|
|
column_count, types, delete)
|
|
|
|
else
|
|
|
|
archetype_fast_delete(world, columns, column_count,
|
|
|
|
row, types, delete)
|
|
|
|
end
|
2024-07-03 15:48:32 +00:00
|
|
|
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
local component_index = world.componentIndex
|
|
|
|
local archetypes = world.archetypes
|
|
|
|
|
|
|
|
local idr = component_index[delete]
|
|
|
|
if idr then
|
2024-08-13 18:58:07 +00:00
|
|
|
local children = {}
|
2024-08-13 18:08:58 +00:00
|
|
|
for archetype_id in idr.cache do
|
|
|
|
local idr_archetype = archetypes[archetype_id]
|
|
|
|
|
|
|
|
for i, child in idr_archetype.entities do
|
|
|
|
table.insert(children, child)
|
|
|
|
end
|
2024-08-13 18:58:07 +00:00
|
|
|
end
|
2024-08-14 15:18:05 +00:00
|
|
|
local flags = idr.flags
|
|
|
|
if bit32.band(flags, ECS_ID_HAS_DELETE) ~= 0 then
|
|
|
|
for _, child in children do
|
|
|
|
-- Cascade deletion to children
|
2024-08-13 18:58:07 +00:00
|
|
|
world_delete(world, child)
|
2024-08-14 15:18:05 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
for _, child in children do
|
2024-08-13 18:58:07 +00:00
|
|
|
world_remove(world, child, delete)
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
|
|
|
end
|
2024-08-14 15:18:05 +00:00
|
|
|
|
|
|
|
component_index[delete] = nil
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: iterate each linked record.
|
|
|
|
-- local r = ECS_PAIR(delete, EcsWildcard)
|
|
|
|
-- local idr_r = component_index[r]
|
|
|
|
-- if idr_r then
|
|
|
|
-- -- Doesn't work for relations atm
|
|
|
|
-- for archetype_id in idr_o.cache do
|
|
|
|
-- local children = {}
|
|
|
|
-- local idr_r_archetype = archetypes[archetype_id]
|
|
|
|
-- local idr_r_types = idr_r_archetype.types
|
|
|
|
|
|
|
|
-- for _, child in idr_r_archetype.entities do
|
|
|
|
-- table.insert(children, child)
|
|
|
|
-- end
|
|
|
|
|
|
|
|
-- for _, id in idr_r_types do
|
|
|
|
-- local relation = ECS_ENTITY_T_HI(id)
|
|
|
|
-- if world_target(world, child, relation) == delete then
|
|
|
|
-- world_remove(world, child, ECS_PAIR(relation, delete))
|
|
|
|
-- end
|
|
|
|
-- end
|
|
|
|
-- end
|
|
|
|
-- end
|
|
|
|
|
|
|
|
local o = ECS_PAIR(EcsWildcard, delete)
|
|
|
|
local idr_o = component_index[o]
|
2024-08-13 18:58:07 +00:00
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
if idr_o then
|
|
|
|
for archetype_id in idr_o.cache do
|
|
|
|
local children = {}
|
|
|
|
local idr_o_archetype = archetypes[archetype_id]
|
2024-08-13 18:58:07 +00:00
|
|
|
-- In the future, this needs to be optimized to only
|
|
|
|
-- look for linked records instead of doing this linearly
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
local idr_o_types = idr_o_archetype.types
|
|
|
|
|
|
|
|
for _, child in idr_o_archetype.entities do
|
|
|
|
table.insert(children, child)
|
|
|
|
end
|
2024-07-03 15:48:32 +00:00
|
|
|
|
2024-08-13 18:58:07 +00:00
|
|
|
for _, id in idr_o_types do
|
|
|
|
if not ECS_IS_PAIR(id) then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
local id_record = component_index[id]
|
2024-08-13 18:58:07 +00:00
|
|
|
|
2024-08-14 15:18:05 +00:00
|
|
|
if id_record then
|
|
|
|
local flags = id_record.flags
|
|
|
|
if bit32.band(flags, ECS_ID_HAS_DELETE) ~= 0 then
|
|
|
|
for _, child in children do
|
|
|
|
-- Cascade deletions of it has Delete as component trait
|
|
|
|
world_delete(world, child)
|
|
|
|
end
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
2024-08-13 18:58:07 +00:00
|
|
|
else
|
2024-08-13 19:23:40 +00:00
|
|
|
local object = ECS_ENTITY_T_LO(id)
|
|
|
|
if object == delete then
|
|
|
|
for _, child in children do
|
2024-08-14 15:18:05 +00:00
|
|
|
world_remove(world, child, id)
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2024-08-14 15:18:05 +00:00
|
|
|
component_index[o] = nil
|
2024-08-13 18:08:58 +00:00
|
|
|
end
|
2024-07-03 15:48:32 +00:00
|
|
|
end
|
|
|
|
|
2024-08-13 18:58:07 +00:00
|
|
|
function world_delete(world: World, entity: i53, track)
|
2024-08-13 18:08:58 +00:00
|
|
|
local entityIndex = world.entityIndex
|
|
|
|
|
|
|
|
local record = entityIndex.sparse[entity]
|
2024-07-07 02:53:17 +00:00
|
|
|
if not record then
|
2024-07-03 15:48:32 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetype = record.archetype
|
2024-08-13 18:08:58 +00:00
|
|
|
local row = record.row
|
2024-07-03 15:48:32 +00:00
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
if archetype then
|
|
|
|
-- In the future should have a destruct mode for
|
|
|
|
-- deleting archetypes themselves. Maybe requires recycling
|
2024-08-13 18:58:07 +00:00
|
|
|
archetype_delete(world, archetype, row, track)
|
2024-07-03 15:48:32 +00:00
|
|
|
end
|
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
record.archetype = nil :: any
|
|
|
|
entityIndex.sparse[entity] = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local function world_contains(world, entity)
|
|
|
|
|
|
|
|
return world.entityIndex.sparse[entity]
|
2024-07-03 15:48:32 +00:00
|
|
|
end
|
|
|
|
|
2024-06-21 22:15:03 +00:00
|
|
|
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
local function noop()
|
2024-08-03 02:17:36 +00:00
|
|
|
end
|
2024-07-14 06:30:20 +00:00
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
local function Arm(query, ...)
|
|
|
|
return query
|
2024-08-03 02:17:36 +00:00
|
|
|
end
|
2024-08-12 20:43:46 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local world_query
|
|
|
|
do
|
2024-08-04 21:07:32 +00:00
|
|
|
local empty_list = {}
|
2024-08-11 01:27:29 +00:00
|
|
|
local EmptyQuery = {
|
|
|
|
__iter = function()
|
2024-08-03 02:49:45 +00:00
|
|
|
return noop
|
|
|
|
end,
|
2024-08-11 01:27:29 +00:00
|
|
|
iter = function()
|
2024-08-08 00:56:57 +00:00
|
|
|
return noop
|
|
|
|
end,
|
2024-08-03 02:49:45 +00:00
|
|
|
drain = Arm,
|
2024-08-11 01:27:29 +00:00
|
|
|
next = noop,
|
|
|
|
replace = noop,
|
2024-08-03 02:49:45 +00:00
|
|
|
with = Arm,
|
|
|
|
without = Arm,
|
2024-08-04 21:07:32 +00:00
|
|
|
archetypes = function()
|
|
|
|
return empty_list
|
2024-08-05 12:32:25 +00:00
|
|
|
end,
|
2024-08-03 02:49:45 +00:00
|
|
|
}
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
setmetatable(EmptyQuery, EmptyQuery)
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_replace_values(row, columns, ...)
|
|
|
|
for i, column in columns do
|
|
|
|
column[row] = select(i, ...)
|
|
|
|
end
|
|
|
|
end
|
2024-07-14 03:38:44 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
function world_query(world, ...)
|
|
|
|
-- breaking
|
|
|
|
if (...) == nil then
|
|
|
|
error("Missing components")
|
|
|
|
end
|
2024-07-30 15:17:18 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local compatible_archetypes = {}
|
|
|
|
local length = 0
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local ids = { ... }
|
|
|
|
local A, B, C, D, E, F, G, H, I = ...
|
|
|
|
local a, b, c, d, e, f, g, h
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local archetypes = world.archetypes
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local idr: ArchetypeMap
|
|
|
|
local componentIndex = world.componentIndex
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
for _, id in ids do
|
|
|
|
local map = componentIndex[id]
|
|
|
|
if not map then
|
|
|
|
return EmptyQuery
|
|
|
|
end
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
if idr == nil or map.size < idr.size then
|
|
|
|
idr = map
|
|
|
|
end
|
|
|
|
end
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
for archetype_id in idr.cache do
|
|
|
|
local compatibleArchetype = archetypes[archetype_id]
|
2024-08-13 23:15:04 +00:00
|
|
|
if #compatibleArchetype.entities == 0 then
|
|
|
|
continue
|
|
|
|
end
|
2024-08-05 15:13:00 +00:00
|
|
|
local records = compatibleArchetype.records
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local skip = false
|
2024-07-30 17:36:53 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
for i, id in ids do
|
2024-08-05 15:13:00 +00:00
|
|
|
local tr = records[id]
|
|
|
|
if not tr then
|
2024-08-03 02:49:45 +00:00
|
|
|
skip = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2024-07-30 17:36:53 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
if skip then
|
|
|
|
continue
|
|
|
|
end
|
2024-07-30 15:17:18 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
length += 1
|
|
|
|
compatible_archetypes[length] = compatibleArchetype
|
2024-07-30 15:17:18 +00:00
|
|
|
end
|
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
if length == 0 then
|
|
|
|
return EmptyQuery
|
|
|
|
end
|
|
|
|
|
|
|
|
local lastArchetype = 1
|
|
|
|
local archetype
|
|
|
|
local columns
|
|
|
|
local entities
|
|
|
|
local i
|
|
|
|
local queryOutput
|
2024-07-30 15:17:18 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local world_query_iter_next
|
2024-07-30 15:17:18 +00:00
|
|
|
|
|
|
|
if not B then
|
2024-08-03 02:49:45 +00:00
|
|
|
function world_query_iter_next(): any
|
|
|
|
local entityId = entities[i]
|
|
|
|
while entityId == nil do
|
|
|
|
lastArchetype += 1
|
|
|
|
archetype = compatible_archetypes[lastArchetype]
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
entities = archetype.entities
|
|
|
|
i = #entities
|
|
|
|
if i == 0 then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
entityId = entities[i]
|
|
|
|
columns = archetype.columns
|
|
|
|
local records = archetype.records
|
|
|
|
a = columns[records[A].column]
|
|
|
|
end
|
2024-07-30 15:17:18 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local row = i
|
|
|
|
i-=1
|
2024-07-30 18:00:40 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
return entityId, a[row]
|
|
|
|
end
|
|
|
|
elseif not C then
|
|
|
|
function world_query_iter_next(): any
|
|
|
|
local entityId = entities[i]
|
|
|
|
while entityId == nil do
|
|
|
|
lastArchetype += 1
|
|
|
|
archetype = compatible_archetypes[lastArchetype]
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
entities = archetype.entities
|
|
|
|
i = #entities
|
|
|
|
if i == 0 then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
entityId = entities[i]
|
|
|
|
columns = archetype.columns
|
|
|
|
local records = archetype.records
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
2024-07-28 12:31:47 +00:00
|
|
|
end
|
2024-07-14 03:38:44 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local row = i
|
|
|
|
i-=1
|
2024-07-07 02:53:17 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
return entityId, a[row], b[row]
|
|
|
|
end
|
|
|
|
elseif not D then
|
|
|
|
function world_query_iter_next(): any
|
|
|
|
local entityId = entities[i]
|
|
|
|
while entityId == nil do
|
|
|
|
lastArchetype += 1
|
|
|
|
archetype = compatible_archetypes[lastArchetype]
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
entities = archetype.entities
|
|
|
|
i = #entities
|
|
|
|
if i == 0 then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
entityId = entities[i]
|
|
|
|
columns = archetype.columns
|
|
|
|
local records = archetype.records
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
end
|
2024-07-14 05:30:13 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local row = i
|
|
|
|
i-=1
|
2024-07-30 18:00:40 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
return entityId, a[row], b[row], c[row]
|
|
|
|
end
|
|
|
|
elseif not E then
|
|
|
|
function world_query_iter_next(): any
|
|
|
|
local entityId = entities[i]
|
|
|
|
while entityId == nil do
|
|
|
|
lastArchetype += 1
|
|
|
|
archetype = compatible_archetypes[lastArchetype]
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
entities = archetype.entities
|
|
|
|
i = #entities
|
|
|
|
if i == 0 then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
entityId = entities[i]
|
|
|
|
columns = archetype.columns
|
|
|
|
local records = archetype.records
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
end
|
|
|
|
|
|
|
|
local row = i
|
|
|
|
i-=1
|
|
|
|
|
|
|
|
return entityId, a[row], b[row], c[row], d[row]
|
2024-07-14 03:38:44 +00:00
|
|
|
end
|
2024-08-03 02:49:45 +00:00
|
|
|
else
|
|
|
|
function world_query_iter_next(): any
|
|
|
|
local entityId = entities[i]
|
|
|
|
while entityId == nil do
|
|
|
|
lastArchetype += 1
|
|
|
|
archetype = compatible_archetypes[lastArchetype]
|
|
|
|
if not archetype then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
entities = archetype.entities
|
|
|
|
i = #entities
|
|
|
|
if i == 0 then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
entityId = entities[i]
|
|
|
|
columns = archetype.columns
|
|
|
|
local records = archetype.records
|
|
|
|
|
|
|
|
if not F then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
elseif not G then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
f = columns[records[F].column]
|
|
|
|
elseif not H then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
f = columns[records[F].column]
|
|
|
|
g = columns[records[G].column]
|
|
|
|
elseif not I then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
f = columns[records[F].column]
|
|
|
|
g = columns[records[G].column]
|
|
|
|
h = columns[records[H].column]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local row = i
|
|
|
|
i-=1
|
|
|
|
|
|
|
|
if not F then
|
|
|
|
return entityId, a[row], b[row], c[row], d[row], e[row]
|
|
|
|
elseif not G then
|
|
|
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row]
|
|
|
|
elseif not H then
|
|
|
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
|
|
|
|
elseif not I then
|
|
|
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
|
|
|
|
end
|
|
|
|
|
2024-08-05 15:13:00 +00:00
|
|
|
local records = archetype.records
|
2024-08-03 02:49:45 +00:00
|
|
|
for j, id in ids do
|
2024-08-05 15:13:00 +00:00
|
|
|
queryOutput[j] = columns[records[id].column][row]
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return entityId, unpack(queryOutput)
|
|
|
|
end
|
|
|
|
end
|
2024-07-14 03:38:44 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local init = false
|
|
|
|
local drain = false
|
2024-07-30 18:00:40 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function query_init(query)
|
|
|
|
if init and drain then
|
2024-08-04 21:07:32 +00:00
|
|
|
return true
|
2024-07-30 18:00:40 +00:00
|
|
|
end
|
2024-07-26 14:21:13 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
init = true
|
|
|
|
lastArchetype = 1
|
|
|
|
archetype = compatible_archetypes[lastArchetype]
|
|
|
|
|
|
|
|
if not archetype then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
queryOutput = {}
|
|
|
|
|
|
|
|
entities = archetype.entities
|
|
|
|
i = #entities
|
|
|
|
columns = archetype.columns
|
|
|
|
|
|
|
|
local records = archetype.records
|
|
|
|
if not B then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
elseif not C then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
elseif not D then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
elseif not E then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
elseif not F then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
elseif not G then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
f = columns[records[F].column]
|
|
|
|
elseif not H then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
f = columns[records[F].column]
|
|
|
|
g = columns[records[G].column]
|
|
|
|
elseif not I then
|
|
|
|
a = columns[records[A].column]
|
|
|
|
b = columns[records[B].column]
|
|
|
|
c = columns[records[C].column]
|
|
|
|
d = columns[records[D].column]
|
|
|
|
e = columns[records[E].column]
|
|
|
|
f = columns[records[F].column]
|
|
|
|
g = columns[records[G].column]
|
|
|
|
h = columns[records[H].column]
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
2024-07-30 17:36:53 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_without(query, ...)
|
2024-08-05 15:13:00 +00:00
|
|
|
local N = select("#", ...)
|
|
|
|
for i = #compatible_archetypes, 1, -1 do
|
|
|
|
local archetype = compatible_archetypes[i]
|
|
|
|
local records = archetype.records
|
|
|
|
local shouldRemove = false
|
|
|
|
|
|
|
|
for j = 1, N do
|
|
|
|
local id = select(j, ...)
|
|
|
|
if records[id] then
|
|
|
|
shouldRemove = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2024-08-03 02:49:45 +00:00
|
|
|
|
2024-08-05 15:13:00 +00:00
|
|
|
if shouldRemove then
|
|
|
|
local last = #compatible_archetypes
|
|
|
|
if last ~= i then
|
|
|
|
compatible_archetypes[i] = compatible_archetypes[last]
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
2024-08-05 15:13:00 +00:00
|
|
|
compatible_archetypes[last] = nil
|
|
|
|
length -= 1
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
2024-08-05 15:13:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if length == 0 then
|
|
|
|
return EmptyQuery
|
|
|
|
end
|
2024-07-28 12:36:53 +00:00
|
|
|
|
2024-08-05 15:13:00 +00:00
|
|
|
return query
|
2024-08-03 02:17:36 +00:00
|
|
|
end
|
2024-07-30 15:17:18 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_replace(query, fn: (...any) -> (...any))
|
2024-08-05 15:13:00 +00:00
|
|
|
query_init(query)
|
|
|
|
|
|
|
|
for i, archetype in compatible_archetypes do
|
|
|
|
local columns = archetype.columns
|
|
|
|
local records = archetype.records
|
|
|
|
for row in archetype.entities do
|
|
|
|
if not B then
|
|
|
|
local va = columns[records[A].column]
|
|
|
|
local pa = fn(va[row])
|
|
|
|
|
|
|
|
va[row] = pa
|
|
|
|
elseif not C then
|
|
|
|
local va = columns[records[A].column]
|
|
|
|
local vb = columns[records[B].column]
|
|
|
|
|
|
|
|
va[row], vb[row] = fn(va[row], vb[row])
|
|
|
|
elseif not D then
|
|
|
|
local va = columns[records[A].column]
|
|
|
|
local vb = columns[records[B].column]
|
|
|
|
local vc = columns[records[C].column]
|
|
|
|
|
|
|
|
va[row], vb[row], vc[row] = fn(va[row], vb[row], vc[row])
|
|
|
|
elseif not E then
|
|
|
|
local va = columns[records[A].column]
|
|
|
|
local vb = columns[records[B].column]
|
|
|
|
local vc = columns[records[C].column]
|
|
|
|
local vd = columns[records[D].column]
|
|
|
|
|
|
|
|
va[row], vb[row], vc[row], vd[row] = fn(
|
|
|
|
va[row], vb[row], vc[row], vd[row])
|
|
|
|
else
|
|
|
|
for j, id in ids do
|
|
|
|
local tr = records[id]
|
|
|
|
queryOutput[j] = columns[tr.column][row]
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
2024-08-05 15:13:00 +00:00
|
|
|
world_query_replace_values(row, columns,
|
|
|
|
fn(unpack(queryOutput)))
|
|
|
|
end
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
2024-08-05 15:13:00 +00:00
|
|
|
end
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
2024-07-30 17:36:53 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_with(query, ...)
|
2024-08-05 15:13:00 +00:00
|
|
|
local N = select("#", ...)
|
|
|
|
for i = #compatible_archetypes, 1, -1 do
|
|
|
|
local archetype = compatible_archetypes[i]
|
|
|
|
local records = archetype.records
|
|
|
|
local shouldRemove = false
|
|
|
|
|
|
|
|
for j = 1, N do
|
|
|
|
local id = select(j, ...)
|
|
|
|
if not records[id] then
|
|
|
|
shouldRemove = true
|
|
|
|
break
|
|
|
|
end
|
2024-08-03 02:49:45 +00:00
|
|
|
end
|
2024-08-05 15:13:00 +00:00
|
|
|
|
|
|
|
if shouldRemove then
|
|
|
|
local last = #compatible_archetypes
|
|
|
|
if last ~= i then
|
|
|
|
compatible_archetypes[i] = compatible_archetypes[last]
|
|
|
|
end
|
|
|
|
compatible_archetypes[last] = nil
|
|
|
|
length -= 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if length == 0 then
|
|
|
|
return EmptyQuery
|
|
|
|
end
|
|
|
|
return query
|
2024-07-30 15:17:18 +00:00
|
|
|
end
|
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
-- Meant for directly iterating over archetypes to minimize
|
|
|
|
-- function call overhead. Should not be used unless iterating over
|
|
|
|
-- hundreds of thousands of entities in bulk.
|
|
|
|
local function world_query_archetypes()
|
|
|
|
return compatible_archetypes
|
|
|
|
end
|
2024-08-03 02:17:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_drain(query)
|
|
|
|
drain = true
|
|
|
|
if query_init(query) then
|
|
|
|
return query
|
|
|
|
end
|
|
|
|
return EmptyQuery
|
|
|
|
end
|
2024-07-28 10:33:38 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_iter(query)
|
|
|
|
query_init(query)
|
|
|
|
return world_query_iter_next
|
|
|
|
end
|
2024-07-28 10:33:38 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local function world_query_next(world)
|
|
|
|
if not drain then
|
|
|
|
error("Did you forget to call query:drain()?")
|
|
|
|
end
|
|
|
|
return world_query_iter_next(world)
|
|
|
|
end
|
2024-07-26 00:55:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
local it = {
|
|
|
|
__iter = world_query_iter,
|
2024-08-07 16:43:01 +00:00
|
|
|
iter = world_query_iter,
|
2024-08-03 02:49:45 +00:00
|
|
|
drain = world_query_drain,
|
|
|
|
next = world_query_next,
|
|
|
|
with = world_query_with,
|
|
|
|
without = world_query_without,
|
|
|
|
replace = world_query_replace,
|
|
|
|
archetypes = world_query_archetypes
|
|
|
|
} :: any
|
2024-07-26 00:55:36 +00:00
|
|
|
|
2024-08-03 02:49:45 +00:00
|
|
|
setmetatable(it, it)
|
|
|
|
|
|
|
|
return it
|
|
|
|
end
|
2024-06-23 23:30:59 +00:00
|
|
|
end
|
|
|
|
|
2024-07-03 15:48:32 +00:00
|
|
|
local World = {}
|
|
|
|
World.__index = World
|
|
|
|
|
2024-07-14 04:35:13 +00:00
|
|
|
World.entity = world_entity
|
|
|
|
World.query = world_query
|
|
|
|
World.remove = world_remove
|
|
|
|
World.clear = world_clear
|
|
|
|
World.delete = world_delete
|
|
|
|
World.component = world_component
|
|
|
|
World.add = world_add
|
|
|
|
World.set = world_set
|
|
|
|
World.get = world_get
|
2024-07-23 02:44:56 +00:00
|
|
|
World.has = world_has
|
2024-07-14 04:35:13 +00:00
|
|
|
World.target = world_target
|
|
|
|
World.parent = world_parent
|
2024-08-13 18:08:58 +00:00
|
|
|
World.contains = world_contains
|
2024-07-03 15:48:32 +00:00
|
|
|
|
2024-07-14 03:38:44 +00:00
|
|
|
function World.new()
|
|
|
|
local self = setmetatable({
|
2024-07-28 12:17:44 +00:00
|
|
|
archetypeIndex = {} :: { [string]: Archetype },
|
|
|
|
archetypes = {} :: Archetypes,
|
|
|
|
componentIndex = {} :: ComponentIndex,
|
|
|
|
entityIndex = {
|
|
|
|
dense = {} :: { [i24]: i53 },
|
|
|
|
sparse = {} :: { [i53]: Record },
|
|
|
|
} :: EntityIndex,
|
|
|
|
nextArchetypeId = 0,
|
|
|
|
nextComponentId = 0,
|
|
|
|
nextEntityId = 0,
|
|
|
|
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
|
|
|
}, World)
|
2024-07-14 03:38:44 +00:00
|
|
|
|
2024-07-29 23:11:22 +00:00
|
|
|
self.ROOT_ARCHETYPE = archetype_create(self, {})
|
2024-07-14 03:38:44 +00:00
|
|
|
|
2024-07-28 12:17:44 +00:00
|
|
|
for i = HI_COMPONENT_ID + 1, EcsRest do
|
|
|
|
-- Initialize built-in components
|
|
|
|
entity_index_new_id(self.entityIndex, i)
|
|
|
|
end
|
2024-07-14 03:38:44 +00:00
|
|
|
|
2024-08-13 18:08:58 +00:00
|
|
|
world_add(self :: any, EcsChildOf, EcsDelete)
|
|
|
|
|
2024-07-14 03:38:44 +00:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2024-08-11 01:27:29 +00:00
|
|
|
export type Pair = number
|
|
|
|
|
|
|
|
type Item = () -> (number, ...any)
|
|
|
|
|
|
|
|
export type Entity<T = nil> = number & {__DO_NOT_USE_OR_YOU_WILL_BE_FIRED: T }
|
|
|
|
|
2024-08-12 20:43:46 +00:00
|
|
|
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
2024-08-11 01:27:29 +00:00
|
|
|
|
|
|
|
type Query<T...> = typeof(setmetatable({}, {
|
|
|
|
__iter = (nil :: any) :: Iter<T...>
|
|
|
|
})) & {
|
|
|
|
iter: Iter<T...>,
|
|
|
|
next: Item,
|
|
|
|
with: (Query<T...>) -> Query<T...>,
|
|
|
|
without: (Query<T...>, ...i53) -> Query<T...>,
|
|
|
|
replace: (Query<T...>, <U...>(T...) -> (U...)) -> (),
|
|
|
|
archetypes: () -> { Archetype },
|
|
|
|
}
|
|
|
|
|
|
|
|
export type World = {
|
|
|
|
archetypeIndex: { [string]: Archetype },
|
|
|
|
archetypes: Archetypes,
|
|
|
|
componentIndex: ComponentIndex,
|
|
|
|
entityIndex: EntityIndex,
|
|
|
|
nextArchetypeId: number,
|
|
|
|
nextComponentId: number,
|
|
|
|
nextEntityId: number,
|
|
|
|
ROOT_ARCHETYPE: Archetype,
|
|
|
|
} & {
|
2024-08-12 20:43:46 +00:00
|
|
|
target: (World, entity: Entity, relation: Entity) -> Entity,
|
|
|
|
parent: (World, entity: Entity) -> Entity,
|
|
|
|
entity: (World) -> Entity,
|
|
|
|
clear: (World, entity: Entity) -> (),
|
|
|
|
delete: (World, entity: Entity) -> (),
|
|
|
|
component: <T>(World) -> Entity<T>,
|
|
|
|
get: (<T>(World, entity: Entity, id: Entity<T>) -> T)
|
|
|
|
& (<A, B>(World, id: Entity, Entity<A>, Entity<B>) -> (A, B))
|
|
|
|
& (<A, B, C>(World, id: Entity, Entity<A>, Entity<B>, Entity<C>) -> (A, B, C))
|
|
|
|
& <A, B, C, D>(World, id: Entity, Entity<A>, Entity<B>, Entity<C>, Entity<D>) -> (A, B, C, D),
|
|
|
|
has: (World, entity: Entity, ...Entity) -> boolean,
|
|
|
|
add: (World, entity: Entity, id: Entity) -> (),
|
|
|
|
set: <T>(World, entity: Entity,
|
2024-08-11 01:27:29 +00:00
|
|
|
id: Entity<T>, data: T) -> (),
|
2024-08-12 20:43:46 +00:00
|
|
|
remove: (World, entity: Entity, id: Entity) -> (),
|
2024-08-11 01:27:29 +00:00
|
|
|
query:
|
|
|
|
(<A>(World, Entity<A>) -> Query<A>)
|
|
|
|
& (<A, B>(World, Entity<A>, Entity<B>) -> Query<A, B>)
|
|
|
|
& (<A, B, C>(World, Entity<A>, Entity<B>, Entity<C>) -> Query<A, B, C>)
|
|
|
|
& (<A, B, C, D>(World, Entity<A>, Entity<B>, Entity<C>,
|
|
|
|
Entity<D>) -> Query<A, B, C, D>)
|
|
|
|
& (<A, B, C, D, E>(World, Entity<A>, Entity<B>, Entity<C>,
|
|
|
|
Entity<D>, Entity<E>) -> Query<A, B, C, D, E>)
|
|
|
|
& (<A, B, C, D, E, F>(World, Entity<A>, Entity<B>, Entity<C>,
|
|
|
|
Entity<D>, Entity<E>, Entity<F>) -> Query<A, B, C, D, E, F>)
|
|
|
|
& (<A, B, C, D, E, F, G>(World, Entity<A>, Entity<B>, Entity<C>,
|
|
|
|
Entity<D>, Entity<E>, Entity<F>, Entity<G>) -> Query<A, B, C, D, E, F, G>)
|
|
|
|
& (<A, B, C, D, E, F, G, H>(World, Entity<A>, Entity<B>, Entity<C>,
|
|
|
|
Entity<D>, Entity<E>, Entity<F>, Entity<G>, Entity<H>) -> Query<A, B, C, D, E, F, G, H>)
|
|
|
|
}
|
2024-08-11 01:30:58 +00:00
|
|
|
|
2024-07-03 00:10:11 +00:00
|
|
|
return {
|
2024-08-12 20:43:46 +00:00
|
|
|
World = World :: { new: () -> World },
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-07-03 15:48:32 +00:00
|
|
|
OnAdd = EcsOnAdd :: Entity,
|
|
|
|
OnRemove = EcsOnRemove :: Entity,
|
|
|
|
OnSet = EcsOnSet :: Entity,
|
2024-08-07 16:43:01 +00:00
|
|
|
ChildOf = EcsChildOf :: Entity,
|
|
|
|
Component = EcsComponent :: Entity,
|
2024-07-28 00:39:19 +00:00
|
|
|
Wildcard = EcsWildcard :: Entity,
|
|
|
|
w = EcsWildcard :: Entity,
|
2024-08-13 19:17:49 +00:00
|
|
|
Delete = EcsDelete :: Entity,
|
2024-07-29 23:11:22 +00:00
|
|
|
Rest = EcsRest :: Entity,
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-07-15 18:29:06 +00:00
|
|
|
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number,
|
|
|
|
|
|
|
|
-- Inwards facing API for testing
|
2024-05-14 15:52:41 +00:00
|
|
|
ECS_ID = ECS_ENTITY_T_LO,
|
2024-05-10 15:59:57 +00:00
|
|
|
ECS_GENERATION_INC = ECS_GENERATION_INC,
|
2024-05-12 22:53:51 +00:00
|
|
|
ECS_GENERATION = ECS_GENERATION,
|
2024-08-12 20:43:46 +00:00
|
|
|
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-08-10 02:55:04 +00:00
|
|
|
IS_PAIR = ECS_IS_PAIR,
|
|
|
|
pair_first = ecs_pair_first,
|
|
|
|
pair_second = ecs_pair_second,
|
2024-07-15 18:29:06 +00:00
|
|
|
entity_index_get_alive = entity_index_get_alive,
|
2024-07-03 00:10:11 +00:00
|
|
|
}
|