/**
 * @file observable.c
 * @brief Observable implementation.
 * 
 * The observable implementation contains functions that find the set of 
 * observers to invoke for an event. The code also contains the implementation
 * of a reachable id cache, which is used to speedup event propagation when
 * relationships are added/removed to/from entities.
 */

#include "private_api.h"

void flecs_observable_init(
    ecs_observable_t *observable)
{
    flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t);
    observable->on_add.event = EcsOnAdd;
    observable->on_remove.event = EcsOnRemove;
    observable->on_set.event = EcsOnSet;
    observable->un_set.event = EcsUnSet;
}

void flecs_observable_fini(
    ecs_observable_t *observable)
{
    ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids), 
        ECS_INTERNAL_ERROR, NULL);
    ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids), 
        ECS_INTERNAL_ERROR, NULL);
    ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids), 
        ECS_INTERNAL_ERROR, NULL);
    ecs_assert(!ecs_map_is_init(&observable->un_set.event_ids), 
        ECS_INTERNAL_ERROR, NULL);

    ecs_sparse_t *events = &observable->events;
    int32_t i, count = flecs_sparse_count(events);
    for (i = 0; i < count; i ++) {
        ecs_event_record_t *er = 
            flecs_sparse_get_dense_t(events, ecs_event_record_t, i);
        ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL);
        (void)er;

        /* All triggers should've unregistered by now */
        ecs_assert(!ecs_map_is_init(&er->event_ids), 
            ECS_INTERNAL_ERROR, NULL);
    }

    flecs_sparse_fini(&observable->events);
}

ecs_event_record_t* flecs_event_record_get(
    const ecs_observable_t *o,
    ecs_entity_t event)
{
    ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL);
    
    /* Builtin events*/
    if      (event == EcsOnAdd)     return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add);
    else if (event == EcsOnRemove)  return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove);
    else if (event == EcsOnSet)     return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set);
    else if (event == EcsUnSet)     return ECS_CONST_CAST(ecs_event_record_t*, &o->un_set);
    else if (event == EcsWildcard)  return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard);

    /* User events */
    return flecs_sparse_try_t(&o->events, ecs_event_record_t, event);
}

ecs_event_record_t* flecs_event_record_ensure(
    ecs_observable_t *o,
    ecs_entity_t event)
{
    ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL);

    ecs_event_record_t *er = flecs_event_record_get(o, event);
    if (er) {
        return er;
    }
    er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event);
    er->event = event;
    return er;
}

static
const ecs_event_record_t* flecs_event_record_get_if(
    const ecs_observable_t *o,
    ecs_entity_t event)
{
    ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL);

    const ecs_event_record_t *er = flecs_event_record_get(o, event);
    if (er) {
        if (ecs_map_is_init(&er->event_ids)) {
            return er;
        }
        if (er->any) {
            return er;
        }
        if (er->wildcard) {
            return er;
        }
        if (er->wildcard_pair) {
            return er;
        }
    }

    return NULL;
}

ecs_event_id_record_t* flecs_event_id_record_get(
    const ecs_event_record_t *er,
    ecs_id_t id)
{
    if (!er) {
        return NULL;
    }

    if (id == EcsAny)                                  return er->any;
    else if (id == EcsWildcard)                        return er->wildcard;
    else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair;
    else {
        if (ecs_map_is_init(&er->event_ids)) {
            return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id);
        }
        return NULL;
    }
}

static
ecs_event_id_record_t* flecs_event_id_record_get_if(
    const ecs_event_record_t *er,
    ecs_id_t id)
{
    ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id);
    if (!ider) {
        return NULL;
    }

    if (ider->observer_count) {
        return ider;
    }

    return NULL;
}

ecs_event_id_record_t* flecs_event_id_record_ensure(
    ecs_world_t *world,
    ecs_event_record_t *er,
    ecs_id_t id)
{
    ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id);
    if (ider) {
        return ider;
    }

    ider = ecs_os_calloc_t(ecs_event_id_record_t);

    if (id == EcsAny) {
        return er->any = ider;
    } else if (id == EcsWildcard) {
        return er->wildcard = ider;
    } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) {
        return er->wildcard_pair = ider;
    }

    ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr);
    ecs_map_insert_ptr(&er->event_ids, id, ider);
    return ider;
}

void flecs_event_id_record_remove(
    ecs_event_record_t *er,
    ecs_id_t id)
{
    if (id == EcsAny) {
        er->any = NULL;
    } else if (id == EcsWildcard) {
        er->wildcard = NULL;
    } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) {
        er->wildcard_pair = NULL;
    } else {
        ecs_map_remove(&er->event_ids, id);
        if (!ecs_map_count(&er->event_ids)) {
            ecs_map_fini(&er->event_ids);
        }
    }
}

