/**
 * @file query.c
 * @brief Cached query implementation.
 * 
 * Cached queries store a list of matched tables. The inputs for a cached query
 * are a filter and an observer. The filter is used to initially populate the
 * cache, and an observer is used to keep the cacne up to date.
 * 
 * Cached queries additionally support features like sorting and grouping. 
 * With sorting, an application can iterate over entities that can be sorted by
 * a component. Grouping allows an application to group matched tables, which is
 * used internally to implement the cascade feature, and can additionally be 
 * used to implement things like world cells.
 */

#include "private_api.h"

static
uint64_t flecs_query_get_group_id(
    ecs_query_t *query,
    ecs_table_t *table)
{
    if (query->group_by) {
        return query->group_by(query->filter.world, table, 
            query->group_by_id, query->group_by_ctx);
    } else {
        return 0;
    }
}

static
void flecs_query_compute_group_id(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL);

    if (query->group_by) {
        ecs_table_t *table = match->table;
        ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);

        match->group_id = flecs_query_get_group_id(query, table);
    } else {
        match->group_id = 0;
    }
}

static
ecs_query_table_list_t* flecs_query_get_group(
    const ecs_query_t *query,
    uint64_t group_id)
{
    return ecs_map_get_deref(&query->groups, ecs_query_table_list_t, group_id);
}

static
ecs_query_table_list_t* flecs_query_ensure_group(
    ecs_query_t *query,
    uint64_t id)
{
    ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, 
        ecs_query_table_list_t, id);

    if (!group) {
        group = ecs_map_insert_alloc_t(&query->groups, 
            ecs_query_table_list_t, id);
        ecs_os_zeromem(group);
        if (query->on_group_create) {
            group->info.ctx = query->on_group_create(
                query->filter.world, id, query->group_by_ctx);
        }
    }

    return group;
}

static
void flecs_query_remove_group(
    ecs_query_t *query,
    uint64_t id)
{
    if (query->on_group_delete) {
        ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, 
            ecs_query_table_list_t, id);
        if (group) {
            query->on_group_delete(query->filter.world, id, 
                group->info.ctx, query->group_by_ctx);
        }
    }

    ecs_map_remove_free(&query->groups, id);
}

static
uint64_t flecs_query_default_group_by(
    ecs_world_t *world, 
    ecs_table_t *table, 
    ecs_id_t id, 
    void *ctx) 
{
    (void)ctx;

    ecs_id_t match;
    if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) {
        return ecs_pair_second(world, match);
    }
    return 0;
}

/* Find the last node of the group after which this group should be inserted */
static
ecs_query_table_match_t* flecs_query_find_group_insertion_node(
    ecs_query_t *query,
    uint64_t group_id)
{
    /* Grouping must be enabled */
    ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL);

    ecs_map_iter_t it = ecs_map_iter(&query->groups);
    ecs_query_table_list_t *list, *closest_list = NULL;
    uint64_t id, closest_id = 0;
    
    bool desc = false;

    if (query->cascade_by) {
        desc = (query->filter.terms[
            query->cascade_by - 1].src.flags & EcsDesc) != 0;
    }

    /* Find closest smaller group id */
    while (ecs_map_next(&it)) {
        id = ecs_map_key(&it);

        if (!desc) {
            if (id >= group_id) {
                continue;
            }
        } else {
            if (id <= group_id) {
                continue;
            }
        }

        list = ecs_map_ptr(&it);
        if (!list->last) {
            ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL);
            continue;
        }

        bool comp;
        if (!desc) {
            comp = ((group_id - id) < (group_id - closest_id));
        } else {
            comp = ((group_id - id) > (group_id - closest_id));
        }

        if (!closest_list || comp) {
            closest_id = id;
            closest_list = list;
        }
    }

    if (closest_list) {
        return closest_list->last;
    } else {
        return NULL; /* Group should be first in query */
    }
}

/* Initialize group with first node */
static
void flecs_query_create_group(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    uint64_t group_id = match->group_id;

    /* If query has grouping enabled & this is a new/empty group, find
     * the insertion point for the group */
    ecs_query_table_match_t *insert_after = flecs_query_find_group_insertion_node(
        query, group_id);

    if (!insert_after) {
        /* This group should appear first in the query list */
        ecs_query_table_match_t *query_first = query->list.first;
        if (query_first) {
            /* If this is not the first match for the query, insert before it */
            match->next = query_first;
            query_first->prev = match;
            query->list.first = match;
        } else {
            /* If this is the first match of the query, initialize its list */
            ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL);
            query->list.first = match;
            query->list.last = match;
        }
    } else {
        ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL);

        /* This group should appear after another group */
        ecs_query_table_match_t *insert_before = insert_after->next;
        match->prev = insert_after;
        insert_after->next = match;
        match->next = insert_before;
        if (insert_before) {
            insert_before->prev = match;
        } else {
            ecs_assert(query->list.last == insert_after, 
                ECS_INTERNAL_ERROR, NULL);
                
            /* This group should appear last in the query list */
            query->list.last = match;
        }
    }
}

/* Find the list the node should be part of */
static
ecs_query_table_list_t* flecs_query_get_node_list(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    if (query->group_by) {
        return flecs_query_get_group(query, match->group_id);
    } else {
        return &query->list;
    }
}

/* Find or create the list the node should be part of */
static
ecs_query_table_list_t* flecs_query_ensure_node_list(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    if (query->group_by) {
        return flecs_query_ensure_group(query, match->group_id);
    } else {
        return &query->list;
    }
}

/* Remove node from list */
static
void flecs_query_remove_table_node(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    ecs_query_table_match_t *prev = match->prev;
    ecs_query_table_match_t *next = match->next;

    ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL);

    ecs_query_table_list_t *list = flecs_query_get_node_list(query, match);

    if (!list || !list->first) {
        /* If list contains no matches, the match must be empty */
        ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL);
        return;
    }

    ecs_assert(prev != NULL || query->list.first == match, 
        ECS_INTERNAL_ERROR, NULL);
    ecs_assert(next != NULL || query->list.last == match, 
        ECS_INTERNAL_ERROR, NULL);

    if (prev) {
        prev->next = next;
    }
    if (next) {
        next->prev = prev;
    }

    ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL);
    list->info.table_count --;

    if (query->group_by) {
        uint64_t group_id = match->group_id;

        /* Make sure query.list is updated if this is the first or last group */
        if (query->list.first == match) {
            ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL);
            query->list.first = next;
            prev = next;
        }
        if (query->list.last == match) {
            ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL);
            query->list.last = prev;
            next = prev;
        }

        ecs_assert(query->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL);
        query->list.info.table_count --;
        list->info.match_count ++;

        /* Make sure group list only contains nodes that belong to the group */
        if (prev && prev->group_id != group_id) {
            /* The previous node belonged to another group */
            prev = next;
        }
        if (next && next->group_id != group_id) {
            /* The next node belonged to another group */
            next = prev;
        }

        /* Do check again, in case both prev & next belonged to another group */
        if ((!prev && !next) || (prev && prev->group_id != group_id)) {
            /* There are no more matches left in this group */
            flecs_query_remove_group(query, group_id);
            list = NULL;
        }
    }

    if (list) {
        if (list->first == match) {
            list->first = next;
        }
        if (list->last == match) {
            list->last = prev;
        }
    }

    match->prev = NULL;
    match->next = NULL;

    query->match_count ++;
}

/* Add node to list */
static
void flecs_query_insert_table_node(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    /* Node should not be part of an existing list */
    ecs_assert(match->prev == NULL && match->next == NULL, 
        ECS_INTERNAL_ERROR, NULL);

    /* If this is the first match, activate system */
    if (!query->list.first && query->filter.entity) {
        ecs_remove_id(query->filter.world, query->filter.entity, EcsEmpty);
    }

    flecs_query_compute_group_id(query, match);

    ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, match);
    if (list->last) {
        ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL);

        ecs_query_table_match_t *last = list->last;
        ecs_query_table_match_t *last_next = last->next;

        match->prev = last;
        match->next = last_next;
        last->next = match;

        if (last_next) {
            last_next->prev = match;
        }

        list->last = match;

        if (query->group_by) {
            /* Make sure to update query list if this is the last group */
            if (query->list.last == last) {
                query->list.last = match;
            }
        }
    } else {
        ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL);

        list->first = match;
        list->last = match;

        if (query->group_by) {
            /* Initialize group with its first node */
            flecs_query_create_group(query, match);
        }
    }

    if (query->group_by) {
        list->info.table_count ++;
        list->info.match_count ++;
    }

    query->list.info.table_count ++;
    query->match_count ++;

    ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL);

    ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL);
}

static
ecs_query_table_match_t* flecs_query_cache_add(
    ecs_world_t *world,
    ecs_query_table_t *elem)
{
    ecs_query_table_match_t *result = 
        flecs_bcalloc(&world->allocators.query_table_match);

    if (!elem->first) {
        elem->first = result;
        elem->last = result;
    } else {
        ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL);
        elem->last->next_match = result;
        elem->last = result;
    }

    return result;
}

typedef struct {
    ecs_table_t *table;
    int32_t column;
} flecs_table_column_t;

