/** * @file storage/table.c * @brief Table storage implementation. * * Tables are the data structure that store the component data. Tables have * columns for each component in the table, and rows for each entity stored in * the table. Once created, the component list for a table doesn't change, but * entities can move from one table to another. * * Each table has a type, which is a vector with the (component) ids in the * table. The vector is sorted by id, which ensures that there can be only one * table for each unique combination of components. * * Not all ids in a table have to be components. Tags are ids that have no * data type associated with them, and as a result don't need to be explicitly * stored beyond an element in the table type. To save space and speed up table * creation, each table has a reference to a "storage table", which is a table * that only includes component ids (so excluding tags). * * Note that the actual data is not stored on the storage table. The storage * table is only used for sharing administration. A column_map member maps * between column indices of the table and its storage table. Tables are * refcounted, which ensures that storage tables won't be deleted if other * tables have references to it. */ #include "../private_api.h" /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severely slow down many ECS operations. */ #ifdef FLECS_SANITIZE static void flecs_table_check_sanity( ecs_table_t *table) { int32_t i, count = ecs_table_count(table); int32_t size = ecs_table_size(table); ecs_assert(count <= size, ECS_INTERNAL_ERROR, NULL); int32_t bs_offset = table->_ ? table->_->bs_offset : 0; int32_t bs_count = table->_ ? table->_->bs_count : 0; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); if (size) { ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { ecs_assert(table->data.entities == NULL, ECS_INTERNAL_ERROR, NULL); } if (table->column_count) { int32_t column_count = table->column_count; ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); int16_t *column_map = table->column_map; ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < column_count; i ++) { int32_t column_map_id = column_map[i + type_count]; ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.columns[i].ti != NULL, ECS_INTERNAL_ERROR, NULL); if (size) { ecs_assert(table->data.columns[i].data != NULL, ECS_INTERNAL_ERROR, NULL); } else { ecs_assert(table->data.columns[i].data == NULL, ECS_INTERNAL_ERROR, NULL); } } } else { ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); } if (bs_count) { ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &table->_->bs_columns[i]; ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), ECS_INTERNAL_ERROR, NULL); } } ecs_assert((table->_->traversable_count == 0) || (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif /* Set flags for type hooks so table operations can quickly check whether a * fast or complex operation that invokes hooks is required. */ static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) { ecs_flags32_t flags = 0; if (ti->hooks.ctor) { flags |= EcsTableHasCtors; } if (ti->hooks.on_add) { flags |= EcsTableHasCtors; } if (ti->hooks.dtor) { flags |= EcsTableHasDtors; } if (ti->hooks.on_remove) { flags |= EcsTableHasDtors; } if (ti->hooks.copy) { flags |= EcsTableHasCopy; } if (ti->hooks.move) { flags |= EcsTableHasMove; } return flags; } static void flecs_table_init_columns( ecs_world_t *world, ecs_table_t *table, int32_t column_count) { int16_t i, cur = 0, ids_count = flecs_ito(int16_t, table->type.count); for (i = 0; i < ids_count; i ++) { ecs_id_t id = table->type.array[i]; if (id < FLECS_HI_COMPONENT_ID) { table->component_map[id] = flecs_ito(int16_t, -(i + 1)); } } if (!column_count) { return; } ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); table->data.columns = columns; ecs_id_t *ids = table->type.array; ecs_table_record_t *records = table->_->records; int16_t *t2s = table->column_map; int16_t *s2t = &table->column_map[ids_count]; for (i = 0; i < ids_count; i ++) { ecs_id_t id = ids[i]; ecs_table_record_t *tr = &records[i]; ecs_component_record_t *cr = tr->hdr.cr; const ecs_type_info_t *ti = cr->type_info; if (!ti || (cr->flags & EcsIdSparse)) { t2s[i] = -1; continue; } t2s[i] = cur; s2t[cur] = i; tr->column = flecs_ito(int16_t, cur); columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); if (id < FLECS_HI_COMPONENT_ID) { table->component_map[id] = flecs_ito(int16_t, cur + 1); } table->flags |= flecs_type_info_flags(ti); cur ++; } int32_t record_count = table->_->record_count; for (; i < record_count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_component_record_t *cr = tr->hdr.cr; ecs_id_t id = cr->id; if (ecs_id_is_wildcard(id)) { ecs_table_record_t *first_tr = &records[tr->index]; tr->column = first_tr->column; } } /* For debug visualization */ #ifdef FLECS_DEBUG_INFO if (table->_->name_column != -1) { table->_->name_column = table->column_map[table->_->name_column]; } if (table->_->doc_name_column != -1) { table->_->doc_name_column = table->column_map[table->_->doc_name_column]; } #endif } /* Initialize table storage */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { flecs_table_init_columns(world, table, table->column_count); ecs_table__t *meta = table->_; int32_t i, bs_count = meta->bs_count; if (bs_count) { meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); for (i = 0; i < bs_count; i ++) { flecs_bitset_init(&meta->bs_columns[i]); } } } /* Initialize table flags. Table flags are used in lots of scenarios to quickly * check the features of a table without having to inspect the table type. Table * flags are typically used to early-out of potentially expensive operations. */ static void flecs_table_init_flags( ecs_world_t *world, ecs_table_t *table) { ecs_id_t *ids = table->type.array; int32_t count = table->type.count; #ifdef FLECS_DEBUG_INFO /* For debug visualization */ table->_->name_column = -1; table->_->doc_name_column = -1; table->_->parent.world = world; table->_->parent.id = 0; #endif int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (id <= EcsLastInternalComponentId) { table->flags |= EcsTableHasModule; } if (id == EcsModule) { table->flags |= EcsTableHasModule; } else if (id == EcsPrefab) { table->flags |= EcsTableIsPrefab; } else if (id == EcsDisabled) { table->flags |= EcsTableIsDisabled; } else if (id == EcsNotQueryable) { table->flags |= EcsTableNotQueryable; } else { if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); table->flags |= EcsTableHasPairs; if (r == EcsIsA) { table->flags |= EcsTableHasIsA; } else if (r == EcsChildOf) { table->flags |= EcsTableHasChildOf; ecs_entity_t tgt = ecs_pair_second(world, id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); /* If table contains entities that are inside one of the * builtin modules, it contains builtin entities */ if (tgt == EcsFlecsCore || tgt == EcsFlecsInternals) { table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } if (ecs_has_id(world, tgt, EcsModule)) { table->flags |= EcsTableHasModule; } #ifdef FLECS_DEBUG_INFO table->_->parent.id = tgt; #endif } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { table->flags |= EcsTableHasName; #ifdef FLECS_DEBUG_INFO table->_->name_column = flecs_ito(int16_t, i); #endif } else if (r == ecs_id(EcsPoly)) { table->flags |= EcsTableHasModule; } #if defined(FLECS_DEBUG_INFO) && defined(FLECS_DOC) else if (id == ecs_pair_t(EcsDocDescription, EcsName)) { table->_->doc_name_column = flecs_ito(int16_t, i); } #endif } else { if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_table__t *meta = table->_; table->flags |= EcsTableHasToggle; if (!meta->bs_count) { meta->bs_offset = flecs_ito(int16_t, i); } meta->bs_count ++; } if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { table->flags |= EcsTableHasOverrides; } } } } } /* Utility function that appends an element to the table record array */ static void flecs_table_append_to_records( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, ecs_id_t id, int32_t column) { /* To avoid a quadratic search, use the O(1) lookup that the index * already provides. */ ecs_component_record_t *cr = flecs_components_ensure(world, id); /* Safe, record is owned by table. */ ecs_table_record_t *tr = ECS_CONST_CAST(ecs_table_record_t*, flecs_component_get_table(cr, table)); if (!tr) { tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); tr->index = flecs_ito(int16_t, column); tr->count = 1; ecs_table_cache_insert(&cr->cache, table, &tr->hdr); } else { tr->count ++; } ecs_assert(tr->hdr.cr != NULL, ECS_INTERNAL_ERROR, NULL); } static void flecs_table_init_overrides( ecs_world_t *world, ecs_table_t *table, const ecs_table_record_t *tr) { ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->column_count) { return; } ecs_table_overrides_t *o = flecs_walloc_t(world, ecs_table_overrides_t); if (tr->count > 1) { table->flags |= EcsTableHasMultiIsA; o->is._n.tr = tr; o->is._n.generations = flecs_wcalloc_n(world, int32_t, tr->count); int32_t i; for (i = 0; i < tr->count; i ++) { o->is._n.generations[i] = -1; } } else { const ecs_table_record_t *first = &table->_->records[tr->index]; const ecs_component_record_t *cr = first->hdr.cr; ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsIsA, ECS_INTERNAL_ERROR, NULL); ecs_assert(first->count == 1, ECS_INTERNAL_ERROR, NULL); o->is._1.generation = -1; o->is._1.pair = cr->pair; } o->refs = flecs_wcalloc_n(world, ecs_ref_t, table->column_count); table->data.overrides = o; } static void flecs_table_fini_overrides( ecs_world_t *world, ecs_table_t *table) { if (!table->column_count) { return; } ecs_table_overrides_t *o = table->data.overrides; if (!o) { return; } if (table->flags & EcsTableHasMultiIsA) { const ecs_table_record_t *tr = o->is._n.tr; flecs_wfree_n(world, int32_t, tr->count, o->is._n.generations); } flecs_wfree_n(world, ecs_ref_t, table->column_count, o->refs); flecs_wfree_t(world, ecs_table_overrides_t, o); } static void flecs_table_update_overrides( ecs_world_t *world, ecs_table_t *table) { if (!(table->flags & EcsTableHasIsA)) { return; } ecs_table_overrides_t *o = table->data.overrides; if (!o) { return; } if (table->flags & EcsTableHasMultiIsA) { const ecs_table_record_t *tr = o->is._n.tr; const ecs_table_record_t *records = table->_->records; int32_t *generations = o->is._n.generations; int32_t i = tr->index, end = i + tr->count; for (; i < end; i ++) { ecs_component_record_t *cr = records[i].hdr.cr; if (cr->pair->reachable.generation != *generations) { break; } generations ++; } if (i == end) { /* Cache is up to date */ return; } generations = o->is._n.generations; i = tr->index; end = i + tr->count; for (; i < end; i ++) { ecs_component_record_t *cr = records[i].hdr.cr; generations[0] = cr->pair->reachable.generation; generations ++; } } else { /* Fast cache validation for tables with single IsA pair */ int32_t generation = o->is._1.pair->reachable.generation; if (o->is._1.generation == generation) { /* Cache is up to date */ return; } o->is._1.generation = generation; } int16_t *map = &table->column_map[table->type.count]; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_id_t id = table->type.array[map[i]]; ecs_entity_t base = 0; if (ecs_search_relation( world, table, 0, id, EcsIsA, EcsUp, &base, NULL, NULL) != -1) { o->refs[i] = ecs_ref_init_id(world, base, id); } else { ecs_os_zeromem(&o->refs[i]); } } } void flecs_table_emit( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { ecs_defer_begin(world); flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &table->type, .event = event, .table = table, .flags = EcsEventTableOnly, .observable = world }); ecs_defer_end(world); } /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { /* Make sure table->flags is initialized */ flecs_table_init_flags(world, table); /* The following code walks the table type to discover which id records the * table needs to register table records with. * * In addition to registering itself with id records for each id in the * table type, a table also registers itself with wildcard id records. For * example, if a table contains (Eats, Apples), it will register itself with * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it * easier for wildcard queries to find the relevant tables. */ int32_t dst_i = 0, dst_count = table->type.count; int32_t src_i = 0, src_count = 0; ecs_id_t *dst_ids = table->type.array; ecs_id_t *src_ids = NULL; ecs_table_record_t *tr = NULL, *src_tr = NULL; if (from) { src_count = from->type.count; src_ids = from->type.array; src_tr = from->_->records; } /* We don't know in advance how large the records array will be, so use * cached vector. This eliminates unnecessary allocations, and/or expensive * iterations to determine how many records we need. */ ecs_allocator_t *a = &world->allocator; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(a, records, ecs_table_record_t); ecs_component_record_t *cr, *childof_cr = NULL; int32_t last_id = -1; /* Track last regular (non-pair) id */ int32_t first_pair = -1; /* Track the first pair in the table */ int32_t first_role = -1; /* Track first id with role */ /* Scan to find boundaries of regular ids, pairs and roles */ for (dst_i = 0; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { first_pair = dst_i; } if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { last_id = dst_i; } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { first_role = dst_i; } /* Build bloom filter for table */ table->bloom_filter = flecs_table_bloom_filter_add(table->bloom_filter, dst_id); } /* The easy part: initialize a record for every id in the type */ for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t src_id = src_ids[src_i]; cr = NULL; if (dst_id == src_id) { ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); cr = (ecs_component_record_t*)src_tr[src_i].hdr.cr; } else if (dst_id < src_id) { cr = flecs_components_ensure(world, dst_id); } if (cr) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = cr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } dst_i += dst_id <= src_id; src_i += dst_id >= src_id; } /* Add remaining ids that the "from" table didn't have */ for (; (dst_i < dst_count); dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; tr = ecs_vec_append_t(a, records, ecs_table_record_t); cr = flecs_components_ensure(world, dst_id); tr->hdr.cr = cr; ecs_assert(tr->hdr.cr != NULL, ECS_INTERNAL_ERROR, NULL); tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } /* We're going to insert records from the vector into the index that * will get patched up later. To ensure the record pointers don't get * invalidated we need to grow the vector so that it won't realloc as * we're adding the next set of records */ if (first_role != -1 || first_pair != -1) { int32_t start = first_role; if (first_pair != -1 && (start == -1 || first_pair < start)) { start = first_pair; } /* Total number of records can never be higher than * - number of regular (non-pair) ids + * - three records for pairs: (R,T), (R,*), (*,T) * - one wildcard (*), one any (_) and one pair wildcard (*,*) record * - one record for (ChildOf, 0) */ int32_t flag_id_count = dst_count - start; int32_t record_count = start + 3 * flag_id_count + 3 + 1; ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); } /* Get records size now so we can check that array did not resize */ int32_t records_size = ecs_vec_size(records); (void)records_size; /* Add records for ids with roles (used by cleanup logic) */ if (first_role != -1) { for (dst_i = first_role; dst_i < dst_count; dst_i ++) { ecs_id_t id = dst_ids[dst_i]; if (!ECS_IS_PAIR(id)) { ecs_entity_t first = 0; ecs_entity_t second = 0; if (ECS_HAS_ID_FLAG(id, PAIR)) { first = ECS_PAIR_FIRST(id); second = ECS_PAIR_SECOND(id); } else { first = id & ECS_COMPONENT_MASK; } if (first) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, first), dst_i); } if (second) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, second), dst_i); } } } } int32_t last_pair = -1; bool has_childof = table->flags & EcsTableHasChildOf; if (first_pair != -1) { /* Add a (Relationship, *) record for each relationship. */ ecs_entity_t r = 0; for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (!ECS_IS_PAIR(dst_id)) { break; /* no more pairs */ } if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); ecs_component_record_t *p_cr = tr->hdr.cr; r = ECS_PAIR_FIRST(dst_id); if (r == EcsChildOf) { childof_cr = p_cr; ecs_assert(childof_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_assert(p_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); cr = p_cr->pair->parent; /* (R, *) */ ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = cr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 0; } ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); tr->count ++; } last_pair = dst_i; /* Add a (*, Target) record for each relationship target. Type * ids are sorted relationship-first, so we can't simply do a single * linear scan to find all occurrences for a target. */ for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); flecs_table_append_to_records( world, table, records, tgt_id, dst_i); } } /* Lastly, add records for all-wildcard ids */ if (last_id >= 0) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = world->cr_wildcard; tr->index = 0; tr->count = flecs_ito(int16_t, last_id + 1); } if (last_pair - first_pair) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = world->cr_wildcard_wildcard; tr->index = flecs_ito(int16_t, first_pair); tr->count = flecs_ito(int16_t, last_pair - first_pair); } if (!has_childof) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); childof_cr = world->cr_childof_0; tr->hdr.cr = childof_cr; tr->index = -1; /* The table doesn't have a (ChildOf, 0) component */ tr->count = 0; table->bloom_filter = flecs_table_bloom_filter_add( table->bloom_filter, ecs_pair(EcsChildOf, 0)); } /* Now that all records have been added, copy them to array */ int32_t i, dst_record_count = ecs_vec_count(records); ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); table->_->record_count = flecs_ito(int16_t, dst_record_count); table->_->records = dst_tr; int32_t column_count = 0; ecs_table_record_t *isa_tr = NULL; /* Register & patch up records */ for (i = 0; i < dst_record_count; i ++) { tr = &dst_tr[i]; cr = dst_tr[i].hdr.cr; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_cache_get(&cr->cache, table)) { /* If this is a target wildcard record it has already been * registered, but the record is now at a different location in * memory. Patch up the linked list with the new address */ /* Ensure that record array hasn't been reallocated */ ecs_assert(records_size == ecs_vec_size(records), ECS_INTERNAL_ERROR, NULL); ecs_table_cache_replace(&cr->cache, table, &tr->hdr); } else { /* Other records are not registered yet */ ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_insert(&cr->cache, table, &tr->hdr); } /* Claim component record so it stays alive as long as the table exists */ flecs_component_claim(world, cr); /* Initialize event flags */ table->flags |= cr->flags & EcsIdEventMask; /* Initialize column index (will be overwritten by init_data) */ tr->column = -1; if (ECS_ID_ON_INSTANTIATE(cr->flags) == EcsOverride) { table->flags |= EcsTableHasOverrides; } if ((i < table->type.count) && (cr->type_info != NULL)) { if (!(cr->flags & EcsIdSparse)) { column_count ++; } } if (cr->id == ecs_pair(EcsIsA, EcsWildcard)) { isa_tr = tr; } } /* Initialize event flags for any record */ table->flags |= world->cr_any->flags & EcsIdEventMask; table->component_map = flecs_wcalloc_n( world, int16_t, FLECS_HI_COMPONENT_ID); if (column_count) { table->column_map = flecs_walloc_n(world, int16_t, dst_count + column_count); } table->column_count = flecs_ito(int16_t, column_count); table->version = 1; flecs_table_init_data(world, table); /* If the table doesn't have an explicit ChildOf pair, it will be in the * root which is registered with the (ChildOf, 0) index. */ ecs_assert(childof_cr != NULL, ECS_INTERNAL_ERROR, NULL); if (table->flags & EcsTableHasName) { flecs_component_name_index_ensure(world, childof_cr); ecs_assert(childof_cr->pair->name_index != NULL, ECS_INTERNAL_ERROR, NULL); } table->_->childof_r = childof_cr->pair; /* If table has IsA pairs, create overrides cache */ if (isa_tr) { flecs_table_init_overrides(world, table, isa_tr); } if (table->flags & EcsTableHasOnTableCreate) { flecs_table_emit(world, table, EcsOnTableCreate); } } /* Unregister table from id records */ static void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) { uint64_t table_id = table->id; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_component_record_t *cr = tr->hdr.cr; ecs_id_t id = cr->id; ecs_assert(tr->hdr.cr == cr, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_components_get(world, id) == cr, ECS_INTERNAL_ERROR, NULL); (void)id; ecs_table_cache_remove(&cr->cache, table_id, &tr->hdr); flecs_component_release(world, cr); } flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); } /* Keep track for what kind of builtin events observers are registered that can * potentially match the table. This allows code to early out of calling the * emit function that notifies observers. */ static void flecs_table_add_trigger_flags( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_entity_t event) { (void)world; ecs_flags32_t flags = 0; if (event == EcsOnAdd) { flags = EcsTableHasOnAdd; } else if (event == EcsOnRemove) { flags = EcsTableHasOnRemove; } else if (event == EcsOnSet) { flags = EcsTableHasOnSet; } else if (event == EcsOnTableCreate) { flags = EcsTableHasOnTableCreate; } else if (event == EcsOnTableDelete) { flags = EcsTableHasOnTableDelete; } else if (event == EcsWildcard) { flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; } table->flags |= flags; /* Add observer flags to incoming edges for id */ if (id && ((flags == EcsTableHasOnAdd) || (flags == EcsTableHasOnRemove))) { flecs_table_edges_add_flags(world, table, id, flags); } } /* Invoke OnRemove observers for all entities in table. Useful during table * deletion or when clearing entities from a table. */ static void flecs_table_notify_on_remove( ecs_world_t *world, ecs_table_t *table) { int32_t count = ecs_table_count(table); if (count) { ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; diff.removed = table->type; diff.removed_flags = table->flags & EcsTableRemoveEdgeFlags; flecs_notify_on_remove(world, table, NULL, 0, count, &diff); } } /* Invoke type hook for entities in table */ static void flecs_table_invoke_hook( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, ecs_entity_t event, ecs_column_t *column, const ecs_entity_t *entities, int32_t row, int32_t count) { int32_t column_index = flecs_ito(int32_t, column - table->data.columns); ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); int32_t type_index = table->column_map[table->type.count + column_index]; ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = &table->_->records[type_index]; ecs_component_record_t *cr = tr->hdr.cr; flecs_invoke_hook(world, table, cr, tr, count, row, entities, table->type.array[type_index], column->ti, event, callback); } static void flecs_table_invoke_ctor_for_array( ecs_world_t *world, ecs_table_t *table, int32_t column_index, void *array, int32_t row, int32_t count, const ecs_type_info_t *ti) { void *ptr = ECS_ELEM(array, ti->size, row); if (table) { ecs_table_overrides_t *o = table->data.overrides; if (o) { ecs_ref_t *r = &o->refs[column_index]; if (r->entity) { void *base_ptr = ecs_ref_get_id(world, r, r->id); ecs_assert(base_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_copy_t copy = ti->hooks.copy_ctor; ecs_iter_action_t on_set = ti->hooks.on_set; ecs_size_t size = ti->size; int32_t i; for (i = 0; i < count; i ++) { if (copy) { copy(ptr, base_ptr, 1, ti); } else { ecs_os_memcpy(ptr, base_ptr, size); } ptr = ECS_OFFSET(ptr, size); } if (on_set) { table->data.columns[column_index].data = array; int32_t record_index = table->column_map[table->type.count + column_index]; const ecs_table_record_t *tr = &table->_->records[record_index]; const ecs_entity_t *entities = &ecs_table_entities(table)[row]; flecs_invoke_hook(world, table, tr->hdr.cr, tr, count, row, entities, ti->component, ti, EcsOnSet, on_set); } return; } } } ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(ptr, count, ti); } } static void flecs_table_invoke_ctor( ecs_world_t *world, ecs_table_t *table, int32_t column_index, int32_t row, int32_t count) { ecs_column_t *column = &table->data.columns[column_index]; ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_invoke_ctor_for_array(world, table, column_index, column->data, row, count, ti); } /* Destruct components */ static void flecs_table_invoke_dtor( ecs_column_t *column, int32_t row, int32_t count) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { void *ptr = ECS_ELEM(column->data, ti->size, row); dtor(ptr, count, ti); } } /* Run hooks that get invoked when component is added to entity */ static void flecs_table_invoke_add_hooks( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_entity_t *entities, int32_t row, int32_t count, bool construct) { ecs_column_t *column = &table->data.columns[column_index]; ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { flecs_table_invoke_ctor(world, table, column_index, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, entities, row, count); } } /* Run hooks that get invoked when component is removed from entity */ static void flecs_table_invoke_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count, bool dtor) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, entities, row, count); } if (dtor) { flecs_table_invoke_dtor(column, row, count); } } static void flecs_table_remove_dont_fragment( ecs_world_t *world, ecs_entity_t e) { ecs_record_t *r = flecs_entities_get(world, e); flecs_entity_remove_non_fragmenting(world, e, r); } /* Destruct all components and/or delete all entities in table */ static void flecs_table_dtor_all( ecs_world_t *world, ecs_table_t *table) { int32_t i, c, count = ecs_table_count(table); if (!count) { return; } const ecs_entity_t *entities = ecs_table_entities(table); int32_t column_count = table->column_count; ecs_assert(!column_count || table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); if (table->_->traversable_count) { /* If table contains monitored entities with traversable relationships, * make sure to invalidate observer cache */ flecs_emit_propagate_invalidate(world, table, 0, count); } /* If table has components with destructors, iterate component columns */ if (table->flags & EcsTableHasDtors) { /* Run on_remove callbacks first before destructing components */ for (c = 0; c < column_count; c++) { ecs_column_t *column = &table->data.columns[c]; ecs_iter_action_t on_remove = column->ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entities[0], 0, count); } } /* Destruct components */ for (c = 0; c < column_count; c++) { flecs_table_invoke_dtor(&table->data.columns[c], 0, count); } /* Iterate entities first, then components. This ensures that only one * entity is invalidated at a time, which ensures that destructors can * safely access other entities. */ for (i = 0; i < count; i ++) { /* Update entity index after invoking destructors so that entity can * be safely used in destructor callbacks. */ ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); flecs_table_remove_dont_fragment(world, e); flecs_entities_remove(world, e); ecs_assert(ecs_is_valid(world, e) == false, ECS_INTERNAL_ERROR, NULL); } /* If table does not have destructors, just update entity index */ } else { for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); flecs_table_remove_dont_fragment(world, e); flecs_entities_remove(world, e); ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); } } } #define FLECS_LOCKED_STORAGE_MSG(operation) \ "a " #operation " operation failed because the table is locked, fix by surrounding the operation with defer_begin()/defer_end()" /* Cleanup table storage */ static void flecs_table_fini_data( ecs_world_t *world, ecs_table_t *table, bool do_on_remove, bool deallocate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table data cleanup")); if (do_on_remove) { flecs_table_notify_on_remove(world, table); } flecs_table_dtor_all(world, table); if (deallocate) { ecs_column_t *columns = table->data.columns; if (columns) { int32_t c, column_count = table->column_count; for (c = 0; c < column_count; c ++) { ecs_column_t *column = &columns[c]; ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); ecs_vec_fini(&world->allocator, &v, column->ti->size); column->data = NULL; } flecs_wfree_n(world, ecs_column_t, table->column_count, columns); table->data.columns = NULL; } } ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; if (bs_columns) { int32_t c, column_count = meta->bs_count; if (deallocate) { for (c = 0; c < column_count; c ++) { flecs_bitset_fini(&bs_columns[c]); } } else { for (c = 0; c < column_count; c++) { bs_columns[c].count = 0; } } if (deallocate) { flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); meta->bs_columns = NULL; } } if (deallocate) { ecs_vec_t v = ecs_vec_from_entities(table); ecs_vec_fini_t(&world->allocator, &v, ecs_entity_t); table->data.entities = NULL; table->data.size = 0; } table->data.count = 0; table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; } const ecs_entity_t* ecs_table_entities( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.entities; } /* Cleanup, no OnRemove, retain allocations */ void ecs_table_clear_entities( ecs_world_t* world, ecs_table_t* table) { flecs_table_fini_data(world, table, true, false); } /* Cleanup, run OnRemove, free allocations */ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, true, true); } /* Unset all components in table. This function is called before a table is * deleted, and invokes all OnRemove handlers, if any */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table) { flecs_table_notify_on_remove(world, table); } /* Free table resources. */ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table) { flecs_poly_assert(world, ecs_world_t); flecs_increment_table_version(world, table); bool is_root = table == &world->store.root; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table deletion")); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), ECS_INTERNAL_ERROR, NULL); (void)world; ecs_os_perf_trace_push("flecs.table.free"); if (!is_root && !(world->flags & EcsWorldQuit)) { if (table->flags & EcsTableHasOnTableDelete) { flecs_table_emit(world, table, EcsOnTableDelete); } } if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &table->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", expr, table->id); ecs_os_free(expr); ecs_log_push_2(); } /* Cleanup data, no OnRemove, free allocations */ flecs_table_fini_data(world, table, false, true); flecs_table_clear_edges(world, table); if (!is_root) { ecs_type_t ids = { .array = table->type.array, .count = table->type.count }; flecs_hashmap_remove_w_hash( &world->store.table_map, &ids, ecs_table_t*, table->_->hash); } flecs_table_fini_overrides(world, table); flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); flecs_wfree_n(world, int16_t, table->column_count + table->type.count, table->column_map); flecs_wfree_n(world, int16_t, FLECS_HI_COMPONENT_ID, table->component_map); flecs_table_records_unregister(world, table); /* Update counters */ world->info.table_count --; world->info.table_delete_total ++; flecs_free_t(&world->allocator, ecs_table__t, table->_); if (!(world->flags & EcsWorldFini)) { ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); flecs_table_free_type(world, table); flecs_sparse_remove_w_gen_t( &world->store.tables, ecs_table_t, table->id); } ecs_log_pop_2(); ecs_os_perf_trace_pop("flecs.table.free"); } /* Free table type. Do this separately from freeing the table as types can be * in use by application destructors. */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table) { flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } /* Reset a table to its initial state. */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table reset")); flecs_table_clear_edges(world, table); } /* Keep track of number of traversable entities in table. A traversable entity * is an entity used as target in a pair with a traversable relationship. The * traversable count and flag are used by code to early out of mechanisms like * event propagation and recursive cleanup. */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value) { int32_t result = table->_->traversable_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { table->flags &= ~EcsTableHasTraversable; } else if (result == value) { table->flags |= EcsTableHasTraversable; } } /* Mark table column dirty. This usually happens as the result of a set * operation, or iteration of a query with [out] fields. */ static void flecs_table_mark_table_dirty( ecs_world_t *world, ecs_table_t *table, int32_t index) { if (table->dirty_state) { table->dirty_state[index] ++; } if (!index) { flecs_increment_table_version(world, table); } } /* Mark table component dirty */ void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table->dirty_state) { int32_t column; if (component < FLECS_HI_COMPONENT_ID) { column = table->component_map[component]; if (column <= 0) { return; } } else { ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr || tr->column == -1) { return; } column = tr->column + 1; } /* Column is offset by 1, 0 is reserved for entity column. */ table->dirty_state[column] ++; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("dirty marking")); } } /* Get (or create) dirty state of table. Used by queries for change tracking */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table) { flecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->dirty_state) { int32_t column_count = table->column_count; table->dirty_state = flecs_alloc_n(&world->allocator, int32_t, column_count + 1); ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < column_count + 1; i ++) { table->dirty_state[i] = 1; } } return table->dirty_state; } /* Table move logic for bitset (toggle component) column */ static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { ecs_table__t *dst_meta = dst_table->_; ecs_table__t *src_meta = src_table->_; if (!dst_meta && !src_meta) { return; } int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; if (!src_column_count && !dst_column_count) { return; } ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; int32_t offset_old = src_meta ? src_meta->bs_offset : 0; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new + offset_new]; ecs_id_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_bitset_t *dst_bs = &dst_columns[i_new]; flecs_bitset_ensure(dst_bs, dst_index + count); int i; for (i = 0; i < count; i ++) { uint64_t value = flecs_bitset_get(src_bs, src_index + i); flecs_bitset_set(dst_bs, dst_index + i, value); } if (clear) { ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } else if (dst_id > src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; flecs_bitset_fini(src_bs); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } } /* Grow table column. When a column needs to be reallocated this function takes * care of correctly invoking ctor/move/dtor hooks. */ static void flecs_table_grow_column( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_vec_t *column, const ecs_type_info_t *ti, int32_t to_add, int32_t dst_size, bool construct) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(column); int32_t size = ecs_vec_size(column); int32_t elem_size = ti->size; int32_t dst_count = count + to_add; bool can_realloc = dst_size != size; ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); /* If the array could possibly realloc and the component has a move action * defined, move old elements manually */ ecs_move_t move_ctor; if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { ecs_assert(ti->hooks.ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); /* Create vector */ ecs_vec_t dst; ecs_vec_init(&world->allocator, &dst, elem_size, dst_size); dst.count = dst_count; void *src_buffer = column->array; void *dst_buffer = dst.array; /* Move (and construct) existing elements to new vector */ move_ctor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ flecs_table_invoke_ctor_for_array( world, table, column_index, dst_buffer, count, to_add, ti); } /* Free old vector */ ecs_vec_fini(&world->allocator, column, elem_size); *column = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { ecs_vec_set_size(&world->allocator, column, elem_size, dst_size); } ecs_vec_grow(&world->allocator, column, elem_size, to_add); if (construct) { flecs_table_invoke_ctor_for_array( world, table, column_index, column->array, count, to_add, ti); } } ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); } /* Grow all data structures in a table */ static int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, int32_t to_add, int32_t size, const ecs_entity_t *ids) { flecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.count + to_add == size, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_table_count(table); int32_t column_count = table->column_count; /* Add entity to column with entity ids */ ecs_vec_t v_entities = ecs_vec_from_entities(table); ecs_vec_set_size_t(&world->allocator, &v_entities, ecs_entity_t, size); ecs_entity_t *e = NULL; if (size) { e = ecs_vec_last_t(&v_entities, ecs_entity_t) + 1; } v_entities.count += to_add; if (v_entities.size > size) { size = v_entities.size; } /* Update table entities/count/size */ int32_t prev_count = table->data.count, prev_size = table->data.size; table->data.entities = v_entities.array; table->data.count = v_entities.count; table->data.size = v_entities.size; /* Initialize entity ids and record ptrs */ int32_t i; if (e) { if (ids) { ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); } else { ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); } } flecs_table_update_overrides(world, table); /* Add elements to each column array */ ecs_column_t *columns = table->data.columns; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); flecs_table_grow_column(world, table, i, &v_column, ti, to_add, size, true); ecs_assert(v_column.size == size, ECS_INTERNAL_ERROR, NULL); ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(v_column.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); column->data = v_column.array; if (to_add) { flecs_table_invoke_add_hooks( world, table, i, e, count, to_add, false); } } ecs_table__t *meta = table->_; int32_t bs_count = meta->bs_count; ecs_bitset_t *bs_columns = meta->bs_columns; /* Add elements to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, to_add); } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); /* Mark columns as potentially reallocated */ flecs_increment_table_column_version(world, table); /* Return index of first added entity */ return count; } /* Append operation for tables that don't have any complex logic */ static void flecs_table_fast_append( ecs_world_t *world, ecs_table_t *table) { /* Add elements to each column array */ ecs_column_t *columns = table->data.columns; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); ecs_vec_append(&world->allocator, &v, ti->size); column->data = v.array; } } /* Append entity to table */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, bool construct, bool on_add) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table append")); flecs_table_check_sanity(table); /* Get count & size before growing entities array. This tells us whether the * arrays will realloc */ int32_t count = ecs_table_count(table); int32_t column_count = table->column_count; ecs_column_t *columns = table->data.columns; /* Grow buffer with entity ids, set new element to new entity */ ecs_vec_t v_entities = ecs_vec_from_entities(table); ecs_entity_t *e = ecs_vec_append_t(&world->allocator, &v_entities, ecs_entity_t); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = table->data.entities = v_entities.array; *e = entity; /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); flecs_increment_table_column_version(world, table); ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); /* Fast path: no switch columns, no lifecycle actions */ if (!(table->flags & (EcsTableIsComplex|EcsTableHasIsA))) { flecs_table_fast_append(world, table); table->data.count = v_entities.count; table->data.size = v_entities.size; return count; } flecs_table_update_overrides(world, table); int32_t prev_count = table->data.count; int32_t prev_size = table->data.size; ecs_assert(table->data.count == v_entities.count - 1, ECS_INTERNAL_ERROR, NULL); table->data.count = v_entities.count; table->data.size = v_entities.size; /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ int32_t size = v_entities.size; /* Grow component arrays with 1 element */ int32_t i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); flecs_table_grow_column(world, table, i, &v_column, ti, 1, size, construct); column->data = v_column.array; ecs_iter_action_t on_add_hook; if (on_add && (on_add_hook = column->ti->hooks.on_add)) { flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, &entities[count], count, 1); } ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(v_column.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); } ecs_table__t *meta = table->_; int32_t bs_count = meta->bs_count; ecs_bitset_t *bs_columns = meta->bs_columns; /* Add element to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, 1); } flecs_table_check_sanity(table); return count; } /* Delete operation for tables that don't have any complex logic */ static void flecs_table_fast_delete( ecs_table_t *table, int32_t row) { ecs_column_t *columns = table->data.columns; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); ecs_vec_remove(&v, ti->size, row); column->data = v.array; } } /* Delete entity from table */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t row, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table delete")); flecs_table_check_sanity(table); int32_t count = ecs_table_count(table); ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); count --; ecs_assert(row <= count, ECS_INTERNAL_ERROR, NULL); /* Move last entity id to row */ ecs_entity_t *entities = table->data.entities; ecs_entity_t entity_to_move = entities[count]; ecs_entity_t entity_to_delete = entities[row]; entities[row] = entity_to_move; /* Update record of moved entity in entity index */ if (row != count) { ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); if (record_to_move) { uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; record_to_move->row = ECS_ROW_TO_RECORD(row, row_flags); ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); /* Destruct component data */ ecs_column_t *columns = table->data.columns; int32_t column_count = table->column_count; int32_t i; /* If this is a table without lifecycle callbacks or special columns, take * fast path that just remove an element from the array(s) */ if (!(table->flags & EcsTableIsComplex)) { if (row != count) { flecs_table_fast_delete(table, row); } table->data.count --; flecs_table_check_sanity(table); return; } /* Last element, destruct & remove */ if (row == count) { /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { flecs_table_invoke_remove_hooks(world, table, &columns[i], &entity_to_delete, row, 1, true); } } /* Not last element, move last element to deleted element & destruct */ } else { /* If table has component destructors, invoke */ if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_type_info_t *ti = column->ti; ecs_size_t size = ti->size; void *dst = ECS_ELEM(column->data, size, row); void *src = ECS_ELEM(column->data, size, count); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (destruct && on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, row, 1); } ecs_move_t move_dtor = ti->hooks.move_dtor; /* If neither move nor move_ctor are set, this indicates that * non-destructive move semantics are not supported for this * type. In such cases, we set the move_dtor as ctor_move_dtor, * which indicates a destructive move operation. This adjustment * ensures compatibility with different language bindings. */ if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { move_dtor = ti->hooks.ctor_move_dtor; } if (move_dtor) { move_dtor(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } } else { flecs_table_fast_delete(table, row); } } /* Remove elements from bitset columns */ ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; int32_t bs_count = meta->bs_count; for (i = 0; i < bs_count; i ++) { flecs_bitset_remove(&bs_columns[i], row); } table->data.count --; flecs_table_check_sanity(table); } /* Move operation for tables that don't have any complex logic */ static void flecs_table_fast_move( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); if (dst_id == src_id) { int32_t size = dst_column->ti->size; void *dst = ECS_ELEM(dst_column->data, size, dst_index); void *src = ECS_ELEM(src_column->data, size, src_index); ecs_os_memcpy(dst, src, size); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } } /* Move entity from src to dst table */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, bool construct) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("move")); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("move")); ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); if (!((dst_table->flags | src_table->flags) & (EcsTableIsComplex|EcsTableHasIsA))) { flecs_table_fast_move(dst_table, dst_index, src_table, src_index); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); return; } flecs_table_update_overrides(world, dst_table); flecs_table_move_bitset_columns( dst_table, dst_index, src_table, src_index, 1, false); /* Call move_dtor for moved away from storage only if the entity is at the * last index in the source table. If it isn't the last entity, the last * entity in the table will be moved to the src storage, which will take * care of cleaning up resources. */ bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); if (dst_id == src_id) { ecs_type_info_t *ti = dst_column->ti; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *dst = ECS_ELEM(dst_column->data, size, dst_index); void *src = ECS_ELEM(src_column->data, size, src_index); ecs_move_t move = ti->hooks.move_ctor; if (use_move_dtor || !move) { /* Also use move_dtor if component doesn't have a move_ctor * registered, to ensure that the dtor gets called to * cleanup resources. */ move = ti->hooks.ctor_move_dtor; } if (move) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } else { if (dst_id < src_id) { flecs_table_invoke_add_hooks(world, dst_table, i_new, &dst_entity, dst_index, 1, construct); } else { flecs_table_invoke_remove_hooks(world, src_table, src_column, &src_entity, src_index, 1, use_move_dtor); } } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } for (; (i_new < dst_column_count); i_new ++) { flecs_table_invoke_add_hooks(world, dst_table, i_new, &dst_entity, dst_index, 1, construct); } for (; (i_old < src_column_count); i_old ++) { flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], &src_entity, src_index, 1, use_move_dtor); } flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); } /* Append n entities to table */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, int32_t to_add, const ecs_entity_t *ids) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table bulk append")); /* Update entity index before calling hooks. */ int32_t i; for (i = 0; i < to_add; i ++) { ecs_record_t *r = flecs_entities_get(world, ids[i]); r->table = table; r->row = ECS_ROW_TO_RECORD(ecs_table_count(table) + i, 0); } flecs_table_check_sanity(table); int32_t cur_count = ecs_table_count(table); int32_t result = flecs_table_grow_data( world, table, to_add, cur_count + to_add, ids); flecs_table_check_sanity(table); return result; } /* Shrink table storage to fit number of entities */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table shrink")); (void)world; flecs_table_check_sanity(table); bool has_payload = table->data.entities != NULL; int32_t old_size = table->data.size; int32_t count = table->data.count; if (count == table->data.size) { return has_payload; } ecs_allocator_t *a = &world->allocator; ecs_column_t *columns = table->data.columns; ecs_entity_t *entities = table->data.entities; if (count) { ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); table->data.entities = flecs_alloc_n(a, ecs_entity_t, count); ecs_os_memcpy_n(table->data.entities, entities, ecs_entity_t, count); } else { table->data.entities = NULL; } flecs_free_n(a, ecs_entity_t, old_size, entities); int32_t i, column_count = table->column_count; for (i = 0; i < column_count; i ++) { const ecs_type_info_t *ti = columns[i].ti; ecs_size_t component_size = ti->size; ecs_move_t move = ti->hooks.ctor_move_dtor; void *data = columns[i].data; if (count) { columns[i].data = flecs_alloc(a, component_size * count); if (move) { move(columns[i].data, data, count, ti); } else { ecs_assert(columns[i].data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy(columns[i].data, data, component_size * count); } } else { columns[i].data = NULL; } flecs_free(a, component_size * old_size, data); } table->data.size = count; flecs_increment_table_column_version(world, table); flecs_table_mark_table_dirty(world, table, 0); flecs_table_check_sanity(table); return has_payload; } /* Swap operation for bitset (toggle component) columns */ static void flecs_table_swap_bitset_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->_->bs_count; if (!column_count) { return; } ecs_bitset_t *columns = table->_->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } /* Swap two rows in a table. Used for table sorting. */ void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2) { (void)world; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table swap")); ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(table); if (row_1 == row_2) { return; } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_entity_t *entities = table->data.entities; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of whether entity is watched */ uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); /* Swap entities & records */ entities[row_1] = e2; entities[row_2] = e1; record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); flecs_table_swap_bitset_columns(table, row_1, row_2); ecs_column_t *columns = table->data.columns; if (!columns) { flecs_table_check_sanity(table); return; } /* Find the maximum size of column elements * and allocate a temporary buffer for swapping */ int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; for (i = 0; i < column_count; i++) { temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].ti->size); } void* tmp = ecs_os_alloca(temp_buffer_size); /* Swap columns */ for (i = 0; i < column_count; i ++) { int32_t size = columns[i].ti->size; ecs_column_t *column = &columns[i]; void *ptr = column->data; const ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); void *el_1 = ECS_ELEM(ptr, size, row_1); void *el_2 = ECS_ELEM(ptr, size, row_2); ecs_move_t move = ti->hooks.move; if (!move) { ecs_os_memcpy(tmp, el_1, size); ecs_os_memcpy(el_1, el_2, size); ecs_os_memcpy(el_2, tmp, size); } else { ecs_move_t move_ctor = ti->hooks.move_ctor; ecs_move_t move_dtor = ti->hooks.move_dtor; ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); move_ctor(tmp, el_1, 1, ti); move(el_1, el_2, 1, ti); move_dtor(el_2, tmp, 1, ti); } } flecs_table_check_sanity(table); } static void flecs_table_merge_vec( ecs_world_t *world, ecs_vec_t *dst, ecs_vec_t *src, int32_t size, int32_t elem_size) { int32_t dst_count = dst->count; if (!dst_count) { ecs_vec_fini(&world->allocator, dst, size); *dst = *src; src->array = NULL; src->count = 0; src->size = 0; } else { int32_t src_count = src->count; if (elem_size) { ecs_vec_set_size(&world->allocator, dst, size, elem_size); } ecs_vec_set_count(&world->allocator, dst, size, dst_count + src_count); void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); void *src_ptr = src->array; ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); ecs_vec_fini(&world->allocator, src, size); } } /* Merge data from one table column into other table column */ static void flecs_table_merge_column( ecs_world_t *world, ecs_vec_t *dst_vec, ecs_vec_t *src_vec, ecs_column_t *dst, ecs_column_t *src, int32_t column_size) { const ecs_type_info_t *ti = dst->ti; ecs_assert(ti == src->ti, ECS_INTERNAL_ERROR, NULL); ecs_size_t elem_size = ti->size; int32_t dst_count = ecs_vec_count(dst_vec); if (!dst_count) { ecs_vec_fini(&world->allocator, dst_vec, elem_size); *dst_vec = *src_vec; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { int32_t src_count = src_vec->count; flecs_table_grow_column(world, NULL, -1, dst_vec, ti, src_count, column_size, false); void *dst_ptr = ECS_ELEM(dst_vec->array, elem_size, dst_count); void *src_ptr = src_vec->array; /* Move values into column */ ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move = ti->hooks.ctor_move_dtor; if (move) { move(dst_ptr, src_ptr, src_count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, elem_size * src_count); } ecs_vec_fini(&world->allocator, src_vec, elem_size); } dst->data = dst_vec->array; src->data = NULL; } /* Merge storage of two tables. */ static void flecs_table_merge_data( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, int32_t dst_count, int32_t src_count) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ ecs_vec_t dst_entities = ecs_vec_from_entities(dst_table); ecs_vec_t src_entities = ecs_vec_from_entities(src_table); flecs_table_merge_vec(world, &dst_entities, &src_entities, ECS_SIZEOF(ecs_entity_t), 0); ecs_assert(dst_entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); int32_t column_size = dst_entities.size; ecs_allocator_t *a = &world->allocator; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); ecs_size_t dst_elem_size = dst_column->ti->size; ecs_size_t src_elem_size = src_column->ti->size; ecs_vec_t dst_vec = ecs_vec_from_column( dst_column, dst_table, dst_elem_size); ecs_vec_t src_vec = ecs_vec_from_column( src_column, src_table, src_elem_size); if (dst_id == src_id) { flecs_table_merge_column(world, &dst_vec, &src_vec, dst_column, src_column, column_size); flecs_table_mark_table_dirty(world, dst_table, i_new + 1); i_new ++; i_old ++; } else if (dst_id < src_id) { /* New column, make sure vector is large enough. */ ecs_vec_set_size(a, &dst_vec, dst_elem_size, column_size); dst_column->data = dst_vec.array; flecs_table_invoke_ctor(world, dst_table, i_new, dst_count, src_count); i_new ++; } else if (dst_id > src_id) { /* Old column does not occur in new table, destruct */ flecs_table_invoke_dtor(src_column, 0, src_count); ecs_vec_fini(a, &src_vec, src_elem_size); src_column->data = NULL; i_old ++; } } flecs_table_move_bitset_columns( dst_table, dst_count, src_table, 0, src_count, true); /* Initialize remaining columns */ for (; i_new < dst_column_count; i_new ++) { ecs_column_t *column = &dst_columns[i_new]; int32_t elem_size = column->ti->size; ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_t vec = ecs_vec_from_column(column, dst_table, elem_size); ecs_vec_set_size(a, &vec, elem_size, column_size); column->data = vec.array; flecs_table_invoke_ctor(world, dst_table, i_new, dst_count, src_count); } /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { ecs_column_t *column = &src_columns[i_old]; int32_t elem_size = column->ti->size; ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); flecs_table_invoke_dtor(column, 0, src_count); ecs_vec_t vec = ecs_vec_from_column(column, src_table, elem_size); ecs_vec_fini(a, &vec, elem_size); column->data = vec.array; } /* Mark entity column as dirty */ flecs_table_mark_table_dirty(world, dst_table, 0); /* Mark columns as potentially reallocated */ flecs_increment_table_column_version(world, dst_table); dst_table->data.entities = dst_entities.array; dst_table->data.count = dst_entities.count; dst_table->data.size = dst_entities.size; src_table->data.entities = src_entities.array; src_table->data.count = src_entities.count; src_table->data.size = src_entities.size; } /* Merge source table into destination table. This typically happens as result * of a bulk operation, like when a component is removed from all entities in * the source table (like for the Remove OnDelete policy). */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table) { ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table merge")); ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table merge")); flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); const ecs_entity_t *src_entities = ecs_table_entities(src_table); int32_t src_count = ecs_table_count(src_table); int32_t dst_count = ecs_table_count(dst_table); /* First, update entity index so old entities point to new type */ int32_t i; for(i = 0; i < src_count; i ++) { ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); record->table = dst_table; } /* Merge table columns */ flecs_table_merge_data(world, dst_table, src_table, dst_count, src_count); if (src_count) { flecs_table_traversable_add(dst_table, src_table->_->traversable_count); flecs_table_traversable_add(src_table, -src_table->_->traversable_count); ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } /* Internal mechanism for propagating information to tables */ void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_event_t *event) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldFini) { return; } switch(event->kind) { case EcsTableTriggersForId: flecs_table_add_trigger_flags(world, table, id, event->event); break; case EcsTableNoTriggersForId: break; /* TODO */ } } int32_t flecs_table_get_toggle_column( ecs_table_t *table, ecs_id_t id) { ecs_id_t *ids = table->type.array; int32_t i = table->_->bs_offset, end = i + table->_->bs_count; for (; i < end; i ++) { if (ids[i] == (ECS_TOGGLE | id)) { return i; } } return -1; } ecs_bitset_t* flecs_table_get_toggle( ecs_table_t *table, ecs_id_t id) { int32_t toggle_column = flecs_table_get_toggle_column(table, id); if (toggle_column == -1) { return NULL; } toggle_column -= table->_->bs_offset; ecs_assert(toggle_column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(toggle_column < table->_->bs_count, ECS_INTERNAL_ERROR, NULL); return &table->_->bs_columns[toggle_column]; } ecs_id_t flecs_column_id( ecs_table_t *table, int32_t column_index) { int32_t type_index = table->column_map[table->type.count + column_index]; return table->type.array[type_index]; } int32_t flecs_table_observed_count( const ecs_table_t *table) { return table->_->traversable_count; } uint64_t flecs_table_bloom_filter_add( uint64_t filter, uint64_t value) { filter |= 1llu << (value % 64); return filter; } bool flecs_table_bloom_filter_test( const ecs_table_t *table, uint64_t filter) { return (table->bloom_filter & filter) == filter; } ecs_table_records_t flecs_table_records( ecs_table_t* table) { return (ecs_table_records_t){ .array = table->_->records, .count = table->_->record_count }; } ecs_component_record_t* flecs_table_record_get_component( const ecs_table_record_t *tr) { return tr->hdr.cr; } uint64_t flecs_table_id( ecs_table_t* table) { return table->id; } const ecs_ref_t* flecs_table_get_override( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, const ecs_component_record_t *cr, ecs_ref_t *storage) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); if (!(table->flags & EcsTableHasIsA)) { return NULL; } if (cr->flags & EcsIdSparse) { ecs_entity_t base = 0; if (ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, &base, NULL, NULL) != -1) { *storage = ecs_ref_init_id(world, base, id); return storage; } } ecs_table_overrides_t *o = table->data.overrides; if (!o) { return NULL; } int32_t column_index = ecs_table_get_column_index(world, table, id); if (column_index == -1) { return NULL; } flecs_table_update_overrides(world, table); ecs_ref_t *r = &o->refs[column_index]; if (!r->entity) { return NULL; } return r; } /* -- Public API -- */ void ecs_table_lock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock ++; } } } void ecs_table_unlock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock --; ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, "table_unlock called more often than table_lock"); } } } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } int32_t ecs_table_get_type_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); if (id < FLECS_HI_COMPONENT_ID) { int16_t res = table->component_map[id]; if (res > 0) { return table->column_map[table->type.count + (res - 1)]; } return -res - 1; } ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return -1; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return -1; } return tr->index; error: return -1; } int32_t ecs_table_get_column_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); if (id < FLECS_HI_COMPONENT_ID) { int16_t res = table->component_map[id]; if (res > 0) { return res - 1; } return -1; } ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return -1; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return -1; } return tr->column; error: return -1; } int32_t ecs_table_column_count( const ecs_table_t *table) { return table->column_count; } int32_t ecs_table_type_to_column_index( const ecs_table_t *table, int32_t index) { ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int16_t *column_map = table->column_map; if (column_map) { return column_map[index]; } error: return -1; } int32_t ecs_table_column_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->column_map[offset + index]; error: return -1; } void* ecs_table_get_column( const ecs_table_t *table, int32_t index, int32_t offset) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, "column index %d out of range for table", index); ecs_column_t *column = &table->data.columns[index]; void *result = column->data; if (offset) { result = ECS_ELEM(result, column->ti->size, offset); } return result; error: return NULL; } void* ecs_table_get_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, int32_t offset) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t index = ecs_table_get_column_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } size_t ecs_table_get_column_size( const ecs_table_t *table, int32_t column) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_ito(size_t, table->data.columns[column].ti->size); error: return 0; } int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.count; } int32_t ecs_table_size( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.size; } bool ecs_table_has_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { return ecs_table_get_type_index(world, table, id) != -1; } ecs_entity_t ecs_table_get_target( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t relationship, int32_t index) { flecs_poly_assert(world, ecs_world_t); ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(relationship, EcsWildcard)); if (!cr) { return 0; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return 0; } if (index > tr->count) { return 0; } ecs_id_t id = table->type.array[tr->index + index]; ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = ECS_PAIR_SECOND(id); return flecs_entities_get_alive(world, tgt); } int32_t ecs_table_get_depth( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t rel) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, "cannot safely determine depth for relationship that is not acyclic " "(add Acyclic property to relationship)"); world = ecs_get_world(world); return flecs_relation_depth(world, rel, table); error: return -1; } bool ecs_table_has_flags( ecs_table_t *table, ecs_flags32_t flags) { return (table->flags & flags) == flags; } bool ecs_table_has_traversable( const ecs_table_t *table) { return table->_->traversable_count != 0; } void ecs_table_swap_rows( ecs_world_t* world, ecs_table_t* table, int32_t row_1, int32_t row_2) { flecs_table_swap(world, table, row_1, row_2); } void* ecs_record_get_by_column( const ecs_record_t *r, int32_t index, size_t c_size) { (void)c_size; ecs_table_t *table = r->table; ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_column_t *column = &table->data.columns[index]; ecs_size_t size = column->ti->size; ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } ecs_record_t* ecs_record_find( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); if (r) { return r; } error: return NULL; } char* ecs_table_str( const ecs_world_t *world, const ecs_table_t *table) { if (table) { return ecs_type_str(world, &table->type); } else { return NULL; } }