From 666a3ef6ded6fed7e334505f8c115321a3684d71 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Thu, 24 Jul 2025 01:38:05 +0200 Subject: [PATCH] Fix inner types --- jecs.luau | 590 ++++++++++++++++++++++++++---------------------- test/tests.luau | 46 ++-- 2 files changed, 341 insertions(+), 295 deletions(-) diff --git a/jecs.luau b/jecs.luau index 4a37712..f50c54a 100755 --- a/jecs.luau +++ b/jecs.luau @@ -31,8 +31,8 @@ export type QueryInner = { world: World, } -export type Entity = number | { __T: T } -export type Id = number | { __T: T } +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...) @@ -66,8 +66,74 @@ export type Observer = { query: QueryInner, } + +type archetype = { + id: number, + types: { i53 }, + type: string, + entities: { i53 }, + columns: { Column }, + columns_map: { [i53]: Column } +} + +type componentrecord = { + records: { [i53]: number }, + counts: { [i53]: number }, + flags: number, + size: number, + + on_add: ((entity: i53, id: i53, value: any?) -> ())?, + on_change: ((entity: i53, id: i53, value: any) -> ())?, + on_remove: ((entity: i53, id: i53) -> ())?, +} +type record = { + archetype: archetype, + row: number, + dense: i24, +} +type entityindex = { + dense_array: Map, + sparse_array: Map, + alive_count: number, + max_id: number, + range_begin: number?, + range_end: number?, +} +type world = { + archetype_edges: Map>, + archetype_index: { [string]: archetype }, + archetypes: { [i53]: archetype }, + component_index: Map, + entity_index: entityindex, + ROOT_ARCHETYPE: archetype, + + max_component_id: number, + max_archetype_id: number, + + observable: Map>, + + range: (self: world, range_begin: number, range_end: number?) -> (), + entity: (self: world, id: i53?) -> i53, + component: (self: world) -> i53, + target: (self: world, id: i53, relation: i53, index: number?) -> i53?, + delete: (self: world, id: i53) -> (), + add: (self: world, id: i53, component: i53) -> (), + set: (self: world, id: i53, component: i53, data: any) -> (), + cleanup: (self: world) -> (), + clear: (self: world, id: i53) -> (), + remove: (self: world, id: i53, component: i53) -> (), + get: (world, ...i53) -> (), + has: (world, ...i53) -> boolean, + parent: (self: world, entity: i53) -> i53?, + contains: (self: world, entity: i53) -> boolean, + exists: (self: world, entity: i53) -> boolean, + each: (self: world, id: i53) -> () -> i53, + children: (self: world, id: i53) -> () -> i53, + query: (world, ...i53) -> Query<...any> +} + export type World = { - archetype_edges: Map>, + archetype_edges: Map>, archetype_index: { [string]: Archetype }, archetypes: Archetypes, component_index: ComponentIndex, @@ -83,7 +149,7 @@ export type World = { range: (self: World, range_begin: number, range_end: number?) -> (), --- Creates a new entity - entity: (self: World, id: Entity?) -> Entity, + entity: (self: World, id: (number | 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, @@ -165,8 +231,8 @@ export type Record = { dense: i24, } export type ComponentRecord = { - records: { [Id]: number }, - counts: { [Id]: number }, + records: { [i24]: number }, + counts: { [i24]: number }, flags: number, size: number, @@ -175,7 +241,7 @@ export type ComponentRecord = { on_remove: ((entity: Entity, id: Entity) -> ())?, } export type ComponentIndex = Map -export type Archetypes = { [Id]: Archetype } +export type Archetypes = { [i24]: Archetype } export type EntityIndex = { dense_array: Map, @@ -249,7 +315,7 @@ end local function ECS_META(id: i53, ty: i53, value: any?) local bundle = ecs_metadata[id] if bundle == nil then - bundle = {} + bundle = {} :: Map ecs_metadata[id] = bundle end bundle[ty] = if value == nil then NULL else value @@ -316,10 +382,10 @@ local function ECS_PAIR_SECOND(e: i53): i24 end local function entity_index_try_get_any( - entity_index: EntityIndex, - entity: Entity -): Record? - local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity::number)] + entity_index: entityindex, + entity: i53 +): record? + local r = entity_index.sparse_array[ECS_ID(entity)] if not r or r.dense == 0 then return nil @@ -328,8 +394,8 @@ local function entity_index_try_get_any( return r end -local function entity_index_try_get(entity_index: EntityIndex, entity: Entity): Record? - local r = entity_index_try_get_any(entity_index, entity :: number) +local function entity_index_try_get(entity_index: entityindex, entity: i53): record? + 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 @@ -342,7 +408,7 @@ local function entity_index_try_get(entity_index: EntityIndex, entity: Entity): return r end -local function entity_index_try_get_fast(entity_index: EntityIndex, entity: Entity): Record? +local function entity_index_try_get_fast(entity_index: entityindex, entity: i53): record? local r = entity_index_try_get_any(entity_index, entity) if r then local r_dense = r.dense @@ -356,11 +422,11 @@ local function entity_index_try_get_fast(entity_index: EntityIndex, entity: Enti return r end -local function entity_index_is_alive(entity_index: EntityIndex, entity: Entity): boolean +local function entity_index_is_alive(entity_index: entityindex, entity: i53): boolean return entity_index_try_get(entity_index, entity) ~= nil end -local function entity_index_get_alive(entity_index: EntityIndex, entity: Entity): Entity? +local function entity_index_get_alive(entity_index: entityindex, entity: i53): i53? local r = entity_index_try_get_any(entity_index, entity :: number) if r then return entity_index.dense_array[r.dense] @@ -368,7 +434,7 @@ local function entity_index_get_alive(entity_index: EntityIndex, entity: Enti return nil end -local function ecs_get_alive(world: World, entity: Entity): Entity +local function ecs_get_alive(world: world, entity: i53): i53 if entity == 0 then return 0 end @@ -393,7 +459,7 @@ end local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range" -local function entity_index_new_id(entity_index: EntityIndex): Entity +local function entity_index_new_id(entity_index: entityindex): i53 local dense_array = entity_index.dense_array local alive_count = entity_index.alive_count local sparse_array = entity_index.sparse_array @@ -414,22 +480,22 @@ local function entity_index_new_id(entity_index: EntityIndex): Entity alive_count += 1 entity_index.alive_count = alive_count dense_array[alive_count] = id - sparse_array[id] = { dense = alive_count } :: Record + sparse_array[id] = { dense = alive_count } :: record return id end -local function ecs_pair_first(world: World, e: i53) +local function ecs_pair_first(world: world, e: i53) local pred = ECS_PAIR_FIRST(e) return ecs_get_alive(world, pred) end -local function ecs_pair_second(world: World, e: i53) +local function ecs_pair_second(world: world, e: i53) local obj = ECS_PAIR_SECOND(e) return ecs_get_alive(world, obj) end -local function query_match(query: QueryInner, archetype: Archetype) +local function query_match(query: QueryInner, archetype: archetype) local columns_map = archetype.columns_map local with = query.filter_with @@ -451,7 +517,7 @@ local function query_match(query: QueryInner, archetype: Archetype) return true end -local function find_observers(world: World, event: Id, component: Id): { Observer }? +local function find_observers(world: world, event: i53, component: i53): { Observer }? local cache = world.observable[event] if not cache then return nil @@ -460,11 +526,11 @@ local function find_observers(world: World, event: Id, component: Id): { Observe end local function archetype_move( - entity_index: EntityIndex, - entity: Entity, - to: Archetype, + entity_index: entityindex, + entity: i53, + to: archetype, dst_row: i24, - from: Archetype, + from: archetype, src_row: i24 ) local src_columns = from.columns @@ -505,7 +571,7 @@ local function archetype_move( src_entities[src_row] = e2 local sparse_array = entity_index.sparse_array - local record2 = sparse_array[ECS_ENTITY_T_LO(e2 :: number)] + local record2 = sparse_array[ECS_ID(e2)] record2.row = src_row else for i, column in src_columns do @@ -525,13 +591,13 @@ local function archetype_move( end end - src_entities[last] = nil :: any + src_entities[last] = nil dst_entities[dst_row] = entity end local function archetype_append( - entity: Entity, - archetype: Archetype + entity: i53, + archetype: archetype ): number local entities = archetype.entities local length = #entities + 1 @@ -540,10 +606,10 @@ local function archetype_append( end local function new_entity( - entity: Entity, - record: Record, - archetype: Archetype -): Record + entity: i53, + record: record, + archetype: archetype +): record local row = archetype_append(entity, archetype) record.archetype = archetype record.row = row @@ -551,10 +617,10 @@ local function new_entity( end local function entity_move( - entity_index: EntityIndex, - entity: Entity, - record: Record, - to: Archetype + entity_index: entityindex, + entity: i53, + record: record, + to: archetype ) local sourceRow = record.row local from = record.archetype @@ -564,11 +630,11 @@ local function entity_move( record.row = dst_row end -local function hash(arr: { Entity }): string +local function hash(arr: { i53 }): string return table.concat(arr, "_") end -local function fetch(id: Id, columns_map: { [Entity]: Column }, row: number): any +local function fetch(id: i53, columns_map: { [i53]: Column }, row: number): any local column = columns_map[id] if not column then @@ -578,8 +644,8 @@ local function fetch(id: Id, columns_map: { [Entity]: Column }, row: number): an return column[row] end -local function world_get(world: World, entity: Entity, - a: Id, b: Id?, c: Id?, d: Id?, e: Id?): ...any +local function world_get(world: world, entity: i53, + a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any local record = entity_index_try_get(world.entity_index, entity) if not record then return nil @@ -608,7 +674,7 @@ local function world_get(world: World, entity: Entity, end end -local function world_has_one_inline(world: World, entity: Entity, id: i53): boolean +local function world_has_one_inline(world: world, entity: i53, id: i53): boolean local record = entity_index_try_get(world.entity_index, entity) if not record then return false @@ -622,7 +688,7 @@ local function world_has_one_inline(world: World, entity: Entity, id: i53): bool return archetype.columns_map[id] ~= nil end -local function world_target(world: World, entity: Entity, relation: Id, index: number?): Entity? +local function world_target(world: world, entity: i53, relation: i53, index: number?): i53? local entity_index = world.entity_index local record = entity_index_try_get(entity_index, entity) if not record then @@ -634,7 +700,7 @@ local function world_target(world: World, entity: Entity, relation: Id, index: n return nil end - local r = ECS_PAIR(relation :: number, EcsWildcard) + local r = ECS_PAIR(relation, EcsWildcard) local idr = world.component_index[r] if not idr then @@ -680,10 +746,10 @@ local function id_record_get(world: World, id: Entity): ComponentRecord? return nil end -local function id_record_ensure(world: World, id: Entity): ComponentRecord +local function id_record_ensure(world: world, id: i53): componentrecord local component_index = world.component_index local entity_index = world.entity_index - local idr: ComponentRecord? = component_index[id] + local idr: componentrecord? = component_index[id] if idr then return idr @@ -698,10 +764,10 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord local is_exclusive = false if is_pair then - relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id :: number)) :: i53 + relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) :: i53 ecs_assert(relation and entity_index_is_alive( entity_index, relation), ECS_INTERNAL_ERROR) - target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id :: number)) :: i53 + target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) :: i53 ecs_assert(target and entity_index_is_alive( entity_index, target), ECS_INTERNAL_ERROR) @@ -748,7 +814,7 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord on_add = on_add, on_change = on_change, on_remove = on_remove, - } :: ComponentRecord + } :: componentrecord component_index[id] = idr @@ -756,9 +822,9 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord end local function archetype_append_to_records( - idr: ComponentRecord, + idr: componentrecord, archetype_id: number, - columns_map: { [Id]: Column }, + columns_map: { [i53]: Column }, id: i53, index: number, column: Column @@ -776,85 +842,16 @@ local function archetype_append_to_records( end end -local function archetype_register(world: World, archetype: Archetype, recycle: boolean) - local archetype_id = archetype.id - local columns_map = archetype.columns_map - local columns = archetype.columns - - if recycle then - local component_index = world.component_index - for i, component_id in archetype.types do - local idr = component_index[component_id] - local column = columns[i] - - archetype_append_to_records(idr, archetype_id, columns_map, component_id :: number, i, column) - - if ECS_IS_PAIR(component_id :: number) then - local relation = ECS_PAIR_FIRST(component_id :: number) - local object = ECS_PAIR_SECOND(component_id :: number) - local r = ECS_PAIR(relation, EcsWildcard) - local idr_r = component_index[r] - - archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column) - - local t = ECS_PAIR(EcsWildcard, object) - local idr_t = component_index[t] - - archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column) - end - end - else - for i, component_id in archetype.types do - local idr = id_record_ensure(world, component_id) - local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG) - local column = if is_tag then NULL_ARRAY else {} - columns[i] = column - - archetype_append_to_records(idr, archetype_id, columns_map, component_id :: number, i, column) - - if ECS_IS_PAIR(component_id :: number) then - local relation = ECS_PAIR_FIRST(component_id :: number) - local object = ECS_PAIR_SECOND(component_id :: number) - local r = ECS_PAIR(relation, EcsWildcard) - local idr_r = id_record_ensure(world, r) - - archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column) - - local t = ECS_PAIR(EcsWildcard, object) - local idr_t = id_record_ensure(world, t) - - archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column) - end - end - - world.archetype_index[archetype.type] = archetype - world.archetypes[archetype_id] = archetype - world.archetype_edges[archetype.id] = {} :: Map - end - - for id in columns_map 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 :: QueryInner, archetype) then - observer.callback(archetype) - end - end - end -end - -local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?): Archetype +local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?): archetype 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 columns_map: { [Id]: Column } = {} + local columns_map: { [i53]: Column } = {} - local archetype: Archetype = { + local archetype: archetype = { columns = columns, columns_map = columns_map, entities = {}, @@ -863,12 +860,49 @@ local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?): types = id_types } - archetype_register(world, archetype, false) + for i, component_id in archetype.types do + local idr = id_record_ensure(world, component_id) + local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG) + local column = if is_tag then NULL_ARRAY else {} + columns[i] = column + + archetype_append_to_records(idr, archetype_id, columns_map, component_id :: number, i, column) + + 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_id, columns_map, r, i, column) + + local t = ECS_PAIR(EcsWildcard, object) + local idr_t = id_record_ensure(world, t) + + archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column) + end + end + + world.archetype_index[archetype.type] = archetype + world.archetypes[archetype_id] = archetype + world.archetype_edges[archetype.id] = {} :: Map + + for id in columns_map 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::any) + end + end + end return archetype end -local function world_range(world: World, range_begin: number, range_end: number?) +local function world_range(world: world, range_begin: number, range_end: number?) local entity_index = world.entity_index entity_index.range_begin = range_begin @@ -884,14 +918,14 @@ local function world_range(world: World, range_begin: number, range_end: number? dense_array[i] = i sparse_array[i] = { dense = 0 - } :: Record + } :: record end entity_index.max_id = range_begin - 1 entity_index.alive_count = range_begin - 1 end end -local function archetype_ensure(world: World, id_types: { Id }): Archetype +local function archetype_ensure(world: world, id_types: { i53 }): archetype if #id_types < 1 then return world.ROOT_ARCHETYPE end @@ -918,10 +952,10 @@ local function find_insert(id_types: { i53 }, toAdd: i53): number end local function find_archetype_without( - world: World, - node: Archetype, - id: Id -): Archetype + world: world, + node: archetype, + id: i53 +): archetype local id_types = node.types local at = table.find(id_types, id) @@ -933,11 +967,11 @@ end local function create_edge_for_remove( - world: World, - node: Archetype, - edge: Map, - id: Id -): Archetype + world: world, + node: archetype, + edge: Map, + id: i53 +): archetype local to = find_archetype_without(world, node, id) local edges = world.archetype_edges local archetype_id = node.id @@ -947,14 +981,14 @@ local function create_edge_for_remove( end local function archetype_traverse_remove( - world: World, - id: Id, - from: Archetype -): Archetype + world: world, + id: i53, + from: archetype +): archetype local edges = world.archetype_edges local edge = edges[from.id] - local to: Archetype = edge[id] + local to: archetype = edge[id] if to == nil then to = find_archetype_without(world, from, id) edge[id] = to @@ -964,7 +998,11 @@ local function archetype_traverse_remove( return to end -local function find_archetype_with(world: World, id: Id, from: Archetype): Archetype +local function find_archetype_with( + world: world, + id: i53, + from: archetype +): archetype local id_types = from.types local dst = table.clone(id_types) @@ -975,7 +1013,11 @@ local function find_archetype_with(world: World, id: Id, from: Archetype): Arche return archetype_ensure(world, dst) end -local function archetype_traverse_add(world: World, id: Id, from: Archetype): Archetype +local function archetype_traverse_add( + world: world, + id: i53, + from: archetype +): archetype from = from or world.ROOT_ARCHETYPE if from.columns_map[id] then return from @@ -993,7 +1035,7 @@ local function archetype_traverse_add(world: World, id: Id, from: Archetype): Ar return to end -local function world_component(world: World): i53 +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, @@ -1022,7 +1064,7 @@ local function archetype_fast_delete(columns: { Column }, column_count: number, end end -local function archetype_delete(world: World, archetype: Archetype, row: number) +local function archetype_delete(world: world, archetype: archetype, row: number) local entity_index = world.entity_index local component_index = world.component_index local columns = archetype.columns @@ -1035,7 +1077,7 @@ local function archetype_delete(world: World, archetype: Archetype, row: number) local delete = move if row ~= last then - local record_to_move = entity_index_try_get_any(entity_index, move :: number) + local record_to_move = entity_index_try_get_any(entity_index, move) if record_to_move then record_to_move.row = row end @@ -1062,7 +1104,7 @@ local function archetype_delete(world: World, archetype: Archetype, row: number) end -local function archetype_destroy(world: World, archetype: Archetype) +local function archetype_destroy(world: world, archetype: archetype) if archetype == world.ROOT_ARCHETYPE then return end @@ -1094,7 +1136,7 @@ local function archetype_destroy(world: World, archetype: Archetype) end for _, observer in observer_list do if query_match(observer.query, archetype) then - observer.callback(archetype) + observer.callback(archetype::any) end end end @@ -1117,7 +1159,7 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any) local columns_map = archetype.columns_map local ids = query.ids - local A, B, C, D, E, F, G, H, I = unpack(ids) + local A, B, C, D, E, F, G, H, I = unpack(ids :: { Id }) local a: Column, b: Column, c: Column, d: Column local e: Column, f: Column, g: Column, h: Column @@ -1428,7 +1470,7 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any) i -= 1 for i = 9, ids_len do - output[i - 8] = columns_map[ids[i]][row] + output[i - 8] = columns_map[ids[i]::any][row] end return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row], unpack(output) @@ -1447,9 +1489,9 @@ local function query_iter(query): () -> (number, ...any) return query_next end -local function query_without(query: QueryInner, ...: i53) +local function query_without(query: QueryInner, ...: Id) local without = { ... } - query.filter_without = without + query.filter_without = without :: any local compatible_archetypes = query.compatible_archetypes for i = #compatible_archetypes, 1, -1 do local archetype = compatible_archetypes[i] @@ -1477,9 +1519,9 @@ local function query_without(query: QueryInner, ...: i53) return query :: any end -local function query_with(query: QueryInner, ...: i53) +local function query_with(query: QueryInner, ...: Id) local compatible_archetypes = query.compatible_archetypes - local with = { ... } + local with = { ... } :: any query.filter_with = with for i = #compatible_archetypes, 1, -1 do @@ -1527,7 +1569,7 @@ local function query_cached(query: QueryInner) local compatible_archetypes = query.compatible_archetypes local lastArchetype = 1 - local A, B, C, D, E, F, G, H, I = unpack(ids) + local A, B, C, D, E, F, G, H, I = unpack(ids :: { Id }) local a: Column, b: Column, c: Column, d: Column local e: Column, f: Column, g: Column, h: Column @@ -1542,10 +1584,10 @@ local function query_cached(query: QueryInner) -- 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 - local on_create_action = observable[EcsOnArchetypeCreate] + local on_create_action = observable[EcsOnArchetypeCreate::any] if not on_create_action then on_create_action = {} :: Map - observable[EcsOnArchetypeCreate] = on_create_action + observable[EcsOnArchetypeCreate::any] = on_create_action end local query_cache_on_create = on_create_action[A] if not query_cache_on_create then @@ -1553,10 +1595,10 @@ local function query_cached(query: QueryInner) on_create_action[A] = query_cache_on_create end - local on_delete_action = observable[EcsOnArchetypeDelete] + local on_delete_action = observable[EcsOnArchetypeDelete::any] if not on_delete_action then on_delete_action = {} :: Map - observable[EcsOnArchetypeDelete] = on_delete_action + observable[EcsOnArchetypeDelete::any] = on_delete_action end local query_cache_on_delete = on_delete_action[A] if not query_cache_on_delete then @@ -1903,7 +1945,7 @@ local function query_cached(query: QueryInner) i -= 1 for i = 9, ids_len do - output[i - 8] = columns_map[ids[i]][row] + output[i - 8] = columns_map[ids[i]::any][row] end return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], unpack(output) @@ -1987,10 +2029,10 @@ local function world_query(world: World, ...) return q end -local function world_each(world: World, id: Id): () -> Entity +local function world_each(world: world, id: i53): () -> i53 local idr = world.component_index[id] if not idr then - return NOOP :: () -> Entity + return NOOP :: () -> i53 end local records = idr.records @@ -1998,18 +2040,18 @@ local function world_each(world: World, id: Id): () -> Entity local archetype_id = next(records, nil) :: number local archetype = archetypes[archetype_id] if not archetype then - return NOOP :: () -> Entity + return NOOP :: () -> i53 end local entities = archetype.entities local row = #entities - return function(): any + return function() local entity = entities[row] while not entity do archetype_id = next(records, archetype_id) :: number if not archetype_id then - return + return nil :: any end archetype = archetypes[archetype_id] entities = archetype.entities @@ -2021,11 +2063,11 @@ local function world_each(world: World, id: Id): () -> Entity end end -local function world_children(world: World, parent: Id) - return world_each(world, ECS_PAIR(EcsChildOf, parent::number)) +local function world_children(world: world, parent: i53) + return world_each(world, ECS_PAIR(EcsChildOf, parent)) end -local function ecs_bulk_insert(world: World, entity: Entity, ids: { Id }, values: { any }) +local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values: { any }) local entity_index = world.entity_index local r = entity_index_try_get(entity_index, entity) if not r then @@ -2103,7 +2145,7 @@ local function ecs_bulk_insert(world: World, entity: Entity, ids: { Id }, values end end -local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id }) +local function ecs_bulk_remove(world: world, entity: i53, ids: { i53 }) local entity_index = world.entity_index local r = entity_index_try_get(entity_index, entity) if not r then @@ -2115,7 +2157,7 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id }) return end - local remove: { [Id]: boolean } = {} + local remove: { [i53]: boolean } = {} local columns_map = from.columns_map @@ -2138,7 +2180,7 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id }) from = to end - local dst_types = table.clone(from.types) :: { Entity } + local dst_types = table.clone(from.types) :: { i53 } for id in remove do local at = table.find(dst_types, id) @@ -2146,27 +2188,28 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id }) end to = archetype_ensure(world, dst_types) + if from ~= to then entity_move(entity_index, entity, r, to) end end local function world_new() - local eindex_dense_array = {} :: { Entity } - local eindex_sparse_array = {} :: { Record } + local eindex_dense_array = {} :: { i53 } + local eindex_sparse_array = {} :: { record } local entity_index = { dense_array = eindex_dense_array, sparse_array = eindex_sparse_array, alive_count = 0, max_id = 0, - } :: EntityIndex + } :: entityindex - local component_index = {} :: ComponentIndex + local component_index = {} :: Map - local archetype_index = {} :: { [string]: Archetype } - local archetypes = {} :: Archetypes - local archetype_edges = {} :: { [number]: { [Id]: Archetype } } + local archetype_index = {} :: { [string]: archetype } + local archetypes = {} :: Map + local archetype_edges = {} :: { [number]: { [i53]: archetype } } local observable = {} @@ -2183,22 +2226,22 @@ local function world_new() max_component_id = ecs_max_component_id, observable = observable, - } :: World + } :: world local ROOT_ARCHETYPE = archetype_create(world, {}, "") world.ROOT_ARCHETYPE = ROOT_ARCHETYPE - local function inner_entity_index_try_get_any(entity: number): Record? + local function inner_entity_index_try_get_any(entity: i53): record? local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)] return r end local function inner_archetype_move( - entity: Entity, - to: Archetype, + entity: i53, + to: archetype, dst_row: i24, - from: Archetype, + from: archetype, src_row: i24 ) local src_columns = from.columns @@ -2227,7 +2270,7 @@ local function world_new() local e2 = src_entities[last] src_entities[src_row] = e2 - local record2 = eindex_sparse_array[ECS_ENTITY_T_LO(e2 :: number)] + local record2 = eindex_sparse_array[ECS_ENTITY_T_LO(e2)] record2.row = src_row else for i, column in src_columns do @@ -2251,10 +2294,10 @@ local function world_new() end local function inner_entity_move( - entity_index: EntityIndex, - entity: Entity, - record: Record, - to: Archetype + entity_index: entityindex, + entity: i53, + record: record, + to: archetype ) local sourceRow = record.row local from = record.archetype @@ -2278,7 +2321,7 @@ local function world_new() -- return r -- end - local function inner_entity_index_try_get_unsafe(entity: number): Record? + local function inner_entity_index_try_get_unsafe(entity: i53): record? local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)] if r then local r_dense = r.dense @@ -2292,13 +2335,13 @@ local function world_new() return r end - local function inner_world_set(world: World, entity: Entity, id: Id, data: a): () - local record = inner_entity_index_try_get_unsafe(entity :: number) + local function inner_world_set(world: world, entity: i53, id: i53, data): () + local record = inner_entity_index_try_get_unsafe(entity) if not record then return end - local from: Archetype = record.archetype + local from: archetype = record.archetype local src = from or ROOT_ARCHETYPE local column = src.columns_map[id] if column then @@ -2312,10 +2355,10 @@ local function world_new() on_change(entity, id, data) end else - local to: Archetype - local idr: ComponentRecord - if ECS_IS_PAIR(id::number) then - local first = ECS_PAIR_FIRST(id::number) + local to: archetype + local idr: componentrecord + if ECS_IS_PAIR(id) then + local first = ECS_PAIR_FIRST(id) local wc = ECS_PAIR(first, EcsWildcard) idr = component_index[wc] @@ -2375,7 +2418,7 @@ local function world_new() local edges = archetype_edges local edge = edges[src.id] - to = edge[id] :: Archetype + to = edge[id] :: archetype if not to then to = find_archetype_with(world, id, src) edge[id] = to @@ -2401,10 +2444,10 @@ local function world_new() end end - local function inner_world_add( - world: World, - entity: Entity, - id: Id + local function inner_world_add( + world: world, + entity: i53, + id: i53 ): () local entity_index = world.entity_index local record = inner_entity_index_try_get_unsafe(entity :: number) @@ -2417,11 +2460,11 @@ local function world_new() if src.columns_map[id] then return end - local to: Archetype - local idr: ComponentRecord + local to: archetype + local idr: componentrecord - if ECS_IS_PAIR(id::number) then - local first = ECS_PAIR_FIRST(id::number) + if ECS_IS_PAIR(id) then + local first = ECS_PAIR_FIRST(id) local wc = ECS_PAIR(first, EcsWildcard) idr = component_index[wc] @@ -2481,7 +2524,7 @@ local function world_new() local edges = archetype_edges local edge = edges[src.id] - to = edge[id] :: Archetype + to = edge[id] if not to then to = find_archetype_with(world, id, src) edge[id] = to @@ -2505,9 +2548,9 @@ local function world_new() end end - local function inner_world_get(world: World, entity: Entity, - a: Id, b: Id?, c: Id?, d: Id?, e: Id?): ...any - local record = inner_entity_index_try_get_unsafe(entity::number) + local function inner_world_get(world: world, entity: i53, + a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any + local record = inner_entity_index_try_get_unsafe(entity) if not record then return nil end @@ -2557,8 +2600,8 @@ local function world_new() (e == nil or error("args exceeded")) end - local function inner_world_target(world: World, entity: Entity, relation: Id, index: number?): Entity? - local record = inner_entity_index_try_get_unsafe(entity :: number) + local function inner_world_target(world: world, entity: i53, relation: i53, index: number?): i53? + local record = inner_entity_index_try_get_unsafe(entity) if not record then return nil end @@ -2568,7 +2611,7 @@ local function world_new() return nil end - local r = ECS_PAIR(relation::number, EcsWildcard) + local r = ECS_PAIR(relation, EcsWildcard) local idr = world.component_index[r] if not idr then @@ -2594,16 +2637,16 @@ local function world_new() end return entity_index_get_alive(world.entity_index, - ECS_PAIR_SECOND(nth :: number)) + ECS_PAIR_SECOND(nth)) end - local function inner_world_parent(world: World, entity: Entity): Entity? + local function inner_world_parent(world: world, entity: i53): i53? return inner_world_target(world, entity, EcsChildOf, 0) end - local function inner_world_entity(world: World, entity: Entity?): Entity + local function inner_world_entity(world: world, entity: i53?): i53 if entity then - local index = ECS_ID(entity :: number) + local index = ECS_ID(entity) local alive_count = entity_index.alive_count local r = eindex_sparse_array[index] if r then @@ -2613,7 +2656,7 @@ local function world_new() r.dense = index dense = index local e_swap = eindex_dense_array[dense] - local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record + local r_swap = inner_entity_index_try_get_any(e_swap) :: record r_swap.dense = dense alive_count += 1 @@ -2629,7 +2672,7 @@ local function world_new() if any ~= entity then if alive_count <= dense then local e_swap = eindex_dense_array[dense] - local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record + local r_swap = inner_entity_index_try_get_any(e_swap) :: record r_swap.dense = dense alive_count += 1 @@ -2644,7 +2687,7 @@ local function world_new() return entity else for i = entity_index.max_id + 1, index do - eindex_sparse_array[i] = { dense = i } :: Record + eindex_sparse_array[i] = { dense = i } :: record eindex_dense_array[i] = i end entity_index.max_id = index @@ -2671,8 +2714,8 @@ local function world_new() return entity_index_new_id(entity_index) end - local function inner_world_remove(world: World, entity: Entity, id: Id) - local record = inner_entity_index_try_get_unsafe(entity :: number) + local function inner_world_remove(world: world, entity: i53, id: i53) + local record = inner_entity_index_try_get_unsafe(entity) if not record then return end @@ -2695,11 +2738,11 @@ local function world_new() end end - local function inner_world_clear(world: World, entity: Entity) - local tgt = ECS_PAIR(EcsWildcard, entity::number) + local function inner_world_clear(world: world, entity: i53) + local tgt = ECS_PAIR(EcsWildcard, entity) local idr_t = component_index[tgt] local idr = component_index[entity] - local rel = ECS_PAIR(entity::number, EcsWildcard) + local rel = ECS_PAIR(entity, EcsWildcard) local idr_r = component_index[rel] if idr then @@ -2727,11 +2770,11 @@ local function world_new() local node = idr_t_archetype for _, id in idr_t_types do - if not ECS_IS_PAIR(id::number) then + if not ECS_IS_PAIR(id) then continue end local object = entity_index_get_alive( - entity_index, ECS_PAIR_SECOND(id::number)) + entity_index, ECS_PAIR_SECOND(id)) if object ~= entity then continue end @@ -2746,7 +2789,7 @@ local function world_new() for i = #entities, 1, -1 do local e = entities[i] - local r = inner_entity_index_try_get_unsafe(e::number) :: Record + local r = inner_entity_index_try_get_unsafe(e) :: record inner_entity_move(entity_index, e, r, node) end end @@ -2775,15 +2818,15 @@ local function world_new() end for i = #entities, 1, -1 do local e = entities[i] - local r = inner_entity_index_try_get_unsafe(e::number) :: Record + local r = inner_entity_index_try_get_unsafe(e) :: record inner_entity_move(entity_index, e, r, node) end end end end - local function inner_world_delete(world: World, entity: Entity) - local record = inner_entity_index_try_get_unsafe(entity::number) + local function inner_world_delete(world: world, entity: i53) + local record = inner_entity_index_try_get_unsafe(entity) if not record then return end @@ -2869,11 +2912,11 @@ local function world_new() local deleted = false for _, id in idr_t_types do - if not ECS_IS_PAIR(id::number) then + if not ECS_IS_PAIR(id) then continue end local object = entity_index_get_alive( - entity_index, ECS_PAIR_SECOND(id::number)) + entity_index, ECS_PAIR_SECOND(id)) if object ~= entity then continue end @@ -2901,7 +2944,7 @@ local function world_new() if not deleted then for i = #entities, 1, -1 do local e = entities[i] - local r = inner_entity_index_try_get_unsafe(e::number) :: Record + local r = inner_entity_index_try_get_unsafe(e) :: record inner_entity_move(entity_index, e, r, node) end end @@ -2946,9 +2989,10 @@ local function world_new() end end end + for i = #entities, 1, -1 do local e = entities[i] - local r = inner_entity_index_try_get_unsafe(e::number) :: Record + local r = inner_entity_index_try_get_unsafe(e) :: record inner_entity_move(entity_index, e, r, node) end end @@ -2964,25 +3008,25 @@ local function world_new() entity_index.alive_count = i_swap - 1 local e_swap = eindex_dense_array[i_swap] - local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record + local r_swap = inner_entity_index_try_get_any(e_swap) :: record r_swap.dense = dense record.archetype = nil :: any record.row = nil :: any record.dense = i_swap eindex_dense_array[dense] = e_swap - eindex_dense_array[i_swap] = ECS_GENERATION_INC(entity :: number) + eindex_dense_array[i_swap] = ECS_GENERATION_INC(entity) end - local function inner_world_exists(world: World, entity: Entity): boolean - return inner_entity_index_try_get_any(entity :: number) ~= nil + local function inner_world_exists(world: world, entity: i53): boolean + return inner_entity_index_try_get_any(entity) ~= nil end - local function inner_world_contains(world: World, entity: Entity): boolean - return entity_index_is_alive(world.entity_index, entity) + local function inner_world_contains(world: world, entity: i53): boolean + return entity_index_is_alive(entity_index, entity) end - local function inner_world_cleanup(world: World) + local function inner_world_cleanup(world: world) for _, archetype in archetypes do if #archetype.entities == 0 then archetype_destroy(world, archetype) @@ -3096,7 +3140,7 @@ end -- end -- -local function ecs_is_tag(world: World, entity: Entity): boolean +local function ecs_is_tag(world: world, entity: i53): boolean local idr = world.component_index[entity] if idr then return bit32.btest(idr.flags, ECS_ID_IS_TAG) @@ -3104,7 +3148,7 @@ local function ecs_is_tag(world: World, entity: Entity): boolean return not world_has_one_inline(world, entity, EcsComponent) end -local function ecs_entity_record(world: World, entity: Entity) +local function ecs_entity_record(world: world, entity: i53) return entity_index_try_get(world.entity_index, entity) end @@ -3127,43 +3171,43 @@ return { Delete = (EcsDelete :: any) :: Entity, Remove = (EcsRemove :: any) :: Entity, Name = (EcsName :: any) :: Entity, - Exclusive = EcsExclusive :: Entity, + Exclusive = (EcsExclusive :: any) :: Entity, ArchetypeCreate = EcsOnArchetypeCreate, ArchetypeDelete = EcsOnArchetypeDelete, Rest = (EcsRest :: any) :: Entity, - pair = (ECS_PAIR :: any) :: (first: Id

, second: Id) -> Pair, + pair = ECS_PAIR :: (first: Id

, second: Id) -> Pair, - IS_PAIR = (ECS_IS_PAIR :: any) :: (pair: Pair) -> boolean, + IS_PAIR = ECS_IS_PAIR :: (pair: Pair) -> boolean, ECS_PAIR_FIRST = ECS_PAIR_FIRST :: (pair: Pair) -> Id

, ECS_PAIR_SECOND = ECS_PAIR_SECOND :: (pair: Pair) -> Id, - pair_first = (ecs_pair_first :: any) :: (world: World, pair: Pair) -> Id

, - pair_second = (ecs_pair_second :: any) :: (world: World, pair: Pair) -> Id, + pair_first = ecs_pair_first :: (world: World, pair: Pair) -> Id

, + pair_second = ecs_pair_second :: (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, - component_record = id_record_get, - record = ecs_entity_record, + id_record_ensure = id_record_ensure :: (World, Id) -> ComponentRecord, + component_record = id_record_get :: (World, Id) -> ComponentRecord?, + record = ecs_entity_record :: (World, Entity) -> Record, - archetype_create = archetype_create, - archetype_ensure = archetype_ensure, + archetype_create = archetype_create :: (World, { Id }, string) -> Archetype, + archetype_ensure = archetype_ensure :: (World, { Id }) -> Archetype, find_insert = find_insert, - find_archetype_with = find_archetype_with, - find_archetype_without = find_archetype_without, + find_archetype_with = find_archetype_with :: (World, Id, Archetype) -> Archetype, + find_archetype_without = find_archetype_without :: (World, Id, Archetype) -> Archetype, create_edge_for_remove = create_edge_for_remove, - archetype_traverse_add = archetype_traverse_add, - archetype_traverse_remove = archetype_traverse_remove, - bulk_insert = ecs_bulk_insert, - bulk_remove = ecs_bulk_remove, + archetype_traverse_add = archetype_traverse_add :: (World, Id, Archetype) -> Archetype, + archetype_traverse_remove = archetype_traverse_remove :: (World, Id, Archetype) -> Archetype, + bulk_insert = ecs_bulk_insert :: (World, Entity, { Id }, { any }) -> (), + bulk_remove = ecs_bulk_remove :: (World, Entity, { Id }) -> (), - entity_move = entity_move, + entity_move = entity_move :: (EntityIndex, Entity, Record, Archetype) -> (), - entity_index_try_get = entity_index_try_get, - entity_index_try_get_fast = entity_index_try_get_fast, - entity_index_try_get_any = entity_index_try_get_any, - entity_index_is_alive = entity_index_is_alive, - entity_index_new_id = entity_index_new_id, + entity_index_try_get = entity_index_try_get :: (EntityIndex, Entity) -> Record?, + entity_index_try_get_fast = entity_index_try_get_fast :: (EntityIndex, Entity) -> Record?, + entity_index_try_get_any = entity_index_try_get_any :: (EntityIndex, Entity) -> Record, + entity_index_is_alive = entity_index_is_alive :: (EntityIndex, Entity) -> boolean, + entity_index_new_id = entity_index_new_id :: (EntityIndex) -> Entity, Query = Query, @@ -3174,16 +3218,16 @@ return { query_archetypes = query_archetypes, query_match = query_match, - find_observers = find_observers, + find_observers = find_observers :: (World, Id, Id) -> { Observer }, -- Inwards facing API for testing - ECS_ID = ECS_ENTITY_T_LO, - ECS_GENERATION_INC = ECS_GENERATION_INC, - ECS_GENERATION = ECS_GENERATION, + ECS_ID = ECS_ENTITY_T_LO :: (Entity) -> number, + ECS_GENERATION_INC = ECS_GENERATION_INC :: (Entity) -> Entity, + ECS_GENERATION = ECS_GENERATION :: (Entity) -> number, ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD, ECS_ID_IS_EXCLUSIVE = ECS_ID_IS_EXCLUSIVE, ECS_ID_DELETE = ECS_ID_DELETE, ECS_META_RESET = ECS_META_RESET, - ECS_COMBINE = ECS_COMBINE, + ECS_COMBINE = ECS_COMBINE :: (number, number) -> Entity, ECS_ENTITY_MASK = ECS_ENTITY_MASK, } diff --git a/test/tests.luau b/test/tests.luau index 56be586..ff79788 100755 --- a/test/tests.luau +++ b/test/tests.luau @@ -175,9 +175,9 @@ TEST("repro", function() world:delete(e1) local e1v1 = world:entity() - CHECK(ECS_ID(e1v1) == e1) + CHECK(ECS_ID(e1v1) == e1::any) local e2v1 = world:entity() - CHECK(ECS_ID(e2v1) == e2) + CHECK(ECS_ID(e2v1) == e2::any) world:set(e2v1, data, 456) CHECK(world:contains(e1v1)) @@ -341,7 +341,7 @@ TEST("world:add()", function() local B = world:component() local C = world:component() - local e_ptr = jecs.Rest :: number + 1 + local e_ptr: jecs.Entity = (jecs.Rest :: any) + 1 world:add(A, jecs.Exclusive) local on_remove_call = false @@ -1127,7 +1127,7 @@ TEST("world:range()", function() client:range(1000, 5000) local e1 = server:entity() - CHECK((e1::number)< 1000) + CHECK((e1::any)< 1000) server:delete(e1) local e2 = client:entity(e1) CHECK(e2 == e1) @@ -1137,12 +1137,12 @@ TEST("world:range()", function() client:delete(e2) local e3 = client:entity() - CHECK(ECS_ID(e3::number) == 1000) + CHECK(ECS_ID(e3) == 1000) local e1v1 = server:entity() local e4 = client:entity(e1v1) - CHECK(ECS_ID(e4::number) == e1) - CHECK(ECS_GENERATION(e4::number) == 1) + CHECK(ECS_ID(e4) == e1::any) + CHECK(ECS_GENERATION(e4) == 1) CHECK(not client:contains(e2)) CHECK(client:contains(e4)) end @@ -1151,15 +1151,16 @@ TEST("world:range()", function() local world = jecs.world() world:range(400, 1000) - local id = world:entity() :: number - local e = world:entity(id + 5) - CHECK(e == id + 5) + local id = world:entity() + local e = world:entity(id::any + 5) + CHECK(e::any == (id::any) + 5) + CHECK(world:contains(e)) local e2 = world:entity(399) CHECK(world:contains(e2)) world:delete(e2) CHECK(not world:contains(e2)) - local e2v1 = world:entity(399) :: number + local e2v1 = world:entity(399) CHECK(world:contains(e2v1)) CHECK(ECS_ID(e2v1) == 399) CHECK(ECS_GENERATION(e2v1) == 0) @@ -1172,13 +1173,13 @@ TEST("world:range()", function() CHECK(world:contains(e2)) world:delete(e2) CHECK(not world:contains(e2)) - local e2v1 = world:entity(405) :: number + local e2v1 = world:entity(405) CHECK(world:contains(e2v1)) CHECK(ECS_ID(e2v1) == 405) CHECK(ECS_GENERATION(e2v1) == 0) world:delete(e2v1) - local e2v2 = world:entity(e2v1) :: number + local e2v2 = world:entity(e2v1) CHECK(ECS_ID(e2v2) == 405) CHECK(ECS_GENERATION(e2v2) == 0) end @@ -1187,9 +1188,10 @@ end) TEST("world:entity()", function() do CASE "desired id" local world = jecs.world() - local id = world:entity() :: number - local e = world:entity(id + 5) - CHECK(e == id + 5) + local id = world:entity() + local offset: jecs.Entity = (id ::any) + 5 + local e = world:entity(offset) + CHECK(e == offset) CHECK(world:contains(e)) local e2 = world:entity(399) CHECK(world:contains(e2)) @@ -1207,7 +1209,7 @@ TEST("world:entity()", function() end do CASE "generations" local world = jecs.world() - local e = world:entity() :: any + local e = world:entity() CHECK(ECS_ID(e) == 1 + jecs.Rest :: any) CHECK(ECS_GENERATION(e) == 0) -- 0 e = ECS_GENERATION_INC(e) @@ -1257,11 +1259,11 @@ TEST("world:entity()", function() local e = world:entity() world:delete(e) end - local e = world:entity() :: number + local e = world:entity() CHECK(ECS_ID(e) == pin) CHECK(ECS_GENERATION(e) == 2^16-1) world:delete(e) - e = world:entity() :: number + e = world:entity() CHECK(ECS_ID(e) == pin) CHECK(ECS_GENERATION(e) == 0) end @@ -1780,7 +1782,7 @@ TEST("world:query()", function() world:add(e2, B) local count = 0 - for id in world:query(A) :: any do + for id in world:query(A) do world:clear(id) count += 1 end @@ -2248,14 +2250,14 @@ TEST("change tracking", function() world:set(testEntity, component, 10) local i = 0 - for entity, number in q1 :: any do + for entity, number in q1 do i += 1 world:add(testEntity, tag) end CHECK(i == 1) - for e, n in q1 :: any do + for e, n in q1 do world:set(e, pair(previous, component), n) end end