static
void flecs_query_get_column_for_term(
    ecs_query_t *query,
    ecs_query_table_match_t *match,
    int32_t t,
    flecs_table_column_t *out)
{
    const ecs_filter_t *filter = &query->filter;
    ecs_world_t *world = filter->world;
    ecs_term_t *term = &filter->terms[t];
    int32_t field = term->field_index;
    ecs_entity_t src = match->sources[field];
    ecs_table_t *table = NULL;
    int32_t column = -1;

    if (term->oper != EcsNot) {
        if (!src) {
            if (term->src.flags != EcsIsEntity) {
                table = match->table;
                column = match->storage_columns[field];
                if (column == -2) {
                    /* Shared field */
                    column = -1;
                }
            }
        } else {
            table = ecs_get_table(world, src);
            if (ecs_term_match_this(term)) {
                int32_t ref_index = -match->columns[field] - 1;
                ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index);
                if (ref->id != 0) {
                    ecs_ref_update(world, ref);
                    column = ref->tr->column;
                }
            } else {
                column = -(match->columns[field] + 1);
            }
        }
    }

    out->table = table;
    out->column = column;
}

/* Get match monitor. Monitors are used to keep track of whether components 
 * matched by the query in a table have changed. */
static
bool flecs_query_get_match_monitor(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    if (match->monitor) {
        return false;
    }

    int32_t *monitor = flecs_balloc(&query->allocators.monitors);
    monitor[0] = 0;

    /* Mark terms that don't need to be monitored. This saves time when reading
     * and/or updating the monitor. */
    const ecs_filter_t *f = &query->filter;
    int32_t i, field = -1, term_count = f->term_count;
    flecs_table_column_t tc;

    for (i = 0; i < term_count; i ++) {
        if (field == f->terms[i].field_index) {
            if (monitor[field + 1] != -1) {
                continue;
            }
        }

        field = f->terms[i].field_index;
        monitor[field + 1] = -1;

        if (f->terms[i].inout != EcsIn && 
            f->terms[i].inout != EcsInOut &&
            f->terms[i].inout != EcsInOutDefault) {
            continue; /* If term isn't read, don't monitor */
        }

        int32_t column = match->columns[field];
        if (column == 0) {
            continue; /* Don't track terms that aren't matched */
        }

        flecs_query_get_column_for_term(query, match, i, &tc);
        if (tc.column == -1) {
            continue; /* Don't track terms that aren't stored */
        }

        monitor[field + 1] = 0;
    }

    /* If matched table needs entity filter, make sure to test fields that could
     * be matched by flattened parents. */
    ecs_entity_filter_t *ef = match->entity_filter;
    if (ef && ef->flat_tree_column != -1) {
        int32_t *fields = ecs_vec_first(&ef->ft_terms);
        int32_t field_count = ecs_vec_count(&ef->ft_terms);
        for (i = 0; i < field_count; i ++) {
            monitor[fields[i] + 1] = 0;
        }
    }

    match->monitor = monitor;

    query->flags |= EcsQueryHasMonitor;

    return true;
}

/* Synchronize match monitor with table dirty state */
static
void flecs_query_sync_match_monitor(
    ecs_query_t *query,
    ecs_query_table_match_t *match)
{
    ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL);
    if (!match->monitor) {
        if (query->flags & EcsQueryHasMonitor) {
            flecs_query_get_match_monitor(query, match);
        } else {
            return;
        }
    }

    int32_t *monitor = match->monitor;
    ecs_table_t *table = match->table;
    if (table) {
        int32_t *dirty_state = flecs_table_get_dirty_state(
            query->filter.world, table);
        ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
        monitor[0] = dirty_state[0]; /* Did table gain/lose entities */
    }

    ecs_filter_t *filter = &query->filter;
    {
        flecs_table_column_t tc;
        int32_t t, term_count = filter->term_count;
        for (t = 0; t < term_count; t ++) {
            int32_t field = filter->terms[t].field_index;
            if (monitor[field + 1] == -1) {
                continue;
            }

            flecs_query_get_column_for_term(query, match, t, &tc);

            monitor[field + 1] = flecs_table_get_dirty_state(
                filter->world, tc.table)[tc.column + 1];
        }
    }

    ecs_entity_filter_t *ef = match->entity_filter;
    if (ef && ef->flat_tree_column != -1) {
        flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms);
        int32_t f, field_count = ecs_vec_count(&ef->ft_terms);
        for (f = 0; f < field_count; f ++) {
            flecs_flat_table_term_t *field = &fields[f];
            flecs_flat_monitor_t *tgt_mon = ecs_vec_first(&field->monitor);
            int32_t tgt, tgt_count = ecs_vec_count(&field->monitor);
            for (tgt = 0; tgt < tgt_count; tgt ++) {
                tgt_mon[tgt].monitor = tgt_mon[tgt].table_state;
            }
        }
    }

    query->prev_match_count = query->match_count;
}

/* Check if single match term has changed */
static
bool flecs_query_check_match_monitor_term(
    ecs_query_t *query,
    ecs_query_table_match_t *match,
    int32_t term)
{
    ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL);

    if (flecs_query_get_match_monitor(query, match)) {
        return true;
    }
    
    int32_t *monitor = match->monitor;
    int32_t state = monitor[term];
    if (state == -1) {
        return false;
    }

    ecs_table_t *table = match->table;
    if (table) {
        int32_t *dirty_state = flecs_table_get_dirty_state(
            query->filter.world, table);
        ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
        if (!term) {
            return monitor[0] != dirty_state[0];
        }
    } else if (!term) {
        return false;
    }

    flecs_table_column_t cur;
    flecs_query_get_column_for_term(query, match, term - 1, &cur);
    ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL);

    return monitor[term] != flecs_table_get_dirty_state(
        query->filter.world, cur.table)[cur.column + 1];
}

/* Check if any term for match has changed */
static
bool flecs_query_check_match_monitor(
    ecs_query_t *query,
    ecs_query_table_match_t *match,
    const ecs_iter_t *it)
{
    ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL);

    if (flecs_query_get_match_monitor(query, match)) {
        return true;
    }

    int32_t *monitor = match->monitor;
    ecs_table_t *table = match->table;
    int32_t *dirty_state = NULL;
    if (table) {
        dirty_state = flecs_table_get_dirty_state(
            query->filter.world, table);
        ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
        if (monitor[0] != dirty_state[0]) {
            return true;
        }
    }

    bool has_flat = false, is_this = false;
    const ecs_filter_t *filter = &query->filter;
    ecs_world_t *world = filter->world;
    int32_t i, j, field_count = filter->field_count;
    int32_t *storage_columns = match->storage_columns;
    int32_t *columns = it ? it->columns : NULL;
    if (!columns) {
        columns = match->columns;
    }
    ecs_vec_t *refs = &match->refs;
    for (i = 0; i < field_count; i ++) {
        int32_t mon = monitor[i + 1];
        if (mon == -1) {
            continue;
        }

        int32_t column = storage_columns[i];
        if (columns[i] >= 0) {
            /* owned component */
            ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL);
            if (mon != dirty_state[column + 1]) {
                return true;
            }
            continue;
        } else if (column == -1) {
            continue; /* owned but not a component */
        }

        column = columns[i];
        if (!column) {
            /* Not matched */
            continue;
        }

        ecs_assert(column < 0, ECS_INTERNAL_ERROR, NULL);
        column = -column;

        /* Find term index from field index, which differ when using || */
        int32_t term_index = i;
        if (filter->terms[i].field_index != i) {
            for (j = i; j < filter->term_count; j ++) {
                if (filter->terms[j].field_index == i) {
                    term_index = j;
                    break;
                }
            }
        }

        is_this = ecs_term_match_this(&filter->terms[term_index]);

        /* Flattened fields are encoded by adding field_count to the column
         * index of the parent component. */
        if (is_this && it && (column > field_count)) {
            has_flat = true;
        } else {
            if (is_this) {
                /* Component reached through traversal from this */
                int32_t ref_index = column - 1;
                ecs_ref_t *ref = ecs_vec_get_t(refs, ecs_ref_t, ref_index);
                if (ref->id != 0) {
                    ecs_ref_update(world, ref);
                    ecs_table_record_t *tr = ref->tr;
                    ecs_table_t *src_table = tr->hdr.table;
                    column = tr->index;
                    column = ecs_table_type_to_column_index(src_table, column);
                    int32_t *src_dirty_state = flecs_table_get_dirty_state(
                        world, src_table);
                    if (mon != src_dirty_state[column + 1]) {
                        return true;
                    }
                }
            } else {
                /* Component from static source */
                ecs_entity_t src = match->sources[i];
                ecs_table_t *src_table = ecs_get_table(world, src);
                ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL);
                column = ecs_table_type_to_column_index(src_table, column - 1);
                int32_t *src_dirty_state = flecs_table_get_dirty_state(
                    world, src_table);
                if (mon != src_dirty_state[column + 1]) {
                    return true;
                }
            }
        }
    }

    if (has_flat) {
        ecs_entity_filter_t *ef = match->entity_filter;
        flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms);
        ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter;
        int32_t cur_tgt = ent_it->target_count - 1;
        field_count = ecs_vec_count(&ef->ft_terms);

        for (i = 0; i < field_count; i ++) {
            flecs_flat_table_term_t *field = &fields[i];
            flecs_flat_monitor_t *fmon = ecs_vec_get_t(&field->monitor, 
                flecs_flat_monitor_t, cur_tgt);
            if (fmon->monitor != fmon->table_state) {
                return true;
            }
        }
    }

    return false;
}

