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-06-10 23:39:43 +00:00
|
|
|
records: { [number]: number },
|
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
|
|
|
|
|
|
|
type ArchetypeRecord = number
|
2024-05-16 22:17:53 +00:00
|
|
|
--[[
|
|
|
|
TODO:
|
|
|
|
{
|
|
|
|
index: number,
|
|
|
|
count: number,
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
type ArchetypeMap = {
|
2024-06-10 23:39:43 +00:00
|
|
|
cache: { ArchetypeRecord },
|
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-05-12 22:53:51 +00:00
|
|
|
local FLAGS_PAIR = 0x8
|
2024-04-28 14:46:40 +00:00
|
|
|
local HI_COMPONENT_ID = 256
|
|
|
|
local ON_ADD = HI_COMPONENT_ID + 1
|
|
|
|
local ON_REMOVE = HI_COMPONENT_ID + 2
|
|
|
|
local ON_SET = HI_COMPONENT_ID + 3
|
2024-05-12 22:53:51 +00:00
|
|
|
local WILDCARD = HI_COMPONENT_ID + 4
|
2024-05-27 01:39:20 +00:00
|
|
|
local REST = HI_COMPONENT_ID + 5
|
2024-05-12 22:53:51 +00:00
|
|
|
|
|
|
|
local ECS_ID_FLAGS_MASK = 0x10
|
|
|
|
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
|
|
|
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function addFlags(isPair: boolean): number
|
2024-05-27 01:39:20 +00:00
|
|
|
local typeFlags = 0x0
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
if isPair then
|
|
|
|
typeFlags = bit32.bor(typeFlags, FLAGS_PAIR) -- HIGHEST bit in the ID.
|
|
|
|
end
|
|
|
|
if false then
|
|
|
|
typeFlags = bit32.bor(typeFlags, 0x4) -- Set the second flag to true
|
|
|
|
end
|
2024-05-12 22:53:51 +00:00
|
|
|
if false then
|
2024-05-27 01:39:20 +00:00
|
|
|
typeFlags = bit32.bor(typeFlags, 0x2) -- Set the third flag to true
|
|
|
|
end
|
|
|
|
if false then
|
|
|
|
typeFlags = bit32.bor(typeFlags, 0x1) -- LAST BIT in the ID.
|
|
|
|
end
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
return typeFlags
|
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
|
|
|
|
return if e > ECS_ENTITY_MASK then (e % ECS_ID_FLAGS_MASK) // FLAGS_PAIR ~= 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-02 23:24:17 +00:00
|
|
|
local function STRIP_GENERATION(e: i53): i24
|
|
|
|
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-06-10 23:39:43 +00:00
|
|
|
return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) :: i53
|
2024-05-27 01:39:20 +00:00
|
|
|
end
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function getAlive(entityIndex: EntityIndex, id: i24): i53
|
|
|
|
return entityIndex.dense[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-05-27 01:39:20 +00:00
|
|
|
local function ECS_PAIR_RELATION(entityIndex, e)
|
|
|
|
return getAlive(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-05-27 01:39:20 +00:00
|
|
|
local function ECS_PAIR_OBJECT(entityIndex, e)
|
|
|
|
return getAlive(entityIndex, ECS_ENTITY_T_LO(e))
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
local function nextEntityId(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
|
|
|
|
|
|
|
local function transitionArchetype(
|
|
|
|
entityIndex: EntityIndex,
|
2024-05-03 00:39:59 +00:00
|
|
|
to: Archetype,
|
2024-04-23 15:10:49 +00:00
|
|
|
destinationRow: i24,
|
2024-05-03 00:39:59 +00:00
|
|
|
from: Archetype,
|
2024-04-23 15:10:49 +00:00
|
|
|
sourceRow: i24
|
|
|
|
)
|
2024-05-03 00:39:59 +00:00
|
|
|
local columns = from.columns
|
|
|
|
local sourceEntities = from.entities
|
|
|
|
local destinationEntities = to.entities
|
|
|
|
local destinationColumns = to.columns
|
|
|
|
local tr = to.records
|
|
|
|
local types = from.types
|
|
|
|
|
|
|
|
for i, column in 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.
|
|
|
|
local targetColumn = destinationColumns[tr[types[i]]]
|
|
|
|
|
|
|
|
-- Sometimes target column may not exist, e.g. when you remove a component.
|
2024-05-04 23:52:01 +00:00
|
|
|
if targetColumn then
|
2024-04-23 15:10:49 +00:00
|
|
|
targetColumn[destinationRow] = column[sourceRow]
|
|
|
|
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.
|
|
|
|
local last = #column
|
2024-05-04 23:52:01 +00:00
|
|
|
if sourceRow ~= last then
|
2024-05-03 00:39:59 +00:00
|
|
|
-- Swap rempves columns to ensure there are no holes in the archetype.
|
|
|
|
column[sourceRow] = column[last]
|
|
|
|
end
|
|
|
|
column[last] = nil
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
local sparse = entityIndex.sparse
|
|
|
|
local movedAway = #sourceEntities
|
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-05-10 15:59:57 +00:00
|
|
|
local e1 = sourceEntities[sourceRow]
|
|
|
|
local e2 = sourceEntities[movedAway]
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
if sourceRow ~= movedAway then
|
2024-05-10 15:59:57 +00:00
|
|
|
sourceEntities[sourceRow] = e2
|
2024-05-03 00:39:59 +00:00
|
|
|
end
|
2024-05-04 23:52:01 +00:00
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
sourceEntities[movedAway] = nil :: any
|
2024-05-10 15:59:57 +00:00
|
|
|
destinationEntities[destinationRow] = e1
|
|
|
|
|
|
|
|
local record1 = sparse[e1]
|
|
|
|
local record2 = sparse[e2]
|
|
|
|
|
|
|
|
record1.row = destinationRow
|
|
|
|
record2.row = sourceRow
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
local function archetypeAppend(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-06-24 01:01:55 +00:00
|
|
|
local function newEntity(entityId: i53, record: Record, archetype: Archetype): Record
|
2024-04-23 15:10:49 +00:00
|
|
|
local row = archetypeAppend(entityId, archetype)
|
|
|
|
record.archetype = archetype
|
|
|
|
record.row = row
|
|
|
|
return record
|
|
|
|
end
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
local function moveEntity(entityIndex: EntityIndex, entityId: i53, record: Record, to: Archetype)
|
2024-04-23 15:10:49 +00:00
|
|
|
local sourceRow = record.row
|
|
|
|
local from = record.archetype
|
|
|
|
local destinationRow = archetypeAppend(entityId, to)
|
|
|
|
transitionArchetype(entityIndex, to, destinationRow, from, sourceRow)
|
|
|
|
record.archetype = to
|
|
|
|
record.row = destinationRow
|
|
|
|
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-05-27 01:39:20 +00:00
|
|
|
local function ensureComponentRecord(
|
|
|
|
componentIndex: ComponentIndex,
|
|
|
|
archetypeId: number,
|
|
|
|
componentId: number,
|
|
|
|
i: number
|
|
|
|
): ArchetypeMap
|
2024-06-24 01:01:55 +00:00
|
|
|
local archetypesMap = componentIndex[componentId]
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-05-12 22:53:51 +00:00
|
|
|
if not archetypesMap then
|
2024-06-24 01:01:55 +00:00
|
|
|
archetypesMap = ({ size = 0, cache = {} } :: any) :: ArchetypeMap
|
2024-05-12 22:53:51 +00:00
|
|
|
componentIndex[componentId] = archetypesMap
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
2024-05-16 22:17:53 +00:00
|
|
|
|
|
|
|
archetypesMap.cache[archetypeId] = i
|
|
|
|
archetypesMap.size += 1
|
|
|
|
|
|
|
|
return archetypesMap
|
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function ECS_ID_IS_WILDCARD(e: i53): boolean
|
2024-05-16 22:17:53 +00:00
|
|
|
assert(ECS_IS_PAIR(e))
|
|
|
|
local first = ECS_ENTITY_T_HI(e)
|
|
|
|
local second = ECS_ENTITY_T_LO(e)
|
|
|
|
return first == WILDCARD or second == WILDCARD
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archetype
|
2024-04-23 15:10:49 +00:00
|
|
|
local ty = hash(types)
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
local id = world.nextArchetypeId + 1
|
|
|
|
world.nextArchetypeId = id
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
local length = #types
|
2024-06-10 23:39:43 +00:00
|
|
|
local columns = (table.create(length) :: any) :: { Column }
|
2024-05-16 22:17:53 +00:00
|
|
|
local componentIndex = world.componentIndex
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-05-12 22:53:51 +00:00
|
|
|
local records = {}
|
|
|
|
for i, componentId in types do
|
2024-05-16 22:17:53 +00:00
|
|
|
ensureComponentRecord(componentIndex, id, componentId, i)
|
2024-05-12 22:53:51 +00:00
|
|
|
records[componentId] = i
|
2024-05-27 01:39:20 +00:00
|
|
|
if ECS_IS_PAIR(componentId) then
|
2024-05-16 22:17:53 +00:00
|
|
|
local relation = ECS_PAIR_RELATION(world.entityIndex, componentId)
|
|
|
|
local object = ECS_PAIR_OBJECT(world.entityIndex, componentId)
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
local idr_r = ECS_PAIR(relation, WILDCARD)
|
2024-05-27 01:39:20 +00:00
|
|
|
ensureComponentRecord(componentIndex, id, idr_r, i)
|
2024-05-16 22:17:53 +00:00
|
|
|
records[idr_r] = i
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
local idr_t = ECS_PAIR(WILDCARD, object)
|
2024-05-27 01:39:20 +00:00
|
|
|
ensureComponentRecord(componentIndex, id, idr_t, i)
|
2024-05-16 22:17:53 +00:00
|
|
|
records[idr_t] = i
|
2024-05-12 22:53:51 +00:00
|
|
|
end
|
2024-05-16 22:17:53 +00:00
|
|
|
columns[i] = {}
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
local archetype: Archetype = {
|
2024-05-27 01:39:20 +00:00
|
|
|
columns = columns,
|
|
|
|
edges = {},
|
|
|
|
entities = {},
|
|
|
|
id = id,
|
|
|
|
records = records,
|
|
|
|
type = ty,
|
|
|
|
types = types,
|
2024-04-23 15:10:49 +00:00
|
|
|
}
|
2024-06-10 23:39:43 +00:00
|
|
|
|
2024-04-23 15:10:49 +00:00
|
|
|
world.archetypeIndex[ty] = archetype
|
|
|
|
world.archetypes[id] = archetype
|
|
|
|
|
|
|
|
return archetype
|
|
|
|
end
|
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
local World = {}
|
|
|
|
World.__index = World
|
2024-06-24 01:01:55 +00:00
|
|
|
|
|
|
|
function World.new(): World
|
2024-05-10 15:59:57 +00:00
|
|
|
local self = setmetatable({
|
2024-06-10 23:39:43 +00:00
|
|
|
archetypeIndex = {} :: { [string]: Archetype },
|
2024-05-27 01:39:20 +00:00
|
|
|
archetypes = {} :: Archetypes,
|
|
|
|
componentIndex = {} :: ComponentIndex,
|
2024-05-10 15:59:57 +00:00
|
|
|
entityIndex = {
|
2024-06-10 23:39:43 +00:00
|
|
|
dense = {} :: { [i24]: i53 },
|
|
|
|
sparse = {} :: { [i53]: Record },
|
2024-05-27 01:39:20 +00:00
|
|
|
} :: EntityIndex,
|
2024-05-10 15:59:57 +00:00
|
|
|
hooks = {
|
2024-05-27 01:39:20 +00:00
|
|
|
[ON_ADD] = {},
|
|
|
|
},
|
|
|
|
nextArchetypeId = 0,
|
|
|
|
nextComponentId = 0,
|
|
|
|
nextEntityId = 0,
|
|
|
|
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
2024-05-10 15:59:57 +00:00
|
|
|
}, World)
|
2024-05-12 22:53:51 +00:00
|
|
|
self.ROOT_ARCHETYPE = archetypeOf(self, {})
|
2024-06-24 01:01:55 +00:00
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
export type World = typeof(World.new())
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
function World.component(world: World): i53
|
2024-05-10 15:59:57 +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-05-10 18:35:41 +00:00
|
|
|
return nextEntityId(world.entityIndex, componentId)
|
2024-05-10 15:59:57 +00:00
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
function World.entity(world: World): i53
|
2024-05-10 18:35:41 +00:00
|
|
|
local entityId = world.nextEntityId + 1
|
|
|
|
world.nextEntityId = entityId
|
|
|
|
return nextEntityId(world.entityIndex, entityId + REST)
|
2024-05-10 15:59:57 +00:00
|
|
|
end
|
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
-- TODO:
|
|
|
|
-- should have an additional `index` parameter which selects the nth target
|
|
|
|
-- this is important when an entity can have multiple relationships with the same target
|
|
|
|
function World.target(world: World, entity: i53, relation: i24): i24?
|
|
|
|
local entityIndex = world.entityIndex
|
|
|
|
local record = entityIndex.sparse[entity]
|
|
|
|
local archetype = record.archetype
|
2024-05-27 01:39:20 +00:00
|
|
|
if not archetype then
|
2024-05-16 22:17:53 +00:00
|
|
|
return nil
|
|
|
|
end
|
2024-06-05 22:38:27 +00:00
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
local componentRecord = world.componentIndex[ECS_PAIR(relation, WILDCARD)]
|
2024-05-27 01:39:20 +00:00
|
|
|
if not componentRecord then
|
2024-05-16 22:17:53 +00:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local archetypeRecord = componentRecord.cache[archetype.id]
|
2024-05-27 01:39:20 +00:00
|
|
|
if not archetypeRecord then
|
2024-05-16 22:17:53 +00:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord])
|
|
|
|
end
|
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
-- should reuse this logic in World.set instead of swap removing in transition archetype
|
2024-06-10 23:39:43 +00:00
|
|
|
local function destructColumns(columns: { Column }, count: number, row: number)
|
2024-05-27 01:39:20 +00:00
|
|
|
if row == count then
|
|
|
|
for _, column in columns do
|
2024-05-10 15:59:57 +00:00
|
|
|
column[count] = nil
|
|
|
|
end
|
|
|
|
else
|
2024-05-27 01:39:20 +00:00
|
|
|
for _, column in columns do
|
2024-05-10 15:59:57 +00:00
|
|
|
column[row] = column[count]
|
|
|
|
column[count] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
local function archetypeDelete(world: World, id: i53)
|
|
|
|
local componentIndex = world.componentIndex
|
2024-05-16 22:17:53 +00:00
|
|
|
local archetypesMap = componentIndex[id]
|
|
|
|
local archetypes = world.archetypes
|
2024-06-24 01:01:55 +00:00
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
if archetypesMap then
|
|
|
|
for archetypeId in archetypesMap.cache do
|
|
|
|
for _, entity in archetypes[archetypeId].entities do
|
2024-05-16 22:17:53 +00:00
|
|
|
world:remove(entity, id)
|
|
|
|
end
|
|
|
|
end
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
componentIndex[id] = nil :: any
|
2024-05-16 22:17:53 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
function World.delete(world: World, entityId: i53)
|
2024-05-16 22:17:53 +00:00
|
|
|
local record = world.entityIndex.sparse[entityId]
|
2024-05-27 01:39:20 +00:00
|
|
|
if not record then
|
2024-05-16 22:17:53 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
local entityIndex = world.entityIndex
|
2024-05-10 15:59:57 +00:00
|
|
|
local sparse, dense = entityIndex.sparse, entityIndex.dense
|
|
|
|
local archetype = record.archetype
|
|
|
|
local row = record.row
|
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
archetypeDelete(world, entityId)
|
2024-05-16 23:05:40 +00:00
|
|
|
-- TODO: should traverse linked )component records to pairs including entityId
|
2024-05-16 22:17:53 +00:00
|
|
|
archetypeDelete(world, ECS_PAIR(entityId, WILDCARD))
|
|
|
|
archetypeDelete(world, ECS_PAIR(WILDCARD, entityId))
|
2024-05-27 01:39:20 +00:00
|
|
|
|
|
|
|
if archetype then
|
2024-05-16 22:17:53 +00:00
|
|
|
local entities = archetype.entities
|
|
|
|
local last = #entities
|
2024-05-10 15:59:57 +00:00
|
|
|
|
2024-05-27 01:39:20 +00:00
|
|
|
if row ~= last then
|
2024-05-16 22:17:53 +00:00
|
|
|
local entityToMove = entities[last]
|
|
|
|
dense[record.dense] = entityToMove
|
|
|
|
sparse[entityToMove] = record
|
|
|
|
end
|
2024-05-10 15:59:57 +00:00
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
entities[row], entities[last] = entities[last], nil :: any
|
2024-05-10 15:59:57 +00:00
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
local columns = archetype.columns
|
2024-05-10 15:59:57 +00:00
|
|
|
|
2024-05-16 22:17:53 +00:00
|
|
|
destructColumns(columns, last, row)
|
2024-05-10 15:59:57 +00:00
|
|
|
end
|
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
sparse[entityId] = nil :: any
|
|
|
|
dense[#dense] = nil :: any
|
2024-07-02 23:24:17 +00:00
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
function World.clear(world: World, entityId: i53)
|
|
|
|
--TODO: use sparse_get (stashed)
|
|
|
|
local record = world.entityIndex.sparse[entityId]
|
|
|
|
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
|
|
|
|
|
|
|
|
moveEntity(world.entityIndex, entityId, record, ROOT_ARCHETYPE)
|
2024-05-10 15:59:57 +00:00
|
|
|
end
|
|
|
|
|
2024-06-10 23:39:43 +00:00
|
|
|
local function ensureArchetype(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
|
|
|
|
|
|
|
|
return archetypeOf(world, types, prev)
|
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function findInsert(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-06-24 01:01:55 +00:00
|
|
|
local function findArchetypeWith(world: World, node: Archetype, componentId: 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-06-15 18:03:12 +00:00
|
|
|
local destinationType = table.clone(node.types) :: { i53 }
|
2024-04-23 15:10:49 +00:00
|
|
|
local at = findInsert(types, componentId)
|
|
|
|
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
|
|
|
|
table.insert(destinationType, at, componentId)
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-04-23 15:10:49 +00:00
|
|
|
return ensureArchetype(world, destinationType, node)
|
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function ensureEdge(archetype: Archetype, componentId: i53): ArchetypeEdge
|
2024-05-04 23:52:01 +00:00
|
|
|
local edges = archetype.edges
|
|
|
|
local edge = edges[componentId]
|
|
|
|
if not edge then
|
|
|
|
edge = {} :: any
|
|
|
|
edges[componentId] = 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-04-28 14:46:40 +00:00
|
|
|
local function archetypeTraverseAdd(world: World, componentId: 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-05-04 23:52:01 +00:00
|
|
|
local edge = ensureEdge(from, componentId)
|
|
|
|
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-05-04 23:52:01 +00:00
|
|
|
add = findArchetypeWith(world, from, componentId)
|
|
|
|
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-05-27 01:39:20 +00:00
|
|
|
function World.add(world: World, entityId: i53, componentId: i53)
|
2024-05-10 15:59:57 +00:00
|
|
|
local entityIndex = world.entityIndex
|
|
|
|
local record = entityIndex.sparse[entityId]
|
2024-05-07 19:32:56 +00:00
|
|
|
local from = record.archetype
|
|
|
|
local to = archetypeTraverseAdd(world, componentId, from)
|
2024-05-07 22:57:22 +00:00
|
|
|
if from and not (from == world.ROOT_ARCHETYPE) then
|
2024-05-10 15:59:57 +00:00
|
|
|
moveEntity(entityIndex, entityId, record, to)
|
2024-05-07 19:32:56 +00:00
|
|
|
else
|
|
|
|
if #to.types > 0 then
|
|
|
|
newEntity(entityId, record, to)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Symmetric like `World.add` but idempotent
|
2024-05-10 15:59:57 +00:00
|
|
|
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
|
|
|
local record = world.entityIndex.sparse[entityId]
|
2024-05-03 00:39:59 +00:00
|
|
|
local from = record.archetype
|
2024-05-10 15:59:57 +00:00
|
|
|
local to = archetypeTraverseAdd(world, componentId, from)
|
2024-05-03 00:39:59 +00:00
|
|
|
|
2024-05-10 15:59:57 +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-05-10 15:59:57 +00:00
|
|
|
local archetypeRecord = to.records[componentId]
|
2024-05-03 00:39:59 +00:00
|
|
|
from.columns[archetypeRecord][record.row] = data
|
|
|
|
-- Should fire an OnSet event here.
|
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-05-03 00:39:59 +00:00
|
|
|
moveEntity(world.entityIndex, entityId, 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
|
|
|
|
newEntity(entityId, record, to)
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
end
|
2024-05-10 15:59:57 +00:00
|
|
|
|
|
|
|
local archetypeRecord = to.records[componentId]
|
2024-05-03 00:39:59 +00:00
|
|
|
to.columns[archetypeRecord][record.row] = data
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-05-07 22:57:22 +00:00
|
|
|
local function archetypeTraverseRemove(world: World, componentId: i53, from: Archetype): Archetype
|
2024-04-23 15:10:49 +00:00
|
|
|
local edge = ensureEdge(from, componentId)
|
|
|
|
|
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-05-07 22:57:22 +00:00
|
|
|
local at = table.find(to, componentId)
|
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-05-04 23:52:01 +00:00
|
|
|
remove = ensureArchetype(world, to, from)
|
|
|
|
edge.remove = remove :: never
|
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-05-04 23:52:01 +00:00
|
|
|
function World.remove(world: World, entityId: i53, componentId: i53)
|
2024-05-10 15:59:57 +00:00
|
|
|
local entityIndex = world.entityIndex
|
|
|
|
local record = entityIndex.sparse[entityId]
|
2024-04-23 15:10:49 +00:00
|
|
|
local sourceArchetype = record.archetype
|
|
|
|
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
2024-05-10 15:59:57 +00:00
|
|
|
moveEntity(entityIndex, entityId, record, destinationArchetype)
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-03 00:39:59 +00:00
|
|
|
-- Keeping the function as small as possible to enable inlining
|
2024-06-24 01:01:55 +00:00
|
|
|
local function get(record: Record, componentId: i24): any
|
2024-04-23 15:10:49 +00:00
|
|
|
local archetype = record.archetype
|
2024-05-27 01:39:20 +00:00
|
|
|
if not archetype then
|
2024-05-16 22:17:53 +00:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2024-05-03 00:39:59 +00:00
|
|
|
local archetypeRecord = archetype.records[componentId]
|
2024-04-23 15:10:49 +00:00
|
|
|
|
|
|
|
if not archetypeRecord then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
return archetype.columns[archetypeRecord][record.row]
|
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
|
2024-04-23 15:10:49 +00:00
|
|
|
local id = entityId
|
2024-05-10 15:59:57 +00:00
|
|
|
local record = world.entityIndex.sparse[id]
|
2024-04-23 15:10:49 +00:00
|
|
|
if not record then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
local va = get(record, a)
|
2024-04-23 15:10:49 +00:00
|
|
|
|
|
|
|
if b == nil then
|
|
|
|
return va
|
|
|
|
elseif c == nil then
|
2024-05-04 23:52:01 +00:00
|
|
|
return va, get(record, b)
|
2024-04-23 15:10:49 +00:00
|
|
|
elseif d == nil then
|
2024-05-04 23:52:01 +00:00
|
|
|
return va, get(record, b), get(record, c)
|
2024-04-23 15:10:49 +00:00
|
|
|
elseif e == nil then
|
2024-05-04 23:52:01 +00:00
|
|
|
return va, get(record, b), get(record, c), get(record, d)
|
2024-04-23 15:10:49 +00:00
|
|
|
else
|
|
|
|
error("args exceeded")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-04 23:52:01 +00:00
|
|
|
-- the less creation the better
|
|
|
|
local function actualNoOperation() end
|
2024-05-27 01:39:20 +00:00
|
|
|
local function noop(_self: Query, ...): () -> ()
|
2024-05-04 23:52:01 +00:00
|
|
|
return actualNoOperation :: any
|
2024-04-30 23:45:42 +00:00
|
|
|
end
|
2024-04-28 14:46:40 +00:00
|
|
|
|
2024-04-30 23:45:42 +00:00
|
|
|
local EmptyQuery = {
|
2024-05-27 01:39:20 +00:00
|
|
|
__iter = noop,
|
|
|
|
without = noop,
|
2024-04-30 23:45:42 +00:00
|
|
|
}
|
|
|
|
EmptyQuery.__index = EmptyQuery
|
2024-05-01 01:45:49 +00:00
|
|
|
setmetatable(EmptyQuery, EmptyQuery)
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-05-01 01:45:49 +00:00
|
|
|
export type Query = typeof(EmptyQuery)
|
2024-05-16 22:37:47 +00:00
|
|
|
|
2024-06-21 22:15:03 +00:00
|
|
|
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
local function preparedQuery(compatibleArchetypes: { Archetype },
|
|
|
|
components: { i53? }, indices: { { number } })
|
2024-06-26 09:51:49 +00:00
|
|
|
|
2024-04-23 15:10:49 +00:00
|
|
|
local queryLength = #components
|
2024-06-23 23:30:59 +00:00
|
|
|
|
2024-06-21 22:15:03 +00:00
|
|
|
local lastArchetype = 1
|
2024-06-26 09:51:49 +00:00
|
|
|
local archetype: Archetype = compatibleArchetypes[lastArchetype]
|
2024-06-15 18:03:12 +00:00
|
|
|
|
2024-06-26 09:51:49 +00:00
|
|
|
if not archetype then
|
2024-05-01 01:45:49 +00:00
|
|
|
return EmptyQuery
|
2024-04-25 14:26:30 +00:00
|
|
|
end
|
2024-05-04 23:52:01 +00:00
|
|
|
|
2024-04-30 15:52:44 +00:00
|
|
|
local queryOutput = {}
|
2024-04-29 13:21:05 +00:00
|
|
|
|
2024-06-21 22:15:03 +00:00
|
|
|
local i = 1
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
local function queryNext(): ...any
|
2024-06-21 22:15:03 +00:00
|
|
|
local entityId = archetype.entities[i]
|
2024-04-23 15:10:49 +00:00
|
|
|
|
2024-06-21 22:15:03 +00:00
|
|
|
while entityId == nil do
|
|
|
|
lastArchetype += 1
|
2024-06-26 09:51:49 +00:00
|
|
|
archetype = compatibleArchetypes[lastArchetype]
|
2024-06-26 13:50:00 +00:00
|
|
|
if not archetype then
|
2024-06-21 22:15:03 +00:00
|
|
|
return
|
2024-04-28 19:00:00 +00:00
|
|
|
end
|
2024-06-21 22:15:03 +00:00
|
|
|
i = 1
|
|
|
|
entityId = archetype.entities[i]
|
|
|
|
end
|
|
|
|
|
|
|
|
local row = i
|
|
|
|
i+=1
|
2024-04-28 19:00:00 +00:00
|
|
|
|
2024-06-21 22:15:03 +00:00
|
|
|
local columns = archetype.columns
|
2024-06-26 09:51:49 +00:00
|
|
|
local tr = indices[lastArchetype]
|
2024-06-21 22:15:03 +00:00
|
|
|
|
|
|
|
if queryLength == 1 then
|
|
|
|
return entityId, columns[tr[1]][row]
|
|
|
|
elseif queryLength == 2 then
|
|
|
|
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
|
|
|
elseif queryLength == 3 then
|
|
|
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
|
|
|
|
elseif queryLength == 4 then
|
|
|
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
|
|
|
|
elseif queryLength == 5 then
|
|
|
|
return entityId,
|
|
|
|
columns[tr[1]][row],
|
|
|
|
columns[tr[2]][row],
|
|
|
|
columns[tr[3]][row],
|
|
|
|
columns[tr[4]][row],
|
|
|
|
columns[tr[5]][row]
|
|
|
|
elseif queryLength == 6 then
|
|
|
|
return entityId,
|
|
|
|
columns[tr[1]][row],
|
|
|
|
columns[tr[2]][row],
|
|
|
|
columns[tr[3]][row],
|
|
|
|
columns[tr[4]][row],
|
|
|
|
columns[tr[5]][row],
|
|
|
|
columns[tr[6]][row]
|
|
|
|
elseif queryLength == 7 then
|
|
|
|
return entityId,
|
|
|
|
columns[tr[1]][row],
|
|
|
|
columns[tr[2]][row],
|
|
|
|
columns[tr[3]][row],
|
|
|
|
columns[tr[4]][row],
|
|
|
|
columns[tr[5]][row],
|
|
|
|
columns[tr[6]][row],
|
|
|
|
columns[tr[7]][row]
|
|
|
|
elseif queryLength == 8 then
|
|
|
|
return entityId,
|
|
|
|
columns[tr[1]][row],
|
|
|
|
columns[tr[2]][row],
|
|
|
|
columns[tr[3]][row],
|
|
|
|
columns[tr[4]][row],
|
|
|
|
columns[tr[5]][row],
|
|
|
|
columns[tr[6]][row],
|
|
|
|
columns[tr[7]][row],
|
|
|
|
columns[tr[8]][row]
|
|
|
|
end
|
|
|
|
|
|
|
|
for i in components do
|
|
|
|
queryOutput[i] = columns[tr[i]][row]
|
2024-04-28 19:00:00 +00:00
|
|
|
end
|
2024-06-21 22:15:03 +00:00
|
|
|
|
2024-06-23 23:30:59 +00:00
|
|
|
return entityId, unpack(queryOutput, 1, queryLength)
|
2024-06-21 22:15:03 +00:00
|
|
|
end
|
|
|
|
|
2024-06-26 13:50:00 +00:00
|
|
|
local function without(self, ...): Query
|
2024-06-15 18:03:12 +00:00
|
|
|
local withoutComponents = { ... }
|
|
|
|
for i = #compatibleArchetypes, 1, -1 do
|
2024-06-26 09:51:49 +00:00
|
|
|
local archetype = compatibleArchetypes[i]
|
2024-06-15 18:03:12 +00:00
|
|
|
local records = archetype.records
|
|
|
|
local shouldRemove = false
|
|
|
|
|
|
|
|
for _, componentId in withoutComponents do
|
|
|
|
if records[componentId] then
|
|
|
|
shouldRemove = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if shouldRemove then
|
|
|
|
table.remove(compatibleArchetypes, i)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return self
|
|
|
|
end
|
2024-06-26 13:50:00 +00:00
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
local query = {
|
2024-06-26 13:50:00 +00:00
|
|
|
__iter = function()
|
2024-07-02 10:53:33 +00:00
|
|
|
i = 1
|
|
|
|
lastArchetype = 1
|
|
|
|
archetype = compatibleArchetypes[1]
|
|
|
|
|
2024-06-26 13:50:00 +00:00
|
|
|
return queryNext
|
|
|
|
end,
|
|
|
|
next = queryNext,
|
|
|
|
without = without
|
|
|
|
}
|
2024-06-15 18:03:12 +00:00
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
return setmetatable(query, query) :: any
|
2024-04-23 15:10:49 +00:00
|
|
|
end
|
|
|
|
|
2024-07-02 12:20:35 +00:00
|
|
|
function World.query(world: World, ...: number): Query
|
2024-06-23 23:30:59 +00:00
|
|
|
-- breaking?
|
|
|
|
if (...) == nil then
|
|
|
|
error("Missing components")
|
|
|
|
end
|
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
local indices: { { number } } = {}
|
|
|
|
local compatibleArchetypes: { Archetype } = {}
|
2024-06-23 23:30:59 +00:00
|
|
|
local length = 0
|
|
|
|
|
2024-07-02 12:20:35 +00:00
|
|
|
local components: { number } = { ... }
|
2024-07-02 10:53:33 +00:00
|
|
|
local archetypes: { Archetype } = world.archetypes :: any
|
2024-06-23 23:30:59 +00:00
|
|
|
|
|
|
|
local firstArchetypeMap: ArchetypeMap
|
|
|
|
local componentIndex = world.componentIndex
|
|
|
|
|
|
|
|
for _, componentId in components do
|
2024-07-02 10:53:33 +00:00
|
|
|
local map: ArchetypeMap = componentIndex[componentId] :: any
|
2024-06-23 23:30:59 +00:00
|
|
|
if not map then
|
|
|
|
return EmptyQuery
|
|
|
|
end
|
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size < map.size then
|
2024-06-23 23:30:59 +00:00
|
|
|
firstArchetypeMap = map
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id in firstArchetypeMap.cache do
|
|
|
|
local archetype = archetypes[id]
|
|
|
|
local archetypeRecords = archetype.records
|
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
local records: { number } = {}
|
2024-06-23 23:30:59 +00:00
|
|
|
local skip = false
|
|
|
|
|
|
|
|
for i, componentId in components do
|
|
|
|
local index = archetypeRecords[componentId]
|
|
|
|
if not index then
|
|
|
|
skip = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
-- index should be index.offset
|
2024-06-26 09:51:49 +00:00
|
|
|
records[i] = index
|
2024-06-23 23:30:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if skip then
|
|
|
|
continue
|
|
|
|
end
|
|
|
|
|
|
|
|
length += 1
|
2024-06-26 09:51:49 +00:00
|
|
|
compatibleArchetypes[length] = archetype
|
2024-07-02 10:53:33 +00:00
|
|
|
indices[length] = records
|
2024-06-23 23:30:59 +00:00
|
|
|
end
|
|
|
|
|
2024-07-02 10:53:33 +00:00
|
|
|
return preparedQuery(compatibleArchetypes, components, indices)
|
2024-06-23 23:30:59 +00:00
|
|
|
end
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53)
|
|
|
|
|
|
|
|
function World.__iter(world: World): WorldIterator
|
2024-07-02 10:53:33 +00:00
|
|
|
local entityIndex = world.entityIndex
|
|
|
|
local sparse = entityIndex.sparse
|
2024-05-05 13:22:02 +00:00
|
|
|
local last
|
|
|
|
|
2024-06-24 01:01:55 +00:00
|
|
|
-- new solver doesnt like the world iterator type even tho its correct
|
|
|
|
-- so any cast here i come
|
2024-07-02 10:53:33 +00:00
|
|
|
local i = 0
|
2024-06-24 01:01:55 +00:00
|
|
|
local function iterator()
|
2024-07-02 10:53:33 +00:00
|
|
|
i+=1
|
2024-07-02 23:24:17 +00:00
|
|
|
local entityId, record = next(sparse, last)
|
2024-07-02 10:53:33 +00:00
|
|
|
if not entityId then
|
2024-06-24 01:01:55 +00:00
|
|
|
return
|
2024-05-05 13:22:02 +00:00
|
|
|
end
|
2024-06-05 22:38:27 +00:00
|
|
|
|
2024-07-02 23:24:17 +00:00
|
|
|
last = entityId
|
2024-05-05 13:22:02 +00:00
|
|
|
|
|
|
|
local archetype = record.archetype
|
2024-05-27 01:39:20 +00:00
|
|
|
if not archetype then
|
2024-05-05 13:22:02 +00:00
|
|
|
-- Returns only the entity id as an entity without data should not return
|
|
|
|
-- data and allow the user to get an error if they don't handle the case.
|
2024-05-10 15:59:57 +00:00
|
|
|
return entityId
|
2024-05-05 13:22:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
local row = record.row
|
|
|
|
local types = archetype.types
|
|
|
|
local columns = archetype.columns
|
|
|
|
local entityData = {}
|
|
|
|
for i, column in columns do
|
|
|
|
-- We use types because the key should be the component ID not the column index
|
|
|
|
entityData[types[i]] = column[row]
|
|
|
|
end
|
2024-05-27 01:39:20 +00:00
|
|
|
|
2024-05-10 15:59:57 +00:00
|
|
|
return entityId, entityData
|
2024-05-05 13:22:02 +00:00
|
|
|
end
|
2024-06-24 01:01:55 +00:00
|
|
|
|
|
|
|
return iterator :: any
|
2024-05-05 13:22:02 +00:00
|
|
|
end
|
|
|
|
|
2024-06-05 21:34:24 +00:00
|
|
|
-- __nominal_type_dont_use could not be any or T as it causes a type error
|
|
|
|
-- or produces a union
|
2024-06-10 23:39:43 +00:00
|
|
|
export type Entity<T = any> = number & { __nominal_type_dont_use: T }
|
2024-06-09 21:58:01 +00:00
|
|
|
export type Pair = number
|
2024-06-05 21:34:24 +00:00
|
|
|
|
2024-06-09 21:58:01 +00:00
|
|
|
export type QueryShim<T...> = typeof(setmetatable({
|
|
|
|
without = function(...): QueryShim<T...>
|
|
|
|
return nil :: any
|
2024-06-10 23:39:43 +00:00
|
|
|
end,
|
2024-06-09 21:58:01 +00:00
|
|
|
}, {
|
|
|
|
__iter = function(): () -> (number, T...)
|
|
|
|
return nil :: any
|
2024-06-10 23:39:43 +00:00
|
|
|
end,
|
2024-06-09 21:58:01 +00:00
|
|
|
}))
|
2024-06-05 21:34:24 +00:00
|
|
|
export type WorldShim = typeof(setmetatable(
|
|
|
|
{} :: {
|
|
|
|
|
|
|
|
--- Creates a new entity
|
2024-06-09 21:58:01 +00:00
|
|
|
entity: (WorldShim) -> Entity,
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Creates a new entity located in the first 256 ids.
|
|
|
|
--- These should be used for static components for fast access.
|
2024-06-09 21:58:01 +00:00
|
|
|
component: <T>(WorldShim) -> Entity<T>,
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Gets the target of an relationship. For example, when a user calls
|
|
|
|
--- `world:target(id, ChildOf(parent))`, you will obtain the parent entity.
|
2024-06-09 21:58:01 +00:00
|
|
|
target: (WorldShim, id: Entity, relation: Entity) -> Entity?,
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Deletes an entity and all it's related components and relationships.
|
|
|
|
delete: (WorldShim, id: Entity) -> (),
|
2024-06-10 23:39:43 +00:00
|
|
|
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Adds a component to the entity with no value
|
2024-06-09 21:58:01 +00:00
|
|
|
add: <T>(WorldShim, id: Entity, component: Entity<T>) -> (),
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Assigns a value to a component on the given entity
|
2024-06-09 21:58:01 +00:00
|
|
|
set: <T>(WorldShim, id: Entity, component: Entity<T>, data: T) -> (),
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Removes a component from the given entity
|
2024-06-09 21:58:01 +00:00
|
|
|
remove: (WorldShim, id: Entity, component: Entity) -> (),
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Retrieves the value of up to 4 components. These values may be nil.
|
2024-06-10 23:39:43 +00:00
|
|
|
get: (<A>(WorldShim, id: any, Entity<A>) -> A)
|
2024-06-09 21:58:01 +00:00
|
|
|
& (<A, B>(WorldShim, id: Entity, Entity<A>, Entity<B>) -> (A, B))
|
|
|
|
& (<A, B, C>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>) -> (A, B, C))
|
|
|
|
& <A, B, C, D>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>, Entity<D>) -> (A, B, C, D),
|
2024-06-10 23:39:43 +00:00
|
|
|
|
2024-06-05 21:34:24 +00:00
|
|
|
--- Searches the world for entities that match a given query
|
2024-06-09 21:58:01 +00:00
|
|
|
query: (<A>(WorldShim, Entity<A>) -> QueryShim<A>)
|
|
|
|
& (<A, B>(WorldShim, Entity<A>, Entity<B>) -> QueryShim<A, B>)
|
|
|
|
& (<A, B, C>(WorldShim, Entity<A>, Entity<B>, Entity<C>) -> QueryShim<A, B, C>)
|
|
|
|
& (<A, B, C, D>(WorldShim, Entity<A>, Entity<B>, Entity<C>, Entity<D>) -> QueryShim<A, B, C, D>)
|
2024-06-10 23:39:43 +00:00
|
|
|
& (<A, B, C, D, E>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>
|
|
|
|
) -> QueryShim<A, B, C, D, E>)
|
|
|
|
& (<A, B, C, D, E, F>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>,
|
|
|
|
Entity<F>
|
|
|
|
) -> QueryShim<A, B, C, D, E, F>)
|
|
|
|
& (<A, B, C, D, E, F, G>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>,
|
|
|
|
Entity<F>,
|
|
|
|
Entity<G>
|
|
|
|
) -> QueryShim<A, B, C, D, E, F, G>)
|
|
|
|
& (<A, B, C, D, E, F, G, H>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>,
|
|
|
|
Entity<F>,
|
|
|
|
Entity<G>,
|
|
|
|
Entity<H>
|
|
|
|
) -> QueryShim<A, B, C, D, E, F, G, H>)
|
|
|
|
& (<A, B, C, D, E, F, G, H, I>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>,
|
|
|
|
Entity<F>,
|
|
|
|
Entity<G>,
|
|
|
|
Entity<H>,
|
|
|
|
Entity<I>
|
|
|
|
) -> QueryShim<A, B, C, D, E, F, G, H, I>)
|
|
|
|
& (<A, B, C, D, E, F, G, H, I, J>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>,
|
|
|
|
Entity<F>,
|
|
|
|
Entity<G>,
|
|
|
|
Entity<H>,
|
|
|
|
Entity<I>,
|
|
|
|
Entity<J>
|
|
|
|
) -> QueryShim<A, B, C, D, E, F, G, H, I, J>)
|
|
|
|
& (<A, B, C, D, E, F, G, H, I, J, K>(
|
|
|
|
WorldShim,
|
|
|
|
Entity<A>,
|
|
|
|
Entity<B>,
|
|
|
|
Entity<C>,
|
|
|
|
Entity<D>,
|
|
|
|
Entity<E>,
|
|
|
|
Entity<F>,
|
|
|
|
Entity<G>,
|
|
|
|
Entity<H>,
|
|
|
|
Entity<I>,
|
|
|
|
Entity<J>,
|
|
|
|
Entity<K>,
|
|
|
|
...Entity<any>
|
|
|
|
) -> QueryShim<A, B, C, D, E, F, G, H, I, J, K>),
|
2024-06-05 21:34:24 +00:00
|
|
|
},
|
|
|
|
{} :: {
|
2024-06-10 23:39:43 +00:00
|
|
|
__iter: (world: WorldShim) -> () -> (number, { [unknown]: unknown? }),
|
2024-06-05 21:34:24 +00:00
|
|
|
}
|
|
|
|
))
|
|
|
|
|
2024-04-26 17:11:54 +00:00
|
|
|
return table.freeze({
|
2024-06-24 23:44:27 +00:00
|
|
|
World = (table.freeze(World) :: any) :: { new: () -> WorldShim },
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-06-09 21:58:01 +00:00
|
|
|
OnAdd = (ON_ADD :: any) :: Entity,
|
|
|
|
OnRemove = (ON_REMOVE :: any) :: Entity,
|
|
|
|
OnSet = (ON_SET :: any) :: Entity,
|
|
|
|
Wildcard = (WILDCARD :: any) :: Entity,
|
|
|
|
w = (WILDCARD :: any) :: Entity,
|
2024-05-12 22:53:51 +00:00
|
|
|
Rest = REST,
|
|
|
|
|
|
|
|
IS_PAIR = ECS_IS_PAIR,
|
2024-05-14 15:52:41 +00:00
|
|
|
ECS_ID = ECS_ENTITY_T_LO,
|
2024-05-10 15:59:57 +00:00
|
|
|
ECS_PAIR = ECS_PAIR,
|
|
|
|
ECS_GENERATION_INC = ECS_GENERATION_INC,
|
2024-05-12 22:53:51 +00:00
|
|
|
ECS_GENERATION = ECS_GENERATION,
|
2024-05-14 15:52:41 +00:00
|
|
|
ECS_PAIR_RELATION = ECS_PAIR_RELATION,
|
|
|
|
ECS_PAIR_OBJECT = ECS_PAIR_OBJECT,
|
2024-05-12 22:53:51 +00:00
|
|
|
|
2024-06-09 21:58:01 +00:00
|
|
|
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number,
|
2024-06-10 23:39:43 +00:00
|
|
|
getAlive = getAlive,
|
2024-05-10 15:59:57 +00:00
|
|
|
})
|