/** * @file table_graph.c * @brief Data structure to speed up table transitions. * * The table graph is used to speed up finding tables in add/remove operations. * For example, if component C is added to an entity in table [A, B], the entity * must be moved to table [A, B, C]. The graph speeds this process up with an * edge for component C that connects [A, B] to [A, B, C]. */ #include "../private_api.h" /* Id sequence (type) utilities */ static uint64_t flecs_type_hash(const void *ptr) { const ecs_type_t *type = ptr; ecs_id_t *ids = type->array; int32_t count = type->count; return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); } static int flecs_type_compare(const void *ptr_1, const void *ptr_2) { const ecs_type_t *type_1 = ptr_1; const ecs_type_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } const ecs_id_t *ids_1 = type_1->array; const ecs_id_t *ids_2 = type_2->array; int result = 0; int32_t i; for (i = 0; !result && (i < count_1); i ++) { ecs_id_t id_1 = ids_1[i]; ecs_id_t id_2 = ids_2[i]; result = (id_1 > id_2) - (id_1 < id_2); } return result; } void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm) { flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, flecs_type_hash, flecs_type_compare, &world->allocator); } /* Find location where to insert id into type */ static int flecs_type_find_insert( const ecs_type_t *type, int32_t offset, ecs_id_t to_add) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = offset; i < count; i ++) { ecs_id_t id = array[i]; if (id == to_add) { return -1; } if (id > to_add) { return i; } } return i; } /* Find location of id in type */ static int flecs_type_find( const ecs_type_t *type, ecs_id_t id) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t cur = array[i]; if (ecs_id_match(cur, id)) { return i; } if (cur > id) { return -1; } } return -1; } /* Count number of matching ids */ static int flecs_type_count_matches( const ecs_type_t *type, ecs_id_t wildcard, int32_t offset) { ecs_id_t *array = type->array; int32_t i = offset, count = type->count; for (; i < count; i ++) { ecs_id_t cur = array[i]; if (!ecs_id_match(cur, wildcard)) { break; } } return i - offset; } /* Create type from source type with id */ static int flecs_type_new_with( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t with) { ecs_id_t *src_array = src->array; int32_t at = flecs_type_find_insert(src, 0, with); if (at == -1) { return -1; } int32_t dst_count = src->count + 1; ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->count = dst_count; dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = src->count - at; if (remain) { ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } dst_array[at] = with; return 0; } /* Create type from source type without ids matching wildcard */ static int flecs_type_new_filtered( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t wildcard, int32_t at) { *dst = flecs_type_copy(world, src); ecs_id_t *dst_array = dst->array; ecs_id_t *src_array = src->array; if (at) { ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t i = at + 1, w = at, count = src->count; for (; i < count; i ++) { ecs_id_t id = src_array[i]; if (!ecs_id_match(id, wildcard)) { dst_array[w] = id; w ++; } } dst->count = w; if (w != count) { dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); } return 0; } /* Create type from source type without id */ static int flecs_type_new_without( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t without) { ecs_id_t *src_array = src->array; int32_t count = 1, at = flecs_type_find(src, without); if (at == -1) { return -1; } int32_t src_count = src->count; if (src_count == 1) { dst->array = NULL; dst->count = 0; return 0; } if (ecs_id_is_wildcard(without)) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = ECS_PAIR_FIRST(without); ecs_entity_t o = ECS_PAIR_SECOND(without); if (r == EcsWildcard && o != EcsWildcard) { return flecs_type_new_filtered(world, dst, src, without, at); } } count += flecs_type_count_matches(src, without, at + 1); } int32_t dst_count = src_count - count; dst->count = dst_count; if (!dst_count) { dst->array = NULL; return 0; } ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = dst_count - at; if (remain) { ecs_os_memcpy_n( &dst_array[at], &src_array[at + count], ecs_id_t, remain); } return 0; } /* Copy type */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src) { int32_t src_count = src->count; if (!src_count) { return (ecs_type_t){ 0 }; } ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); return (ecs_type_t) { .array = ids, .count = src_count }; } /* Free type */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type) { int32_t count = type->count; if (count) { flecs_wfree_n(world, ecs_id_t, type->count, type->array); } } /* Add to type */ static void flecs_type_add( ecs_world_t *world, ecs_type_t *type, ecs_id_t add) { ecs_type_t new_type; int res = flecs_type_new_with(world, &new_type, type, add); if (res != -1) { flecs_type_free(world, type); type->array = new_type.array; type->count = new_type.count; } } /* Graph edge utilities */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &builder->added, ecs_id_t, 256); ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); } void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &builder->added, ecs_id_t); ecs_vec_fini_t(a, &builder->removed, ecs_id_t); } void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder) { ecs_vec_clear(&builder->added); ecs_vec_clear(&builder->removed); } static void flecs_table_diff_build_type( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *type, int32_t offset) { int32_t count = vec->count - offset; ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); if (count) { type->array = flecs_wdup_n(world, ecs_id_t, count, ECS_ELEM_T(vec->array, ecs_id_t, offset)); type->count = count; ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); } } void flecs_table_diff_build( ecs_world_t *world, ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, int32_t removed_offset) { flecs_table_diff_build_type(world, &builder->added, &diff->added, added_offset); flecs_table_diff_build_type(world, &builder->removed, &diff->removed, removed_offset); } void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff) { diff->added = (ecs_type_t){ .array = builder->added.array, .count = builder->added.count }; diff->removed = (ecs_type_t){ .array = builder->removed.array, .count = builder->removed.count }; } static void flecs_table_diff_build_add_type_to_vec( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *add) { if (!add || !add->count) { return; } int32_t offset = vec->count; ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), add->array, ecs_id_t, add->count); } void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src) { flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); } static void flecs_table_diff_free( ecs_world_t *world, ecs_table_diff_t *diff) { flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); flecs_bfree(&world->allocators.table_diff, diff); } static ecs_graph_edge_t* flecs_table_ensure_hi_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { if (!edges->hi) { edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); ecs_map_init_w_params(edges->hi, &world->allocators.ptr); } ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); ecs_graph_edge_t *edge = r[0]; if (edge) { return edge; } if (id < FLECS_HI_COMPONENT_ID) { edge = &edges->lo[id]; } else { edge = flecs_bcalloc(&world->allocators.graph_edge); } r[0] = edge; return edge; } static ecs_graph_edge_t* flecs_table_ensure_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { ecs_graph_edge_t *edge; if (id < FLECS_HI_COMPONENT_ID) { if (!edges->lo) { edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); } edge = &edges->lo[id]; } else { edge = flecs_table_ensure_hi_edge(world, edges, id); } return edge; } static void flecs_table_disconnect_edge( ecs_world_t *world, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); (void)id; /* Remove backref from destination table */ ecs_graph_edge_hdr_t *next = edge->hdr.next; ecs_graph_edge_hdr_t *prev = edge->hdr.prev; if (next) { next->prev = prev; } if (prev) { prev->next = next; } /* Remove data associated with edge */ ecs_table_diff_t *diff = edge->diff; if (diff) { flecs_table_diff_free(world, diff); } /* If edge id is low, clear it from fast lookup array */ if (id < FLECS_HI_COMPONENT_ID) { ecs_os_memset_t(edge, 0, ecs_graph_edge_t); } else { flecs_bfree(&world->allocators.graph_edge, edge); } } static void flecs_table_remove_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_disconnect_edge(world, id, edge); ecs_map_remove(edges->hi, id); } static void flecs_table_init_edges( ecs_graph_edges_t *edges) { edges->lo = NULL; edges->hi = NULL; } static void flecs_table_init_node( ecs_graph_node_t *node) { flecs_table_init_edges(&node->add); flecs_table_init_edges(&node->remove); } bool flecs_table_records_update_empty( ecs_table_t *table) { bool result = false; bool is_empty = ecs_table_count(table) == 0; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; result |= ecs_table_cache_set_empty(cache, table, is_empty); } return result; } static void flecs_init_table( ecs_world_t *world, ecs_table_t *table, ecs_table_t *prev) { table->flags = 0; table->dirty_state = NULL; table->_->lock = 0; table->_->generation = 0; flecs_table_init_node(&table->node); flecs_table_init(world, table, prev); } static ecs_table_t *flecs_create_table( ecs_world_t *world, ecs_type_t *type, flecs_hashmap_result_t table_elem, ecs_table_t *prev) { ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); result->id = flecs_sparse_last_id(&world->store.tables); result->type = *type; if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &result->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", expr, result->id); ecs_os_free(expr); } ecs_log_push_2(); /* Store table in table hashmap */ *(ecs_table_t**)table_elem.value = result; /* Set keyvalue to one that has the same lifecycle as the table */ *(ecs_type_t*)table_elem.key = result->type; result->_->hash = table_elem.hash; flecs_init_table(world, result, prev); /* Update counters */ world->info.table_count ++; world->info.table_record_count += result->_->record_count; world->info.table_storage_count += result->column_count; world->info.empty_table_count ++; world->info.table_create_total ++; if (!result->column_count) { world->info.tag_table_count ++; } else { world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); } ecs_log_pop_2(); return result; } static ecs_table_t* flecs_table_ensure( ecs_world_t *world, ecs_type_t *type, bool own_type, ecs_table_t *prev) { ecs_poly_assert(world, ecs_world_t); int32_t id_count = type->count; if (!id_count) { return &world->store.root; } ecs_table_t *table; flecs_hashmap_result_t elem = flecs_hashmap_ensure( &world->store.table_map, type, ecs_table_t*); if ((table = *(ecs_table_t**)elem.value)) { if (own_type) { flecs_type_free(world, type); } return table; } /* If we get here, table needs to be created which is only allowed when the * application is not currently in progress */ ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); /* If we get here, the table has not been found, so create it. */ if (own_type) { return flecs_create_table(world, type, elem, prev); } ecs_type_t copy = flecs_type_copy(world, type); return flecs_create_table(world, ©, elem, prev); } static void flecs_diff_insert_added( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; } static void flecs_diff_insert_removed( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_allocator_t *a = &world->allocator; ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; } static void flecs_compute_table_diff( ecs_world_t *world, ecs_table_t *node, ecs_table_t *next, ecs_graph_edge_t *edge, ecs_id_t id) { if (ECS_IS_PAIR(id)) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( ECS_PAIR_FIRST(id), EcsWildcard)); if (idr->flags & EcsIdUnion) { if (node != next) { id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)); } else { ecs_table_diff_t *diff = flecs_bcalloc( &world->allocators.table_diff); diff->added.count = 1; diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); edge->diff = diff; return; } } } ecs_type_t node_type = node->type; ecs_type_t next_type = next->type; ecs_id_t *ids_node = node_type.array; ecs_id_t *ids_next = next_type.array; int32_t i_node = 0, node_count = node_type.count; int32_t i_next = 0, next_count = next_type.count; int32_t added_count = 0; int32_t removed_count = 0; bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); /* First do a scan to see how big the diff is, so we don't have to realloc * or alloc more memory than required. */ for (; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; bool added = id_next < id_node; bool removed = id_node < id_next; trivial_edge &= !added || id_next == id; trivial_edge &= !removed || id_node == id; added_count += added; removed_count += removed; i_node += id_node <= id_next; i_next += id_next <= id_node; } added_count += next_count - i_next; removed_count += node_count - i_node; trivial_edge &= (added_count + removed_count) <= 1 && !ecs_id_is_wildcard(id); if (trivial_edge) { /* If edge is trivial there's no need to create a diff element for it */ return; } ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; int32_t added_offset = builder->added.count; int32_t removed_offset = builder->removed.count; for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; if (id_next < id_node) { flecs_diff_insert_added(world, builder, id_next); } else if (id_node < id_next) { flecs_diff_insert_removed(world, builder, id_node); } i_node += id_node <= id_next; i_next += id_next <= id_node; } for (; i_next < next_count; i_next ++) { flecs_diff_insert_added(world, builder, ids_next[i_next]); } for (; i_node < node_count; i_node ++) { flecs_diff_insert_removed(world, builder, ids_node[i_node]); } ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff = diff; flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } static void flecs_add_overrides_for_base( ecs_world_t *world, ecs_type_t *dst_type, ecs_id_t pair) { ecs_entity_t base = ecs_pair_second(world, pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_t *base_table = ecs_get_table(world, base); if (!base_table) { return; } ecs_id_t *ids = base_table->type.array; ecs_flags32_t flags = base_table->flags; if (flags & EcsTableHasOverrides) { int32_t i, count = base_table->type.count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; ecs_id_t to_add = 0; if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { to_add = id & ~ECS_OVERRIDE; } else { ecs_table_record_t *tr = &base_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdAlwaysOverride) { to_add = id; } } if (to_add) { ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); bool exclusive = false; if (ECS_IS_PAIR(to_add)) { ecs_id_record_t *idr = flecs_id_record_get(world, wc); if (idr) { exclusive = (idr->flags & EcsIdExclusive) != 0; } } if (!exclusive) { flecs_type_add(world, dst_type, to_add); } else { int32_t column = flecs_type_find(dst_type, wc); if (column == -1) { flecs_type_add(world, dst_type, to_add); } else { dst_type->array[column] = to_add; } } } } } if (flags & EcsTableHasIsA) { ecs_table_record_t *tr = flecs_id_record_get_table( world->idr_isa_wildcard, base_table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = tr->index, end = i + tr->count; for (; i != end; i ++) { flecs_add_overrides_for_base(world, dst_type, ids[i]); } } } static void flecs_add_with_property( ecs_world_t *world, ecs_id_record_t *idr_with_wildcard, ecs_type_t *dst_type, ecs_entity_t r, ecs_entity_t o) { r = ecs_get_alive(world, r); /* Check if component/relationship has With pairs, which contain ids * that need to be added to the table. */ ecs_table_t *table = ecs_get_table(world, r); if (!table) { return; } ecs_table_record_t *tr = flecs_id_record_get_table( idr_with_wildcard, table); if (tr) { int32_t i = tr->index, end = i + tr->count; ecs_id_t *ids = table->type.array; for (; i < end; i ++) { ecs_id_t id = ids[i]; ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); ecs_id_t ra = ECS_PAIR_SECOND(id); ecs_id_t a = ra; if (o) { a = ecs_pair(ra, o); } flecs_type_add(world, dst_type, a); flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); } } } static ecs_table_t* flecs_find_table_with( ecs_world_t *world, ecs_table_t *node, ecs_id_t with) { ecs_ensure_id(world, with); ecs_id_record_t *idr = NULL; ecs_entity_t r = 0, o = 0; if (ECS_IS_PAIR(with)) { r = ECS_PAIR_FIRST(with); o = ECS_PAIR_SECOND(with); idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); if (idr->flags & EcsIdUnion) { ecs_type_t dst_type; ecs_id_t union_id = ecs_pair(EcsUnion, r); int res = flecs_type_new_with( world, &dst_type, &node->type, union_id); if (res == -1) { return node; } return flecs_table_ensure(world, &dst_type, true, node); } else if (idr->flags & EcsIdExclusive) { /* Relationship is exclusive, check if table already has it */ ecs_table_record_t *tr = flecs_id_record_get_table(idr, node); if (tr) { /* Table already has an instance of the relationship, create * a new id sequence with the existing id replaced */ ecs_type_t dst_type = flecs_type_copy(world, &node->type); ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); dst_type.array[tr->index] = with; return flecs_table_ensure(world, &dst_type, true, node); } } } else { idr = flecs_id_record_ensure(world, with); r = with; } /* Create sequence with new id */ ecs_type_t dst_type; int res = flecs_type_new_with(world, &dst_type, &node->type, with); if (res == -1) { return node; /* Current table already has id */ } if (r == EcsIsA) { /* If adding a prefab, check if prefab has overrides */ flecs_add_overrides_for_base(world, &dst_type, with); } else if (r == EcsChildOf) { o = ecs_get_alive(world, o); if (ecs_has_id(world, o, EcsPrefab)) { flecs_type_add(world, &dst_type, EcsPrefab); } } if (idr->flags & EcsIdWith) { ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, ecs_pair(EcsWith, EcsWildcard)); /* If id has With property, add targets to type */ flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); } return flecs_table_ensure(world, &dst_type, true, node); } static ecs_table_t* flecs_find_table_without( ecs_world_t *world, ecs_table_t *node, ecs_id_t without) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = 0; ecs_id_record_t *idr = NULL; r = ECS_PAIR_FIRST(without); idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); if (idr && idr->flags & EcsIdUnion) { without = ecs_pair(EcsUnion, r); } } /* Create sequence with new id */ ecs_type_t dst_type; int res = flecs_type_new_without(world, &dst_type, &node->type, without); if (res == -1) { return node; /* Current table does not have id */ } return flecs_table_ensure(world, &dst_type, true, node); } static void flecs_table_init_edge( ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); edge->from = table; edge->to = to; edge->id = id; } static void flecs_init_edge_for_add( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { flecs_table_init_edge(table, edge, id, to); flecs_table_ensure_hi_edge(world, &table->node.add, id); if (table != to || table->flags & EcsTableHasUnion) { /* Add edges are appended to refs.next */ ecs_graph_edge_hdr_t *to_refs = &to->node.refs; ecs_graph_edge_hdr_t *next = to_refs->next; to_refs->next = &edge->hdr; edge->hdr.prev = to_refs; edge->hdr.next = next; if (next) { next->prev = &edge->hdr; } flecs_compute_table_diff(world, table, to, edge, id); } } static void flecs_init_edge_for_remove( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { flecs_table_init_edge(table, edge, id, to); flecs_table_ensure_hi_edge(world, &table->node.remove, id); if (table != to) { /* Remove edges are appended to refs.prev */ ecs_graph_edge_hdr_t *to_refs = &to->node.refs; ecs_graph_edge_hdr_t *prev = to_refs->prev; to_refs->prev = &edge->hdr; edge->hdr.next = to_refs; edge->hdr.prev = prev; if (prev) { prev->next = &edge->hdr; } flecs_compute_table_diff(world, table, to, edge, id); } } static ecs_table_t* flecs_create_edge_for_remove( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { ecs_table_t *to = flecs_find_table_without(world, node, id); flecs_init_edge_for_remove(world, node, edge, id, to); return to; } static ecs_table_t* flecs_create_edge_for_add( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { ecs_table_t *to = flecs_find_table_with(world, node, id); flecs_init_edge_for_add(world, node, edge, id, to); return to; } ecs_table_t* flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *node, ecs_id_t *id_ptr, ecs_table_diff_t *diff) { ecs_poly_assert(world, ecs_world_t); node = node ? node : &world->store.root; /* Removing 0 from an entity is not valid */ ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t id = id_ptr[0]; ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id); ecs_table_t *to = edge->to; if (!to) { to = flecs_create_edge_for_remove(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } if (node != to) { if (edge->diff) { *diff = *edge->diff; } else { diff->added.count = 0; diff->removed.array = id_ptr; diff->removed.count = 1; } } return to; error: return NULL; } ecs_table_t* flecs_table_traverse_add( ecs_world_t *world, ecs_table_t *node, ecs_id_t *id_ptr, ecs_table_diff_t *diff) { ecs_poly_assert(world, ecs_world_t); ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); node = node ? node : &world->store.root; /* Adding 0 to an entity is not valid */ ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t id = id_ptr[0]; ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); ecs_table_t *to = edge->to; if (!to) { to = flecs_create_edge_for_add(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } if (node != to || edge->diff) { if (edge->diff) { *diff = *edge->diff; } else { diff->added.array = id_ptr; diff->added.count = 1; diff->removed.count = 0; } } return to; error: return NULL; } ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type) { ecs_poly_assert(world, ecs_world_t); return flecs_table_ensure(world, type, false, NULL); } void flecs_init_root_table( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); world->store.root.type = (ecs_type_t){0}; world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); flecs_init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ uint64_t new_id = flecs_sparse_new_id(&world->store.tables); ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); (void)new_id; } void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table) { (void)world; ecs_poly_assert(world, ecs_world_t); ecs_log_push_1(); ecs_map_iter_t it; ecs_graph_node_t *table_node = &table->node; ecs_graph_edges_t *node_add = &table_node->add; ecs_graph_edges_t *node_remove = &table_node->remove; ecs_map_t *add_hi = node_add->hi; ecs_map_t *remove_hi = node_remove->hi; ecs_graph_edge_hdr_t *node_refs = &table_node->refs; /* Cleanup outgoing edges */ it = ecs_map_iter(add_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } it = ecs_map_iter(remove_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } /* Cleanup incoming add edges */ ecs_graph_edge_hdr_t *next, *cur = node_refs->next; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->next; flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); } while ((cur = next)); } /* Cleanup incoming remove edges */ cur = node_refs->prev; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->prev; flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); } while ((cur = next)); } if (node_add->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); } if (node_remove->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); } ecs_map_fini(add_hi); ecs_map_fini(remove_hi); flecs_free_t(&world->allocator, ecs_map_t, add_hi); flecs_free_t(&world->allocator, ecs_map_t, remove_hi); table_node->add.lo = NULL; table_node->remove.lo = NULL; table_node->add.hi = NULL; table_node->remove.hi = NULL; ecs_log_pop_1(); } /* Public convenience functions for traversing table graph */ ecs_table_t* ecs_table_add_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; return flecs_table_traverse_add(world, table, &id, &diff); } ecs_table_t* ecs_table_remove_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; return flecs_table_traverse_remove(world, table, &id, &diff); } ecs_table_t* ecs_table_find( ecs_world_t *world, const ecs_id_t *ids, int32_t id_count) { ecs_type_t type = { .array = ECS_CONST_CAST(ecs_id_t*, ids), .count = id_count }; return flecs_table_ensure(world, &type, false, NULL); }