/* Check if any term for matched table has changed */
static
bool flecs_query_check_table_monitor(
    ecs_query_t *query,
    ecs_query_table_t *table,
    int32_t term)
{
    ecs_query_table_match_t *cur, *end = table->last->next;

    for (cur = table->first; cur != end; cur = cur->next) {
        ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur;
        if (term == -1) {
            if (flecs_query_check_match_monitor(query, match, NULL)) {
                return true;
            }
        } else {
            if (flecs_query_check_match_monitor_term(query, match, term)) {
                return true;
            } 
        }
    }

    return false;
}

static
bool flecs_query_check_query_monitor(
    ecs_query_t *query)
{
    ecs_table_cache_iter_t it;
    if (flecs_table_cache_iter(&query->cache, &it)) {
        ecs_query_table_t *qt;
        while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) {
            if (flecs_query_check_table_monitor(query, qt, -1)) {
                return true;
            }
        }
    }

    return false;
}

static
void flecs_query_init_query_monitors(
    ecs_query_t *query)
{
    ecs_query_table_match_t *cur = query->list.first;

    /* Ensure each match has a monitor */
    for (; cur != NULL; cur = cur->next) {
        ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur;
        flecs_query_get_match_monitor(query, match);
    }
}

/* The group by function for cascade computes the tree depth for the table type.
 * This causes tables in the query cache to be ordered by depth, which ensures
 * breadth-first iteration order. */
static
uint64_t flecs_query_group_by_cascade(
    ecs_world_t *world,
    ecs_table_t *table,
    ecs_id_t id,
    void *ctx)
{
    (void)id;
    ecs_term_t *term = ctx;
    ecs_entity_t rel = term->src.trav;
    int32_t depth = flecs_relation_depth(world, rel, table);
    return flecs_ito(uint64_t, depth);
}

static
void flecs_query_add_ref(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_query_table_match_t *qm,
    ecs_entity_t component,
    ecs_entity_t entity,
    ecs_size_t size)
{
    ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL);
    ecs_ref_t *ref = ecs_vec_append_t(&world->allocator, &qm->refs, ecs_ref_t);
    ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL);
    
    if (size) {
        *ref = ecs_ref_init_id(world, entity, component);
    } else {
        *ref = (ecs_ref_t){
            .entity = entity,
            .id = 0
        };
    }

    query->flags |= EcsQueryHasRefs;
}

static
ecs_query_table_match_t* flecs_query_add_table_match(
    ecs_query_t *query,
    ecs_query_table_t *qt,
    ecs_table_t *table)
{
    /* Add match for table. One table can have more than one match, if
     * the query contains wildcards. */
    ecs_query_table_match_t *qm = flecs_query_cache_add(query->filter.world, qt);
    qm->table = table;

    qm->columns = flecs_balloc(&query->allocators.columns);
    qm->storage_columns = flecs_balloc(&query->allocators.columns);
    qm->ids = flecs_balloc(&query->allocators.ids);
    qm->sources = flecs_balloc(&query->allocators.sources);

    /* Insert match to iteration list if table is not empty */
    if (!table || ecs_table_count(table) != 0) {
        flecs_query_insert_table_node(query, qm);
    }

    return qm;
}

static
void flecs_query_set_table_match(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_query_table_match_t *qm,
    ecs_table_t *table,
    ecs_iter_t *it)
{
    ecs_allocator_t *a = &world->allocator;
    ecs_filter_t *filter = &query->filter;
    int32_t i, term_count = filter->term_count;
    int32_t field_count = filter->field_count;
    ecs_term_t *terms = filter->terms;

    /* Reset resources in case this is an existing record */
    ecs_vec_reset_t(a, &qm->refs, ecs_ref_t);
    ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count);
    ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count);
    ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count);

    if (table) {
        /* Initialize storage columns for faster access to component storage */
        for (i = 0; i < field_count; i ++) {
            if (terms[i].inout == EcsInOutNone) {
                qm->storage_columns[i] = -1;
                continue;
            }

            int32_t column = qm->columns[i];
            if (column > 0) {
                qm->storage_columns[i] = ecs_table_type_to_column_index(table,
                    qm->columns[i] - 1);
            } else {
                /* Shared field (not from table) */
                qm->storage_columns[i] = -2;
            }
        }

        flecs_entity_filter_init(world, &qm->entity_filter, filter, 
            table, qm->ids, qm->columns);

        if (qm->entity_filter) {
            query->flags &= ~EcsQueryTrivialIter;
        }
        if (table->flags & EcsTableHasUnion) {
            query->flags &= ~EcsQueryTrivialIter;
        }
    }

    /* Add references for substituted terms */
    for (i = 0; i < term_count; i ++) {
        ecs_term_t *term = &terms[i];
        if (!ecs_term_match_this(term)) {
            /* non-This terms are set during iteration */
            continue;
        }

        int32_t field = terms[i].field_index;
        ecs_entity_t src = it->sources[field];
        ecs_size_t size = 0;
        if (it->sizes) {
            size = it->sizes[field];
        }
        if (src) {
            ecs_id_t id = it->ids[field];
            ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL);

            if (id) {
                flecs_query_add_ref(world, query, qm, id, src, size);

                /* Use column index to bind term and ref */
                if (qm->columns[field] != 0) {
                    qm->columns[field] = -ecs_vec_count(&qm->refs);
                }
            }
        }
    }
}

static
ecs_query_table_t* flecs_query_table_insert(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_table_t *table)
{
    ecs_query_table_t *qt = flecs_bcalloc(&world->allocators.query_table);
    if (table) {
        qt->table_id = table->id;
    } else {
        qt->table_id = 0;
    }
    ecs_table_cache_insert(&query->cache, table, &qt->hdr);
    return qt;
}

/** Populate query cache with tables */
static
void flecs_query_match_tables(
    ecs_world_t *world,
    ecs_query_t *query)
{
    ecs_table_t *table = NULL;
    ecs_query_table_t *qt = NULL;

    ecs_iter_t it = ecs_filter_iter(world, &query->filter);
    ECS_BIT_SET(it.flags, EcsIterIsInstanced);
    ECS_BIT_SET(it.flags, EcsIterNoData);
    ECS_BIT_SET(it.flags, EcsIterTableOnly);
    ECS_BIT_SET(it.flags, EcsIterEntityOptional);

    while (ecs_filter_next(&it)) {
        if ((table != it.table) || (!it.table && !qt)) {
            /* New table matched, add record to cache */
            table = it.table;
            qt = flecs_query_table_insert(world, query, table);
        }

        ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table);
        flecs_query_set_table_match(world, query, qm, table, &it);
    }
}

static
bool flecs_query_match_table(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_table_t *table)
{
    if (!ecs_map_is_init(&query->cache.index)) {
        return false;
    }

    ecs_query_table_t *qt = NULL;
    ecs_filter_t *filter = &query->filter;
    int var_id = ecs_filter_find_this_var(filter);
    if (var_id == -1) {
        /* If query doesn't match with This term, it can't match with tables */
        return false;
    }

    ecs_iter_t it = flecs_filter_iter_w_flags(world, filter, EcsIterMatchVar|
        EcsIterIsInstanced|EcsIterNoData|EcsIterEntityOptional);
    ecs_iter_set_var_as_table(&it, var_id, table);

    while (ecs_filter_next(&it)) {
        ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL);
        if (qt == NULL) {
            table = it.table;
            qt = flecs_query_table_insert(world, query, table);
        }

        ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table);
        flecs_query_set_table_match(world, query, qm, table, &it);
    }

    return qt != NULL;
}

ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static)

static
void flecs_query_sort_table(
    ecs_world_t *world,
    ecs_table_t *table,
    int32_t column_index,
    ecs_order_by_action_t compare,
    ecs_sort_table_action_t sort)
{
    ecs_data_t *data = &table->data;
    if (!ecs_vec_count(&data->entities)) {
        /* Nothing to sort */
        return;
    }

    int32_t count = flecs_table_data_count(data);
    if (count < 2) {
        return;
    }

    ecs_entity_t *entities = ecs_vec_first(&data->entities);

    void *ptr = NULL;
    int32_t size = 0;
    if (column_index != -1) {
        ecs_column_t *column = &data->columns[column_index];
        ecs_type_info_t *ti = column->ti;
        size = ti->size;
        ptr = ecs_vec_first(&column->data);
    }

    if (sort) {
        sort(world, table, entities, ptr, size, 0, count - 1, compare);
    } else {
        flecs_query_sort_table_generic(world, table, entities, ptr, size, 0, count - 1, compare);
    }
}

/* Helper struct for building sorted table ranges */
typedef struct sort_helper_t {
    ecs_query_table_match_t *match;
    ecs_entity_t *entities;
    const void *ptr;
    int32_t row;
    int32_t elem_size;
    int32_t count;
    bool shared;
} sort_helper_t;

static
const void* ptr_from_helper(
    sort_helper_t *helper)
{
    ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL);
    if (helper->shared) {
        return helper->ptr;
    } else {
        return ECS_ELEM(helper->ptr, helper->elem_size, helper->row);
    }
}

static
ecs_entity_t e_from_helper(
    sort_helper_t *helper)
{
    if (helper->row < helper->count) {
        return helper->entities[helper->row];
    } else {
        return 0;
    }
}