static
int32_t flecs_event_observers_get(
    const ecs_event_record_t *er,
    ecs_id_t id,
    ecs_event_id_record_t **iders)
{
    if (!er) {
        return 0;
    }

    /* Populate array with observer sets matching the id */
    int32_t count = 0;

    if (id != EcsAny) {
        iders[0] = flecs_event_id_record_get_if(er, EcsAny);
        count += iders[count] != 0;
    }

    iders[count] = flecs_event_id_record_get_if(er, id);
    count += iders[count] != 0;

    if (id != EcsAny) {
        if (ECS_IS_PAIR(id)) {
            ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id));
            ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard);
            ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard);
            iders[count] = flecs_event_id_record_get_if(er, id_fwc);
            count += iders[count] != 0;
            iders[count] = flecs_event_id_record_get_if(er, id_swc);
            count += iders[count] != 0;
            iders[count] = flecs_event_id_record_get_if(er, id_pwc);
            count += iders[count] != 0;
        } else {
            iders[count] = flecs_event_id_record_get_if(er, EcsWildcard);
            count += iders[count] != 0;
        }
    }

    return count;
}

bool flecs_observers_exist(
    ecs_observable_t *observable,
    ecs_id_t id,
    ecs_entity_t event)
{
    const ecs_event_record_t *er = flecs_event_record_get_if(observable, event);
    if (!er) {
        return false;
    }

    return flecs_event_id_record_get_if(er, id) != NULL;
}

static
void flecs_emit_propagate(
    ecs_world_t *world,
    ecs_iter_t *it,
    ecs_id_record_t *idr,
    ecs_id_record_t *tgt_idr,
    ecs_entity_t trav,
    ecs_event_id_record_t **iders,
    int32_t ider_count);

static
void flecs_emit_propagate_id(
    ecs_world_t *world,
    ecs_iter_t *it,
    ecs_id_record_t *idr,
    ecs_id_record_t *cur,
    ecs_entity_t trav,
    ecs_event_id_record_t **iders,
    int32_t ider_count)
{
    ecs_table_cache_iter_t idt;
    if (!flecs_table_cache_all_iter(&cur->cache, &idt)) {
        return;
    }

    const ecs_table_record_t *tr;
    while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) {
        ecs_table_t *table = tr->hdr.table;
        if (!ecs_table_count(table)) {
            continue;
        }

        bool owned = flecs_id_record_get_table(idr, table) != NULL;

        int32_t e, entity_count = ecs_table_count(table);
        it->table = table;
        it->other_table = NULL;
        it->offset = 0;
        it->count = entity_count;
        if (entity_count) {
            it->entities = ecs_vec_first(&table->data.entities);
        }

        /* Treat as new event as this could invoke observers again for
         * different tables. */
        int32_t evtx = ++ world->event_id;

        int32_t ider_i;
        for (ider_i = 0; ider_i < ider_count; ider_i ++) {
            ecs_event_id_record_t *ider = iders[ider_i];
            flecs_observers_invoke(world, &ider->up, it, table, trav, evtx);

            if (!owned) {
                /* Owned takes precedence */
                flecs_observers_invoke(
                    world, &ider->self_up, it, table, trav, evtx);
            }
        }

        if (!table->_->traversable_count) {
            continue;
        }

        ecs_entity_t *entities = ecs_vec_first(&table->data.entities);
        for (e = 0; e < entity_count; e ++) {
            ecs_record_t *r = flecs_entities_get(world, entities[e]);
            ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
            ecs_id_record_t *idr_t = r->idr;
            if (idr_t) {
                /* Only notify for entities that are used in pairs with
                 * traversable relationships */
                flecs_emit_propagate(world, it, idr, idr_t, trav,
                    iders, ider_count);
            }
        }
    }
}

static
void flecs_emit_propagate(
    ecs_world_t *world,
    ecs_iter_t *it,
    ecs_id_record_t *idr,
    ecs_id_record_t *tgt_idr,
    ecs_entity_t propagate_trav,
    ecs_event_id_record_t **iders,
    int32_t ider_count)
{
    ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL);    

    if (ecs_should_log_3()) {
        char *idstr = ecs_id_str(world, tgt_idr->id);
        ecs_dbg_3("propagate events/invalidate cache for %s", idstr);
        ecs_os_free(idstr);
    }
    ecs_log_push_3();

    /* Propagate to records of traversable relationships */
    ecs_id_record_t *cur = tgt_idr;
    while ((cur = cur->trav.next)) {
        cur->reachable.generation ++; /* Invalidate cache */

        /* Get traversed relationship */
        ecs_entity_t trav = ECS_PAIR_FIRST(cur->id);
        if (propagate_trav && propagate_trav != trav) {
            continue;
        }

        flecs_emit_propagate_id(
            world, it, idr, cur, trav, iders, ider_count);
    }

    ecs_log_pop_3();
}

static
void flecs_emit_propagate_invalidate_tables(
    ecs_world_t *world,
    ecs_id_record_t *tgt_idr)
{
    ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL);

    if (ecs_should_log_3()) {
        char *idstr = ecs_id_str(world, tgt_idr->id);
        ecs_dbg_3("invalidate reachable cache for %s", idstr);
        ecs_os_free(idstr);
    }

    /* Invalidate records of traversable relationships */
    ecs_id_record_t *cur = tgt_idr;
    while ((cur = cur->trav.next)) {
        ecs_reachable_cache_t *rc = &cur->reachable;
        if (rc->current != rc->generation) {
            /* Subtree is already marked invalid */
            continue;
        }

        rc->generation ++;

        ecs_table_cache_iter_t idt;
        if (!flecs_table_cache_all_iter(&cur->cache, &idt)) {
            continue;
        }

        const ecs_table_record_t *tr;
        while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) {
            ecs_table_t *table = tr->hdr.table;
            if (!table->_->traversable_count) {
                continue;
            }

            int32_t e, entity_count = ecs_table_count(table);
            ecs_entity_t *entities = ecs_vec_first(&table->data.entities);

            for (e = 0; e < entity_count; e ++) {
                ecs_record_t *r = flecs_entities_get(world, entities[e]);
                ecs_id_record_t *idr_t = r->idr;
                if (idr_t) {
                    /* Only notify for entities that are used in pairs with
                     * traversable relationships */
                    flecs_emit_propagate_invalidate_tables(world, idr_t);
                }
            }
        }
    }
}

