/**
 * @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;
}