static
void flecs_query_build_sorted_table_range(
    ecs_query_t *query,
    ecs_query_table_list_t *list)
{
    ecs_world_t *world = query->filter.world;
    ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED,
        "cannot sort query in multithreaded mode");

    ecs_entity_t id = query->order_by_component;
    ecs_order_by_action_t compare = query->order_by;
    int32_t table_count = list->info.table_count;
    if (!table_count) {
        return;
    }

    ecs_vec_init_if_t(&query->table_slices, ecs_query_table_match_t);
    int32_t to_sort = 0;
    int32_t order_by_term = query->order_by_term;

    sort_helper_t *helper = flecs_alloc_n(
        &world->allocator, sort_helper_t, table_count);
    ecs_query_table_match_t *cur, *end = list->last->next;
    for (cur = list->first; cur != end; cur = cur->next) {
        ecs_table_t *table = cur->table;
        ecs_data_t *data = &table->data;

        ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL);

        if (id) {
            const ecs_term_t *term = &query->filter.terms[order_by_term];
            int32_t field = term->field_index;
            int32_t column = cur->columns[field];
            ecs_size_t size = query->filter.sizes[field];
            ecs_assert(column != 0, ECS_INTERNAL_ERROR, NULL);
            if (column >= 0) {
                column = table->column_map[column - 1];
                ecs_vec_t *vec = &data->columns[column].data;
                helper[to_sort].ptr = ecs_vec_first(vec);
                helper[to_sort].elem_size = size;
                helper[to_sort].shared = false;
            } else {
                ecs_entity_t src = cur->sources[field];
                ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL);
                ecs_record_t *r = flecs_entities_get(world, src);
                ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
                ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL);

                if (term->src.flags & EcsUp) {
                    ecs_entity_t base = 0;
                    ecs_search_relation(world, r->table, 0, id, 
                        EcsIsA, term->src.flags & EcsTraverseFlags, &base, 0, 0);
                    if (base && base != src) { /* Component could be inherited */
                        r = flecs_entities_get(world, base);
                    }
                }

                helper[to_sort].ptr = ecs_table_get_id(
                    world, r->table, id, ECS_RECORD_TO_ROW(r->row));
                helper[to_sort].elem_size = size;
                helper[to_sort].shared = true;
            }
            ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL);
            ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL);
        } else {
            helper[to_sort].ptr = NULL;
            helper[to_sort].elem_size = 0;
            helper[to_sort].shared = false;
        }

        helper[to_sort].match = cur;
        helper[to_sort].entities = ecs_vec_first(&data->entities);
        helper[to_sort].row = 0;
        helper[to_sort].count = ecs_table_count(table);
        to_sort ++;      
    }

    ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL);

    bool proceed;
    do {
        int32_t j, min = 0;
        proceed = true;

        ecs_entity_t e1;
        while (!(e1 = e_from_helper(&helper[min]))) {
            min ++;
            if (min == to_sort) {
                proceed = false;
                break;
            }
        }

        if (!proceed) {
            break;
        }

        for (j = min + 1; j < to_sort; j++) {
            ecs_entity_t e2 = e_from_helper(&helper[j]);
            if (!e2) {
                continue;
            }

            const void *ptr1 = ptr_from_helper(&helper[min]);
            const void *ptr2 = ptr_from_helper(&helper[j]);

            if (compare(e1, ptr1, e2, ptr2) > 0) {
                min = j;
                e1 = e_from_helper(&helper[min]);
            }
        }

        sort_helper_t *cur_helper = &helper[min];
        if (!cur || cur->columns != cur_helper->match->columns) {
            cur = ecs_vec_append_t(NULL, &query->table_slices, 
                ecs_query_table_match_t);
            *cur = *(cur_helper->match);
            cur->offset = cur_helper->row;
            cur->count = 1;
        } else {
            cur->count ++;
        }

        cur_helper->row ++;
    } while (proceed);

    /* Iterate through the vector of slices to set the prev/next ptrs. This
     * can't be done while building the vector, as reallocs may occur */
    int32_t i, count = ecs_vec_count(&query->table_slices);    
    ecs_query_table_match_t *nodes = ecs_vec_first(&query->table_slices);
    for (i = 0; i < count; i ++) {
        nodes[i].prev = &nodes[i - 1];
        nodes[i].next = &nodes[i + 1];
    }

    nodes[0].prev = NULL;
    nodes[i - 1].next = NULL;

    flecs_free_n(&world->allocator, sort_helper_t, table_count, helper);
}

static
void flecs_query_build_sorted_tables(
    ecs_query_t *query)
{
    ecs_vec_clear(&query->table_slices);

    if (query->group_by) {
        /* Populate sorted node list in grouping order */
        ecs_query_table_match_t *cur = query->list.first;
        if (cur) {
            do {
                /* Find list for current group */
                uint64_t group_id = cur->group_id;
                ecs_query_table_list_t *list = ecs_map_get_deref(
                    &query->groups, ecs_query_table_list_t, group_id);
                ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL);

                /* Sort tables in current group */
                flecs_query_build_sorted_table_range(query, list);

                /* Find next group to sort */
                cur = list->last->next;
            } while (cur);
        }
    } else {
        flecs_query_build_sorted_table_range(query, &query->list);
    }
}

static
void flecs_query_sort_tables(
    ecs_world_t *world,
    ecs_query_t *query)
{
    ecs_order_by_action_t compare = query->order_by;
    if (!compare) {
        return;
    }

    ecs_sort_table_action_t sort = query->sort_table;
    
    ecs_entity_t order_by_component = query->order_by_component;
    int32_t order_by_term = query->order_by_term;

    /* Iterate over non-empty tables. Don't bother with empty tables as they
     * have nothing to sort */

    bool tables_sorted = false;

    ecs_id_record_t *idr = flecs_id_record_get(world, order_by_component);
    ecs_table_cache_iter_t it;
    ecs_query_table_t *qt;
    flecs_table_cache_iter(&query->cache, &it);

    while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) {
        ecs_table_t *table = qt->hdr.table;
        bool dirty = false;

        if (flecs_query_check_table_monitor(query, qt, 0)) {
            dirty = true;
        }

        int32_t column = -1;
        if (order_by_component) {
            if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) {
                dirty = true;
            }

            if (dirty) {
                column = -1;

                const ecs_table_record_t *tr = flecs_id_record_get_table(
                    idr, table);
                if (tr) {
                    column = tr->column;
                }

                if (column == -1) {
                    /* Component is shared, no sorting is needed */
                    dirty = false;
                }
            }
        }

        if (!dirty) {
            continue;
        }

        /* Something has changed, sort the table. Prefers using 
         * flecs_query_sort_table when available */
        flecs_query_sort_table(world, table, column, compare, sort);
        tables_sorted = true;
    }

    if (tables_sorted || query->match_count != query->prev_match_count) {
        flecs_query_build_sorted_tables(query);
        query->match_count ++; /* Increase version if tables changed */
    }
}

static
bool flecs_query_has_refs(
    ecs_query_t *query)
{
    ecs_term_t *terms = query->filter.terms;
    int32_t i, count = query->filter.term_count;
    for (i = 0; i < count; i ++) {
        if (terms[i].src.flags & (EcsUp | EcsIsEntity)) {
            return true;
        }
    }

    return false;
}

static
void flecs_query_for_each_component_monitor(
    ecs_world_t *world,
    ecs_query_t *query,
    void(*callback)(
        ecs_world_t* world,
        ecs_id_t id,
        ecs_query_t *query))
{
    ecs_term_t *terms = query->filter.terms;
    int32_t i, count = query->filter.term_count;

    for (i = 0; i < count; i++) {
        ecs_term_t *term = &terms[i];
        ecs_term_id_t *src = &term->src;

        if (src->flags & EcsUp) {
            callback(world, ecs_pair(src->trav, EcsWildcard), query);
            if (src->trav != EcsIsA) {
                callback(world, ecs_pair(EcsIsA, EcsWildcard), query);
            }
            callback(world, term->id, query);

        } else if (src->flags & EcsSelf && !ecs_term_match_this(term)) {
            callback(world, term->id, query);
        }
    }
}

static
bool flecs_query_is_term_id_supported(
    ecs_term_id_t *term_id)
{
    if (!(term_id->flags & EcsIsVariable)) {
        return true;
    }
    if (ecs_id_is_wildcard(term_id->id)) {
        return true;
    }
    return false;
}