void flecs_emit_propagate_invalidate(
    ecs_world_t *world,
    ecs_table_t *table,
    int32_t offset,
    int32_t count)
{
    ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities, 
        ecs_entity_t, offset);
    int32_t i;
    for (i = 0; i < count; i ++) {
        ecs_record_t *record = flecs_entities_get(world, entities[i]);
        if (!record) {
            /* If the event is emitted after a bulk operation, it's possible
             * that it hasn't been populated with entities yet. */
            continue;
        }

        ecs_id_record_t *idr_t = record->idr;
        if (idr_t) {
            /* Event is used as target in traversable relationship, propagate */
            flecs_emit_propagate_invalidate_tables(world, idr_t);
        }
    }
}

static
void flecs_override_copy(
    ecs_world_t *world,
    ecs_table_t *table,
    const ecs_type_info_t *ti,
    void *dst,
    const void *src,
    int32_t offset,
    int32_t count)
{
    void *ptr = dst;
    ecs_copy_t copy = ti->hooks.copy;
    ecs_size_t size = ti->size;
    int32_t i;
    if (copy) {
        for (i = 0; i < count; i ++) {
            copy(ptr, src, count, ti);
            ptr = ECS_OFFSET(ptr, size);
        }
    } else {
        for (i = 0; i < count; i ++) {
            ecs_os_memcpy(ptr, src, size);
            ptr = ECS_OFFSET(ptr, size);
        }
    }

    ecs_iter_action_t on_set = ti->hooks.on_set;
    if (on_set) {
        ecs_entity_t *entities = ecs_vec_get_t(
            &table->data.entities, ecs_entity_t, offset);
        flecs_invoke_hook(world, table, count, offset, entities,
            dst, ti->component, ti, EcsOnSet, on_set);
    }
}

static
void* flecs_override(
    ecs_iter_t *it, 
    const ecs_type_t *emit_ids,
    ecs_id_t id,
    ecs_table_t *table,
    ecs_id_record_t *idr)
{
    if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) {
        return NULL;
    }

    int32_t i = 0, count = emit_ids->count;
    ecs_id_t *ids = emit_ids->array;
    for (i = 0; i < count; i ++) {
        if (ids[i] == id) {
            /* If an id was both inherited and overridden in the same event
             * (like what happens during an auto override), we need to copy the
             * value of the inherited component to the new component.
             * Also flag to the callee that this component was overridden, so
             * that an OnSet event can be emmitted for it.
             * Note that this is different from a component that was overridden
             * after it was inherited, as this does not change the actual value
             * of the component for the entity (it is copied from the existing
             * overridden component), and does not require an OnSet event. */
            ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
            if (!tr) {
                continue;
            }

            int32_t index = tr->column;
            ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);

            ecs_column_t *column = &table->data.columns[index];
            ecs_size_t size = column->ti->size;
            return ecs_vec_get(&column->data, size, it->offset);
        }
    }

    return NULL;
}

static
void flecs_emit_forward_up(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_id_record_t *idr,
    ecs_vec_t *stack,
    ecs_vec_t *reachable_ids,
    int32_t evtx);

static
void flecs_emit_forward_id(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_id_record_t *idr,
    ecs_entity_t tgt,
    ecs_table_t *tgt_table,
    int32_t column,
    int32_t offset,
    ecs_entity_t trav,
    int32_t evtx)
{
    ecs_id_t id = idr->id;
    ecs_entity_t event = er ? er->event : 0;
    bool inherit = trav == EcsIsA;
    bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1);
    ecs_event_id_record_t *iders[5];
    ecs_event_id_record_t *iders_onset[5];

    /* Skip id if there are no observers for it */
    int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders);
    int32_t ider_onset_i, ider_onset_count = 0;
    if (er_onset) {
        ider_onset_count = flecs_event_observers_get(
            er_onset, id, iders_onset);
    }

    if (!may_override && (!ider_count && !ider_onset_count)) {
        return;
    }

    it->ids[0] = id;
    it->sources[0] = tgt;
    it->event_id = id;
    it->ptrs[0] = NULL;
    it->sizes[0] = 0;

    int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column);
    if (storage_i != -1) {
        ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_column_t *c = &tgt_table->data.columns[storage_i];
        it->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset);
        it->sizes[0] = c->ti->size;
    }

    ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
    bool owned = tr != NULL;

    for (ider_i = 0; ider_i < ider_count; ider_i ++) {
        ecs_event_id_record_t *ider = iders[ider_i];
        flecs_observers_invoke(world, &ider->up, it, table, trav, evtx);

        /* Owned takes precedence */
        if (!owned) {
            flecs_observers_invoke(world, &ider->self_up, it, table, trav, evtx);
        }
    }

    /* Emit OnSet events for newly inherited components */
    if (storage_i != -1) {
        bool override = false;

        /* If component was added together with IsA relationship, still emit
         * OnSet event, as it's a new value for the entity. */
        void *base_ptr = it->ptrs[0];
        void *ptr = flecs_override(it, emit_ids, id, table, idr);
        if (ptr) {
            override = true;
            it->ptrs[0] = ptr;
        }

        if (ider_onset_count) {
            it->event = er_onset->event;

            for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) {
                ecs_event_id_record_t *ider = iders_onset[ider_onset_i];
                flecs_observers_invoke(world, &ider->up, it, table, trav, evtx);

                /* Owned takes precedence */
                if (!owned) {
                    flecs_observers_invoke(
                        world, &ider->self_up, it, table, trav, evtx);
                } else if (override) {
                    ecs_entity_t src = it->sources[0];
                    it->sources[0] = 0;
                    flecs_observers_invoke(world, &ider->self, it, table, 0, evtx);
                    flecs_observers_invoke(world, &ider->self_up, it, table, 0, evtx);
                    it->sources[0] = src;
                }
            }

            it->event = event;
            it->ptrs[0] = base_ptr;
        }
    }
}

