2584 lines
81 KiB
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;
|
|
}
|