static
int flecs_query_process_signature(
    ecs_world_t *world,
    ecs_query_t *query)
{
    ecs_term_t *terms = query->filter.terms;
    int32_t i, count = query->filter.term_count;

    for (i = 0; i < count; i ++) {
        ecs_term_t *term = &terms[i];
        ecs_term_id_t *first = &term->first;
        ecs_term_id_t *src = &term->src;
        ecs_term_id_t *second = &term->second;
        ecs_inout_kind_t inout = term->inout;

        bool is_src_ok = flecs_query_is_term_id_supported(src);
        bool is_first_ok = flecs_query_is_term_id_supported(first);
        bool is_second_ok = flecs_query_is_term_id_supported(second);

        (void)first;
        (void)second;
        (void)is_src_ok;
        (void)is_first_ok;
        (void)is_second_ok;

        /* Queries do not support named variables */
        ecs_check(is_src_ok || ecs_term_match_this(term),
            ECS_UNSUPPORTED, NULL);
        ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL);
        ecs_check(is_second_ok,  ECS_UNSUPPORTED, NULL);
        ecs_check(!(src->flags & EcsFilter), ECS_INVALID_PARAMETER,
            "invalid usage of Filter for query");

        if (inout != EcsIn && inout != EcsInOutNone) {
            /* Non-this terms default to EcsIn */
            if (ecs_term_match_this(term) || inout != EcsInOutDefault) {
                query->flags |= EcsQueryHasOutTerms;
            }

            bool match_non_this = !ecs_term_match_this(term) || 
                (term->src.flags & EcsUp);
            if (match_non_this && inout != EcsInOutDefault) {
                query->flags |= EcsQueryHasNonThisOutTerms;
            }
        }

        if (src->flags & EcsCascade) {
            /* Query can only have one cascade column */
            ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL);
            query->cascade_by = i + 1;
        }
    }

    query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs);

    if (!(query->flags & EcsQueryIsSubquery)) {
        flecs_query_for_each_component_monitor(world, query, flecs_monitor_register);
    }

    return 0;
error:
    return -1;
}

/** When a table becomes empty remove it from the query list, or vice versa. */
static
void flecs_query_update_table(
    ecs_query_t *query,
    ecs_table_t *table,
    bool empty)
{
    int32_t prev_count = ecs_query_table_count(query);
    ecs_table_cache_set_empty(&query->cache, table, empty);
    int32_t cur_count = ecs_query_table_count(query);

    if (prev_count != cur_count) {
        ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table);
        ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_query_table_match_t *cur, *next;

        for (cur = qt->first; cur != NULL; cur = next) {
            next = cur->next_match;

            if (empty) {
                ecs_assert(ecs_table_count(table) == 0, 
                    ECS_INTERNAL_ERROR, NULL);

                flecs_query_remove_table_node(query, cur);
            } else {
                ecs_assert(ecs_table_count(table) != 0, 
                    ECS_INTERNAL_ERROR, NULL);
                flecs_query_insert_table_node(query, cur);
            }
        }
    }

    ecs_assert(cur_count || query->list.first == NULL, 
        ECS_INTERNAL_ERROR, NULL);
}

static
void flecs_query_add_subquery(
    ecs_world_t *world, 
    ecs_query_t *parent, 
    ecs_query_t *subquery) 
{
    ecs_vec_init_if_t(&parent->subqueries, ecs_query_t*);
    ecs_query_t **elem = ecs_vec_append_t(
        NULL, &parent->subqueries, ecs_query_t*);
    *elem = subquery;

    ecs_table_cache_t *cache = &parent->cache;
    ecs_table_cache_iter_t it;
    ecs_query_table_t *qt;
    flecs_table_cache_all_iter(cache, &it);
    while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) {
        flecs_query_match_table(world, subquery, qt->hdr.table);
    }
}

static
void flecs_query_notify_subqueries(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_query_event_t *event)
{
    if (query->subqueries.array) {
        ecs_query_t **queries = ecs_vec_first(&query->subqueries);
        int32_t i, count = ecs_vec_count(&query->subqueries);

        ecs_query_event_t sub_event = *event;
        sub_event.parent_query = query;

        for (i = 0; i < count; i ++) {
            ecs_query_t *sub = queries[i];
            flecs_query_notify(world, sub, &sub_event);
        }
    }
}

/* Remove table */
static
void flecs_query_table_match_free(
    ecs_query_t *query,
    ecs_query_table_t *elem,
    ecs_query_table_match_t *first)
{
    ecs_query_table_match_t *cur, *next;
    ecs_world_t *world = query->filter.world;

    for (cur = first; cur != NULL; cur = next) {
        flecs_bfree(&query->allocators.columns, cur->columns);
        flecs_bfree(&query->allocators.columns, cur->storage_columns);
        flecs_bfree(&query->allocators.ids, cur->ids);
        flecs_bfree(&query->allocators.sources, cur->sources);

        if (cur->monitor) {
            flecs_bfree(&query->allocators.monitors, cur->monitor);
        }
        if (!elem->hdr.empty) {
            flecs_query_remove_table_node(query, cur);
        }
        
        ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t);
        flecs_entity_filter_fini(world, cur->entity_filter);

        next = cur->next_match;

        flecs_bfree(&world->allocators.query_table_match, cur);
    }
}

static
void flecs_query_table_free(
    ecs_query_t *query,
    ecs_query_table_t *elem)
{
    flecs_query_table_match_free(query, elem, elem->first);
    flecs_bfree(&query->filter.world->allocators.query_table, elem);
}

static
void flecs_query_unmatch_table(
    ecs_query_t *query,
    ecs_table_t *table,
    ecs_query_table_t *elem)
{
    if (!elem) {
        elem = ecs_table_cache_get(&query->cache, table);
    }
    if (elem) {
        ecs_table_cache_remove(&query->cache, elem->table_id, &elem->hdr);
        flecs_query_table_free(query, elem);
    }
}

/* Rematch system with tables after a change happened to a watched entity */
static
void flecs_query_rematch_tables(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_query_t *parent_query)
{
    ecs_iter_t it, parent_it;
    ecs_table_t *table = NULL;
    ecs_query_table_t *qt = NULL;
    ecs_query_table_match_t *qm = NULL;

    if (query->monitor_generation == world->monitor_generation) {
        return;
    }

    query->monitor_generation = world->monitor_generation;

    if (parent_query) {
        parent_it = ecs_query_iter(world, parent_query);
        it = ecs_filter_chain_iter(&parent_it, &query->filter);
    } else {
        it = ecs_filter_iter(world, &query->filter);
    }

    ECS_BIT_SET(it.flags, EcsIterIsInstanced);
    ECS_BIT_SET(it.flags, EcsIterNoData);
    ECS_BIT_SET(it.flags, EcsIterEntityOptional);

    world->info.rematch_count_total ++;
    int32_t rematch_count = ++ query->rematch_count;

    ecs_time_t t = {0};
    if (world->flags & EcsWorldMeasureFrameTime) {
        ecs_time_measure(&t);
    }

    while (ecs_filter_next(&it)) {
        if ((table != it.table) || (!it.table && !qt)) {
            if (qm && qm->next_match) {
                flecs_query_table_match_free(query, qt, qm->next_match);
                qm->next_match = NULL;
            }

            table = it.table;

            qt = ecs_table_cache_get(&query->cache, table);
            if (!qt) {
                qt = flecs_query_table_insert(world, query, table);
            }

            ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL);
            qt->rematch_count = rematch_count;
            qm = NULL;
        }
        if (!qm) {
            qm = qt->first;
        } else {
            qm = qm->next_match;
        }
        if (!qm) {
            qm = flecs_query_add_table_match(query, qt, table);
        }

        flecs_query_set_table_match(world, query, qm, table, &it);

        if (table && ecs_table_count(table) && query->group_by) {
            if (flecs_query_get_group_id(query, table) != qm->group_id) {
                /* Update table group */
                flecs_query_remove_table_node(query, qm);
                flecs_query_insert_table_node(query, qm);
            }
        }
    }

    if (qm && qm->next_match) {
        flecs_query_table_match_free(query, qt, qm->next_match);
        qm->next_match = NULL;
    }

    /* Iterate all tables in cache, remove ones that weren't just matched */
    ecs_table_cache_iter_t cache_it;
    if (flecs_table_cache_all_iter(&query->cache, &cache_it)) {
        while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) {
            if (qt->rematch_count != rematch_count) {
                flecs_query_unmatch_table(query, qt->hdr.table, qt);
            }
        }
    }

    if (world->flags & EcsWorldMeasureFrameTime) {
        world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t);
    }
}

static
void flecs_query_remove_subquery(
    ecs_query_t *parent, 
    ecs_query_t *sub)
{
    ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(parent->subqueries.array, ECS_INTERNAL_ERROR, NULL);

    int32_t i, count = ecs_vec_count(&parent->subqueries);
    ecs_query_t **sq = ecs_vec_first(&parent->subqueries);

    for (i = 0; i < count; i ++) {
        if (sq[i] == sub) {
            break;
        }
    }

    ecs_vec_remove_t(&parent->subqueries, ecs_query_t*, i);
}

/* -- Private API -- */

void flecs_query_notify(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_query_event_t *event)
{
    bool notify = true;

    switch(event->kind) {
    case EcsQueryTableMatch:
        /* Creation of new table */
        if (flecs_query_match_table(world, query, event->table)) {
            if (query->subqueries.array) {
                flecs_query_notify_subqueries(world, query, event);
            }
        }
        notify = false;
        break;
    case EcsQueryTableUnmatch:
        /* Deletion of table */
        flecs_query_unmatch_table(query, event->table, NULL);
        break;
    case EcsQueryTableRematch:
        /* Rematch tables of query */
        flecs_query_rematch_tables(world, query, event->parent_query);
        break;        
    case EcsQueryOrphan:
        ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL);
        query->flags |= EcsQueryIsOrphaned;
        query->parent = NULL;
        break;
    }

    if (notify) {
        flecs_query_notify_subqueries(world, query, event);
    }
}