static
void flecs_emit_forward_and_cache_id(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_id_record_t *idr,
    ecs_entity_t tgt,
    ecs_record_t *tgt_record,
    ecs_table_t *tgt_table,
    const ecs_table_record_t *tgt_tr,
    int32_t column,
    int32_t offset,
    ecs_vec_t *reachable_ids,
    ecs_entity_t trav,
    int32_t evtx)
{
    /* Cache forwarded id for (rel, tgt) pair */
    ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator,
        reachable_ids, ecs_reachable_elem_t);
    elem->tr = tgt_tr;
    elem->record = tgt_record;
    elem->src = tgt;
    elem->id = idr->id;
#ifndef NDEBUG
    elem->table = tgt_table;
#endif
    ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL);

    flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr,
        tgt, tgt_table, column, offset, trav, evtx);
}

static
int32_t flecs_emit_stack_at(
    ecs_vec_t *stack,
    ecs_id_record_t *idr)
{
    int32_t sp = 0, stack_count = ecs_vec_count(stack);
    ecs_table_t **stack_elems = ecs_vec_first(stack);

    for (sp = 0; sp < stack_count; sp ++) {
        ecs_table_t *elem = stack_elems[sp];
        if (flecs_id_record_get_table(idr, elem)) {
            break;
        }
    }

    return sp;
}

static
bool flecs_emit_stack_has(
    ecs_vec_t *stack,
    ecs_id_record_t *idr)
{
    return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack);
}

static
void flecs_emit_forward_cached_ids(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_reachable_cache_t *rc,
    ecs_vec_t *reachable_ids,
    ecs_vec_t *stack,
    ecs_entity_t trav,
    int32_t evtx)
{
    ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, 
        ecs_reachable_elem_t);
    int32_t i, count = ecs_vec_count(&rc->ids);
    for (i = 0; i < count; i ++) {
        ecs_reachable_elem_t *rc_elem = &elems[i];
        const ecs_table_record_t *rc_tr = rc_elem->tr;
        ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache;
        ecs_record_t *rc_record = rc_elem->record;

        ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_assert(flecs_entities_get(world, rc_elem->src) == 
            rc_record, ECS_INTERNAL_ERROR, NULL);
        ecs_dbg_assert(rc_record->table == rc_elem->table, 
            ECS_INTERNAL_ERROR, NULL);

        if (flecs_emit_stack_has(stack, rc_idr)) {
            continue;
        }

        int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row);
        flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids,
            it, table, rc_idr, rc_elem->src,
                rc_record, rc_record->table, rc_tr, rc_tr->index,
                    rc_offset, reachable_ids, trav, evtx);
    }
}

static
void flecs_emit_dump_cache(
    ecs_world_t *world,
    const ecs_vec_t *vec)
{
    ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t);
    for (int i = 0; i < ecs_vec_count(vec); i ++) {
        ecs_reachable_elem_t *elem = &elems[i];
        char *idstr = ecs_id_str(world, elem->id);
        char *estr = ecs_id_str(world, elem->src);
        ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", 
            idstr, (uint32_t)elem->id,
            estr, (uint32_t)elem->src,
            elem->table);
        ecs_os_free(idstr);
        ecs_os_free(estr);
    }
    if (!ecs_vec_count(vec)) {
        ecs_dbg_3("- no entries");
    }
}

