Files
PixelDefense/engine/libs/flecs/src/storage/table.c

2584 lines
81 KiB
C

/**
* @file 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 severly slow down many ECS operations. */
#ifdef FLECS_SANITIZE
static
void flecs_table_check_sanity(ecs_table_t *table) {
int32_t size = ecs_vec_size(&table->data.entities);
int32_t count = ecs_vec_count(&table->data.entities);
int32_t i;
int32_t sw_offset = table->_ ? table->_->sw_offset : 0;
int32_t sw_count = table->_ ? table->_->sw_count : 0;
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((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL);
ecs_assert((bs_count + bs_offset) <= type_count, 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);
int32_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 ++) {
ecs_vec_t *column = &table->data.columns[i].data;
ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL);
ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL);
int32_t column_map_id = column_map[i + type_count];
ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL);
}
} else {
ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL);
}
if (sw_count) {
ecs_assert(table->_->sw_columns != NULL,
ECS_INTERNAL_ERROR, NULL);
for (i = 0; i < sw_count; i ++) {
ecs_switch_t *sw = &table->_->sw_columns[i];
ecs_assert(ecs_vec_count(&sw->values) == count,
ECS_INTERNAL_ERROR, NULL);
ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion,
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)
{
if (!column_count) {
return;
}
int32_t i, cur = 0, ids_count = table->type.count;
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;
int32_t *t2s = table->column_map;
int32_t *s2t = &table->column_map[ids_count];
for (i = 0; i < ids_count; i ++) {
ecs_table_record_t *tr = &records[i];
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;
const ecs_type_info_t *ti = idr->type_info;
if (!ti) {
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);
columns[cur].id = ids[i];
columns[cur].size = ti->size;
if (ECS_IS_PAIR(ids[i])) {
ecs_table_record_t *wc_tr = flecs_id_record_get_table(
idr->parent, table);
if (wc_tr->index == tr->index) {
wc_tr->column = tr->column;
}
}
#ifdef FLECS_DEBUG
ecs_vec_init(NULL, &columns[cur].data, ti->size, 0);
#endif
table->flags |= flecs_type_info_flags(ti);
cur ++;
}
}
/* Initialize table storage */
void flecs_table_init_data(
ecs_world_t *world,
ecs_table_t *table)
{
ecs_data_t *storage = &table->data;
ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0);
flecs_table_init_columns(world, table, table->column_count);
ecs_table__t *meta = table->_;
int32_t i, sw_count = meta->sw_count;
int32_t bs_count = meta->bs_count;
if (sw_count) {
meta->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count);
for (i = 0; i < sw_count; i ++) {
flecs_switch_init(&meta->sw_columns[i],
&world->allocator, 0);
}
}
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;
int32_t i;
for (i = 0; i < count; i ++) {
ecs_id_t id = ids[i];
if (id <= EcsLastInternalComponentId) {
table->flags |= EcsTableHasBuiltins;
}
if (id == EcsModule) {
table->flags |= EcsTableHasBuiltins;
table->flags |= EcsTableHasModule;
} else if (id == EcsPrefab) {
table->flags |= EcsTableIsPrefab;
} else if (id == EcsDisabled) {
table->flags |= EcsTableIsDisabled;
} 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 obj = ecs_pair_second(world, id);
ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL);
if (obj == EcsFlecs || obj == EcsFlecsCore ||
ecs_has_id(world, obj, EcsModule))
{
/* If table contains entities that are inside one of the
* builtin modules, it contains builtin entities */
table->flags |= EcsTableHasBuiltins;
table->flags |= EcsTableHasModule;
}
} else if (id == ecs_pair_t(EcsIdentifier, EcsName)) {
table->flags |= EcsTableHasName;
} else if (r == EcsUnion) {
ecs_table__t *meta = table->_;
table->flags |= EcsTableHasUnion;
if (!meta->sw_count) {
meta->sw_offset = flecs_ito(int16_t, i);
}
meta->sw_count ++;
} else if (r == ecs_id(EcsTarget)) {
ecs_table__t *meta = table->_;
table->flags |= EcsTableHasTarget;
meta->ft_offset = flecs_ito(int16_t, i);
} else if (r == ecs_id(EcsPoly)) {
table->flags |= EcsTableHasBuiltins;
}
} 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, 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_id_record_t *idr = flecs_id_record_ensure(world, id);
ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table(
idr, 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(&idr->cache, table, &tr->hdr);
} else {
tr->count ++;
}
ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL);
}
/* 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_id_record_t *idr, *childof_idr = 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;
}
}
/* 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];
idr = NULL;
if (dst_id == src_id) {
ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL);
idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache;
} else if (dst_id < src_id) {
idr = flecs_id_record_ensure(world, dst_id);
}
if (idr) {
tr = ecs_vec_append_t(a, records, ecs_table_record_t);
tr->hdr.cache = (ecs_table_cache_t*)idr;
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);
idr = flecs_id_record_ensure(world, dst_id);
tr->hdr.cache = (ecs_table_cache_t*)idr;
ecs_assert(tr->hdr.cache != 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);
}
/* 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_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache;
r = ECS_PAIR_FIRST(dst_id);
if (r == EcsChildOf) {
childof_idr = p_idr;
}
idr = p_idr->parent; /* (R, *) */
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
tr = ecs_vec_append_t(a, records, ecs_table_record_t);
tr->hdr.cache = (ecs_table_cache_t*)idr;
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.cache = (ecs_table_cache_t*)world->idr_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.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard;
tr->index = flecs_ito(int16_t, first_pair);
tr->count = flecs_ito(int16_t, last_pair - first_pair);
}
if (dst_count) {
tr = ecs_vec_append_t(a, records, ecs_table_record_t);
tr->hdr.cache = (ecs_table_cache_t*)world->idr_any;
tr->index = 0;
tr->count = 1;
}
if (dst_count && !has_childof) {
tr = ecs_vec_append_t(a, records, ecs_table_record_t);
childof_idr = world->idr_childof_0;
tr->hdr.cache = (ecs_table_cache_t*)childof_idr;
tr->index = 0;
tr->count = 1;
}
/* 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;
/* Register & patch up records */
for (i = 0; i < dst_record_count; i ++) {
tr = &dst_tr[i];
idr = (ecs_id_record_t*)dst_tr[i].hdr.cache;
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
if (ecs_table_cache_get(&idr->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 */
ecs_table_cache_replace(&idr->cache, table, &tr->hdr);
} else {
/* Other records are not registered yet */
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_table_cache_insert(&idr->cache, table, &tr->hdr);
}
/* Claim id record so it stays alive as long as the table exists */
flecs_id_record_claim(world, idr);
/* Initialize event flags */
table->flags |= idr->flags & EcsIdEventMask;
/* Initialize column index (will be overwritten by init_columns) */
tr->column = -1;
if (idr->flags & EcsIdAlwaysOverride) {
table->flags |= EcsTableHasOverrides;
}
if ((i < table->type.count) && (idr->type_info != NULL)) {
column_count ++;
}
}
if (column_count) {
table->column_map = flecs_walloc_n(world, int32_t,
dst_count + column_count);
}
table->column_count = flecs_ito(int16_t, column_count);
flecs_table_init_data(world, table);
if (table->flags & EcsTableHasName) {
ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL);
table->_->name_index =
flecs_id_record_name_index_ensure(world, childof_idr);
ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL);
}
if (table->flags & EcsTableHasOnTableCreate) {
flecs_emit(world, world, &(ecs_event_desc_t) {
.ids = &table->type,
.event = EcsOnTableCreate,
.table = table,
.flags = EcsEventTableOnly,
.observable = world
});
}
}
/* 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_table_cache_t *cache = tr->hdr.cache;
ecs_id_t id = ((ecs_id_record_t*)cache)->id;
ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL);
ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL);
ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache,
ECS_INTERNAL_ERROR, NULL);
(void)id;
ecs_table_cache_remove(cache, table_id, &tr->hdr);
flecs_id_record_release(world, (ecs_id_record_t*)cache);
}
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_entity_t event)
{
(void)world;
if (event == EcsOnAdd) {
table->flags |= EcsTableHasOnAdd;
} else if (event == EcsOnRemove) {
table->flags |= EcsTableHasOnRemove;
} else if (event == EcsOnSet) {
table->flags |= EcsTableHasOnSet;
} else if (event == EcsUnSet) {
table->flags |= EcsTableHasUnSet;
} else if (event == EcsOnTableFill) {
table->flags |= EcsTableHasOnTableFill;
} else if (event == EcsOnTableEmpty) {
table->flags |= EcsTableHasOnTableEmpty;
}
}
/* 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,
ecs_data_t *data)
{
int32_t count = data->entities.count;
if (count) {
flecs_notify_on_remove(world, table, NULL, 0, count, &table->type);
}
}
/* 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,
ecs_entity_t *entities,
int32_t row,
int32_t count)
{
void *ptr = ecs_vec_get(&column->data, column->size, row);
flecs_invoke_hook(world, table, count, row, entities, ptr, column->id,
column->ti, event, callback);
}
/* Construct components */
static
void flecs_table_invoke_ctor(
ecs_column_t *column,
int32_t row,
int32_t count)
{
ecs_type_info_t *ti = column->ti;
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_xtor_t ctor = ti->hooks.ctor;
if (ctor) {
void *ptr = ecs_vec_get(&column->data, column->size, row);
ctor(ptr, count, ti);
}
}
/* Destruct components */
static
void flecs_table_invoke_dtor(
ecs_column_t *column,
int32_t row,
int32_t count)
{
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_vec_get(&column->data, column->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,
ecs_column_t *column,
ecs_entity_t *entities,
int32_t row,
int32_t count,
bool construct)
{
ecs_type_info_t *ti = column->ti;
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
if (construct) {
flecs_table_invoke_ctor(column, 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_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);
}
}
/* Destruct all components and/or delete all entities in table in range */
static
void flecs_table_dtor_all(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t row,
int32_t count,
bool update_entity_index,
bool is_delete)
{
/* Can't delete and not update the entity index */
ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL);
int32_t ids_count = table->column_count;
ecs_entity_t *entities = data->entities.array;
int32_t i, c, end = row + count;
if (is_delete && table->_->traversable_count) {
/* If table contains monitored entities with traversable relationships,
* make sure to invalidate observer cache */
flecs_emit_propagate_invalidate(world, table, row, count);
}
/* If table has components with destructors, iterate component columns */
if (table->flags & EcsTableHasDtors) {
/* Throw up a lock just to be sure */
table->_->lock = true;
/* Run on_remove callbacks first before destructing components */
for (c = 0; c < ids_count; c++) {
ecs_column_t *column = &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[row], row, count);
}
}
/* Destruct components */
for (c = 0; c < ids_count; c++) {
flecs_table_invoke_dtor(&data->columns[c], row, 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 = row; i < end; i ++) {
/* Update entity index after invoking destructors so that entity can
* be safely used in destructor callbacks. */
if (update_entity_index) {
ecs_entity_t e = entities[i];
ecs_assert(!e || ecs_is_valid(world, e),
ECS_INTERNAL_ERROR, NULL);
if (is_delete) {
flecs_entities_remove(world, e);
ecs_assert(ecs_is_valid(world, e) == false,
ECS_INTERNAL_ERROR, NULL);
} else {
// If this is not a delete, clear the entity index record
ecs_record_t *record = flecs_entities_get(world, e);
record->table = NULL;
record->row = 0;
}
} else {
/* This should only happen in rare cases, such as when the data
* cleaned up is not part of the world (like with snapshots) */
}
}
table->_->lock = false;
/* If table does not have destructors, just update entity index */
} else if (update_entity_index) {
if (is_delete) {
for (i = row; i < end; i ++) {
ecs_entity_t e = entities[i];
ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL);
flecs_entities_remove(world, e);
ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL);
}
} else {
for (i = row; i < end; i ++) {
ecs_entity_t e = entities[i];
ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL);
ecs_record_t *record = flecs_entities_get(world, e);
record->table = NULL;
record->row = record->row & ECS_ROW_FLAGS_MASK;
(void)e;
}
}
}
}
/* Cleanup table storage */
static
void flecs_table_fini_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
bool do_on_remove,
bool update_entity_index,
bool is_delete,
bool deactivate)
{
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
if (!data) {
return;
}
if (do_on_remove) {
flecs_table_notify_on_remove(world, table, data);
}
int32_t count = flecs_table_data_count(data);
if (count) {
flecs_table_dtor_all(world, table, data, 0, count,
update_entity_index, is_delete);
}
ecs_column_t *columns = data->columns;
if (columns) {
int32_t c, column_count = table->column_count;
for (c = 0; c < column_count; c ++) {
/* Sanity check */
ecs_assert(columns[c].data.count == data->entities.count,
ECS_INTERNAL_ERROR, NULL);
ecs_vec_fini(&world->allocator,
&columns[c].data, columns[c].size);
}
flecs_wfree_n(world, ecs_column_t, column_count, columns);
data->columns = NULL;
}
ecs_table__t *meta = table->_;
ecs_switch_t *sw_columns = meta->sw_columns;
if (sw_columns) {
int32_t c, column_count = meta->sw_count;
for (c = 0; c < column_count; c ++) {
flecs_switch_fini(&sw_columns[c]);
}
flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns);
meta->sw_columns = NULL;
}
ecs_bitset_t *bs_columns = meta->bs_columns;
if (bs_columns) {
int32_t c, column_count = meta->bs_count;
for (c = 0; c < column_count; c ++) {
flecs_bitset_fini(&bs_columns[c]);
}
flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns);
meta->bs_columns = NULL;
}
ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t);
if (deactivate && count) {
flecs_table_set_empty(world, table);
}
table->_->traversable_count = 0;
table->flags &= ~EcsTableHasTraversable;
}
/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */
void flecs_table_clear_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data)
{
flecs_table_fini_data(world, table, data, false, false, false, false);
}
/* Cleanup, no OnRemove, clear entity index, deactivate table */
void flecs_table_clear_entities_silent(
ecs_world_t *world,
ecs_table_t *table)
{
flecs_table_fini_data(world, table, &table->data, false, true, false, true);
}
/* Cleanup, run OnRemove, clear entity index, deactivate table */
void flecs_table_clear_entities(
ecs_world_t *world,
ecs_table_t *table)
{
flecs_table_fini_data(world, table, &table->data, true, true, false, true);
}
/* Cleanup, run OnRemove, delete from entity index, deactivate table */
void flecs_table_delete_entities(
ecs_world_t *world,
ecs_table_t *table)
{
flecs_table_fini_data(world, table, &table->data, true, true, true, true);
}
/* Unset all components in table. This function is called before a table is
* deleted, and invokes all UnSet handlers, if any */
void flecs_table_remove_actions(
ecs_world_t *world,
ecs_table_t *table)
{
(void)world;
flecs_table_notify_on_remove(world, table, &table->data);
}
/* Free table resources. */
void flecs_table_free(
ecs_world_t *world,
ecs_table_t *table)
{
bool is_root = table == &world->store.root;
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
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;
if (!is_root && !(world->flags & EcsWorldQuit)) {
if (table->flags & EcsTableHasOnTableDelete) {
flecs_emit(world, world, &(ecs_event_desc_t) {
.ids = &table->type,
.event = EcsOnTableDelete,
.table = table,
.flags = EcsEventTableOnly,
.observable = world
});
}
}
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();
}
world->info.empty_table_count -= (ecs_table_count(table) == 0);
/* Cleanup data, no OnRemove, delete from entity index, don't deactivate */
flecs_table_fini_data(world, table, &table->data, false, true, true, false);
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_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state);
flecs_wfree_n(world, int32_t, table->column_count + table->type.count,
table->column_map);
flecs_table_records_unregister(world, table);
/* Update counters */
world->info.table_count --;
world->info.table_record_count -= table->_->record_count;
world->info.table_storage_count -= table->column_count;
world->info.table_delete_total ++;
if (!table->column_count) {
world->info.tag_table_count --;
} else {
world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex);
}
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_t(&world->store.tables, ecs_table_t, table->id);
}
ecs_log_pop_2();
}
/* 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, NULL);
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)
{
(void)world;
if (table->dirty_state) {
table->dirty_state[index] ++;
}
}
/* Mark table component dirty */
void flecs_table_mark_dirty(
ecs_world_t *world,
ecs_table_t *table,
ecs_entity_t component)
{
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
if (table->dirty_state) {
ecs_id_record_t *idr = flecs_id_record_get(world, component);
if (!idr) {
return;
}
const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
if (!tr || tr->column == -1) {
return;
}
table->dirty_state[tr->column + 1] ++;
}
}
/* 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)
{
ecs_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 switch (union relationship) column */
static
void flecs_table_move_switch_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->sw_count : 0;
int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->sw_count : 0;
if (!src_column_count && !dst_column_count) {
return;
}
ecs_switch_t *src_columns = src_meta ? src_meta->sw_columns : NULL;
ecs_switch_t *dst_columns = dst_meta ? dst_meta->sw_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->sw_offset : 0;
int32_t offset_old = src_meta ? src_meta->sw_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_entity_t dst_id = dst_ids[i_new + offset_new];
ecs_entity_t src_id = src_ids[i_old + offset_old];
if (dst_id == src_id) {
ecs_switch_t *src_switch = &src_columns[i_old];
ecs_switch_t *dst_switch = &dst_columns[i_new];
flecs_switch_ensure(dst_switch, dst_index + count);
int i;
for (i = 0; i < count; i ++) {
uint64_t value = flecs_switch_get(src_switch, src_index + i);
flecs_switch_set(dst_switch, dst_index + i, value);
}
if (clear) {
ecs_assert(count == flecs_switch_count(src_switch),
ECS_INTERNAL_ERROR, NULL);
flecs_switch_clear(src_switch);
}
} else if (dst_id > src_id) {
ecs_switch_t *src_switch = &src_columns[i_old];
flecs_switch_clear(src_switch);
}
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_switch_t *src_switch = &src_columns[i_old];
ecs_assert(count == flecs_switch_count(src_switch),
ECS_INTERNAL_ERROR, NULL);
flecs_switch_clear(src_switch);
}
}
}
/* 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_column_t *column,
int32_t to_add,
int32_t dst_size,
bool construct)
{
ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_type_info_t *ti = column->ti;
int32_t size = column->size;
int32_t count = column->data.count;
int32_t src_size = column->data.size;
int32_t dst_count = count + to_add;
bool can_realloc = dst_size != src_size;
void *result = NULL;
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_xtor_t ctor = ti->hooks.ctor;
ecs_assert(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, size, dst_size);
dst.count = dst_count;
void *src_buffer = column->data.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) */
result = ECS_ELEM(dst_buffer, size, count);
ctor(result, to_add, ti);
}
/* Free old vector */
ecs_vec_fini(&world->allocator, &column->data, size);
column->data = 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->data, size, dst_size);
}
result = ecs_vec_grow(&world->allocator, &column->data, size, to_add);
ecs_xtor_t ctor;
if (construct && (ctor = ti->hooks.ctor)) {
/* If new elements need to be constructed and component has a
* constructor, construct */
ctor(result, to_add, ti);
}
}
ecs_assert(column->data.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,
ecs_data_t *data,
int32_t to_add,
int32_t size,
const ecs_entity_t *ids)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t cur_count = flecs_table_data_count(data);
int32_t column_count = table->column_count;
/* Add entity to column with entity ids */
ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size);
ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1;
data->entities.count += to_add;
if (data->entities.size > size) {
size = data->entities.size;
}
/* Initialize entity ids and record ptrs */
int32_t i;
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);
}
/* Add elements to each column array */
ecs_column_t *columns = data->columns;
for (i = 0; i < column_count; i ++) {
flecs_table_grow_column(world, &columns[i], to_add, size, true);
ecs_assert(columns[i].data.size == size, ECS_INTERNAL_ERROR, NULL);
flecs_table_invoke_add_hooks(world, table, &columns[i], e,
cur_count, to_add, false);
}
ecs_table__t *meta = table->_;
int32_t sw_count = meta->sw_count;
int32_t bs_count = meta->bs_count;
ecs_switch_t *sw_columns = meta->sw_columns;
ecs_bitset_t *bs_columns = meta->bs_columns;
/* Add elements to each switch column */
for (i = 0; i < sw_count; i ++) {
ecs_switch_t *sw = &sw_columns[i];
flecs_switch_addn(sw, to_add);
}
/* 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);
if (!(world->flags & EcsWorldReadonly) && !cur_count) {
flecs_table_set_empty(world, table);
}
/* Return index of first added entity */
return cur_count;
}
/* Append operation for tables that don't have any complex logic */
static
void flecs_table_fast_append(
ecs_world_t *world,
ecs_column_t *columns,
int32_t count)
{
/* Add elements to each column array */
int32_t i;
for (i = 0; i < count; i ++) {
ecs_column_t *column = &columns[i];
ecs_vec_append(&world->allocator, &column->data, column->size);
}
}
/* 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, NULL);
ecs_assert(!(table->flags & EcsTableHasTarget),
ECS_INVALID_OPERATION, NULL);
flecs_table_check_sanity(table);
/* Get count & size before growing entities array. This tells us whether the
* arrays will realloc */
ecs_data_t *data = &table->data;
int32_t count = data->entities.count;
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_entity_t *e = ecs_vec_append_t(&world->allocator,
&data->entities, ecs_entity_t);
ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL);
*e = entity;
/* If the table is monitored indicate that there has been a change */
flecs_table_mark_table_dirty(world, table, 0);
ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL);
/* Fast path: no switch columns, no lifecycle actions */
if (!(table->flags & EcsTableIsComplex)) {
flecs_table_fast_append(world, columns, column_count);
if (!count) {
flecs_table_set_empty(world, table); /* See below */
}
return count;
}
ecs_entity_t *entities = data->entities.array;
/* 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 = data->entities.size;
/* Grow component arrays with 1 element */
int32_t i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
flecs_table_grow_column(world, column, 1, size, construct);
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(columns[i].data.size ==
data->entities.size, ECS_INTERNAL_ERROR, NULL);
ecs_assert(columns[i].data.count ==
data->entities.count, ECS_INTERNAL_ERROR, NULL);
}
ecs_table__t *meta = table->_;
int32_t sw_count = meta->sw_count;
int32_t bs_count = meta->bs_count;
ecs_switch_t *sw_columns = meta->sw_columns;
ecs_bitset_t *bs_columns = meta->bs_columns;
/* Add element to each switch column */
for (i = 0; i < sw_count; i ++) {
ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_switch_t *sw = &sw_columns[i];
flecs_switch_add(sw);
}
/* 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);
}
/* If this is the first entity in this table, signal queries so that the
* table moves from an inactive table to an active table. */
if (!count) {
flecs_table_set_empty(world, table);
}
flecs_table_check_sanity(table);
return count;
}
/* Delete last operation for tables that don't have any complex logic */
static
void flecs_table_fast_delete_last(
ecs_column_t *columns,
int32_t column_count)
{
int i;
for (i = 0; i < column_count; i ++) {
ecs_vec_remove_last(&columns[i].data);
}
}
/* Delete operation for tables that don't have any complex logic */
static
void flecs_table_fast_delete(
ecs_column_t *columns,
int32_t column_count,
int32_t index)
{
int i;
for (i = 0; i < column_count; i ++) {
ecs_column_t *column = &columns[i];
ecs_vec_remove(&column->data, column->size, index);
}
}
/* Delete entity from table */
void flecs_table_delete(
ecs_world_t *world,
ecs_table_t *table,
int32_t index,
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, NULL);
ecs_assert(!(table->flags & EcsTableHasTarget),
ECS_INVALID_OPERATION, NULL);
flecs_table_check_sanity(table);
ecs_data_t *data = &table->data;
int32_t count = data->entities.count;
ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL);
count --;
ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL);
/* Move last entity id to index */
ecs_entity_t *entities = data->entities.array;
ecs_entity_t entity_to_move = entities[count];
ecs_entity_t entity_to_delete = entities[index];
entities[index] = entity_to_move;
ecs_vec_remove_last(&data->entities);
/* Update record of moved entity in entity index */
if (index != 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(index, 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);
/* If table is empty, deactivate it */
if (!count) {
flecs_table_set_empty(world, table);
}
/* Destruct component data */
ecs_column_t *columns = 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 (index == count) {
flecs_table_fast_delete_last(columns, column_count);
} else {
flecs_table_fast_delete(columns, column_count, index);
}
flecs_table_check_sanity(table);
return;
}
/* Last element, destruct & remove */
if (index == 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, index, 1, true);
}
}
flecs_table_fast_delete_last(columns, column_count);
/* 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 = column->size;
void *dst = ecs_vec_get(&column->data, size, index);
void *src = ecs_vec_last(&column->data, size);
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, index, 1);
}
ecs_move_t move_dtor = ti->hooks.move_dtor;
if (move_dtor) {
move_dtor(dst, src, 1, ti);
} else {
ecs_os_memcpy(dst, src, size);
}
ecs_vec_remove_last(&column->data);
}
} else {
flecs_table_fast_delete(columns, column_count, index);
}
}
/* Remove elements from switch columns */
ecs_table__t *meta = table->_;
ecs_switch_t *sw_columns = meta->sw_columns;
int32_t sw_count = meta->sw_count;
for (i = 0; i < sw_count; i ++) {
flecs_switch_remove(&sw_columns[i], index);
}
/* Remove elements from bitset columns */
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], index);
}
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 = dst_column->id;
ecs_id_t src_id = src_column->id;
if (dst_id == src_id) {
int32_t size = dst_column->size;
void *dst = ecs_vec_get(&dst_column->data, size, dst_index);
void *src = ecs_vec_get(&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, NULL);
ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL);
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)) {
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_move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false);
flecs_table_move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false);
/* If the source and destination entities are the same, move component
* between tables. If the entities are not the same (like when cloning) use
* a copy. */
bool same_entity = dst_entity == src_entity;
/* 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 = dst_column->id;
ecs_id_t src_id = src_column->id;
if (dst_id == src_id) {
int32_t size = dst_column->size;
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
void *dst = ecs_vec_get(&dst_column->data, size, dst_index);
void *src = ecs_vec_get(&src_column->data, size, src_index);
ecs_type_info_t *ti = dst_column->ti;
if (same_entity) {
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 {
ecs_copy_t copy = ti->hooks.copy_ctor;
if (copy) {
copy(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,
dst_column, &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, &dst_columns[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,
ecs_data_t *data,
int32_t to_add,
const ecs_entity_t *ids)
{
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
flecs_table_check_sanity(table);
int32_t cur_count = flecs_table_data_count(data);
int32_t result = flecs_table_grow_data(
world, table, data, to_add, cur_count + to_add, ids);
flecs_table_check_sanity(table);
return result;
}
/* Set allocated table size */
void flecs_table_set_size(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data,
int32_t size)
{
ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL);
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
flecs_table_check_sanity(table);
int32_t cur_count = flecs_table_data_count(data);
if (cur_count < size) {
flecs_table_grow_data(world, table, data, 0, size, NULL);
flecs_table_check_sanity(table);
}
}
/* 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_LOCKED_STORAGE, NULL);
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
(void)world;
flecs_table_check_sanity(table);
ecs_data_t *data = &table->data;
bool has_payload = data->entities.array != NULL;
ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t);
int32_t i, count = table->column_count;
for (i = 0; i < count; i ++) {
ecs_column_t *column = &data->columns[i];
ecs_vec_reclaim(&world->allocator, &column->data, column->size);
}
return has_payload;
}
/* Return number of entities in table */
int32_t flecs_table_data_count(
const ecs_data_t *data)
{
return data ? data->entities.count : 0;
}
/* Swap operation for switch (union relationship) columns */
static
void flecs_table_swap_switch_columns(
ecs_table_t *table,
int32_t row_1,
int32_t row_2)
{
int32_t i = 0, column_count = table->_->sw_count;
if (!column_count) {
return;
}
ecs_switch_t *columns = table->_->sw_columns;
for (i = 0; i < column_count; i ++) {
ecs_switch_t *sw = &columns[i];
flecs_switch_swap(sw, row_1, row_2);
}
}
/* 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, NULL);
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.array;
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_switch_columns(table, row_1, row_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].size);
}
void* tmp = ecs_os_alloca(temp_buffer_size);
/* Swap columns */
for (i = 0; i < column_count; i ++) {
int32_t size = columns[i].size;
void *ptr = columns[i].data.array;
void *el_1 = ECS_ELEM(ptr, size, row_1);
void *el_2 = ECS_ELEM(ptr, size, row_2);
ecs_os_memcpy(tmp, el_1, size);
ecs_os_memcpy(el_1, el_2, size);
ecs_os_memcpy(el_2, tmp, size);
}
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_column_t *dst,
ecs_column_t *src,
int32_t column_size)
{
ecs_size_t size = dst->size;
int32_t dst_count = dst->data.count;
if (!dst_count) {
ecs_vec_fini(&world->allocator, &dst->data, size);
*dst = *src;
src->data.array = NULL;
src->data.count = 0;
src->data.size = 0;
/* If the new table is not empty, copy the contents from the
* src into the dst. */
} else {
int32_t src_count = src->data.count;
flecs_table_grow_column(world, dst, src_count, column_size, true);
void *dst_ptr = ECS_ELEM(dst->data.array, size, dst_count);
void *src_ptr = src->data.array;
/* Move values into column */
ecs_type_info_t *ti = dst->ti;
ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_move_t move = ti->hooks.move_dtor;
if (move) {
move(dst_ptr, src_ptr, src_count, ti);
} else {
ecs_os_memcpy(dst_ptr, src_ptr, size * src_count);
}
ecs_vec_fini(&world->allocator, &src->data, size);
}
}
/* 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 src_count,
int32_t dst_count,
ecs_data_t *src_data,
ecs_data_t *dst_data)
{
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_data->columns;
ecs_column_t *dst_columns = dst_data->columns;
ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL);
if (!src_count) {
return;
}
/* Merge entities */
flecs_table_merge_vec(world, &dst_data->entities, &src_data->entities,
ECS_SIZEOF(ecs_entity_t), 0);
ecs_assert(dst_data->entities.count == src_count + dst_count,
ECS_INTERNAL_ERROR, NULL);
int32_t column_size = dst_data->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 = dst_column->id;
ecs_id_t src_id = src_column->id;
if (dst_id == src_id) {
flecs_table_merge_column(world, 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_size_t size = dst_column->size;
ecs_vec_set_size(a, &dst_column->data, size, column_size);
ecs_vec_set_count(a, &dst_column->data, size, src_count + dst_count);
flecs_table_invoke_ctor(dst_column, 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_column->data, src_column->size);
i_old ++;
}
}
flecs_table_move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true);
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 size = column->size;
ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL);
ecs_vec_set_size(a, &column->data, size, column_size);
ecs_vec_set_count(a, &column->data, size, src_count + dst_count);
flecs_table_invoke_ctor(column, dst_count, src_count);
}
/* Destruct remaining columns */
for (; i_old < src_column_count; i_old ++) {
ecs_column_t *column = &src_columns[i_old];
flecs_table_invoke_dtor(column, 0, src_count);
ecs_vec_fini(a, &column->data, column->size);
}
/* Mark entity column as dirty */
flecs_table_mark_table_dirty(world, dst_table, 0);
}
/* 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_data_t *dst_data,
ecs_data_t *src_data)
{
ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL);
flecs_table_check_sanity(src_table);
flecs_table_check_sanity(dst_table);
bool move_data = false;
/* If there is nothing to merge to, just clear the old table */
if (!dst_table) {
flecs_table_clear_data(world, src_table, src_data);
flecs_table_check_sanity(src_table);
return;
} else {
ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL);
}
/* If there is no data to merge, drop out */
if (!src_data) {
return;
}
if (!dst_data) {
dst_data = &dst_table->data;
if (dst_table == src_table) {
move_data = true;
}
}
ecs_entity_t *src_entities = src_data->entities.array;
int32_t src_count = src_data->entities.count;
int32_t dst_count = dst_data->entities.count;
/* 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 */
if (move_data) {
*dst_data = *src_data;
} else {
flecs_table_merge_data(world, dst_table, src_table, src_count, dst_count,
src_data, dst_data);
}
if (src_count) {
if (!dst_count) {
flecs_table_set_empty(world, dst_table);
}
flecs_table_set_empty(world, src_table);
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);
}
/* Replace data with other data. Used by snapshots to restore previous state. */
void flecs_table_replace_data(
ecs_world_t *world,
ecs_table_t *table,
ecs_data_t *data)
{
int32_t prev_count = 0;
ecs_data_t *table_data = &table->data;
ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL);
ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL);
flecs_table_check_sanity(table);
prev_count = table_data->entities.count;
flecs_table_notify_on_remove(world, table, table_data);
flecs_table_clear_data(world, table, table_data);
if (data) {
table->data = *data;
} else {
flecs_table_init_data(world, table);
}
int32_t count = ecs_table_count(table);
if (!prev_count && count) {
flecs_table_set_empty(world, table);
} else if (prev_count && !count) {
flecs_table_set_empty(world, table);
}
flecs_table_check_sanity(table);
}
/* Internal mechanism for propagating information to tables */
void flecs_table_notify(
ecs_world_t *world,
ecs_table_t *table,
ecs_table_event_t *event)
{
if (world->flags & EcsWorldFini) {
return;
}
switch(event->kind) {
case EcsTableTriggersForId:
flecs_table_add_trigger_flags(world, table, event->event);
break;
case EcsTableNoTriggersForId:
break;
}
}
/* -- Public API -- */
void ecs_table_lock(
ecs_world_t *world,
ecs_table_t *table)
{
if (table) {
if (ecs_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 (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) {
table->_->lock --;
ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, NULL);
}
}
}
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)
{
ecs_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);
ecs_id_record_t *idr = flecs_id_record_get(world, id);
if (!idr) {
return -1;
}
ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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)
{
ecs_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);
ecs_id_record_t *idr = flecs_id_record_get(world, id);
if (!idr) {
return -1;
}
ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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);
int32_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, NULL);
ecs_column_t *column = &table->data.columns[index];
void *result = column->data.array;
if (offset) {
result = ECS_ELEM(result, column->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].size);
error:
return 0;
}
int32_t ecs_table_count(
const ecs_table_t *table)
{
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
return flecs_table_data_count(&table->data);
}
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;
}
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, NULL);
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;
}
int32_t flecs_table_column_to_union_index(
const ecs_table_t *table,
int32_t column)
{
int32_t sw_count = table->_->sw_count;
if (sw_count) {
int32_t sw_offset = table->_->sw_offset;
if (column >= sw_offset && column < (sw_offset + sw_count)){
return column - sw_offset;
}
}
return -1;
}
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);
}
int32_t flecs_table_observed_count(
const ecs_table_t *table)
{
return table->_->traversable_count;
}
void* ecs_record_get_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->size;
ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size,
ECS_INVALID_PARAMETER, NULL);
return ecs_vec_get(&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;
}