static
void flecs_query_order_by(
    ecs_world_t *world,
    ecs_query_t *query,
    ecs_entity_t order_by_component,
    ecs_order_by_action_t order_by,
    ecs_sort_table_action_t action)
{
    ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL);
    ecs_check(!ecs_id_is_wildcard(order_by_component), 
        ECS_INVALID_PARAMETER, NULL);

    /* Find order_by_component term & make sure it is queried for */
    const ecs_filter_t *filter = &query->filter;
    int32_t i, count = filter->term_count;
    int32_t order_by_term = -1;

    if (order_by_component) {
        for (i = 0; i < count; i ++) {
            ecs_term_t *term = &filter->terms[i];
            
            /* Only And terms are supported */
            if (term->id == order_by_component && term->oper == EcsAnd) {
                order_by_term = i;
                break;
            }
        }

        ecs_check(order_by_term != -1, ECS_INVALID_PARAMETER, 
            "sorted component not is queried for");
    }

    query->order_by_component = order_by_component;
    query->order_by = order_by;
    query->order_by_term = order_by_term;
    query->sort_table = action;

    ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t);
    flecs_query_sort_tables(world, query);  

    if (!query->table_slices.array) {
        flecs_query_build_sorted_tables(query);
    }

    query->flags &= ~EcsQueryTrivialIter;
error:
    return;
}

static
void flecs_query_group_by(
    ecs_query_t *query,
    ecs_entity_t sort_component,
    ecs_group_by_action_t group_by)
{   
    /* Cannot change grouping once a query has been created */
    ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL);
    ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL);

    if (!group_by) {
        /* Builtin function that groups by relationship */
        group_by = flecs_query_default_group_by;   
    }

    query->group_by_id = sort_component;
    query->group_by = group_by;

    ecs_map_init_w_params(&query->groups, 
        &query->filter.world->allocators.query_table_list);
error:
    return;
}

/* Implementation for iterable mixin */
static
void flecs_query_iter_init(
    const ecs_world_t *world,
    const ecs_poly_t *poly,
    ecs_iter_t *iter,
    ecs_term_t *filter)
{
    ecs_poly_assert(poly, ecs_query_t);

    if (filter) {
        iter[1] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly));
        iter[0] = ecs_term_chain_iter(&iter[1], filter);
    } else {
        iter[0] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly));
    }
}

static
void flecs_query_on_event(
    ecs_iter_t *it)
{
    /* Because this is the observer::run callback, checking if this is event is
     * already handled is not done for us. */
    ecs_world_t *world = it->world;
    ecs_observer_t *o = it->ctx;
    if (o->last_event_id) {
        if (o->last_event_id[0] == world->event_id) {
            return;
        }
        o->last_event_id[0] = world->event_id;
    }

    ecs_query_t *query = o->ctx;
    ecs_table_t *table = it->table;
    ecs_entity_t event = it->event;

    if (event == EcsOnTableCreate) {
        /* Creation of new table */
        if (flecs_query_match_table(world, query, table)) {
            if (query->subqueries.array) {
                ecs_query_event_t evt = {
                    .kind = EcsQueryTableMatch,
                    .table = table,
                    .parent_query = query
                };
                flecs_query_notify_subqueries(world, query, &evt);
            }
        }
        return;
    }

    ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL);

    /* The observer isn't doing the matching because the query can do it more
     * efficiently by checking the table with the query cache. */
    if (ecs_table_cache_get(&query->cache, table) == NULL) {
        return;
    }

    if (event == EcsOnTableEmpty) {
        flecs_query_update_table(query, table, true);
    } else
    if (event == EcsOnTableFill) {
        flecs_query_update_table(query, table, false);
    } else if (event == EcsOnTableDelete) {
        /* Deletion of table */
        flecs_query_unmatch_table(query, table, NULL);
        if (query->subqueries.array) {
                ecs_query_event_t evt = {
                    .kind = EcsQueryTableUnmatch,
                    .table = table,
                    .parent_query = query
                };
                flecs_query_notify_subqueries(world, query, &evt);
        }
        return;
    }
}

static
void flecs_query_table_cache_free(
    ecs_query_t *query)
{
    ecs_table_cache_iter_t it;
    ecs_query_table_t *qt;

    if (flecs_table_cache_all_iter(&query->cache, &it)) {
        while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) {
            flecs_query_table_free(query, qt);
        }
    }

    ecs_table_cache_fini(&query->cache);
}

static
void flecs_query_allocators_init(
    ecs_query_t *query)
{
    int32_t field_count = query->filter.field_count;
    if (field_count) {
        flecs_ballocator_init(&query->allocators.columns, 
            field_count * ECS_SIZEOF(int32_t));
        flecs_ballocator_init(&query->allocators.ids, 
            field_count * ECS_SIZEOF(ecs_id_t));
        flecs_ballocator_init(&query->allocators.sources,
            field_count * ECS_SIZEOF(ecs_entity_t));
        flecs_ballocator_init(&query->allocators.monitors,
            (1 + field_count) * ECS_SIZEOF(int32_t));
    }
}

static
void flecs_query_allocators_fini(
    ecs_query_t *query)
{
    int32_t field_count = query->filter.field_count;
    if (field_count) {
        flecs_ballocator_fini(&query->allocators.columns);
        flecs_ballocator_fini(&query->allocators.ids);
        flecs_ballocator_fini(&query->allocators.sources);
        flecs_ballocator_fini(&query->allocators.monitors);
    }
}

static
void flecs_query_fini(
    ecs_query_t *query)
{
    ecs_world_t *world = query->filter.world;

    ecs_group_delete_action_t on_delete = query->on_group_delete;
    if (on_delete) {
        ecs_map_iter_t it = ecs_map_iter(&query->groups);
        while (ecs_map_next(&it)) {
            ecs_query_table_list_t *group = ecs_map_ptr(&it);
            uint64_t group_id = ecs_map_key(&it);
            on_delete(world, group_id, group->info.ctx, query->group_by_ctx);
        }
        query->on_group_delete = NULL;
    }

    if (query->group_by_ctx_free) {
        if (query->group_by_ctx) {
            query->group_by_ctx_free(query->group_by_ctx);
        }
    }

    if ((query->flags & EcsQueryIsSubquery) &&
        !(query->flags & EcsQueryIsOrphaned))
    {
        flecs_query_remove_subquery(query->parent, query);
    }

    flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){
        .kind = EcsQueryOrphan
    });

    flecs_query_for_each_component_monitor(world, query, 
        flecs_monitor_unregister);
    flecs_query_table_cache_free(query);

    ecs_map_fini(&query->groups);

    ecs_vec_fini_t(NULL, &query->subqueries, ecs_query_t*);
    ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t);
    ecs_filter_fini(&query->filter);

    flecs_query_allocators_fini(query);

    if (query->ctx_free) {
        query->ctx_free(query->ctx);
    }
    if (query->binding_ctx_free) {
        query->binding_ctx_free(query->binding_ctx);
    }

    ecs_poly_free(query, ecs_query_t);
}

/* -- Public API -- */