static
void flecs_emit_forward_table_up(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_entity_t tgt,
    ecs_table_t *tgt_table,
    ecs_record_t *tgt_record,
    ecs_id_record_t *tgt_idr,
    ecs_vec_t *stack,
    ecs_vec_t *reachable_ids,
    int32_t evtx)
{
    ecs_allocator_t *a = &world->allocator;
    int32_t i, id_count = tgt_table->type.count;
    ecs_id_t *ids = tgt_table->type.array;
    int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row);
    int32_t rc_child_offset = ecs_vec_count(reachable_ids);
    int32_t stack_count = ecs_vec_count(stack);

    /* If tgt_idr is out of sync but is not the current id record being updated,
     * keep track so that we can update two records for the cost of one. */
    ecs_reachable_cache_t *rc = &tgt_idr->reachable;
    bool parent_revalidate = (reachable_ids != &rc->ids) && 
        (rc->current != rc->generation);
    if (parent_revalidate) {
        ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t);
    }

    if (ecs_should_log_3()) {
        char *idstr = ecs_id_str(world, tgt_idr->id);
        ecs_dbg_3("forward events from %s", idstr);
        ecs_os_free(idstr);
    }
    ecs_log_push_3();

    /* Function may have to copy values from overridden components if an IsA
     * relationship was added together with other components. */
    ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id);
    bool inherit = trav == EcsIsA;

    for (i = 0; i < id_count; i ++) {
        ecs_id_t id = ids[i];
        ecs_table_record_t *tgt_tr = &tgt_table->_->records[i];
        ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache;
        if (inherit && (idr->flags & EcsIdDontInherit)) {
            continue;
        }

        /* Id has the same relationship, traverse to find ids for forwarding */
        if (ECS_PAIR_FIRST(id) == trav) {
            ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, 
                ecs_table_t*);
            t[0] = tgt_table;

            ecs_reachable_cache_t *idr_rc = &idr->reachable;
            if (idr_rc->current == idr_rc->generation) {
                /* Cache hit, use cached ids to prevent traversing the same
                 * hierarchy multiple times. This especially speeds up code 
                 * where (deep) hierarchies are created. */
                if (ecs_should_log_3()) {
                    char *idstr = ecs_id_str(world, id);
                    ecs_dbg_3("forward cached for %s", idstr);
                    ecs_os_free(idstr);
                }
                ecs_log_push_3();
                flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it,
                    table, idr_rc, reachable_ids, stack, trav, evtx);
                ecs_log_pop_3();
            } else {
                /* Cache is dirty, traverse upwards */
                do {
                    flecs_emit_forward_up(world, er, er_onset, emit_ids, it, 
                        table, idr, stack, reachable_ids, evtx);
                    if (++i >= id_count) {
                        break;
                    }

                    id = ids[i];
                    if (ECS_PAIR_FIRST(id) != trav) {
                        break;
                    }
                } while (true);
            }

            ecs_vec_remove_last(stack);
            continue;
        }

        int32_t stack_at = flecs_emit_stack_at(stack, idr);
        if (parent_revalidate && (stack_at == (stack_count - 1))) {
            /* If parent id record needs to be revalidated, add id */
            ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, 
                ecs_reachable_elem_t);
            elem->tr = tgt_tr;
            elem->record = tgt_record;
            elem->src = tgt;
            elem->id = idr->id;
#ifndef NDEBUG
            elem->table = tgt_table;
#endif
        }

        /* Skip id if it's masked by a lower table in the tree */
        if (stack_at != stack_count) {
            continue;
        }

        flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it,
            table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, 
                offset, reachable_ids, trav, evtx);
    }

    if (parent_revalidate) {
        /* If this is not the current cache being updated, but it's marked
         * as out of date, use intermediate results to populate cache. */
        int32_t rc_parent_offset = ecs_vec_count(&rc->ids);

        /* Only add ids that were added for this table */
        int32_t count = ecs_vec_count(reachable_ids);
        count -= rc_child_offset;

        /* Append ids to any ids that already were added /*/
        if (count) {
            ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count);
            ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, 
                ecs_reachable_elem_t, rc_parent_offset);
            ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids,
                ecs_reachable_elem_t, rc_child_offset);
            ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count);
        }

        rc->current = rc->generation;

        if (ecs_should_log_3()) {
            char *idstr = ecs_id_str(world, tgt_idr->id);
            ecs_dbg_3("cache revalidated for %s:", idstr);
            ecs_os_free(idstr);
            flecs_emit_dump_cache(world, &rc->ids);
        }
    }

    ecs_log_pop_3();
}

static
void flecs_emit_forward_up(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_id_record_t *idr,
    ecs_vec_t *stack,
    ecs_vec_t *reachable_ids,
    int32_t evtx)
{
    ecs_id_t id = idr->id;
    ecs_entity_t tgt = ECS_PAIR_SECOND(id);
    tgt = flecs_entities_get_generation(world, tgt);
    ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL);
    ecs_record_t *tgt_record = flecs_entities_try(world, tgt);
    ecs_table_t *tgt_table;
    if (!tgt_record || !(tgt_table = tgt_record->table)) {
        return;
    }

    flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, 
        tgt, tgt_table, tgt_record, idr, stack, reachable_ids, evtx);
}

