diff --git a/rokit.toml b/rokit.toml index 907eb03..5f95fc2 100755 --- a/rokit.toml +++ b/rokit.toml @@ -1,4 +1,4 @@ [tools] wally = "upliftgames/wally@0.3.2" -rojo = "rojo-rbx/rojo@7.4.4" -luau = "luau-lang/luau@0.701" +rojo = "rojo-rbx/rojo@7.7.0-rc.1" +luau = "luau-lang/luau@0.703.0" diff --git a/src/jecs.luau b/src/jecs.luau index fef3e92..6d0ae17 100755 --- a/src/jecs.luau +++ b/src/jecs.luau @@ -378,10 +378,11 @@ local EcsRest = HI_COMPONENT_ID + 15 local NULL_ARRAY = table.freeze({}) :: Column local NULL = newproxy(false) -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 ECS_INTERNAL_ERROR_INVALID_ENTITIES = [[ + You tried passing a pair that has invalid entities that are either unalive + or non-existing entities. You can enable DEBUG mode by passing in true to + jecs.world(true) and try doing it again in order to get better assertions so + that you can understand what went wrong. ]] local function ecs_assert(condition, msg: string?) @@ -1312,354 +1313,503 @@ end local function query_iter_init(query: QueryInner): () -> (number, ...any) local world_query_iter_next - local compatible_archetypes = query_archetypes(query::any) :: { Archetype } - local lastArchetype = 1 - local archetype = compatible_archetypes[1] - if not archetype then + local compatible_archetypes_u = query_archetypes(query::any) :: { Archetype } + local last_archetype_u = 1 + local archetype_u = compatible_archetypes_u[1] + if not archetype_u then return NOOP :: () -> (number, ...any) end - local entities = archetype.entities - local i = #entities - local columns_map = archetype.columns_map + local entities_u = archetype_u.entities + local i_u = #entities_u + local columns_map_u = archetype_u.columns_map - local ids = query.ids - local A, B, C, D, E, F, G, H, I = unpack(ids :: { Component }) - local a: Column, b: Column, c: Column, d: Column - local e: Column, f: Column, g: Column, h: Column + local ids_u = query.ids + local id0, id1, id2, id3, id4, id5, id6, id7, id8 = unpack(ids_u :: { Component }) + local col0_u: Column, col1_u: Column, col2_u: Column, col3_u: Column + local col4_u: Column, col5_u: Column, col6_u: Column, col7_u: Column - if not A then + if not id0 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] + e = entities[i_u] + entities_u = entities end - i -= 1 - return entity + i_u -= 1 + return e end query.next = world_query_iter_next return world_query_iter_next - elseif not B then - a = columns_map[A] - elseif not C then - a = columns_map[A] - b = columns_map[B] - elseif not D then - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - elseif not E then - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - elseif not F then - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - elseif not G then - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - elseif not H then - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] + elseif not id1 then + col0_u = columns_map_u[id0] + elseif not id2 then + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + elseif not id3 then + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + col2_u = columns_map_u[id2] + elseif not id4 then + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + col2_u = columns_map_u[id2] + col3_u = columns_map_u[id3] + elseif not id5 then + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + col2_u = columns_map_u[id2] + col3_u = columns_map_u[id3] + col4_u = columns_map_u[id4] + elseif not id6 then + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + col2_u = columns_map_u[id2] + col3_u = columns_map_u[id3] + col4_u = columns_map_u[id4] + col5_u = columns_map_u[id5] + elseif not id7 then + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + col2_u = columns_map_u[id2] + col3_u = columns_map_u[id3] + col4_u = columns_map_u[id4] + col5_u = columns_map_u[id5] + col6_u = columns_map_u[id6] else - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] - h = columns_map[H] + col0_u = columns_map_u[id0] + col1_u = columns_map_u[id1] + col2_u = columns_map_u[id2] + col3_u = columns_map_u[id3] + col4_u = columns_map_u[id4] + col5_u = columns_map_u[id5] + col6_u = columns_map_u[id6] + col7_u = columns_map_u[id7] end - if not B then + if not id1 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col0_u = col0 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row] + return e, col0[row] end - elseif not C then + elseif not id2 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 + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col0_u = col0 + col1_u = col1 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row] + return e, captured_a[row], captured_b[row] end - elseif not D then + elseif not id3 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col0_u = col0 + col1_u = col1 + col2_u = col2 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row] + return e, col0[row], col1[row], col2[row] end - elseif not E then + elseif not id4 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row] + return e, col0[row], col1[row], col2[row], col3[row] end - elseif not F then + elseif not id5 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row] end - elseif not G then + elseif not id6 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row], f[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row] end - elseif not H then + elseif not id7 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + local col6 = col6_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col6 = columns_map[id6] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 + col6_u = col6 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row], captured_g[row] end - elseif not I then + elseif not id8 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + local col6 = col6_u + local col7 = col7_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] - h = columns_map[H] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col6 = columns_map[id6] + col7 = columns_map[id7] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 + col6_u = col6 + col7_u = col7 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row], captured_g[row], captured_h[row] end else local output = {} local ids_len = #ids function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + local col6 = col6_u + local col7 = col7_u + local captured_ids = ids + local captured_columns_map = columns_map + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] + e = entities[i_u] + entities_u = entities columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] - h = columns_map[H] + columns_map = captured_columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col6 = columns_map[id6] + col7 = columns_map[id7] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 + col6_u = col6 + col7_u = col7 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 for i = 9, ids_len do 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) + return e, col0[row], col1[row], col2[row], col3[row], col4[row], col5[row], col6[row], col7[row], unpack(output) end end @@ -1677,10 +1827,10 @@ end local function query_cached(query: QueryInner) local ids = query.ids - local lastArchetype = 1 + local last_archetype = 1 local A, B, C, D, E, F, G, H, I = unpack(ids :: { Component }) - if not A then + if not id0 then A = query.filter_with[1] end local a: Column, b: Column, c: Column, d: Column @@ -1753,43 +1903,43 @@ local function query_cached(query: QueryInner) table.insert(query_cache_on_delete, observer_for_delete) local function cached_query_iter() - lastArchetype = 1 - archetype = compatible_archetypes[lastArchetype] + last_archetype = 1 + archetype = compatible_archetypes[last_archetype] if not archetype then return NOOP end entities = archetype.entities i = #entities columns_map = archetype.columns_map - if not A then - elseif not B then + if not id0 then + elseif not id1 then a = columns_map[A] - elseif not C then + elseif not id2 then a = columns_map[A] b = columns_map[B] - elseif not D then + elseif not id3 then a = columns_map[A] b = columns_map[B] c = columns_map[C] - elseif not E then + elseif not id4 then a = columns_map[A] b = columns_map[B] c = columns_map[C] d = columns_map[D] - elseif not F then + elseif not id5 then a = columns_map[A] b = columns_map[B] c = columns_map[C] d = columns_map[D] e = columns_map[E] - elseif not G then + elseif not id6 then a = columns_map[A] b = columns_map[B] c = columns_map[C] d = columns_map[D] e = columns_map[E] f = columns_map[F] - elseif not H then + elseif not id7 then a = columns_map[A] b = columns_map[B] c = columns_map[C] @@ -1811,291 +1961,440 @@ local function query_cached(query: QueryInner) return world_query_iter_next end - if not A then + if not id0 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] + e = entities[i_u] + captured_entities = entities end - i -= 1 - return entity + i_u -= 1 + return e end - elseif not B then + elseif not id1 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col0_u = col0 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row] + return e, col0[row] end - elseif not C then + elseif not id2 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col0_u = col0 + col1_u = col1 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row] + return e, captured_a[row], captured_b[row] end - elseif not D then + elseif not id3 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col0_u = col0 + col1_u = col1 + col2_u = col2 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row] + return e, captured_a[row], captured_b[row], captured_c[row] end - elseif not E then + elseif not id4 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + captured_a = captured_columns_map[A] + captured_b = captured_columns_map[B] + captured_c = captured_columns_map[C] + captured_d = captured_columns_map[D] + a = captured_a + b = captured_b + c = captured_c + d = captured_d end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row] end - elseif not F then + elseif not id5 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row] end - elseif not G then + elseif not id6 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row], f[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row] end - elseif not H then + elseif not id7 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + local col6 = col6_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col6 = columns_map[id6] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 + col6_u = col6 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row], captured_g[row] end - elseif not I then + elseif not id8 then function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + local col6 = col6_u + local col7 = col7_u + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] - columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] - h = columns_map[H] + e = entities[i_u] + entities_u = entities + local columns_map = archetype.columns_map + columns_map_u = columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col6 = columns_map[id6] + col7 = columns_map[id7] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 + col6_u = col6 + col7_u = col7 end - local row = i - i -= 1 + local row = i_u + i_u -= 1 - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row], captured_g[row], captured_h[row] end else local output = {} local ids_len = #ids function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then + local entities = entities_u + local e = entities[i_u] + local col0 = col0_u + local col1 = col1_u + local col2 = col2_u + local col3 = col3_u + local col4 = col4_u + local col5 = col5_u + local col6 = col6_u + local col7 = col7_u + local captured_ids = ids + local captured_columns_map = columns_map + + while e == nil do + last_archetype_u += 1 + local compatible_archetypes = compatible_archetypes_u + local archetype = compatible_archetypes[last_archetype_u] + archetype_u = archetype + + if not archetype then return nil end - entities = archetype.entities - i = #entities - if i == 0 then + i_u = #entities + if i_u == 0 then continue end - entity = entities[i] + e = entities[i_u] + entities_u = entities columns_map = archetype.columns_map - a = columns_map[A] - b = columns_map[B] - c = columns_map[C] - d = columns_map[D] - e = columns_map[E] - f = columns_map[F] - g = columns_map[G] - h = columns_map[H] + columns_map = captured_columns_map + col0 = columns_map[id0] + col1 = columns_map[id1] + col2 = columns_map[id2] + col3 = columns_map[id3] + col4 = columns_map[id4] + col5 = columns_map[id5] + col6 = columns_map[id6] + col7 = columns_map[id7] + col0_u = col0 + col1_u = col1 + col2_u = col2 + col3_u = col3 + col4_u = col4 + col5_u = col5 + col6_u = col6 + col7_u = col7 end local row = i i -= 1 for i = 9, ids_len do - output[i - 8] = columns_map[ids[i]::any][row] + output[i - 8] = captured_columns_map[captured_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) + return e, captured_a[row], captured_b[row], captured_c[row], captured_d[row], captured_e[row], captured_f[row], captured_g[row], captured_h[row], unpack(output) end end @@ -2358,7 +2657,10 @@ local function ecs_bulk_remove(world: world, entity: i53, ids: { i53 }) end end -local function world_new() +type Context = { + debug: boolean? +} +local function world_new(DEBUG: boolean?) local eindex_dense_array = {} :: { i53 } local eindex_sparse_array = {} :: { record } @@ -2524,6 +2826,8 @@ local function world_new() -- end local function entity_index_try_get_unsafe(entity: i53): record? + local eindex_sparse_array = eindex_sparse_array + local eindex_dense_array = eindex_dense_array local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)] if r then local r_dense = r.dense @@ -2560,6 +2864,7 @@ local function world_new() end local from: archetype = record.archetype + local ROOT_ARCHETYPE = ROOT_ARCHETYPE local src = from or ROOT_ARCHETYPE local column = src.columns_map[id] if column then @@ -2666,6 +2971,7 @@ local function world_new() end local from = record.archetype + local ROOT_ARCHETYPE = ROOT_ARCHETYPE local src = from or ROOT_ARCHETYPE if src.columns_map[id] then return @@ -3387,6 +3693,58 @@ local function world_new() world.children = world_children world.range = world_range + if DEBUG then + -- NOTE(marcus): Make it easy to grep the debug functions and + -- being able to read the specification, without having to look + -- at the implementation to understand invariants. + local function world_remove_checked(world: world, entity: i53, id: i53) + local entity_id = ECS_ID(entity) + local wrong_entity = eindex_sparse_array[entity_id] ~= entity_id + if wrong_entity then + error([[ + This Entity handle has an outdated generation. You are + probably holding onto an entity that you got from outside the ECS + ]], 2) + end + + if ECS_ID_IS_WILDCARD(id) then + error([[ + You tried to pass in a wildcard pair. This is strictly + forbidden. You probably want to iterate the targets and + remove them one by one. You can also populate a list of + targets to remove and use jecs.bulk_remove. + ]], 2) + end + + world_remove(world, entity, id) + end + local function world_add_checked(world: world, entity: i53, id: i53) + local entity_id = ECS_ID(entity) + local wrong_entity = eindex_sparse_array[entity_id] ~= entity_id + if wrong_entity then + error([[ + This Entity handle has an outdated generation. You are + probably holding onto an entity that you got from outside the ECS + ]], 2) + end + + world_add(world, entity, id) + end + local function world_set_checked(world: world, entity: i53, id: i53, value: any) + local entity_id = ECS_ID(entity) + local wrong_entity = eindex_sparse_array[entity_id] ~= entity_id + if wrong_entity then + error([[ + This Entity handle has an outdated generation. You are + probably holding onto an entity that you got from outside the ECS + ]], 2) + end + world_set(world, entity, id, value) + end + world.remove = world_remove_unchecked + world.add = world_add_checked + end + for i = 1, EcsRest do entity_index_new_id(entity_index) end diff --git a/test/benches/default.project.json b/test/benches/default.project.json index 3f11dca..b2519ce 100755 --- a/test/benches/default.project.json +++ b/test/benches/default.project.json @@ -12,16 +12,16 @@ "ReplicatedStorage": { "$className": "ReplicatedStorage", "Lib": { - "$path": "../src/jecs.luau" + "$path": "../../src/jecs.luau" }, "benches": { - "$path": "benches" + "$path": "visual" }, "mirror": { - "$path": "mirror.luau" + "$path": "../../src/mirror.luau" }, "DevPackages": { - "$path": "benches/visual/DevPackages" + "$path": "visual/DevPackages" } } } diff --git a/test/benches/query.luau b/test/benches/query.luau index 32560a9..1a74bdb 100755 --- a/test/benches/query.luau +++ b/test/benches/query.luau @@ -27,48 +27,11 @@ do G: jecs.Id, H: jecs.Id ) - BENCH("1 component", function() - for _ in world:query(A) do - end - end) - - BENCH("2 component", function() - for _ in world:query(B, A) do - end - end) - BENCH("4 component", function() for _ in world:query(D, C, B, A) do end end) - BENCH("8 component", function() - for _ in world:query(H, G, F, E, D, C, B, A) do - end - end) - - local e = world:entity() - world:set(e, A, true) - world:set(e, B, true) - world:set(e, C, true) - world:set(e, D, true) - world:set(e, E, true) - world:set(e, F, true) - world:set(e, G, true) - world:set(e, H, true) - - BENCH("Update Data", function() - for _ = 1, 100 do - world:set(e, A, false) - world:set(e, B, false) - world:set(e, C, false) - world:set(e, D, false) - world:set(e, E, false) - world:set(e, F, false) - world:set(e, G, false) - world:set(e, H, false) - end - end) end local D1 = ecs:component() @@ -86,8 +49,9 @@ do local added = 0 local archetypes = {} - for i = 1, 2 ^ 16 - 2 do + for i = 1, 2 ^ 12 - 2 do local entity = ecs:entity() + ecs:add(entity, entity) local combination = "" @@ -153,48 +117,10 @@ do G: jecs.Id, H: jecs.Id ) - BENCH("1 component", function() - for _ in world:query(A) do - end - end) - - BENCH("2 component", function() - for _ in world:query(B, A) do - end - end) - BENCH("4 component", function() for _ in world:query(D, C, B, A) do end end) - - BENCH("8 component", function() - for _ in world:query(H, G, F, E, D, C, B, A) do - end - end) - - local e = world:entity() - world:set(e, A, true) - world:set(e, B, true) - world:set(e, C, true) - world:set(e, D, true) - world:set(e, E, true) - world:set(e, F, true) - world:set(e, G, true) - world:set(e, H, true) - - BENCH("Update Data", function() - for _ = 1, 100 do - world:set(e, A, false) - world:set(e, B, false) - world:set(e, C, false) - world:set(e, D, false) - world:set(e, E, false) - world:set(e, F, false) - world:set(e, G, false) - world:set(e, H, false) - end - end) end local D1 = ecs:component() @@ -207,13 +133,14 @@ do local D8 = ecs:component() local function flip() - return math.random() >= 0.15 + return math.random() >= 0.5 end local added = 0 local archetypes = {} - for i = 1, 2 ^ 16 - 2 do + for i = 1, 2 ^ 12 - 2 do local entity = ecs:entity() + ecs:add(entity, entity) local combination = "" @@ -246,11 +173,9 @@ do ecs:set(entity, D8, { value = true }) end - if #combination == 7 then - added += 1 + if flip() then ecs:set(entity, D1, { value = true }) end - archetypes[combination] = true end local a = 0 diff --git a/test/benches/visual/query.bench.luau b/test/benches/visual/query.bench.luau index f1df0da..66b54ac 100755 --- a/test/benches/visual/query.bench.luau +++ b/test/benches/visual/query.bench.luau @@ -50,85 +50,54 @@ local E8 = mcs:component() local registry2 = ecr.registry() local function flip() - return math.random() >= 0.25 + return math.random() >= 0.5 end -local N = 2 ^ 16 - 2 +local N = 2 ^ 12- 2 local archetypes = {} local hm = 0 for i = 1, N do - local id = registry2.create() - local combination = "" - local n = newWorld:spawn() local entity = ecs:entity() local m = mcs:entity() + if flip() then + ecs:add(entity, entity) + mcs:add(m, m) + end if flip() then - registry2:set(id, B1, { value = true }) - ecs:set(entity, D1, { value = true }) - newWorld:insert(n, A1({ value = true })) mcs:set(m, E1, { value = 2 }) + ecs:set(entity, D1, {value = true}) end if flip() then - combination ..= "B" - registry2:set(id, B2, { value = true }) ecs:set(entity, D2, { value = true }) mcs:set(m, E2, { value = 2 }) - newWorld:insert(n, A2({ value = true })) end if flip() then - combination ..= "C" - registry2:set(id, B3, { value = true }) ecs:set(entity, D3, { value = true }) mcs:set(m, E3, { value = 2 }) - newWorld:insert(n, A3({ value = true })) end if flip() then - combination ..= "D" - registry2:set(id, B4, { value = true }) ecs:set(entity, D4, { value = true }) mcs:set(m, E4, { value = 2 }) - - newWorld:insert(n, A4({ value = true })) end if flip() then - combination ..= "E" - registry2:set(id, B5, { value = true }) ecs:set(entity, D5, { value = true }) mcs:set(m, E5, { value = 2 }) - - newWorld:insert(n, A5({ value = true })) end if flip() then - combination ..= "F" - registry2:set(id, B6, { value = true }) ecs:set(entity, D6, { value = true }) mcs:set(m, E6, { value = 2 }) - newWorld:insert(n, A6({ value = true })) end if flip() then - combination ..= "G" - registry2:set(id, B7, { value = true }) ecs:set(entity, D7, { value = true }) mcs:set(m, E7, { value = 2 }) - newWorld:insert(n, A7({ value = true })) end if flip() then - combination ..= "H" - registry2:set(id, B8, { value = true }) - newWorld:insert(n, A8({ value = true })) ecs:set(entity, D8, { value = true }) mcs:set(m, E8, { value = 2 }) end - if combination:find("BCDF") then - if not archetypes[combination] then - print(combination) - end - hm += 1 - end - archetypes[combination] = true end print("TEST", hm) @@ -140,30 +109,38 @@ end print(count) +local mq = mcs:query(E1, E2, E3, E4) +local jq = ecs:query(D1, D2, D3, D4) + return { ParameterGenerator = function() return end, Functions = { - Matter = function() - for entityId, firstComponent in newWorld:query(A2, A4, A6, A8) do - end - end, - - ECR = function() - for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do - end - end, - - -- Mirror = function() - -- for entityId, firstComponent in mcs:query(E2, E4, E6, E8) do + -- Matter = function() + -- for entityId, firstComponent in newWorld:query(A2, A4, A6, A8) do -- end -- end, - Jecs = function() - for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do + -- ECR = function() + -- for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do + -- end + -- end, + + Mirror = function() + for i = 1, 10 do + for entityId, firstComponent in mq:iter() do + end end end, + + Jecs = function() + for i = 1, 10 do + for entityId, firstComponent in jq:iter() do + end + end + + end, }, } diff --git a/test/tests.luau b/test/tests.luau index 5133342..6e233f4 100755 --- a/test/tests.luau +++ b/test/tests.luau @@ -24,81 +24,7 @@ type Id = jecs.Id local entity_visualiser = require("@modules/entity_visualiser") local dwi = entity_visualiser.stringify -TEST("optimize idr_r removal", function() - - local pair = jecs.pair - local world = jecs.world() - local rel = world:component() - local A = world:component() - local B = world:component() - - local t1 = world:entity() - local t2 = world:entity() - - local entities = {} :: { jecs.Entity } - - for i = 1, 10 do - - local e1 = world:entity() - local e2 = world:entity() - - world:set(e1, A, true) - world:set(e2, A, true) - world:add(e1, pair(B, t1)) - world:add(e1, pair(B, t2)) - world:add(e2, pair(B, t1)) - world:add(e2, pair(B, t2)) - - table.insert(entities, e1) - table.insert(entities, e2) - end - - local e1 = world:entity() - local e2 = world:entity() - - table.insert(entities, e1) - table.insert(entities, e2) - - world:set(e1, A, true) - world:set(e2, A, true) - world:add(e1, pair(B, t1)) - world:add(e1, pair(B, t2)) - world:add(e2, pair(B, t1)) - world:add(e2, pair(B, t2)) - - BENCH("delete B", function() - world:delete(B) - end) - - for _, e in entities do - CHECK(world:has(e, A)) - CHECK(not world:target(e, B)) - CHECK(not world:target(e, B)) - end - -end) -TEST("deleting t1's archetype before invoking its onremove hooks", function() - local pair = jecs.pair - local world = jecs.world() - local rel = world:component() - - local t1 = world:entity() - local t2 = world:entity() - - --[[ - weirdly enough if i do this (only when adding childof relation after adding (rel, t2) to t1) it does not error. Probably a red herring - - world:add(t2, pair(rel, t1)) - world:add(t1, pair(rel, t2)) - world:add(t2, pair(jecs.ChildOf, t1)) - --]] - - -- this causes world:delete to error - world:add(t2, pair(jecs.ChildOf, t1)) - world:add(t1, pair(rel, t2)) - - world:delete(t1) -end) +FOCUS() TEST("reproduce idr_t nil archetype bug", function() local world = jecs.world() @@ -124,43 +50,56 @@ TEST("reproduce idr_t nil archetype bug", function() local dst = src and jecs.archetype_traverse_remove(world, id, src) end) - local batches = 10 - local batchSize = 20 + local batchSize = 200 local trackedEntities: { [number]: { parentId: number? } } = {} - for batch = 1, batches do - for i = 1, batchSize do - local root = world:entity() - world:add(root, jecs.pair(jecs.ChildOf, char)) + for i = 1, batchSize do - -- Removing animator from trackEntity1 causes it to stop happening - local trackEntity1 = world:entity() - world:set(trackEntity1, cts.Animator, 0) - world:add(trackEntity1, jecs.pair(jecs.ChildOf, root)) - trackedEntities[trackEntity1] = { parentId = root } + local root = world:entity() + -- world:add(root, jecs.pair(jecs.ChildOf, char)) - -- Removing animator from trackEntity2 causes it to happen less frequently - local trackEntity2 = world:entity() - world:set(trackEntity2, cts.Animator, 0) - world:add(trackEntity2, jecs.pair(jecs.ChildOf, root)) - trackedEntities[trackEntity2] = { parentId = root } + -- Removing animator from trackEntity1 causes it to stop happening + local trackEntity1 = world:entity() + world:set(trackEntity1, cts.Animator, 0) + world:add(trackEntity1, jecs.pair(jecs.ChildOf, root)) + world:set(trackEntity1, jecs.Name, "trackEntity1v"..i) + trackedEntities[trackEntity1] = { parentId = root } - -- Removing this, but keeping Animator on the other 2 causes it to stop happening - world:set(trackEntity1, cts.VelocitizeAnimationWeight, 0) + -- Removing animator from trackEntity2 causes it to happen less frequently + local trackEntity2 = world:entity() + world:set(trackEntity2, cts.Animator, 0) + world:add(trackEntity2, jecs.pair(jecs.ChildOf, root)) + world:set(trackEntity2, jecs.Name, "trackEntity2v"..i) + trackedEntities[trackEntity2] = { parentId = root } - for entityId, info in trackedEntities do - if world:contains(entityId) and not world:parent(entityId :: any) then - print(`bugged entity found: {entityId}`) - print(`original parent: {info.parentId}`) - print(`batch = {batch}, i = {i}`) - print("==========================================") - trackedEntities[entityId] = nil - world:delete(entityId) - end - end - end - end + -- Removing this, but keeping Animator on the other 2 causes it to stop happening + world:set(trackEntity1, cts.VelocitizeAnimationWeight, 0) + + local q = world:query(jecs.pair(jecs.ChildOf, jecs.w)):cached() + + + print("---- delete start root") + print("--- root", world:contains(root), root, jecs.ECS_ID(root), jecs.ECS_GENERATION(root)) + world:delete(root) + print("---- delete end root") + + for entity in q do + local parent = world:parent(entity) :: jecs.Entity + print("--- root", world:contains(root), jecs.ECS_ID(root), jecs.ECS_GENERATION(root)) + print(world:get(entity, jecs.Name), jecs.ECS_ID(parent), jecs.ECS_GENERATION(parent), "root ->", root) + CHECK(world:parent(entity) == nil) + end + for entityId, info in trackedEntities do + if world:contains(entityId) and not world:parent(entityId :: any) then + print(`bugged entity found: {entityId}`) + print(`original parent: {info.parentId}`) + print(`batch = {batch}, i = {i}`) + print("==========================================") + trackedEntities[entityId] = nil + end + end + end end) TEST("Ensure archetype edges get cleaned", function()