ecs_query_t* ecs_query_init(
    ecs_world_t *world,
    const ecs_query_desc_t *desc)
{
    ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL);
    ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL);

    ecs_query_t *result = ecs_poly_new(ecs_query_t);
    ecs_observer_desc_t observer_desc = { .filter = desc->filter };
    ecs_entity_t entity = desc->filter.entity;

    observer_desc.filter.flags = EcsFilterMatchEmptyTables;
    observer_desc.filter.storage = &result->filter;
    result->filter = ECS_FILTER_INIT;

    if (ecs_filter_init(world, &observer_desc.filter) == NULL) {
        goto error;
    }

    ECS_BIT_COND(result->flags, EcsQueryTrivialIter, 
        !!(result->filter.flags & EcsFilterMatchOnlyThis));

    flecs_query_allocators_init(result);

    if (result->filter.term_count) {
        observer_desc.entity = entity;
        observer_desc.run = flecs_query_on_event;
        observer_desc.ctx = result;
        observer_desc.events[0] = EcsOnTableEmpty;
        observer_desc.events[1] = EcsOnTableFill;
        if (!desc->parent) {
            observer_desc.events[2] = EcsOnTableCreate;
            observer_desc.events[3] = EcsOnTableDelete;
        }
        observer_desc.filter.flags |= EcsFilterNoData;
        observer_desc.filter.instanced = true;

        /* ecs_filter_init could have moved away resources from the terms array
         * in the descriptor, so use the terms array from the filter. */
        observer_desc.filter.terms_buffer = result->filter.terms;
        observer_desc.filter.terms_buffer_count = result->filter.term_count;
        observer_desc.filter.expr = NULL; /* Already parsed */

        entity = ecs_observer_init(world, &observer_desc);
        if (!entity) {
            goto error;
        }
    }

    result->iterable.init = flecs_query_iter_init;
    result->dtor = (ecs_poly_dtor_t)flecs_query_fini;
    result->prev_match_count = -1;

    result->ctx = desc->ctx;
    result->binding_ctx = desc->binding_ctx;
    result->ctx_free = desc->ctx_free;
    result->binding_ctx_free = desc->binding_ctx_free;

    if (ecs_should_log_1()) {
        char *filter_expr = ecs_filter_str(world, &result->filter);
        ecs_dbg_1("#[green]query#[normal] [%s] created", 
            filter_expr ? filter_expr : "");
        ecs_os_free(filter_expr);
    }

    ecs_log_push_1();

    if (flecs_query_process_signature(world, result)) {
        goto error;
    }

    /* Group before matching so we won't have to move tables around later */
    int32_t cascade_by = result->cascade_by;
    if (cascade_by) {
        flecs_query_group_by(result, result->filter.terms[cascade_by - 1].id,
            flecs_query_group_by_cascade);
        result->group_by_ctx = &result->filter.terms[cascade_by - 1];
    }

    if (desc->group_by || desc->group_by_id) {
        /* Can't have a cascade term and group by at the same time, as cascade
         * uses the group_by mechanism */
        ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL);
        flecs_query_group_by(result, desc->group_by_id, desc->group_by);
        result->group_by_ctx = desc->group_by_ctx;
        result->on_group_create = desc->on_group_create;
        result->on_group_delete = desc->on_group_delete;
        result->group_by_ctx_free = desc->group_by_ctx_free;
    }

    if (desc->parent != NULL) {
        result->flags |= EcsQueryIsSubquery;
    }

    /* If the query refers to itself, add the components that were queried for
     * to the query itself. */
    if (entity)  {
        int32_t t, term_count = result->filter.term_count;
        ecs_term_t *terms = result->filter.terms;

        for (t = 0; t < term_count; t ++) {
            ecs_term_t *term = &terms[t];
            if (term->src.id == entity) {
                ecs_add_id(world, entity, term->id);
            }
        }        
    }

    if (!entity) {
        entity = ecs_new_id(world);
    }

    EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t);
    if (poly->poly) {
        /* If entity already had poly query, delete previous */
        flecs_query_fini(poly->poly);
    }
    poly->poly = result;
    result->filter.entity = entity;

    /* Ensure that while initially populating the query with tables, they are
     * in the right empty/non-empty list. This ensures the query won't miss
     * empty/non-empty events for tables that are currently out of sync, but
     * change back to being in sync before processing pending events. */
    ecs_run_aperiodic(world, EcsAperiodicEmptyTables);

    ecs_table_cache_init(world, &result->cache);

    if (!desc->parent) {
        flecs_query_match_tables(world, result);
    } else {
        flecs_query_add_subquery(world, desc->parent, result);
        result->parent = desc->parent;
    }

    if (desc->order_by) {
        flecs_query_order_by(
            world, result, desc->order_by_component, desc->order_by,
            desc->sort_table);
    }

    if (!ecs_query_table_count(result) && result->filter.term_count) {
        ecs_add_id(world, entity, EcsEmpty);
    }

    ecs_poly_modified(world, entity, ecs_query_t);

    ecs_log_pop_1();

    return result;
error:
    if (result) {
        ecs_filter_fini(&result->filter);
        ecs_os_free(result);
    }
    return NULL;
}

void ecs_query_fini(
    ecs_query_t *query)
{
    ecs_poly_assert(query, ecs_query_t);
    ecs_delete(query->filter.world, query->filter.entity);
}

const ecs_filter_t* ecs_query_get_filter(
    const ecs_query_t *query)
{
    ecs_poly_assert(query, ecs_query_t);
    return &query->filter;
}

static
void flecs_query_set_var(
    ecs_iter_t *it)
{
    ecs_check(it->constrained_vars == 1, ECS_INVALID_OPERATION, 
        "can only set $this variable for queries");

    ecs_var_t *var = &it->variables[0];
    ecs_table_t *table = var->range.table;
    if (!table) {
        goto nodata;
    }

    ecs_query_iter_t *qit = &it->priv.iter.query;
    ecs_query_t *query = qit->query;
    ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table);
    if (!qt) {
        goto nodata;
    }

    qit->node = qt->first;
    qit->last = qt->last->next_match;
    it->offset = var->range.offset;
    it->count = var->range.count;
    return;
error:
nodata:
    it->priv.iter.query.node = NULL;
    it->priv.iter.query.last = NULL;
    return;
}

ecs_iter_t ecs_query_iter(
    const ecs_world_t *stage,
    ecs_query_t *query)
{
    ecs_poly_assert(query, ecs_query_t);
    ecs_check(!(query->flags & EcsQueryIsOrphaned),
        ECS_INVALID_PARAMETER, NULL);

    ecs_world_t *world = query->filter.world;
    ecs_poly_assert(world, ecs_world_t);

    /* Process table events to ensure that the list of iterated tables doesn't
     * contain empty tables. */
    flecs_process_pending_tables(world);

    /* If query has order_by, apply sort */
    flecs_query_sort_tables(world, query);

    /* If monitors changed, do query rematching */
    if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) {
        flecs_eval_component_monitors(world);
    }

    /* Prepare iterator */

    int32_t table_count;
    if (ecs_vec_count(&query->table_slices)) {
        table_count = ecs_vec_count(&query->table_slices);
    } else {
        table_count = ecs_query_table_count(query);
    }

    ecs_query_iter_t it = {
        .query = query,
        .node = query->list.first,
        .last = NULL
    };

    if (query->order_by && query->list.info.table_count) {
        it.node = ecs_vec_first(&query->table_slices);
    }

    ecs_iter_t result = {
        .real_world = world,
        .world = ECS_CONST_CAST(ecs_world_t*, stage),
        .terms = query->filter.terms,
        .field_count = query->filter.field_count,
        .table_count = table_count,
        .variable_count = 1,
        .priv.iter.query = it,
        .next = ecs_query_next,
        .set_var = flecs_query_set_var
    };

    flecs_filter_apply_iter_flags(&result, &query->filter);

    ecs_filter_t *filter = &query->filter;
    ecs_iter_t fit;
    if (!(query->flags & EcsQueryTrivialIter)) {
        /* Check if non-This terms (like singleton terms) still match */
        if (!(filter->flags & EcsFilterMatchOnlyThis)) {
            fit = flecs_filter_iter_w_flags(ECS_CONST_CAST(ecs_world_t*, stage),
                &query->filter, EcsIterIgnoreThis);
            if (!ecs_filter_next(&fit)) {
                /* No match, so return nothing */
                goto noresults;
            }
        }

        flecs_iter_init(stage, &result, flecs_iter_cache_all);

        /* Copy the data */
        if (!(filter->flags & EcsFilterMatchOnlyThis)) {
            int32_t field_count = filter->field_count;
            if (field_count) {
                if (result.ptrs) {
                    ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, field_count);
                }
                ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, field_count);
                ecs_os_memcpy_n(result.columns, fit.columns, int32_t, field_count);
                ecs_os_memcpy_n(result.sources, fit.sources, int32_t, field_count);
            }
            ecs_iter_fini(&fit);
        }
    } else {
        /* Trivial iteration, use arrays from query cache */
        flecs_iter_init(stage, &result, 
            flecs_iter_cache_ptrs|flecs_iter_cache_variables);
    }

    result.sizes = query->filter.sizes;

    return result;
error:
noresults:
    result.priv.iter.query.node = NULL;
    return result;
}

void ecs_query_set_group(
    ecs_iter_t *it,
    uint64_t group_id)
{
    ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);
    ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL);

    ecs_query_iter_t *qit = &it->priv.iter.query;
    ecs_query_t *q = qit->query;
    ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL);

    ecs_query_table_list_t *node = flecs_query_get_group(q, group_id);
    if (!node) {
        qit->node = NULL;
        return;
    }

    ecs_query_table_match_t *first = node->first;
    if (first) {
        qit->node = node->first;
        qit->last = node->last->next;
    } else {
        qit->node = NULL;
        qit->last = NULL;
    }
    
error:
    return;
}

const ecs_query_group_info_t* ecs_query_get_group_info(
    const ecs_query_t *query,
    uint64_t group_id)
{
    ecs_query_table_list_t *node = flecs_query_get_group(query, group_id);
    if (!node) {
        return NULL;
    }
    
    return &node->info;
}

void* ecs_query_get_group_ctx(
    const ecs_query_t *query,
    uint64_t group_id)
{
    const ecs_query_group_info_t *info = 
        ecs_query_get_group_info(query, group_id);
    if (!info) {
        return NULL;
    } else {
        return info->ctx;
    }
}

static
void flecs_query_mark_columns_dirty(
    ecs_query_t *query,
    ecs_query_table_match_t *qm)
{
    ecs_table_t *table = qm->table;
    ecs_filter_t *filter = &query->filter;
    if ((table && table->dirty_state) || (query->flags & EcsQueryHasNonThisOutTerms)) {
        ecs_term_t *terms = filter->terms;
        int32_t i, count = filter->term_count;

        for (i = 0; i < count; i ++) {
            ecs_term_t *term = &terms[i];
            ecs_inout_kind_t inout = term->inout;
            if (inout == EcsIn || inout == EcsInOutNone) {
                /* Don't mark readonly terms dirty */
                continue;
            }

            flecs_table_column_t tc;
            flecs_query_get_column_for_term(query, qm, i, &tc);

            if (tc.column == -1) {
                continue;
            }

            ecs_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL);
            int32_t *dirty_state = tc.table->dirty_state;
            if (!dirty_state) {
                continue;
            }

            if (table != tc.table) {
                if (inout == EcsInOutDefault) {
                    continue;
                }
            }

            ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL);

            dirty_state[tc.column + 1] ++;
        }
    }
}