static
void flecs_emit_forward(
    ecs_world_t *world,
    const ecs_event_record_t *er,
    const ecs_event_record_t *er_onset,
    const ecs_type_t *emit_ids,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_id_record_t *idr,
    int32_t evtx)
{
    ecs_reachable_cache_t *rc = &idr->reachable;

    if (rc->current != rc->generation) {
        /* Cache miss, iterate the tree to find ids to forward */
        if (ecs_should_log_3()) {
            char *idstr = ecs_id_str(world, idr->id);
            ecs_dbg_3("reachable cache miss for %s", idstr);
            ecs_os_free(idstr);
        }
        ecs_log_push_3();

        ecs_vec_t stack;
        ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0);
        ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t);
        flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, 
            idr, &stack, &rc->ids, evtx);
        it->sources[0] = 0;
        ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*);

        if (it->event == EcsOnAdd || it->event == EcsOnRemove) {
            /* Only OnAdd/OnRemove events can validate top-level cache, which
             * is for the id for which the event is emitted. 
             * The reason for this is that we don't want to validate the cache
             * while the administration for the mutated entity isn't up to 
             * date yet. */
            rc->current = rc->generation;
        }

        if (ecs_should_log_3()) {
            ecs_dbg_3("cache after rebuild:");
            flecs_emit_dump_cache(world, &rc->ids);
        }

        ecs_log_pop_3();
    } else {
        /* Cache hit, use cached values instead of walking the tree */
        if (ecs_should_log_3()) {
            char *idstr = ecs_id_str(world, idr->id);
            ecs_dbg_3("reachable cache hit for %s", idstr);
            ecs_os_free(idstr);
            flecs_emit_dump_cache(world, &rc->ids);
        }

        ecs_entity_t trav = ECS_PAIR_FIRST(idr->id);
        ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, 
            ecs_reachable_elem_t);
        int32_t i, count = ecs_vec_count(&rc->ids);
        for (i = 0; i < count; i ++) {
            ecs_reachable_elem_t *elem = &elems[i];
            const ecs_table_record_t *tr = elem->tr;
            ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
            ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache;
            ecs_record_t *r = elem->record;

            ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL);
            ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
            ecs_assert(flecs_entities_get(world, elem->src) == r,
                ECS_INTERNAL_ERROR, NULL);

            ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL);

            int32_t offset = ECS_RECORD_TO_ROW(r->row);
            flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table,
                rc_idr, elem->src, r->table, tr->index, offset, trav, evtx);
        }
    }
}

/* The emit function is responsible for finding and invoking the observers 
 * matching the emitted event. The function is also capable of forwarding events
 * for newly reachable ids (after adding a relationship) and propagating events
 * downwards. Both capabilities are not just useful in application logic, but
 * are also an important building block for keeping query caches in sync. */
void flecs_emit(
    ecs_world_t *world,
    ecs_world_t *stage,
    ecs_event_desc_t *desc)
{
    ecs_poly_assert(world, ecs_world_t);
    ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL);

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

    const ecs_type_t *ids = desc->ids;
    ecs_entity_t event = desc->event;
    ecs_table_t *table = desc->table, *other_table = desc->other_table;
    int32_t offset = desc->offset;
    int32_t i, r, count = desc->count;
    ecs_flags32_t table_flags = table->flags;

    /* Deferring cannot be suspended for observers */
    int32_t defer = world->stages[0].defer;
    if (defer < 0) {
        world->stages[0].defer *= -1;
    }

    /* Table events are emitted for internal table operations only, and do not
     * provide component data and/or entity ids. */
    bool table_event = desc->flags & EcsEventTableOnly;
    if (!count && !table_event) {
        /* If no count is provided, forward event for all entities in table */
        count = ecs_table_count(table) - offset;
    }

    /* When the NoOnSet flag is provided, no OnSet/UnSet events should be 
     * generated when new components are inherited. */
    bool no_on_set = desc->flags & EcsEventNoOnSet;

    ecs_id_t ids_cache = 0;
    void *ptrs_cache = NULL;
    ecs_size_t sizes_cache = 0;
    int32_t columns_cache = 0;
    ecs_entity_t sources_cache = 0;

    ecs_iter_t it = {
        .world = stage,
        .real_world = world,
        .event = event,
        .table = table,
        .field_count = 1,
        .ids = &ids_cache,
        .ptrs = &ptrs_cache,
        .sizes = &sizes_cache,
        .columns = &columns_cache,
        .sources = &sources_cache,
        .other_table = other_table,
        .offset = offset,
        .count = count,
        .param = ECS_CONST_CAST(void*, desc->param),
        .flags = desc->flags | EcsIterIsValid
    };

    /* The world event id is used to determine if an observer has already been
     * triggered for an event. Observers for multiple components are split up
     * into multiple observers for a single component, and this counter is used
     * to make sure a multi observer only triggers once, even if multiple of its
     * single-component observers trigger. */
    int32_t evtx = ++world->event_id;

    ecs_observable_t *observable = ecs_get_observable(desc->observable);
    ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL);

    /* Event records contain all observers for a specific event. In addition to
     * the emitted event, also request data for the Wildcard event (for 
     * observers subscribing to the wildcard event), OnSet and UnSet events. The
     * latter to are used for automatically emitting OnSet/UnSet events for 
     * inherited components, for example when an IsA relationship is added to an
     * entity. This doesn't add much overhead, as fetching records is cheap for
     * builtin event types. */
    const ecs_event_record_t *er = flecs_event_record_get_if(observable, event);
    const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard);
    const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet);
    const ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet);

    ecs_data_t *storage = NULL;
    ecs_column_t *columns = NULL;
    if (count) {
        storage = &table->data;
        columns = storage->columns;
        it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset);
    }

    int32_t id_count = ids->count;
    ecs_id_t *id_array = ids->array;

    /* If a table has IsA relationships, OnAdd/OnRemove events can trigger 
     * (un)overriding a component. When a component is overridden its value is
     * initialized with the value of the overridden component. */
    bool can_override = count && (table_flags & EcsTableHasIsA) && (
        (event == EcsOnAdd) || (event == EcsOnRemove));

    /* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove
     * event) this will cause the components of the target entity to be 
     * propagated to the source entity. This makes it possible for observers to
     * get notified of any new reachable components though the relationship. */
    bool can_forward = event != EcsOnSet;

    /* Set if event has been propagated */
    bool propagated = false;

    /* Does table has observed entities */
    bool has_observed = table_flags & EcsTableHasTraversable;

    /* When a relationship is removed, the events reachable through that 
     * relationship should emit UnSet events. This is part of the behavior that
     * allows observers to be agnostic of whether a component is inherited. */
    bool can_unset = count && (event == EcsOnRemove) && !no_on_set;

    ecs_event_id_record_t *iders[5] = {0};
    int32_t unset_count = 0;

