Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
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
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)