export type SystemPhase = "pre" | "update" | "post"
export type ComponentTable = { [number]: T }
type ComponentStore = { [string]: ComponentTable }
type ComponentEntityLists = { [string]: { number } }
type ComponentIndexMaps = { [string]: { [number]: number } }
type EntityComponentSets = { [number]: { [string]: boolean } }
export type SystemFn = (world: World, dt: number) -> ()
export type WorldStats = {
entityCount: number,
componentCounts: { [string]: number },
systemCounts: {
pre: number,
update: number,
post: number,
},
lastStepDt: number,
totalSteps: number,
}
export type WorldOptions = {
initialEntityCapacity: number?,
initialComponentEntityCapacity: number?,
scratchSize: number?,
debugProfiling: boolean?,
debugAssertions: boolean?,
}
export type DebugOptions = {
profiling: boolean?,
assertions: boolean?,
}
type Archetype = {
id: number,
signatureKey: string,
components: { [string]: boolean },
entities: { number },
entityIndex: { [number]: number },
}
type QueryPlan = {
includeSet: { [string]: boolean },
excludeSet: { [string]: boolean }?,
archetypes: { Archetype },
}
export type World = {
_nextEntityId: number,
_freeEntityIds: { number },
_entityCapacity: number,
_componentEntityCapacity: number,
_scratchSize: number,
_alive: { [number]: boolean },
_components: ComponentStore,
_componentEntities: ComponentEntityLists,
_componentIndex: ComponentIndexMaps,
_entityComponents: EntityComponentSets,
_componentScratch: { string },
_archetypes: { [number]: Archetype },
_archetypeBySignature: { [string]: Archetype },
_entityArchetype: { [number]: number },
_nextArchetypeId: number,
_queries: { [string]: QueryPlan },
_systemsPre: { SystemFn },
_systemsUpdate: { SystemFn },
_systemsPost: { SystemFn },
_hasSystems: boolean,
_shardSystemsPre: { [number]: { SystemFn } },
_shardSystemsUpdate: { [number]: { SystemFn } },
_shardSystemsPost: { [number]: { SystemFn } },
_debugProfiling: boolean,
_debugAssertions: boolean,
_lastStepDt: number,
_totalSteps: number,
_inStepDepth: number,
_queueKinds: { number },
_queueEntityIds: { number },
_queueComponentNames: { string? },
_queueComponentData: { any },
Spawn: (self: World) -> number,
Despawn: (self: World, entityId: number) -> (),
HasEntity: (self: World, entityId: number) -> boolean,
GetComponentTable: (self: World, name: string) -> ComponentTable,
AddComponent: (self: World, entityId: number, name: string, data: T) -> (),
GetComponent: (self: World, entityId: number, name: string) -> T?,
RemoveComponent: (self: World, entityId: number, name: string) -> (),
RegisterSystem: (self: World, phase: SystemPhase, system: SystemFn) -> (),
RegisterSystemShard: (self: World, phase: SystemPhase, shardId: number, system: SystemFn) -> (),
Step: (self: World, dt: number) -> (),
StepShard: (self: World, dt: number, shardId: number) -> (),
ForEach: (self: World, name: string, fn: (entityId: number, data: T) -> ()) -> (),
ForEach2: (
self: World,
nameA: string,
nameB: string,
fn: (entityId: number, dataA: A, dataB: B) -> ()
) -> (),
ForEachQuery: (self: World, include: { string }, exclude: { string }?, fn: (entityId: number) -> ()) -> (),
Clear: (self: World) -> (),
SetDebugOptions: (self: World, options: DebugOptions) -> (),
GetStats: (self: World) -> WorldStats,
QueueDespawn: (self: World, entityId: number) -> (),
QueueAddComponent: (self: World, entityId: number, name: string, data: T) -> (),
QueueRemoveComponent: (self: World, entityId: number, name: string) -> (),
}
type ECSWorldModule = {
new: (options: WorldOptions?) -> World,
}
local ECSWorld = {} :: ECSWorldModule
local DEFAULT_ENTITY_CAPACITY = 256
local DEFAULT_COMPONENT_ENTITY_CAPACITY = 64
local DEFAULT_SCRATCH_SIZE = 16
local QUEUE_KIND_DESPAWN = 1
local QUEUE_KIND_ADD = 2
local QUEUE_KIND_REMOVE = 3
local debugLib = debug :: any
local profileBegin: ((string) -> ())? = nil
local profileEnd: (() -> ())? = nil
if debugLib and debugLib.profilebegin and debugLib.profileend then
profileBegin = debugLib.profilebegin :: (string) -> ()
profileEnd = debugLib.profileend :: () -> ()
end
local TablePool = {}
TablePool._pool = {} :: { { any } }
function TablePool.acquire(minCapacity: number): { any }
local pool = TablePool._pool
local count = #pool
if count > 0 then
local t = pool[count]
pool[count] = nil
return t
end
if minCapacity > 0 then
local t = table.create(minCapacity)
return t
end
return {}
end
function TablePool.release(t: { any })
table.clear(t)
local pool = TablePool._pool
pool[#pool + 1] = t
end
local function runSystems(list: { SystemFn }, world: World, dt: number)
local count = #list
if count == 0 then
return
end
for i = 1, count do
list[i](world, dt)
end
end
local function getComponentTable(self: World, name: string): ComponentTable
local components = self._components
local existing = components[name]
if existing ~= nil then
return existing :: ComponentTable
end
local newTbl = {} :: ComponentTable
components[name] = newTbl
return newTbl
end
local function ensureComponentMeta(self: World, name: string)
local entitiesByComponent = self._componentEntities
local indexByComponent = self._componentIndex
if entitiesByComponent[name] == nil then
local list = table.create(self._componentEntityCapacity)
table.clear(list)
entitiesByComponent[name] = list :: { number }
end
if indexByComponent[name] == nil then
indexByComponent[name] = {}
end
end
local function computeSignature(compSet: { [string]: boolean }?): string?
if compSet == nil then
return nil
end
local count = 0
for _ in compSet do
count += 1
end
if count == 0 then
return nil
end
local scratch = TablePool.acquire(count)
local i = 1
for name in compSet do
scratch[i] = name
i += 1
end
table.sort(scratch, function(a, b)
return (a :: string) < (b :: string)
end)
local key = table.concat(scratch :: { string }, "\0", 1, count)
TablePool.release(scratch)
return key
end
local function archetypeMatchesQuery(
archetype: Archetype,
includeSet: { [string]: boolean },
excludeSet: { [string]: boolean }?
): boolean
for name in includeSet do
if not archetype.components[name] then
return false
end
end
if excludeSet ~= nil then
for name in excludeSet do
if archetype.components[name] then
return false
end
end
end
return true
end
local function getOrCreateArchetype(self: World, signatureKey: string, compSet: { [string]: boolean }): Archetype
local bySignature = self._archetypeBySignature
local existing = bySignature[signatureKey]
if existing ~= nil then
return existing
end
local id = self._nextArchetypeId + 1
self._nextArchetypeId = id
local componentsCopy: { [string]: boolean } = {}
for name in compSet do
componentsCopy[name] = true
end
local archetype: Archetype = {
id = id,
signatureKey = signatureKey,
components = componentsCopy,
entities = {},
entityIndex = {},
}
self._archetypes[id] = archetype
bySignature[signatureKey] = archetype
local queries = self._queries
for _, plan in queries do
if archetypeMatchesQuery(archetype, plan.includeSet, plan.excludeSet) then
local list = plan.archetypes
list[#list + 1] = archetype
end
end
return archetype
end
local function updateEntityArchetype(self: World, entityId: number, compSet: { [string]: boolean }?)
local signatureKey = computeSignature(compSet)
local entityArchetype = self._entityArchetype
local oldId = entityArchetype[entityId]
if signatureKey == nil then
if oldId ~= nil then
local oldArch = self._archetypes[oldId]
if oldArch ~= nil then
local entities = oldArch.entities
local index = oldArch.entityIndex
local idx = index[entityId]
if idx ~= nil then
local lastIndex = #entities
local lastId = entities[lastIndex]
entities[lastIndex] = nil
if idx ~= lastIndex then
entities[idx] = lastId
index[lastId] = idx
end
index[entityId] = nil
end
end
entityArchetype[entityId] = nil
end
return
end
local archetype = getOrCreateArchetype(self, signatureKey, compSet :: { [string]: boolean })
if oldId == archetype.id then
return
end
if oldId ~= nil then
local oldArch = self._archetypes[oldId]
if oldArch ~= nil then
local entities = oldArch.entities
local index = oldArch.entityIndex
local idx = index[entityId]
if idx ~= nil then
local lastIndex = #entities
local lastId = entities[lastIndex]
entities[lastIndex] = nil
if idx ~= lastIndex then
entities[idx] = lastId
index[lastId] = idx
end
index[entityId] = nil
end
end
end
local entities = archetype.entities
local index = archetype.entityIndex
if index[entityId] == nil then
local newIndex = #entities + 1
entities[newIndex] = entityId
index[entityId] = newIndex
end
entityArchetype[entityId] = archetype.id
end
local function detachComponent(self: World, entityId: number, name: string)
local components = self._components
local dataAny = components[name]
if dataAny == nil then
return
end
local data = dataAny :: ComponentTable
if data[entityId] == nil then
return
end
data[entityId] = nil
local entitiesByComponent = self._componentEntities
local indexByComponent = self._componentIndex
local entityList = entitiesByComponent[name]
local indexMap = indexByComponent[name]
if entityList ~= nil and indexMap ~= nil then
local idx = indexMap[entityId]
if idx ~= nil then
local lastIndex = #entityList
local lastId = entityList[lastIndex]
entityList[lastIndex] = nil
if idx ~= lastIndex then
entityList[idx] = lastId
indexMap[lastId] = idx
end
indexMap[entityId] = nil
end
end
local entityComponents = self._entityComponents
local compSet = entityComponents[entityId]
if compSet ~= nil then
compSet[name] = nil
if next(compSet) == nil then
entityComponents[entityId] = nil
updateEntityArchetype(self, entityId, nil)
else
updateEntityArchetype(self, entityId, compSet)
end
end
end
local function spawnEntity(self: World): number
local free = self._freeEntityIds
local freeCount = #free
local id: number
if freeCount > 0 then
id = free[freeCount]
free[freeCount] = nil
else
local nextId = self._nextEntityId + 1
self._nextEntityId = nextId
id = nextId
if nextId > self._entityCapacity then
self._entityCapacity = nextId
end
end
self._alive[id] = true
return id
end
local function hasEntity(self: World, entityId: number): boolean
return self._alive[entityId] == true
end
local function addComponentImmediate(self: World, entityId: number, name: string, data: T)
if not self._alive[entityId] then
if self._debugAssertions then
error("ECSWorld.AddComponent: entity is not alive")
end
return
end
local tbl = getComponentTable(self, name) :: ComponentTable
tbl[entityId] = data
ensureComponentMeta(self, name)
local entityList = self._componentEntities[name] :: { number }
local indexMap = self._componentIndex[name] :: { [number]: number }
local entityComponents = self._entityComponents
local compSet = entityComponents[entityId]
if compSet == nil then
local newSet: { [string]: boolean } = {}
entityComponents[entityId] = newSet
compSet = newSet
end
compSet[name] = true
if indexMap[entityId] == nil then
local newIndex = #entityList + 1
entityList[newIndex] = entityId
indexMap[entityId] = newIndex
end
updateEntityArchetype(self, entityId, compSet)
end
local function removeComponentImmediate(self: World, entityId: number, name: string)
detachComponent(self, entityId, name)
end
local function despawnEntityImmediate(self: World, entityId: number)
if not self._alive[entityId] then
if self._debugAssertions then
error("ECSWorld.Despawn: entity is not alive")
end
return
end
self._alive[entityId] = nil
local entityComponents = self._entityComponents
local compSet = entityComponents[entityId]
if compSet ~= nil then
local scratch = self._componentScratch
local count = 0
for name in compSet do
count += 1
scratch[count] = name
end
for i = 1, count do
local compName = scratch[i]
scratch[i] = nil
detachComponent(self, entityId, compName)
end
end
local free = self._freeEntityIds
free[#free + 1] = entityId
end
local function getComponent(self: World, entityId: number, name: string): T?
local tbl = self._components[name]
if tbl == nil then
return nil
end
return (tbl :: ComponentTable)[entityId]
end
local function enqueueOp(self: World, kind: number, entityId: number, compName: string?, data: any)
local kinds = self._queueKinds
local index = #kinds + 1
kinds[index] = kind
self._queueEntityIds[index] = entityId
self._queueComponentNames[index] = compName
self._queueComponentData[index] = data
end
local function flushQueue(self: World)
local kinds = self._queueKinds
local count = #kinds
if count == 0 then
return
end
local entities = self._queueEntityIds
local names = self._queueComponentNames
local data = self._queueComponentData
for i = 1, count do
local kind = kinds[i]
local entityId = entities[i]
if kind == QUEUE_KIND_DESPAWN then
despawnEntityImmediate(self, entityId)
elseif kind == QUEUE_KIND_ADD then
local name = names[i]
if name ~= nil then
addComponentImmediate(self, entityId, name, data[i])
end
elseif kind == QUEUE_KIND_REMOVE then
local name = names[i]
if name ~= nil then
removeComponentImmediate(self, entityId, name)
end
end
kinds[i] = 0
entities[i] = 0
names[i] = nil
data[i] = nil
end
table.clear(kinds)
table.clear(entities)
table.clear(names)
table.clear(data)
end
local function addComponent(self: World, entityId: number, name: string, data: T)
if self._inStepDepth > 0 then
enqueueOp(self, QUEUE_KIND_ADD, entityId, name, data)
else
addComponentImmediate(self, entityId, name, data)
end
end
local function removeComponent(self: World, entityId: number, name: string)
if self._inStepDepth > 0 then
enqueueOp(self, QUEUE_KIND_REMOVE, entityId, name, nil)
else
removeComponentImmediate(self, entityId, name)
end
end
local function despawnEntity(self: World, entityId: number)
if self._inStepDepth > 0 then
enqueueOp(self, QUEUE_KIND_DESPAWN, entityId, nil, nil)
else
despawnEntityImmediate(self, entityId)
end
end
local function queueDespawn(self: World, entityId: number)
enqueueOp(self, QUEUE_KIND_DESPAWN, entityId, nil, nil)
end
local function queueAddComponent(self: World, entityId: number, name: string, data: T)
enqueueOp(self, QUEUE_KIND_ADD, entityId, name, data)
end
local function queueRemoveComponent(self: World, entityId: number, name: string)
enqueueOp(self, QUEUE_KIND_REMOVE, entityId, name, nil)
end
local function registerSystem(self: World, phase: SystemPhase, system: SystemFn)
if phase == "pre" then
local list = self._systemsPre
list[#list + 1] = system
elseif phase == "update" then
local list = self._systemsUpdate
list[#list + 1] = system
elseif phase == "post" then
local list = self._systemsPost
list[#list + 1] = system
else
error(("ECSWorld.RegisterSystem: invalid phase '%s'"):format(tostring(phase)))
end
self._hasSystems = true
end
local function registerSystemShard(self: World, phase: SystemPhase, shardId: number, system: SystemFn)
if shardId <= 0 then
if self._debugAssertions then
error("ECSWorld.RegisterSystemShard: shardId must be > 0")
end
return
end
if phase == "pre" then
local byShard = self._shardSystemsPre
local list = byShard[shardId]
if list == nil then
list = {}
byShard[shardId] = list
end
list[#list + 1] = system
elseif phase == "update" then
local byShard = self._shardSystemsUpdate
local list = byShard[shardId]
if list == nil then
list = {}
byShard[shardId] = list
end
list[#list + 1] = system
elseif phase == "post" then
local byShard = self._shardSystemsPost
local list = byShard[shardId]
if list == nil then
list = {}
byShard[shardId] = list
end
list[#list + 1] = system
else
error(("ECSWorld.RegisterSystemShard: invalid phase '%s'"):format(tostring(phase)))
end
end
local function step(self: World, dt: number)
if dt <= 0 or not self._hasSystems then
return
end
self._lastStepDt = dt
self._totalSteps += 1
self._inStepDepth += 1
local profilingEnabled = self._debugProfiling and profileBegin ~= nil and profileEnd ~= nil
if profilingEnabled then
local pb = profileBegin :: (string) -> ()
local pe = profileEnd :: () -> ()
pb("ECSWorld::Step(pre)")
runSystems(self._systemsPre, self, dt)
pe()
pb("ECSWorld::Step(update)")
runSystems(self._systemsUpdate, self, dt)
pe()
pb("ECSWorld::Step(post)")
runSystems(self._systemsPost, self, dt)
pe()
else
runSystems(self._systemsPre, self, dt)
runSystems(self._systemsUpdate, self, dt)
runSystems(self._systemsPost, self, dt)
end
flushQueue(self)
self._inStepDepth -= 1
end
local function stepShard(self: World, dt: number, shardId: number)
if dt <= 0 then
return
end
local pre = self._shardSystemsPre[shardId]
local update = self._shardSystemsUpdate[shardId]
local post = self._shardSystemsPost[shardId]
if pre == nil and update == nil and post == nil then
return
end
self._lastStepDt = dt
self._totalSteps += 1
self._inStepDepth += 1
local profilingEnabled = self._debugProfiling and profileBegin ~= nil and profileEnd ~= nil
if profilingEnabled then
local pb = profileBegin :: (string) -> ()
local pe = profileEnd :: () -> ()
if pre ~= nil and #pre > 0 then
pb(("ECSWorld::StepShard(%d,pre)"):format(shardId))
runSystems(pre, self, dt)
pe()
end
if update ~= nil and #update > 0 then
pb(("ECSWorld::StepShard(%d,update)"):format(shardId))
runSystems(update, self, dt)
pe()
end
if post ~= nil and #post > 0 then
pb(("ECSWorld::StepShard(%d,post)"):format(shardId))
runSystems(post, self, dt)
pe()
end
else
if pre ~= nil then
runSystems(pre, self, dt)
end
if update ~= nil then
runSystems(update, self, dt)
end
if post ~= nil then
runSystems(post, self, dt)
end
end
flushQueue(self)
self._inStepDepth -= 1
end
local function forEach(self: World, name: string, fn: (entityId: number, data: T) -> ())
local entityList = self._componentEntities[name]
if entityList == nil then
return
end
local dataTblAny = self._components[name]
if dataTblAny == nil then
return
end
local dataTbl = dataTblAny :: ComponentTable
local alive = self._alive
local count = #entityList
local fnLocal = fn
for i = 1, count do
local entityId = entityList[i]
if alive[entityId] then
fnLocal(entityId, dataTbl[entityId])
end
end
end
local function forEach2(
self: World,
nameA: string,
nameB: string,
fn: (entityId: number, dataA: A, dataB: B) -> ()
)
local entitiesA = self._componentEntities[nameA]
local entitiesB = self._componentEntities[nameB]
if entitiesA == nil or entitiesB == nil then
return
end
local dataAAny = self._components[nameA]
local dataBAny = self._components[nameB]
if dataAAny == nil or dataBAny == nil then
return
end
local alive = self._alive
local listSmall, listLarge
local dataSmallAny, dataLargeAny
local nameSmall, nameLarge
if #entitiesA <= #entitiesB then
listSmall, listLarge = entitiesA, entitiesB
dataSmallAny, dataLargeAny = dataAAny, dataBAny
nameSmall, nameLarge = nameA, nameB
else
listSmall, listLarge = entitiesB, entitiesA
dataSmallAny, dataLargeAny = dataBAny, dataAAny
nameSmall, nameLarge = nameB, nameA
end
local count = #listSmall
if count == 0 then
return
end
local indexLarge = self._componentIndex[nameLarge]
if indexLarge == nil then
return
end
local fnLocal = fn
if nameSmall == nameA then
local dataA = dataSmallAny :: ComponentTable
local dataB = dataLargeAny :: ComponentTable
for i = 1, count do
local entityId = listSmall[i]
if alive[entityId] and indexLarge[entityId] ~= nil then
fnLocal(entityId, dataA[entityId], dataB[entityId])
end
end
else
local dataB = dataSmallAny :: ComponentTable
local dataA = dataLargeAny :: ComponentTable
for i = 1, count do
local entityId = listSmall[i]
if alive[entityId] and indexLarge[entityId] ~= nil then
fnLocal(entityId, dataA[entityId], dataB[entityId])
end
end
end
end
local function getOrCreateQueryPlan(
self: World,
include: { string },
exclude: { string }?
): QueryPlan
local includeSet: { [string]: boolean } = {}
local excludeSet: { [string]: boolean }? = nil
local includeCount = #include
for i = 1, includeCount do
includeSet[include[i]] = true
end
local excludeCount = 0
if exclude ~= nil then
excludeSet = {}
excludeCount = #exclude
for i = 1, excludeCount do
(excludeSet :: { [string]: boolean })[exclude[i]] = true
end
end
local scratch = TablePool.acquire(includeCount + excludeCount)
local idx = 1
for name in includeSet do
scratch[idx] = name
idx += 1
end
if excludeSet ~= nil then
for name in (excludeSet :: { [string]: boolean }) do
scratch[idx] = ("!%s"):format(name)
idx += 1
end
end
table.sort(scratch, function(a, b)
return (a :: string) < (b :: string)
end)
local key = table.concat(scratch :: { string }, "|", 1, idx - 1)
TablePool.release(scratch)
local queries = self._queries
local existing = queries[key]
if existing ~= nil then
return existing
end
local archetypesList: { Archetype } = {}
for _, archetype in self._archetypes do
if archetypeMatchesQuery(archetype, includeSet, excludeSet) then
archetypesList[#archetypesList + 1] = archetype
end
end
local plan: QueryPlan = {
includeSet = includeSet,
excludeSet = excludeSet,
archetypes = archetypesList,
}
queries[key] = plan
return plan
end
local function forEachQuery(self: World, include: { string }, exclude: { string }?, fn: (entityId: number) -> ())
if #include == 0 then
return
end
local plan = getOrCreateQueryPlan(self, include, exclude)
local alive = self._alive
local archetypes = plan.archetypes
local countArch = #archetypes
local fnLocal = fn
for i = 1, countArch do
local archetype = archetypes[i]
local entities = archetype.entities
local countEntities = #entities
for j = 1, countEntities do
local entityId = entities[j]
if alive[entityId] then
fnLocal(entityId)
end
end
end
end
local function clearWorld(self: World)
self._nextEntityId = 0
self._lastStepDt = 0
self._totalSteps = 0
table.clear(self._freeEntityIds)
table.clear(self._alive)
for _, tbl in self._components do
table.clear(tbl)
end
for _, list in self._componentEntities do
table.clear(list)
end
for _, indexMap in self._componentIndex do
table.clear(indexMap)
end
table.clear(self._entityComponents)
table.clear(self._componentScratch)
for _, archetype in self._archetypes do
table.clear(archetype.entities)
table.clear(archetype.entityIndex)
end
table.clear(self._entityArchetype)
table.clear(self._queueKinds)
table.clear(self._queueEntityIds)
table.clear(self._queueComponentNames)
table.clear(self._queueComponentData)
end
local function setDebugOptions(self: World, options: DebugOptions)
local profiling = options.profiling
if profiling ~= nil then
self._debugProfiling = profiling
end
local assertions = options.assertions
if assertions ~= nil then
self._debugAssertions = assertions
end
end
local function getStats(self: World): WorldStats
local entityCount = 0
for _ in self._alive do
entityCount += 1
end
local componentCounts: { [string]: number } = {}
for name, list in self._componentEntities do
componentCounts[name] = #list
end
local systemCounts = {
pre = #self._systemsPre,
update = #self._systemsUpdate,
post = #self._systemsPost,
}
return {
entityCount = entityCount,
componentCounts = componentCounts,
systemCounts = systemCounts,
lastStepDt = self._lastStepDt,
totalSteps = self._totalSteps,
}
end
function ECSWorld.new(options: WorldOptions?): World
local opt: WorldOptions = (options or {}) :: WorldOptions
local entityCapacity = opt.initialEntityCapacity or DEFAULT_ENTITY_CAPACITY
local componentEntityCapacity = opt.initialComponentEntityCapacity or DEFAULT_COMPONENT_ENTITY_CAPACITY
local scratchSize = opt.scratchSize or DEFAULT_SCRATCH_SIZE
if entityCapacity < 1 then
entityCapacity = DEFAULT_ENTITY_CAPACITY
end
if componentEntityCapacity < 1 then
componentEntityCapacity = DEFAULT_COMPONENT_ENTITY_CAPACITY
end
if scratchSize < 1 then
scratchSize = DEFAULT_SCRATCH_SIZE
end
local debugProfiling = opt.debugProfiling == true
local debugAssertions = opt.debugAssertions == true
local freeEntityIds: { number } = table.create(entityCapacity) :: { number }
table.clear(freeEntityIds)
local world: World = {
_nextEntityId = 0,
_freeEntityIds = freeEntityIds,
_entityCapacity = entityCapacity,
_componentEntityCapacity = componentEntityCapacity,
_scratchSize = scratchSize,
_alive = {},
_components = {} :: ComponentStore,
_componentEntities = {} :: ComponentEntityLists,
_componentIndex = {} :: ComponentIndexMaps,
_entityComponents = {} :: EntityComponentSets,
_componentScratch = table.create(scratchSize) :: { string },
_archetypes = {},
_archetypeBySignature = {},
_entityArchetype = {},
_nextArchetypeId = 0,
_queries = {},
_systemsPre = {},
_systemsUpdate = {},
_systemsPost = {},
_hasSystems = false,
_shardSystemsPre = {},
_shardSystemsUpdate = {},
_shardSystemsPost = {},
_debugProfiling = debugProfiling,
_debugAssertions = debugAssertions,
_lastStepDt = 0,
_totalSteps = 0,
_inStepDepth = 0,
_queueKinds = {},
_queueEntityIds = {},
_queueComponentNames = {},
_queueComponentData = {},
Spawn = spawnEntity,
Despawn = despawnEntity,
HasEntity = hasEntity,
GetComponentTable = getComponentTable,
AddComponent = addComponent,
GetComponent = getComponent,
RemoveComponent = removeComponent,
RegisterSystem = registerSystem,
RegisterSystemShard = registerSystemShard,
Step = step,
StepShard = stepShard,
ForEach = forEach,
ForEach2 = forEach2,
ForEachQuery = forEachQuery,
Clear = clearWorld,
SetDebugOptions = setDebugOptions,
GetStats = getStats,
QueueDespawn = queueDespawn,
QueueAddComponent = queueAddComponent,
QueueRemoveComponent = queueRemoveComponent,
}
return world
end
return ECSWorld