--!optimize 2 --!native --!strict --draft 4 type i53 = number type i24 = number type Ty = { i53 } type ArchetypeId = number type Column = { any } type Map = { [K]: V } type ecs_archetype_t = { id: number, types: Ty, type: string, entities: { number }, columns: { Column }, records: { [i53]: number }, counts: { [i53]: number }, } export type Archetype = { id: number, types: Ty, type: string, entities: { number }, columns: { Column }, records: { [Id]: number }, counts: { [Id]: number }, } type ecs_record_t = { archetype: ecs_archetype_t, row: number, dense: i24, } type ecs_id_record_t = { cache: { number }, counts: { number }, flags: number, size: number, hooks: { on_add: ((entity: i53, data: any?) -> ())?, on_change: ((entity: i53, data: any) -> ())?, on_remove: ((entity: i53) -> ())?, }, } type ecs_id_index_t = Map type ecs_archetypes_map_t = { [string]: ecs_archetype_t } type ecs_archetypes_t = { ecs_archetype_t } type ecs_entity_index_t = { dense_array: Map, sparse_array: Map, alive_count: number, max_id: number, } type ecs_query_data_t = { compatible_archetypes: { ecs_archetype_t }, ids: { i53 }, filter_with: { i53 }, filter_without: { i53 }, next: () -> (number, ...any), world: ecs_world_t, } type ecs_observer_t = { callback: (archetype: ecs_archetype_t) -> (), query: ecs_query_data_t, } type ecs_observable_t = Map> type ecs_world_t = { archetype_edges: Map>, entity_index: ecs_entity_index_t, component_index: ecs_id_index_t, archetypes: ecs_archetypes_t, archetype_index: ecs_archetypes_map_t, max_archetype_id: number, max_component_id: number, ROOT_ARCHETYPE: ecs_archetype_t, observable: Map>, } local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256 -- stylua: ignore start local EcsOnAdd = HI_COMPONENT_ID + 1 local EcsOnRemove = HI_COMPONENT_ID + 2 local EcsOnChange = HI_COMPONENT_ID + 3 local EcsWildcard = HI_COMPONENT_ID + 4 local EcsChildOf = HI_COMPONENT_ID + 5 local EcsComponent = HI_COMPONENT_ID + 6 local EcsOnDelete = HI_COMPONENT_ID + 7 local EcsOnDeleteTarget = HI_COMPONENT_ID + 8 local EcsDelete = HI_COMPONENT_ID + 9 local EcsRemove = HI_COMPONENT_ID + 10 local EcsName = HI_COMPONENT_ID + 11 local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12 local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13 local EcsRest = HI_COMPONENT_ID + 14 local ECS_ID_DELETE = 0b01 local ECS_ID_IS_TAG = 0b10 local ECS_ID_MASK = 0b00 local ECS_ENTITY_MASK = bit32.lshift(1, 24) local ECS_GENERATION_MASK = bit32.lshift(1, 16) local NULL_ARRAY = table.freeze({}) local ECS_INTERNAL_ERROR = [[ This is an internal error, please file a bug report via the following link: https://github.com/Ukendio/jecs/issues/new?template=BUG-REPORT.md ]] local function ECS_COMBINE(id: number, generation: number): i53 return id + (generation * ECS_ENTITY_MASK) end local ECS_PAIR_OFFSET = 2^48 local function ECS_IS_PAIR(e: number): boolean return e > ECS_PAIR_OFFSET end local function ECS_GENERATION_INC(e: i53): i53 if e > ECS_ENTITY_MASK then local id = e % ECS_ENTITY_MASK local generation = e // ECS_ENTITY_MASK local next_gen = generation + 1 if next_gen >= ECS_GENERATION_MASK then return id end return ECS_COMBINE(id, next_gen) end return ECS_COMBINE(e, 1) end local function ECS_ENTITY_T_LO(e: i53): i24 return e % ECS_ENTITY_MASK end local function ECS_GENERATION(e: i53) return e // ECS_ENTITY_MASK end local function ECS_ENTITY_T_HI(e: i53): i24 return e // ECS_ENTITY_MASK end local function ECS_PAIR(pred: i53, obj: i53): i53 pred %= ECS_ENTITY_MASK obj %= ECS_ENTITY_MASK return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET end local function ECS_PAIR_FIRST(e: i53): i24 return (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK end local function ECS_PAIR_SECOND(e: i53): i24 return (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK end local function entity_index_try_get_any( entity_index: ecs_entity_index_t, entity: number ): ecs_record_t? local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)] if not r or r.dense == 0 then return nil end return r end local function entity_index_try_get(entity_index: ecs_entity_index_t, entity: number): ecs_record_t? local r = entity_index_try_get_any(entity_index, entity) if r then local r_dense = r.dense if r_dense > entity_index.alive_count then return nil end if entity_index.dense_array[r_dense] ~= entity then return nil end end return r end local function entity_index_try_get_fast(entity_index: ecs_entity_index_t, entity: number): ecs_record_t? local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)] if r then if entity_index.dense_array[r.dense] ~= entity then return nil end end return r end local function entity_index_is_alive(entity_index: ecs_entity_index_t, entity: i53) return entity_index_try_get(entity_index, entity) ~= nil end local function entity_index_get_alive(index: ecs_entity_index_t, entity: i53): i53? local r = entity_index_try_get_any(index, entity) if r then return index.dense_array[r.dense] end return nil end local function ecs_get_alive(world, entity) if entity == 0 then return 0 end local eindex = world.entity_index if entity_index_is_alive(eindex, entity) then return entity end if entity > ECS_ENTITY_MASK then return 0 end local current = entity_index_get_alive(eindex, entity) if not current or not entity_index_is_alive(eindex, current) then return 0 end return current end local function entity_index_new_id(entity_index: ecs_entity_index_t): i53 local dense_array = entity_index.dense_array local alive_count = entity_index.alive_count local max_id = entity_index.max_id if alive_count ~= max_id then alive_count += 1 entity_index.alive_count = alive_count local id = dense_array[alive_count] return id end local id = max_id + 1 entity_index.max_id = id alive_count += 1 entity_index.alive_count = alive_count dense_array[alive_count] = id entity_index.sparse_array[id] = { dense = alive_count } :: ecs_record_t return id end local function ecs_pair_first(world: ecs_world_t, e: i53) local pred = ECS_PAIR_FIRST(e) return ecs_get_alive(world, pred) end local function ecs_pair_second(world: ecs_world_t, e: i53) local obj = ECS_PAIR_SECOND(e) return ecs_get_alive(world, obj) end local function query_match(query: ecs_query_data_t, archetype: ecs_archetype_t) local records = archetype.records local with = query.filter_with for _, id in with do if not records[id] then return false end end local without = query.filter_without if without then for _, id in without do if records[id] then return false end end end return true end local function find_observers(world: ecs_world_t, event: i53, component: i53): { ecs_observer_t }? local cache = world.observable[event] if not cache then return nil end return cache[component] :: any end local function archetype_move( entity_index: ecs_entity_index_t, to: ecs_archetype_t, dst_row: i24, from: ecs_archetype_t, src_row: i24 ) local src_columns = from.columns local dst_columns = to.columns local dst_entities = to.entities local src_entities = from.entities local last = #src_entities local id_types = from.types local records = to.records for i, column in src_columns do if column == NULL_ARRAY then continue end -- Retrieves the new column index from the source archetype's record from each component -- We have to do this because the columns are tightly packed and indexes may not correspond to each other. local tr = records[id_types[i]] -- Sometimes target column may not exist, e.g. when you remove a component. if tr then dst_columns[tr][dst_row] = column[src_row] end -- If the entity is the last row in the archetype then swapping it would be meaningless. if src_row ~= last then -- Swap rempves columns to ensure there are no holes in the archetype. column[src_row] = column[last] end column[last] = nil end local moved = #src_entities -- Move the entity from the source to the destination archetype. -- Because we have swapped columns we now have to update the records -- corresponding to the entities' rows that were swapped. local e1 = src_entities[src_row] local e2 = src_entities[moved] if src_row ~= moved then src_entities[src_row] = e2 end src_entities[moved] = nil :: any dst_entities[dst_row] = e1 local sparse_array = entity_index.sparse_array local record1 = sparse_array[ECS_ENTITY_T_LO(e1)] local record2 = sparse_array[ECS_ENTITY_T_LO(e2)] record1.row = dst_row record2.row = src_row end local function archetype_append( entity: i53, archetype: ecs_archetype_t ): number local entities = archetype.entities local length = #entities + 1 entities[length] = entity return length end local function new_entity( entity: i53, record: ecs_record_t, archetype: ecs_archetype_t ): ecs_record_t local row = archetype_append(entity, archetype) record.archetype = archetype record.row = row return record end local function entity_move( entity_index: ecs_entity_index_t, entity: i53, record: ecs_record_t, to: ecs_archetype_t ) local sourceRow = record.row local from = record.archetype local dst_row = archetype_append(entity, to) archetype_move(entity_index, to, dst_row, from, sourceRow) record.archetype = to record.row = dst_row end local function hash(arr: { number }): string return table.concat(arr, "_") end local function fetch(id: i53, records: { number }, columns: { Column }, row: number): any local tr = records[id] if not tr then return nil end return columns[tr][row] end local function world_get(world: ecs_world_t, entity: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any local record = entity_index_try_get_fast(world.entity_index, entity) if not record then return nil end local archetype = record.archetype if not archetype then return nil end local records = archetype.records local columns = archetype.columns local row = record.row local va = fetch(a, records, columns, row) if not b then return va elseif not c then return va, fetch(b, records, columns, row) elseif not d then return va, fetch(b, records, columns, row), fetch(c, records, columns, row) elseif not e then return va, fetch(b, records, columns, row), fetch(c, records, columns, row), fetch(d, records, columns, row) else error("args exceeded") end end local function world_has_one_inline(world: ecs_world_t, entity: i53, id: i53): boolean local record = entity_index_try_get_fast(world.entity_index, entity) if not record then return false end local archetype = record.archetype if not archetype then return false end local records = archetype.records return records[id] ~= nil end local function world_has(world: ecs_world_t, entity: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean local record = entity_index_try_get_fast(world.entity_index, entity) if not record then return false end local archetype = record.archetype if not archetype then return false end local records = archetype.records return records[a] ~= nil and (b == nil or records[b] ~= nil) and (c == nil or records[c] ~= nil) and (d == nil or records[d] ~= nil) and (e == nil or error("args exceeded")) end local function world_target(world: ecs_world_t, entity: i53, relation: i24, index: number?): i24? local nth = index or 0 local record = entity_index_try_get_fast(world.entity_index, entity) if not record then return nil end local archetype = record.archetype if not archetype then return nil end local r = ECS_PAIR(relation, EcsWildcard) local count = archetype.counts[r] if not count then return nil end if nth >= count then nth = nth + count + 1 end nth = archetype.types[nth + archetype.records[r]] if not nth then return nil end return entity_index_get_alive(world.entity_index, ECS_PAIR_SECOND(nth)) end local function ECS_ID_IS_WILDCARD(e: i53): boolean local first = ECS_ENTITY_T_HI(e) local second = ECS_ENTITY_T_LO(e) return first == EcsWildcard or second == EcsWildcard end local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t local component_index = world.component_index local entity_index = world.entity_index local idr: ecs_id_record_t = component_index[id] if not idr then local flags = ECS_ID_MASK local relation = id local target = 0 local is_pair = ECS_IS_PAIR(id) if is_pair then relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) :: i53 assert(relation and entity_index_is_alive( entity_index, relation), ECS_INTERNAL_ERROR) target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) :: i53 assert(target and entity_index_is_alive( entity_index, target), ECS_INTERNAL_ERROR) end local cleanup_policy = world_target(world, relation, EcsOnDelete, 0) local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget, 0) local has_delete = false if cleanup_policy == EcsDelete or cleanup_policy_target == EcsDelete then has_delete = true end local on_add, on_change, on_remove = world_get(world, relation, EcsOnAdd, EcsOnChange, EcsOnRemove) local is_tag = not world_has_one_inline(world, relation, EcsComponent) if is_tag and is_pair then is_tag = not world_has_one_inline(world, target, EcsComponent) end flags = bit32.bor( flags, if has_delete then ECS_ID_DELETE else 0, if is_tag then ECS_ID_IS_TAG else 0 ) idr = { size = 0, cache = {}, counts = {}, flags = flags, hooks = { on_add = on_add, on_change = on_change, on_remove = on_remove, }, } component_index[id] = idr end return idr end local function archetype_append_to_records( idr: ecs_id_record_t, archetype: ecs_archetype_t, id: i53, index: number ) local archetype_id = archetype.id local archetype_records = archetype.records local archetype_counts = archetype.counts local idr_columns = idr.cache local idr_counts = idr.counts local tr = idr_columns[archetype_id] if not tr then idr_columns[archetype_id] = index idr_counts[archetype_id] = 1 archetype_records[id] = index archetype_counts[id] = 1 else local max_count = idr_counts[archetype_id] + 1 idr_counts[archetype_id] = max_count archetype_counts[id] = max_count end end local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev: i53?): ecs_archetype_t local archetype_id = (world.max_archetype_id :: number) + 1 world.max_archetype_id = archetype_id local length = #id_types local columns = (table.create(length) :: any) :: { Column } local records: { number } = {} local counts: {number} = {} local archetype: ecs_archetype_t = { columns = columns, entities = {}, id = archetype_id, records = records, counts = counts, type = ty, types = id_types, } for i, component_id in id_types do local idr = id_record_ensure(world, component_id) archetype_append_to_records(idr, archetype, component_id, i) if ECS_IS_PAIR(component_id) then local relation = ECS_PAIR_FIRST(component_id) local object = ECS_PAIR_SECOND(component_id) local r = ECS_PAIR(relation, EcsWildcard) local idr_r = id_record_ensure(world, r) archetype_append_to_records(idr_r, archetype, r, i) local t = ECS_PAIR(EcsWildcard, object) local idr_t = id_record_ensure(world, t) archetype_append_to_records(idr_t, archetype, t, i) end if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then columns[i] = {} else columns[i] = NULL_ARRAY end end for id in records do local observer_list = find_observers(world, EcsOnArchetypeCreate, id) if not observer_list then continue end for _, observer in observer_list do if query_match(observer.query, archetype) then observer.callback(archetype) end end end world.archetype_index[ty] = archetype world.archetypes[archetype_id] = archetype world.archetype_edges[archetype.id] = {} return archetype end local function world_entity(world: ecs_world_t): i53 return entity_index_new_id(world.entity_index) end local function world_parent(world: ecs_world_t, entity: i53) return world_target(world, entity, EcsChildOf, 0) end local function archetype_ensure(world: ecs_world_t, id_types): ecs_archetype_t if #id_types < 1 then return world.ROOT_ARCHETYPE end local ty = hash(id_types) local archetype = world.archetype_index[ty] if archetype then return archetype end return archetype_create(world, id_types, ty) end local function find_insert(id_types: { i53 }, toAdd: i53): number for i, id in id_types do if id == toAdd then error("Duplicate component id") return -1 end if id > toAdd then return i end end return #id_types + 1 end local function find_archetype_without( world: ecs_world_t, node: ecs_archetype_t, id: i53 ): ecs_archetype_t local id_types = node.types local at = table.find(id_types, id) local dst = table.clone(id_types) table.remove(dst, at) return archetype_ensure(world, dst) end local function create_edge_for_remove( world: ecs_world_t, node: ecs_archetype_t, edge: Map, id: i53 ): ecs_archetype_t local to = find_archetype_without(world, node, id) local edges = world.archetype_edges local archetype_id = node.id edges[archetype_id][id] = to edges[to.id][id] = node return to end local function archetype_traverse_remove( world: ecs_world_t, id: i53, from: ecs_archetype_t ): ecs_archetype_t local edges = world.archetype_edges local edge = edges[from.id] local to = edge[id] if not to then to = find_archetype_without(world, from, id) edge[id] = to edges[to.id][id] = from end return to :: ecs_archetype_t end local function find_archetype_with(world, id, from) local id_types = from.types local at = find_insert(id_types, id) local dst = table.clone(id_types) :: { i53 } table.insert(dst, at, id) return archetype_ensure(world, dst) end local function archetype_traverse_add(world, id, from: ecs_archetype_t) from = from or world.ROOT_ARCHETYPE if from.records[id] then return from end local edges = world.archetype_edges local edge = edges[from.id] local to = edge[id] if not to then to = find_archetype_with(world, id, from) edge[id] = to edges[to.id][id] = from end return to end local function world_add( world: ecs_world_t, entity: i53, id: i53 ): () local entity_index = world.entity_index local record = entity_index_try_get_fast(entity_index, entity) if not record then return end local from = record.archetype local to = archetype_traverse_add(world, id, from) if from == to then return end if from then entity_move(entity_index, entity, record, to) else if #to.types > 0 then new_entity(entity, record, to) end end local idr = world.component_index[id] local on_add = idr.hooks.on_add if on_add then on_add(entity) end end local function world_set(world: ecs_world_t, entity: i53, id: i53, data: unknown): () local entity_index = world.entity_index local record = entity_index_try_get_fast(entity_index, entity) if not record then return end local from: ecs_archetype_t = record.archetype local to: ecs_archetype_t = archetype_traverse_add(world, id, from) local idr = world.component_index[id] local idr_hooks = idr.hooks if from == to then local tr = to.records[id] local column = from.columns[tr] column[record.row] = data -- If the archetypes are the same it can avoid moving the entity -- and just set the data directly. local on_change = idr_hooks.on_change if on_change then on_change(entity, data) end return end if from then -- If there was a previous archetype, then the entity needs to move the archetype entity_move(entity_index, entity, record, to) else if #to.types > 0 then -- When there is no previous archetype it should create the archetype new_entity(entity, record, to) end end local tr = to.records[id] local column = to.columns[tr] column[record.row] = data local on_add = idr_hooks.on_add if on_add then on_add(entity, data) end end local function world_component(world: World): i53 local id = (world.max_component_id :: number) + 1 if id > 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.max_component_id = id return id end local function world_remove(world: ecs_world_t, entity: i53, id: i53) local entity_index = world.entity_index local record = entity_index_try_get_fast(entity_index, entity) if not record then return end local from = record.archetype if not from then return end if from.records[id] then local idr = world.component_index[id] local on_remove = idr.hooks.on_remove if on_remove then on_remove(entity) end local to = archetype_traverse_remove(world, id, record.archetype) entity_move(entity_index, entity, record, to) end end local function archetype_fast_delete_last(columns: { Column }, column_count: number, types: { i53 }, entity: i53) for i, column in columns do if column ~= NULL_ARRAY then column[column_count] = nil end end end local function archetype_fast_delete(columns: { Column }, column_count: number, row, types, entity) for i, column in columns do if column ~= NULL_ARRAY then column[row] = column[column_count] column[column_count] = nil end end end local function archetype_delete(world: ecs_world_t, archetype: ecs_archetype_t, row: number) local entity_index = world.entity_index local component_index = world.component_index local columns = archetype.columns local id_types = archetype.types local entities = archetype.entities local column_count = #entities local last = #entities local move = entities[last] -- We assume first that the entity is the last in the archetype local delete = move if row ~= last then local record_to_move = entity_index_try_get_any(entity_index, move) if record_to_move then record_to_move.row = row end delete = entities[row] entities[row] = move end for _, id in id_types do local idr = component_index[id] local on_remove = idr.hooks.on_remove if on_remove then on_remove(delete) end end entities[last] = nil :: any if row == last then archetype_fast_delete_last(columns, column_count, id_types, delete) else archetype_fast_delete(columns, column_count, row, id_types, delete) end end local function world_clear(world: ecs_world_t, entity: i53) local entity_index = world.entity_index local component_index = world.component_index local archetypes = world.archetypes local tgt = ECS_PAIR(EcsWildcard, entity) local idr_t = component_index[tgt] local idr = component_index[entity] local rel = ECS_PAIR(entity, EcsWildcard) local idr_r = component_index[rel] if idr then local count = 0 local queue = {} for archetype_id in idr.cache do local idr_archetype = archetypes[archetype_id] local entities = idr_archetype.entities local n = #entities count += n table.move(entities, 1, n, #queue + 1, queue) end for _, e in queue do world_remove(world, e, entity) end end if idr_t then local queue local ids local count = 0 local archetype_ids = idr_t.cache for archetype_id in archetype_ids do local idr_t_archetype = archetypes[archetype_id] local idr_t_types = idr_t_archetype.types local entities = idr_t_archetype.entities local removal_queued = false for _, id in idr_t_types do if not ECS_IS_PAIR(id) then continue end local object = entity_index_get_alive( entity_index, ECS_PAIR_SECOND(id)) if object ~= entity then continue end if not ids then ids = {} end ids[id] = true removal_queued = true end if not removal_queued then continue end if not queue then queue = {} end local n = #entities table.move(entities, 1, n, count + 1, queue) count += n end for id in ids do for _, child in queue do world_remove(world, child, id) end end end if idr_r then local count = 0 local archetype_ids = idr_r.cache local ids = {} local queue = {} for archetype_id in archetype_ids do local idr_r_archetype = archetypes[archetype_id] local entities = idr_r_archetype.entities local tr = idr_r_archetype.records[rel] local tr_count = idr_r_archetype.counts[rel] local types = idr_r_archetype.types for i = tr, tr + tr_count - 1 do ids[types[i]] = true end local n = #entities table.move(entities, 1, n, count + 1, queue) count += n end for _, e in queue do for id in ids do world_remove(world, e, id) end end end end local function archetype_destroy(world: ecs_world_t, archetype: ecs_archetype_t) if archetype == world.ROOT_ARCHETYPE then return end local component_index = world.component_index local archetype_edges = world.archetype_edges for id, edge in archetype_edges[archetype.id] do archetype_edges[edge.id][id] = nil end local archetype_id = archetype.id world.archetypes[archetype_id] = nil :: any world.archetype_index[archetype.type] = nil :: any local records = archetype.records for id in records do local observer_list = find_observers(world, EcsOnArchetypeDelete, id) if not observer_list then continue end for _, observer in observer_list do if query_match(observer.query, archetype) then observer.callback(archetype) end end end for id in records do local idr = component_index[id] idr.cache[archetype_id] = nil :: any idr.counts[archetype_id] = nil idr.size -= 1 records[id] = nil :: any if idr.size == 0 then component_index[id] = nil :: any end end end local function world_cleanup(world: ecs_world_t) local archetypes = world.archetypes for _, archetype in archetypes do if #archetype.entities == 0 then archetype_destroy(world, archetype) end end local new_archetypes = table.create(#archetypes) :: { ecs_archetype_t } local new_archetype_map = {} for index, archetype in archetypes do new_archetypes[index] = archetype new_archetype_map[archetype.type] = archetype end world.archetypes = new_archetypes world.archetype_index = new_archetype_map end local function world_delete(world: ecs_world_t, entity: i53) local entity_index = world.entity_index local record = entity_index_try_get(entity_index, entity) if not record then return end local archetype = record.archetype local row = record.row if archetype then -- In the future should have a destruct mode for -- deleting archetypes themselves. Maybe requires recycling archetype_delete(world, archetype, row) end local delete = entity local component_index = world.component_index local archetypes = world.archetypes local tgt = ECS_PAIR(EcsWildcard, delete) local rel = ECS_PAIR(delete, EcsWildcard) local idr_t = component_index[tgt] local idr = component_index[delete] local idr_r = component_index[rel] if idr then local flags = idr.flags if bit32.band(flags, ECS_ID_DELETE) ~= 0 then for archetype_id in idr.cache do local idr_archetype = archetypes[archetype_id] local entities = idr_archetype.entities local n = #entities for i = n, 1, -1 do world_delete(world, entities[i]) end archetype_destroy(world, idr_archetype) end else for archetype_id in idr.cache do local idr_archetype = archetypes[archetype_id] local entities = idr_archetype.entities local n = #entities for i = n, 1, -1 do world_remove(world, entities[i], delete) end archetype_destroy(world, idr_archetype) end end end if idr_t then local children local ids local count = 0 local archetype_ids = idr_t.cache for archetype_id in archetype_ids do local idr_t_archetype = archetypes[archetype_id] local idr_t_types = idr_t_archetype.types local entities = idr_t_archetype.entities local removal_queued = false for _, id in idr_t_types do if not ECS_IS_PAIR(id) then continue end local object = entity_index_get_alive( entity_index, ECS_PAIR_SECOND(id)) if object ~= delete then continue end local id_record = component_index[id] local flags = id_record.flags local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE) if flags_delete_mask ~= 0 then for i = #entities, 1, -1 do local child = entities[i] world_delete(world, child) end break else if not ids then ids = {} end ids[id] = true removal_queued = true end end if not removal_queued then continue end if not children then children = {} end local n = #entities table.move(entities, 1, n, count + 1, children) count += n end if ids then for _, child in children do for id in ids do world_remove(world, child, id) end end end for archetype_id in archetype_ids do archetype_destroy(world, archetypes[archetype_id]) end end if idr_r then local archetype_ids = idr_r.cache local flags = idr_r.flags if bit32.band(flags, ECS_ID_DELETE) ~= 0 then for archetype_id in archetype_ids do local idr_r_archetype = archetypes[archetype_id] local entities = idr_r_archetype.entities local n = #entities for i = n, 1, -1 do world_delete(world, entities[i]) end archetype_destroy(world, idr_r_archetype) end else local children = {} local count = 0 local ids = {} for archetype_id in archetype_ids do local idr_r_archetype = archetypes[archetype_id] local entities = idr_r_archetype.entities local tr = idr_r_archetype.records[rel] local tr_count = idr_r_archetype.counts[rel] local types = idr_r_archetype.types for i = tr, tr_count - 1 do ids[types[tr]] = true end local n = #entities table.move(entities, 1, n, count + 1, children) count += n end for _, child in children do for id in ids do world_remove(world, child, id) end end for archetype_id in archetype_ids do archetype_destroy(world, archetypes[archetype_id]) end end end local dense_array = entity_index.dense_array local index_of_deleted_entity = record.dense local index_of_last_alive_entity = entity_index.alive_count entity_index.alive_count = index_of_last_alive_entity - 1 local last_alive_entity = dense_array[index_of_last_alive_entity] local r_swap = entity_index_try_get_any(entity_index, last_alive_entity) :: ecs_record_t r_swap.dense = index_of_deleted_entity record.archetype = nil :: any record.row = nil :: any record.dense = index_of_last_alive_entity dense_array[index_of_deleted_entity] = last_alive_entity dense_array[index_of_last_alive_entity] = ECS_GENERATION_INC(entity) end local function world_contains(world: ecs_world_t, entity): boolean return entity_index_is_alive(world.entity_index, entity) end local function NOOP() end export type QueryInner = { compatible_archetypes: { Archetype }, ids: { i53 }, filter_with: { i53 }, filter_without: { i53 }, next: () -> (number, ...any), world: World, } local function query_iter_init(query: ecs_query_data_t): () -> (number, ...any) local world_query_iter_next local compatible_archetypes = query.compatible_archetypes local lastArchetype = 1 local archetype = compatible_archetypes[1] if not archetype then return NOOP :: () -> (number, ...any) end local columns = archetype.columns local entities = archetype.entities local i = #entities local records = archetype.records local ids = query.ids local A, B, C, D, E, F, G, H, I = unpack(ids) local a: Column, b: Column, c: Column, d: Column local e: Column, f: Column, g: Column, h: Column if not B then a = columns[records[A]] elseif not C then a = columns[records[A]] b = columns[records[B]] elseif not D then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] elseif not E then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] elseif not F then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] elseif not G then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] elseif not H then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] elseif not I then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] h = columns[records[H]] end if not B then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] end local row = i i -= 1 return entity, a[row] end elseif not C then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] end local row = i i -= 1 return entity, a[row], b[row] end elseif not D then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] end local row = i i -= 1 return entity, a[row], b[row], c[row] end elseif not E then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row] end elseif not F then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row] end elseif not G then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row], f[row] end elseif not H then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] end elseif not I then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] h = columns[records[H]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] end else local output = {} function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records end local row = i i -= 1 for j, id in ids do output[j] = columns[records[id]][row] end return entity, unpack(output) end end query.next = world_query_iter_next return world_query_iter_next end local function query_iter(query): () -> (number, ...any) local query_next = query.next if not query_next then query_next = query_iter_init(query) end return query_next end local function query_without(query: ecs_query_data_t, ...: i53) local without = { ... } query.filter_without = without local compatible_archetypes = query.compatible_archetypes for i = #compatible_archetypes, 1, -1 do local archetype = compatible_archetypes[i] local records = archetype.records local matches = true for _, id in without do if records[id] then matches = false break end end if matches then continue end local last = #compatible_archetypes if last ~= i then compatible_archetypes[i] = compatible_archetypes[last] end compatible_archetypes[last] = nil :: any end return query :: any end local function query_with(query: ecs_query_data_t, ...: i53) local compatible_archetypes = query.compatible_archetypes local with = { ... } query.filter_with = with for i = #compatible_archetypes, 1, -1 do local archetype = compatible_archetypes[i] local records = archetype.records local matches = true for _, id in with do if not records[id] then matches = false break end end if matches then continue end local last = #compatible_archetypes if last ~= i then compatible_archetypes[i] = compatible_archetypes[last] end compatible_archetypes[last] = nil :: any end return query :: any end -- Meant for directly iterating over archetypes to minimize -- function call overhead. Should not be used unless iterating over -- hundreds of thousands of entities in bulk. local function query_archetypes(query) return query.compatible_archetypes end local function query_cached(query: ecs_query_data_t) local with = query.filter_with local ids = query.ids if with then table.move(ids, 1, #ids, #with + 1, with) else query.filter_with = ids end local compatible_archetypes = query.compatible_archetypes local lastArchetype = 1 local A, B, C, D, E, F, G, H, I = unpack(ids) local a: Column, b: Column, c: Column, d: Column local e: Column, f: Column, g: Column, h: Column local world_query_iter_next local columns: { Column } local entities: { number } local i: number local archetype: ecs_archetype_t local records: { number } local archetypes = query.compatible_archetypes local world = query.world :: { observable: ecs_observable_t } -- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively -- because the event will be emitted for all components of that Archetype. local observable = world.observable :: ecs_observable_t local on_create_action = observable[EcsOnArchetypeCreate] if not on_create_action then on_create_action = {} observable[EcsOnArchetypeCreate] = on_create_action end local query_cache_on_create = on_create_action[A] if not query_cache_on_create then query_cache_on_create = {} on_create_action[A] = query_cache_on_create end local on_delete_action = observable[EcsOnArchetypeDelete] if not on_delete_action then on_delete_action = {} observable[EcsOnArchetypeDelete] = on_delete_action end local query_cache_on_delete = on_delete_action[A] if not query_cache_on_delete then query_cache_on_delete = {} on_delete_action[A] = query_cache_on_delete end local function on_create_callback(archetype) table.insert(archetypes, archetype) end local function on_delete_callback(archetype) local i = table.find(archetypes, archetype) :: number local n = #archetypes archetypes[i] = archetypes[n] archetypes[n] = nil end local observer_for_create = { query = query, callback = on_create_callback } local observer_for_delete = { query = query, callback = on_delete_callback } table.insert(query_cache_on_create, observer_for_create) table.insert(query_cache_on_delete, observer_for_delete) local function cached_query_iter() lastArchetype = 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return NOOP end entities = archetype.entities i = #entities records = archetype.records columns = archetype.columns if not B then a = columns[records[A]] elseif not C then a = columns[records[A]] b = columns[records[B]] elseif not D then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] elseif not E then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] elseif not F then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] elseif not G then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] elseif not H then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] elseif not I then a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] h = columns[records[H]] end return world_query_iter_next end if not B then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] end local row = i i -= 1 return entity, a[row] end elseif not C then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] end local row = i i -= 1 return entity, a[row], b[row] end elseif not D then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] end local row = i i -= 1 return entity, a[row], b[row], c[row] end elseif not E then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row] end elseif not F then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row] end elseif not G then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row], f[row] end elseif not H then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] end elseif not I then function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records a = columns[records[A]] b = columns[records[B]] c = columns[records[C]] d = columns[records[D]] e = columns[records[E]] f = columns[records[F]] g = columns[records[G]] h = columns[records[H]] end local row = i i -= 1 return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] end else local queryOutput = {} function world_query_iter_next(): any local entity = entities[i] while entity == nil do lastArchetype += 1 archetype = compatible_archetypes[lastArchetype] if not archetype then return nil end entities = archetype.entities i = #entities if i == 0 then continue end entity = entities[i] columns = archetype.columns records = archetype.records end local row = i i -= 1 if not F then return entity, a[row], b[row], c[row], d[row], e[row] elseif not G then return entity, a[row], b[row], c[row], d[row], e[row], f[row] elseif not H then return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] elseif not I then return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] end for j, id in ids do queryOutput[j] = columns[records[id]][row] end return entity, unpack(queryOutput) end end local cached_query = query :: any cached_query.archetypes = query_archetypes cached_query.__iter = cached_query_iter cached_query.iter = cached_query_iter setmetatable(cached_query, cached_query) return cached_query end local Query = {} Query.__index = Query Query.__iter = query_iter Query.iter = query_iter_init Query.without = query_without Query.with = query_with Query.archetypes = query_archetypes Query.cached = query_cached local function world_query(world: ecs_world_t, ...) local compatible_archetypes = {} local length = 0 local ids = { ... } local archetypes = world.archetypes local idr: ecs_id_record_t? local component_index = world.component_index local q = setmetatable({ ids = ids, compatible_archetypes = compatible_archetypes, world = world, }, Query) for _, id in ids do local map = component_index[id] if not map then return q end if idr == nil or map.size < idr.size then idr = map end end if not idr then return q end for archetype_id in idr.cache do local compatibleArchetype = archetypes[archetype_id] if #compatibleArchetype.entities == 0 then continue end local records = compatibleArchetype.records local skip = false for i, id in ids do local tr = records[id] if not tr then skip = true break end end if skip then continue end length += 1 compatible_archetypes[length] = compatibleArchetype end return q end local function world_each(world: ecs_world_t, id: i53): () -> () local idr = world.component_index[id] if not idr then return NOOP end local idr_cache = idr.cache local archetypes = world.archetypes local archetype_id = next(idr_cache, nil) :: number local archetype = archetypes[archetype_id] if not archetype then return NOOP end local entities = archetype.entities local row = #entities return function(): any local entity = entities[row] while not entity do archetype_id = next(idr_cache, archetype_id) :: number if not archetype_id then return end archetype = archetypes[archetype_id] entities = archetype.entities row = #entities entity = entities[row] end row -= 1 return entity end end local function world_children(world: ecs_world_t, parent: i53) return world_each(world, ECS_PAIR(EcsChildOf, parent)) end export type Record = { archetype: Archetype, row: number, dense: i24, } export type ComponentRecord = { cache: { [Id]: number }, counts: { [Id]: number }, flags: number, size: number, hooks: { on_add: ((entity: Entity) -> ())?, on_set: ((entity: Entity, data: any) -> ())?, on_remove: ((entity: Entity) -> ())?, }, } export type ComponentIndex = Map export type Archetypes = { [Id]: Archetype } export type EntityIndex = { dense_array: Map, sparse_array: Map, alive_count: number, max_id: number, } local World = {} World.__index = World World.entity = world_entity World.query = world_query World.remove = world_remove World.clear = world_clear World.delete = world_delete World.component = world_component World.add = world_add World.set = world_set World.get = world_get World.has = world_has World.target = world_target World.parent = world_parent World.contains = world_contains World.cleanup = world_cleanup World.each = world_each World.children = world_children local function world_new() local entity_index = { dense_array = {}, sparse_array = {}, alive_count = 0, max_id = 0, } :: ecs_entity_index_t local self = setmetatable({ archetype_edges = {}, archetype_index = {} :: { [string]: Archetype }, archetypes = {} :: Archetypes, component_index = {} :: ComponentIndex, entity_index = entity_index, ROOT_ARCHETYPE = (nil :: any) :: Archetype, max_archetype_id = 0, max_component_id = 0, observable = {} :: Observable, }, World) :: any self.ROOT_ARCHETYPE = archetype_create(self, {}, "") for i = 1, HI_COMPONENT_ID do local e = entity_index_new_id(entity_index) world_add(self, e, EcsComponent) end for i = HI_COMPONENT_ID + 1, EcsRest do -- Initialize built-in components entity_index_new_id(entity_index) end world_add(self, EcsName, EcsComponent) world_add(self, EcsOnChange, EcsComponent) world_add(self, EcsOnAdd, EcsComponent) world_add(self, EcsOnRemove, EcsComponent) world_add(self, EcsWildcard, EcsComponent) world_add(self, EcsRest, EcsComponent) world_set(self, EcsOnAdd, EcsName, "jecs.OnAdd") world_set(self, EcsOnRemove, EcsName, "jecs.OnRemove") world_set(self, EcsOnChange, EcsName, "jecs.OnChange") world_set(self, EcsWildcard, EcsName, "jecs.Wildcard") world_set(self, EcsChildOf, EcsName, "jecs.ChildOf") world_set(self, EcsComponent, EcsName, "jecs.Component") world_set(self, EcsOnDelete, EcsName, "jecs.OnDelete") world_set(self, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget") world_set(self, EcsDelete, EcsName, "jecs.Delete") world_set(self, EcsRemove, EcsName, "jecs.Remove") world_set(self, EcsName, EcsName, "jecs.Name") world_set(self, EcsRest, EcsRest, "jecs.Rest") world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete)) return self end World.new = world_new export type Entity = { __T: T } export type Id = { __T: T } export type Pair = Id

type ecs_id_t = Id | Pair | Pair<"Tag", T> export type Item = (self: Query) -> (Entity, T...) export type Iter = (query: Query) -> () -> (Entity, T...) export type Query = typeof(setmetatable({}, { __iter = (nil :: any) :: Iter, })) & { iter: Iter, with: (self: Query, ...Id) -> Query, without: (self: Query, ...Id) -> Query, archetypes: (self: Query) -> { Archetype }, cached: (self: Query) -> Query, } export type Observer = { callback: (archetype: Archetype) -> (), query: QueryInner, } export type Observable = { [Id]: { [Id]: { { Observer } } } } export type World = { archetype_index: { [string]: Archetype }, archetypes: Archetypes, component_index: ComponentIndex, entity_index: EntityIndex, ROOT_ARCHETYPE: Archetype, max_component_id: number, max_archetype_id: number, observable: any, --- Creates a new entity entity: (self: World, id: Entity?) -> Entity, --- Creates a new entity located in the first 256 ids. --- These should be used for static components for fast access. component: (self: World) -> Entity, --- Gets the target of an relationship. For example, when a user calls --- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity. target: (self: World, id: Entity, relation: Id, index: number?) -> Entity?, --- Deletes an entity and all it's related components and relationships. delete: (self: World, id: Entity) -> (), --- Adds a component to the entity with no value add: (self: World, id: Entity, component: Id) -> (), --- Assigns a value to a component on the given entity set: (self: World, id: Entity, component: Id, data: T) -> (), cleanup: (self: World) -> (), -- Clears an entity from the world clear: (self: World, id: Entity) -> (), --- Removes a component from the given entity remove: (self: World, id: Entity, component: Id) -> (), --- Retrieves the value of up to 4 components. These values may be nil. get: ((self: World, id: Entity, Id) -> A?) & ((self: World, id: Entity, Id, Id) -> (A?, B?)) & ((self: World, id: Entity, Id, Id, Id) -> (A?, B?, C?)) & (self: World, id: Entity, Id, Id, Id, Id) -> (A?, B?, C?, D?), --- Returns whether the entity has the ID. has: ((World, Entity, A) -> boolean) & ((World, Entity, A, B) -> boolean) & ((World, Entity, A, B, C) -> boolean) & (World, Entity, A, B, C, D) -> boolean, --- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil. parent:(self: World, entity: Entity) -> Entity, --- Checks if the world contains the given entity contains:(self: World, entity: Entity) -> boolean, each: (self: World, id: Id) -> () -> Entity, children: (self: World, id: Id) -> () -> Entity, --- Searches the world for entities that match a given query query: ((World, Id) -> Query) & ((World, Id, Id) -> Query) & ((World, Id, Id, Id) -> Query) & ((World, Id, Id, Id, Id) -> Query) & ((World, Id, Id, Id, Id, Id) -> Query) & ((World, Id, Id, Id, Id, Id, Id) -> Query) & ((World, Id, Id, Id, Id, Id, Id, Id) -> Query) & ((World, Id, Id, Id, Id, Id, Id, Id, Id, ...Id) -> Query) } -- type function ecs_id_t(entity) -- local ty = entity:components()[2] -- local __T = ty:readproperty(types.singleton("__T")) -- if not __T then -- return ty:readproperty(types.singleton("__jecs_pair_value")) -- end -- return __T -- end -- type function ecs_pair_t(first, second) -- if ecs_id_t(first):is("nil") then -- return second -- else -- return first -- end -- end return { World = World :: { new: () -> World }, world = world_new :: () -> World, OnAdd = EcsOnAdd :: Entity<(entity: Entity) -> ()>, OnRemove = EcsOnRemove :: Entity<(entity: Entity) -> ()>, OnChange = EcsOnChange :: Entity<(entity: Entity, data: any) -> ()>, ChildOf = EcsChildOf :: Entity, Component = EcsComponent :: Entity, Wildcard = EcsWildcard :: Entity, w = EcsWildcard :: Entity, OnDelete = EcsOnDelete :: Entity, OnDeleteTarget = EcsOnDeleteTarget :: Entity, Delete = EcsDelete :: Entity, Remove = EcsRemove :: Entity, Name = EcsName :: Entity, Rest = EcsRest :: Entity, pair = (ECS_PAIR :: any) :: (first: Id

, second: Id) -> Pair, -- Inwards facing API for testing ECS_ID = ECS_ENTITY_T_LO, ECS_GENERATION_INC = ECS_GENERATION_INC, ECS_GENERATION = ECS_GENERATION, ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD, ECS_ID_DELETE = ECS_ID_DELETE, IS_PAIR = (ECS_IS_PAIR :: any) :: (pair: Pair) -> boolean, pair_first = (ecs_pair_first :: any) :: (world: World, pair: Pair) -> Id

, pair_second = (ecs_pair_second :: any) :: (world: World, pair: Pair) -> Id, entity_index_get_alive = entity_index_get_alive, archetype_append_to_records = archetype_append_to_records, id_record_ensure = id_record_ensure, archetype_create = archetype_create, archetype_ensure = archetype_ensure, find_insert = find_insert, find_archetype_with = find_archetype_with, find_archetype_without = find_archetype_without, create_edge_for_remove = create_edge_for_remove, archetype_traverse_add = archetype_traverse_add, archetype_traverse_remove = archetype_traverse_remove, entity_move = entity_move, entity_index_try_get = entity_index_try_get, entity_index_try_get_any = entity_index_try_get_any, entity_index_try_get_fast = entity_index_try_get_fast, entity_index_is_alive = entity_index_is_alive, entity_index_new_id = entity_index_new_id, query_iter = query_iter, query_iter_init = query_iter_init, query_with = query_with, query_without = query_without, query_archetypes = query_archetypes, query_match = query_match, find_observers = find_observers, }