bool ecs_query_next_table(
    ecs_iter_t *it)
{
    ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);

    flecs_iter_validate(it);

    ecs_query_iter_t *iter = &it->priv.iter.query;
    ecs_query_table_match_t *node = iter->node;
    ecs_query_t *query = iter->query;

    ecs_query_table_match_t *prev = iter->prev;
    if (prev) {
        if (query->flags & EcsQueryHasMonitor) {
            flecs_query_sync_match_monitor(query, prev);
        }
        if (query->flags & EcsQueryHasOutTerms) {
            if (it->count) {
                flecs_query_mark_columns_dirty(query, prev);
            }
        }
    }

    if (node != iter->last) {
        it->table = node->table;
        it->group_id = node->group_id;
        it->count = 0;
        iter->node = node->next;
        iter->prev = node;
        return true;
    }

error:
    query->match_count = query->prev_match_count;
    ecs_iter_fini(it);
    return false;
}

static
void flecs_query_populate_trivial(
    ecs_iter_t *it,
    ecs_query_table_match_t *match)
{;
    ecs_table_t *table = match->table;
    int32_t offset, count;
    if (!it->constrained_vars) {
        it->offset = offset = 0;
        it->count = count = ecs_table_count(table);
    } else {
        offset = it->offset;
        count = it->count;
    }

    it->ids = match->ids;
    it->sources = match->sources;
    it->columns = match->columns;
    it->group_id = match->group_id;
    it->instance_count = 0;
    it->references = ecs_vec_first(&match->refs);

    if (!it->references) {
        ecs_data_t *data = &table->data;
        if (!(it->flags & EcsIterNoData)) {
            int32_t i;
            for (i = 0; i < it->field_count; i ++) {
                int32_t column = match->storage_columns[i];
                if (column < 0) {
                    it->ptrs[i] = NULL;
                    continue;
                }

                ecs_size_t size = it->sizes[i];
                if (!size) {
                    it->ptrs[i] = NULL;
                    continue;
                }

                it->ptrs[i] = ecs_vec_get(&data->columns[column].data,
                    it->sizes[i], offset);
            }
        }

        it->frame_offset += it->table ? ecs_table_count(it->table) : 0;
        it->table = table;
        it->entities = ecs_vec_get_t(&data->entities, ecs_entity_t, offset);
    } else {
        flecs_iter_populate_data(
            it->real_world, it, table, offset, count, it->ptrs);
    }
}

int ecs_query_populate(
    ecs_iter_t *it,
    bool when_changed)
{
    ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);
    ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), 
        ECS_INVALID_PARAMETER, NULL);

    ecs_query_iter_t *iter = &it->priv.iter.query;
    ecs_query_t *query = iter->query;
    ecs_query_table_match_t *match = iter->prev;
    ecs_assert(match != NULL, ECS_INVALID_OPERATION, NULL);
    if (query->flags & EcsQueryTrivialIter) {
        flecs_query_populate_trivial(it, match);
        return EcsIterNextYield;
    }

    ecs_table_t *table = match->table;
    ecs_world_t *world = query->filter.world;
    const ecs_filter_t *filter = &query->filter;
    ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter;
    ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_table_range_t *range = &ent_it->range;
    int32_t t, term_count = filter->term_count;
    int result;

repeat:
    result = EcsIterNextYield;

    ecs_os_memcpy_n(it->sources, match->sources, ecs_entity_t, 
        filter->field_count);

    for (t = 0; t < term_count; t ++) {
        ecs_term_t *term = &filter->terms[t];
        int32_t field = term->field_index;
        if (!ecs_term_match_this(term)) {
            continue;
        }

        it->ids[field] = match->ids[field];
        it->columns[field] = match->columns[field];
    }

    if (table) {
        range->offset = match->offset;
        range->count = match->count;
        if (!range->count) {
            range->count = ecs_table_count(table);
            ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL);
        }

        if (match->entity_filter) {
            ent_it->entity_filter = match->entity_filter;
            ent_it->columns = match->columns;
            ent_it->range.table = table;
            ent_it->it = it;
            result = flecs_entity_filter_next(ent_it);
            if (result == EcsIterNext) {
                goto done;
            }
        }

        it->group_id = match->group_id;
    } else {
        range->offset = 0;
        range->count = 0;
    }

    if (when_changed) {
        if (!ecs_query_changed(NULL, it)) {
            if (result == EcsIterYield) {
                goto repeat;
            } else {
                result = EcsIterNext;
                goto done;
            }
        }
    }

    it->references = ecs_vec_first(&match->refs);
    it->instance_count = 0;

    flecs_iter_populate_data(world, it, table, range->offset, range->count,
        it->ptrs);

error:
done:
    return result;
}

bool ecs_query_next(
    ecs_iter_t *it)
{
    ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);

    if (flecs_iter_next_row(it)) {
        return true;
    }

    return flecs_iter_next_instanced(it, ecs_query_next_instanced(it));
error:
    return false;
}

bool ecs_query_next_instanced(
    ecs_iter_t *it)
{
    ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);

    ecs_query_iter_t *iter = &it->priv.iter.query;
    ecs_query_t *query = iter->query;
    ecs_flags32_t flags = query->flags;

    ecs_query_table_match_t *prev, *next, *cur = iter->node, *last = iter->last;
    if ((prev = iter->prev)) {
        /* Match has been iterated, update monitor for change tracking */
        if (flags & EcsQueryHasMonitor) {
            flecs_query_sync_match_monitor(query, prev);
        }
        if (flags & EcsQueryHasOutTerms) {
            flecs_query_mark_columns_dirty(query, prev);
        }
    }

    flecs_iter_validate(it);
    iter->skip_count = 0;

    /* Trivial iteration: each entry in the cache is a full match and ids are
     * only matched on $this or through traversal starting from $this. */
    if (flags & EcsQueryTrivialIter) {
        if (cur == last) {
            goto done;
        }
        iter->node = cur->next;
        iter->prev = cur;
        flecs_query_populate_trivial(it, cur);
        return true;
    }

    /* Non-trivial iteration: query matches with static sources, or matches with
     * tables that require per-entity filtering. */
    for (; cur != last; cur = next) {
        next = cur->next;
        iter->prev = cur;
        switch(ecs_query_populate(it, false)) {
        case EcsIterNext: iter->node = next; continue;
        case EcsIterYield: next = cur; /* fall through */
        case EcsIterNextYield: goto yield;
        default: ecs_abort(ECS_INTERNAL_ERROR, NULL);
        }
    }

done: error:
    query->match_count = query->prev_match_count;
    ecs_iter_fini(it);
    return false;

yield:
    iter->node = next;
    iter->prev = cur;
    return true;
}

bool ecs_query_changed(
    ecs_query_t *query,
    const ecs_iter_t *it)
{
    if (it) {
        ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);
        ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), 
            ECS_INVALID_PARAMETER, NULL);

        ecs_query_table_match_t *qm = 
            (ecs_query_table_match_t*)it->priv.iter.query.prev;
        ecs_assert(qm != NULL, ECS_INVALID_PARAMETER, NULL);

        if (!query) {
            query = it->priv.iter.query.query;
        } else {
            ecs_check(query == it->priv.iter.query.query, 
                ECS_INVALID_PARAMETER, NULL);
        }

        ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL);
        ecs_poly_assert(query, ecs_query_t);

        return flecs_query_check_match_monitor(query, qm, it);
    }

    ecs_poly_assert(query, ecs_query_t);
    ecs_check(!(query->flags & EcsQueryIsOrphaned), 
        ECS_INVALID_PARAMETER, NULL);

    flecs_process_pending_tables(query->filter.world);

    if (!(query->flags & EcsQueryHasMonitor)) {
        query->flags |= EcsQueryHasMonitor;
        flecs_query_init_query_monitors(query);
        return true; /* Monitors didn't exist yet */
    }

    if (query->match_count != query->prev_match_count) {
        return true;
    }

    return flecs_query_check_query_monitor(query);
error:
    return false;
}

void ecs_query_skip(
    ecs_iter_t *it)
{
    ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL);
    ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), 
        ECS_INVALID_PARAMETER, NULL);

    if (it->instance_count > it->count) {
        it->priv.iter.query.skip_count ++;
        if (it->priv.iter.query.skip_count == it->instance_count) {
            /* For non-instanced queries, make sure all entities are skipped */
            it->priv.iter.query.prev = NULL;
        }
    } else {
        it->priv.iter.query.prev = NULL;
    }
}

bool ecs_query_orphaned(
    const ecs_query_t *query)
{
    ecs_poly_assert(query, ecs_query_t);
    return query->flags & EcsQueryIsOrphaned;
}

char* ecs_query_str(
    const ecs_query_t *query)
{
    return ecs_filter_str(query->filter.world, &query->filter);
}

int32_t ecs_query_table_count(
    const ecs_query_t *query)
{
    ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables);
    return query->cache.tables.count;
}

int32_t ecs_query_empty_table_count(
    const ecs_query_t *query)
{
    ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables);
    return query->cache.empty_tables.count;
}

int32_t ecs_query_entity_count(
    const ecs_query_t *query)
{
    ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables);
    
    int32_t result = 0;
    ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last;
    if (!last) {
        return 0;
    }

    for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) {
        result += ecs_table_count(cur->table);
    }

    return result;
}

void* ecs_query_get_ctx(
    const ecs_query_t *query)
{
    return query->ctx;
}

void* ecs_query_get_binding_ctx(
    const ecs_query_t *query)
{
    return query->binding_ctx;
}