repeat_event:
    /* This is the core event logic, which is executed for each event. By 
     * default this is just the event kind from the ecs_event_desc_t struct, but
     * can also include the Wildcard and UnSet events. The latter is emitted as
     * counterpart to OnSet, for any removed ids associated with data. */
    for (i = 0; i < id_count; i ++) {
        /* Emit event for each id passed to the function. In most cases this 
         * will just be one id, like a component that was added, removed or set.
         * In some cases events are emitted for multiple ids.
         * 
         * One example is when an id was added with a "With" property, or 
         * inheriting from a prefab with overrides. In these cases an entity is 
         * moved directly to the archetype with the additional components. */
        ecs_id_record_t *idr = NULL;
        const ecs_type_info_t *ti = NULL;
        ecs_id_t id = id_array[i];
        int32_t ider_i, ider_count = 0;
        bool is_pair = ECS_IS_PAIR(id);
        void *override_ptr = NULL;
        ecs_entity_t base = 0;

        /* Check if this id is a pair of an traversable relationship. If so, we 
         * may have to forward ids from the pair's target. */
        if ((can_forward && is_pair) || can_override) {
            idr = flecs_query_id_record_get(world, id);
            ecs_flags32_t idr_flags = idr->flags;

            if (is_pair && (idr_flags & EcsIdTraversable)) {
                const ecs_event_record_t *er_fwd = NULL;
                if (ECS_PAIR_FIRST(id) == EcsIsA) {
                    if (event == EcsOnAdd) {
                        if (!world->stages[0].base) {
                            /* Adding an IsA relationship can trigger prefab
                             * instantiation, which can instantiate prefab 
                             * hierarchies for the entity to which the 
                             * relationship was added. */
                            ecs_entity_t tgt = ECS_PAIR_SECOND(id);

                            /* Setting this value prevents flecs_instantiate 
                             * from being called recursively, in case prefab
                             * children also have IsA relationships. */
                            world->stages[0].base = tgt;
                            flecs_instantiate(world, tgt, table, offset, count);
                            world->stages[0].base = 0;
                        }

                        /* Adding an IsA relationship will emit OnSet events for
                         * any new reachable components. */
                        er_fwd = er_onset;
                    } else if (event == EcsOnRemove) {
                        /* Vice versa for removing an IsA relationship. */
                        er_fwd = er_unset;
                    }
                }

                /* Forward events for components from pair target */
                flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr, evtx);
            }

            if (can_override && (!(idr_flags & EcsIdDontInherit))) {
                /* Initialize overridden components with value from base */
                ti = idr->type_info;
                if (ti) {
                    ecs_table_record_t *base_tr = NULL;
                    int32_t base_column = ecs_search_relation(world, table, 
                        0, id, EcsIsA, EcsUp, &base, NULL, &base_tr);
                    if (base_column != -1) {
                        /* Base found with component */
                        ecs_table_t *base_table = base_tr->hdr.table;
                        base_column = base_tr->column;
                        ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL);
                        ecs_record_t *base_r = flecs_entities_get(world, base);
                        ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL);
                        int32_t base_row = ECS_RECORD_TO_ROW(base_r->row);
                        ecs_vec_t *base_v = &base_table->data.columns[base_column].data;
                        override_ptr = ecs_vec_get(base_v, ti->size, base_row);
                    }
                }
            }
        }

        if (er) {
            /* Get observer sets for id. There can be multiple sets of matching
             * observers, in case an observer matches for wildcard ids. For
             * example, both observers for (ChildOf, p) and (ChildOf, *) would
             * match an event for (ChildOf, p). */
            ider_count = flecs_event_observers_get(er, id, iders);
            idr = idr ? idr : flecs_query_id_record_get(world, id);
            ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
        }

        if (can_unset) {
            /* Increase UnSet count in case this is a component (has data). This
             * will cause the event loop to be ran again as UnSet event. */
            idr = idr ? idr : flecs_query_id_record_get(world, id);
            ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
            unset_count += (idr->type_info != NULL);
        }

        if (!ider_count && !override_ptr) {
            /* If nothing more to do for this id, early out */
            continue;
        }

        ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
        ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
        if (tr == NULL) {
            /* When a single batch contains multiple add's for an exclusive
             * relationship, it's possible that an id was in the added list
             * that is no longer available for the entity. */
            continue;
        }

        int32_t column = tr->index, storage_i;
        it.columns[0] = column + 1;
        it.ptrs[0] = NULL;
        it.sizes[0] = 0;
        it.event_id = id;
        it.ids[0] = id;

        if (count) {
            storage_i = tr->column;
            if (storage_i != -1) {
                /* If this is a component, fetch pointer & size */
                ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
                ecs_column_t *c = &columns[storage_i];
                ecs_size_t size = c->ti->size;
                void *ptr = ecs_vec_get(&c->data, size, offset);
                it.sizes[0] = size;

                if (override_ptr) {
                    if (event == EcsOnAdd) {
                        /* If this is a new override, initialize the component
                         * with the value of the overridden component. */
                        flecs_override_copy(
                            world, table, ti, ptr, override_ptr, offset, count);
                    } else if (er_onset) {
                        /* If an override was removed, this re-exposes the
                         * overridden component. Because this causes the actual
                         * (now inherited) value of the component to change, an
                         * OnSet event must be emitted for the base component.*/
                        ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL);
                        ecs_event_id_record_t *iders_set[5] = {0};
                        int32_t ider_set_i, ider_set_count = 
                            flecs_event_observers_get(er_onset, id, iders_set);
                        if (ider_set_count) {
                            /* Set the source temporarily to the base and base
                             * component pointer. */
                            it.sources[0] = base;
                            it.ptrs[0] = ptr;
                            for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) {
                                ecs_event_id_record_t *ider = iders_set[ider_set_i];
                                flecs_observers_invoke(world, &ider->self_up, &it, table, EcsIsA, evtx);
                                flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA, evtx);
                            }
                            it.sources[0] = 0;
                        }
                    }
                }

                it.ptrs[0] = ptr;
            } else {
                if (it.event == EcsUnSet) {
                    /* Only valid for components, not tags */
                    continue;
                }
            }
        }

        /* Actually invoke observers for this event/id */
        for (ider_i = 0; ider_i < ider_count; ider_i ++) {
            ecs_event_id_record_t *ider = iders[ider_i];
            flecs_observers_invoke(world, &ider->self, &it, table, 0, evtx);
            flecs_observers_invoke(world, &ider->self_up, &it, table, 0, evtx);
        }

        if (!ider_count || !count || !has_observed) {
            continue;
        }

        /* If event is propagated, we don't have to manually invalidate entities
         * lower in the tree(s). */
        propagated = true;

        /* The table->traversable_count value indicates if the table contains any
         * entities that are used as targets of traversable relationships. If the
         * entity/entities for which the event was generated is used as such a
         * target, events must be propagated downwards. */
        ecs_entity_t *entities = it.entities;
        it.entities = NULL;

        for (r = 0; r < count; r ++) {
            ecs_record_t *record = flecs_entities_get(world, entities[r]);
            if (!record) {
                /* If the event is emitted after a bulk operation, it's possible
                 * that it hasn't been populated with entities yet. */
                continue;
            }

            ecs_id_record_t *idr_t = record->idr;
            if (idr_t) {
                /* Entity is used as target in traversable pairs, propagate */
                ecs_entity_t e = entities[r];
                it.sources[0] = e;
                flecs_emit_propagate(
                    world, &it, idr, idr_t, 0, iders, ider_count);
            }
        }

        it.table = table;
        it.other_table = other_table;
        it.entities = entities;
        it.count = count;
        it.offset = offset;
        it.sources[0] = 0;
    }

    if (count && can_forward && has_observed && !propagated) {
        flecs_emit_propagate_invalidate(world, table, offset, count);
    }

    can_override = false; /* Don't override twice */
    can_unset = false; /* Don't unset twice */
    can_forward = false; /* Don't forward twice */

    if (unset_count && er_unset && (er != er_unset)) {
        /* Repeat event loop for UnSet event */
        unset_count = 0;
        er = er_unset;
        it.event = EcsUnSet;
        goto repeat_event;
    }

    if (wcer && er != wcer) {
        /* Repeat event loop for Wildcard event */
        er = wcer;
        it.event = event;
        goto repeat_event;
    }

error:
    world->stages[0].defer = defer;

    if (measure_time) {
        world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t);
    }
    return;
}

void ecs_emit(
    ecs_world_t *stage,
    ecs_event_desc_t *desc)
{
    ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage));
    if (desc->entity) {
        ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL);
        ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL);
        ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL);
        ecs_record_t *r = flecs_entities_get(world, desc->entity);
        ecs_table_t *table;
        if (!r || !(table = r->table)) {
            /* Empty entities can't trigger observers */
            return;
        }
        desc->table = table;
        desc->offset = ECS_RECORD_TO_ROW(r->row);
        desc->count = 1;
    }
    if (!desc->observable) {
        desc->observable = world;
    }
    ecs_type_t default_ids = (ecs_type_t){ 
        .count = 1, 
        .array = (ecs_id_t[]){ EcsAny }
    };
    if (!desc->ids || !desc->ids->count) {
        desc->ids = &default_ids;
    }
    flecs_emit(world, stage, desc);
}
