#include <api.h>

void Commands_defer_new(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_assert(!ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));    

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_frame_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_fini(world);
}

void Commands_defer_bulk_new(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    const ecs_entity_t *temp_ids = ecs_bulk_new(world, Position, 3);
    ecs_entity_t ids[3];
    memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 3);

    test_assert(!ecs_has(world, ids[0], Position));
    test_assert(!ecs_has(world, ids[1], Position));
    test_assert(!ecs_has(world, ids[2], Position));   

    ecs_defer_end(world);

    test_assert(!ecs_has(world, ids[0], Position));
    test_assert(!ecs_has(world, ids[1], Position));
    test_assert(!ecs_has(world, ids[2], Position));

    ecs_defer_end(world);

    test_assert(ecs_has(world, ids[0], Position));
    test_assert(ecs_has(world, ids[1], Position));
    test_assert(ecs_has(world, ids[2], Position));  

    ecs_frame_end(world);

    test_assert(ecs_has(world, ids[0], Position));
    test_assert(ecs_has(world, ids[1], Position));
    test_assert(ecs_has(world, ids[2], Position));

    ecs_fini(world);
}

void Commands_defer_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_add(world, e, Position);

    test_assert(!ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));    

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_frame_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_fini(world);
}

void Commands_defer_add_two(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_add(world, e, Position);
    ecs_add(world, e, Velocity);

    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));    
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_frame_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}

void Commands_defer_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_remove(world, e, Position);

    test_assert(ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));    

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));

    ecs_frame_end(world);

    test_assert(!ecs_has(world, e, Position));

    ecs_fini(world);
}

void Commands_defer_remove_two(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, Position);
    ecs_add(world, e, Velocity);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_remove(world, e, Position);
    ecs_remove(world, e, Velocity);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));   
    test_assert(ecs_has(world, e, Velocity)); 

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_frame_end(world);

    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_fini(world);
}

void Commands_defer_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    ecs_set(world, e, Position, {1, 2});

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 1);
    test_int(p->y, 2);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    /* set will not be deferred if the component exists */
    ecs_set(world, e, Position, {3, 4});

    test_assert(ecs_has(world, e, Position));
    p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 3);
    test_int(p->y, 4);

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));  
    p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 3);
    test_int(p->y, 4);  

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 3);
    test_int(p->y, 4);

    ecs_frame_end(world);

    test_assert(ecs_has(world, e, Position));
    p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 3);
    test_int(p->y, 4);

    ecs_fini(world);
}

void Commands_defer_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_delete(world, e);

    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e));  

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_defer_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_defer_begin(world);
    ecs_defer_begin(world);

    ecs_set(world, e, Velocity, {1, 2});

    ecs_defer_end(world);
    test_assert(!ecs_has(world, e, Velocity));
    ecs_defer_end(world);
    test_assert(ecs_has(world, e, Velocity));

    const Velocity *v = ecs_get(world, e, Velocity);
    test_assert(v != NULL);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

void Commands_defer_twice_in_progress(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 0);
    ecs_readonly_begin(world);
    ecs_world_t *stage = ecs_get_stage(world, 0);

    ecs_defer_begin(stage);
    ecs_defer_begin(stage);

    ecs_set(stage, e, Velocity, {1, 2});

    ecs_defer_end(stage);
    test_assert(!ecs_has(stage, e, Velocity));
    ecs_defer_end(stage);
    test_assert(!ecs_has(stage, e, Velocity));

    ecs_readonly_end(world);
    test_assert(ecs_has(world, e, Velocity));
    ecs_frame_end(world);
    test_assert(ecs_has(world, e, Velocity));

    const Velocity *v = ecs_get(world, e, Velocity);
    test_assert(v != NULL);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

static
void AddVelocity(ecs_iter_t *it) {
    ecs_id_t ecs_id(Velocity) = ecs_field_id(it, 2);

    ecs_defer_begin(it->world);

    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_add(it->world, it->entities[i], Velocity);
    }

    ecs_defer_end(it->world);
}

void Commands_run_w_defer(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_SYSTEM(world, AddVelocity, 0, Position, [in] Velocity());

    ecs_entity_t e = ecs_new(world, Position);

    ecs_run(world, AddVelocity, 0, NULL);

    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}

void Commands_system_in_progress_w_defer(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_SYSTEM(world, AddVelocity, EcsOnUpdate, Position, [in] Velocity());

    ecs_entity_t e = ecs_new(world, Position);

    ecs_progress(world, 0);

    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}

static bool on_set_invoked = 0;

static
void OnSetTestInvoked(ecs_iter_t *it) {
    on_set_invoked = 1;
}

void Commands_defer_get_mut_no_modify(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_defer_begin(world);

    Velocity *v = ecs_get_mut(world, e, Velocity);
    v->x = 1;
    v->y = 2;

    test_assert(!on_set_invoked);

    ecs_defer_end(world);

    test_assert(!on_set_invoked);

    test_assert(ecs_has(world, e, Velocity));
    const Velocity *vptr = ecs_get(world, e, Velocity);
    test_int(vptr->x, 1);
    test_int(vptr->y, 2);

    ecs_fini(world);
}

void Commands_defer_get_mut_w_modify(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_defer_begin(world);

    Velocity *v = ecs_get_mut(world, e, Velocity);
    v->x = 1;
    v->y = 2;
    test_assert(!on_set_invoked);

    ecs_modified(world, e, Velocity);

    test_assert(!on_set_invoked);

    ecs_defer_end(world);

    test_assert(on_set_invoked);

    test_assert(ecs_has(world, e, Velocity));
    const Velocity *vptr = ecs_get(world, e, Velocity);
    test_int(vptr->x, 1);
    test_int(vptr->y, 2);

    ecs_fini(world);
}

void Commands_defer_modify(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Velocity);

    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Velocity);

    ecs_entity_t e = ecs_new(world, Velocity);

    ecs_defer_begin(world);

    ecs_modified(world, e, Velocity);

    test_assert(!on_set_invoked);

    ecs_defer_end(world);

    test_assert(on_set_invoked);

    ecs_fini(world);
}

void Commands_defer_set_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_defer_begin(world);

    ecs_set_pair(world, e, Velocity, ecs_id(Position), {1, 2});

    ecs_defer_end(world);

    test_assert(ecs_has_pair(world, e, ecs_id(Velocity), ecs_id(Position)));

    ecs_fini(world);    
}

void Commands_defer_clear(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_clear(world, e);

    test_assert(ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));

    ecs_frame_end(world);

    test_assert(!ecs_has(world, e, Position));

    ecs_fini(world);    
}

void Commands_defer_add_after_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    ecs_add(world, e, Velocity);

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    test_int(ecs_count(world, Position), 0);
    test_int(ecs_count(world, Velocity), 0);

    ecs_fini(world);  
}

void Commands_defer_set_after_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    ecs_set(world, e, Velocity, {1, 2});

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    test_int(ecs_count(world, Position), 0);
    test_int(ecs_count(world, Velocity), 0);

    ecs_fini(world);  
}

void Commands_defer_get_mut_after_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    Velocity *v = ecs_get_mut(world, e, Velocity);
    v->x = 1;
    v->y = 2;

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    test_int(ecs_count(world, Position), 0);
    test_int(ecs_count(world, Velocity), 0);

    ecs_fini(world);  
}

void Commands_defer_get_mut_after_delete_2nd_to_last(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);

    /* Create 2nd position. This will cause deletion of the entity in the sparse
     * set to take a different path since it's not the last. */
    ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    Velocity *v = ecs_get_mut(world, e, Velocity);
    v->x = 1;
    v->y = 2;

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    test_int(ecs_count(world, Position), 1);
    test_int(ecs_count(world, Velocity), 0);

    ecs_fini(world);  
}

void Commands_defer_add_child_to_deleted_parent(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t parent = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_add(world, child, Velocity);
    ecs_delete(world, parent);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, parent));
    test_assert(!ecs_is_alive(world, child));

    ecs_fini(world);    
}

void Commands_recreate_deleted_entity_while_deferred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e_old = ecs_new(world, 0);
    test_assert(e_old != 0);

    ecs_delete(world, e_old);
    test_assert(!ecs_is_alive(world, e_old));

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(e != e_old);
    test_assert((e & ECS_ENTITY_MASK) == (e_old & ECS_ENTITY_MASK));

    test_assert(!ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));    

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_frame_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_fini(world);
}

void Commands_defer_add_to_recycled_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t id = ecs_new_id(world);
    test_assert(id != 0);

    ecs_delete(world, id);

    ecs_entity_t id_2 = ecs_new_id(world);
    test_assert(id != id_2);
    test_assert((int32_t)id == (int32_t)id_2);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_id(world, id_2);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, id));
    test_assert(ecs_is_alive(world, id_2));
    test_assert(ecs_is_alive(world, child));
    test_assert(ecs_has_id(world, child, id_2));

    ecs_fini(world); 
}

void Commands_defer_add_to_recycled_id_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t id = ecs_new_id(world);
    test_assert(id != 0);

    ecs_delete(world, id);

    ecs_entity_t id_2 = ecs_new_id(world);
    test_assert(id != id_2);
    test_assert((int32_t)id == (int32_t)id_2);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_id(world, ECS_TOGGLE | id_2);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, id));
    test_assert(ecs_is_alive(world, id_2));
    test_assert(ecs_is_alive(world, child));
    test_assert(ecs_has_id(world, child, ECS_TOGGLE | id_2));

    ecs_fini(world); 
}

void Commands_defer_add_to_recycled_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t target = ecs_new_id(world);
    test_assert(target != 0);

    ecs_entity_t rel = ecs_new_id(world);
    test_assert(rel != 0);

    ecs_delete(world, rel);

    ecs_entity_t rel_2 = ecs_new_id(world);
    test_assert(rel != rel_2);
    test_assert((int32_t)rel == (int32_t)rel_2);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, rel_2, target);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(ecs_is_alive(world, target));
    test_assert(!ecs_is_alive(world, rel));
    test_assert(ecs_is_alive(world, rel_2));
    test_assert(ecs_is_alive(world, child));
    test_assert(ecs_has_pair(world, child, rel_2, target));

    ecs_fini(world); 
}

void Commands_defer_add_to_recycled_object(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_TAG(world, Rel);

    ecs_entity_t target = ecs_new(world, 0);
    test_assert(target != 0);

    ecs_delete(world, target);

    ecs_entity_t object_2 = ecs_new_id(world);
    test_assert(target != object_2);
    test_assert((int32_t)target == (int32_t)object_2);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, Rel, object_2);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, target));
    test_assert(ecs_is_alive(world, object_2));
    test_assert(ecs_is_alive(world, child));
    test_assert(ecs_has_pair(world, child, Rel, object_2));

    ecs_fini(world); 
}

void Commands_defer_add_to_recycled_object_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t parent = ecs_new(world, 0);
    test_assert(parent != 0);

    ecs_delete(world, parent);

    ecs_entity_t parent_2 = ecs_new_id(world);
    test_assert(parent != parent_2);
    test_assert((int32_t)parent == (int32_t)parent_2);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent_2);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, parent));
    test_assert(ecs_is_alive(world, parent_2));
    test_assert(ecs_is_alive(world, child));
    test_assert(ecs_has_pair(world, child, EcsChildOf, parent_2));

    ecs_fini(world); 
}

void Commands_defer_add_to_deleted_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t id = ecs_new_id(world);
    test_assert(id != 0);
    
    ecs_frame_begin(world, 1);
    ecs_defer_begin(world);

    ecs_delete(world, id);
    ecs_entity_t child = ecs_new_w_id(world, id);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);
    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, id));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_id(world, child, id));

    ecs_fini(world); 
}

void Commands_defer_add_to_deleted_id_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t id = ecs_new_id(world);
    test_assert(id != 0);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, id);
    ecs_entity_t child = ecs_new_w_id(world, ECS_TOGGLE | id);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, id));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_id(world, child, ECS_TOGGLE | id));

    ecs_fini(world); 
}

void Commands_defer_add_to_deleted_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t target = ecs_new_id(world);
    test_assert(target != 0);

    ecs_entity_t rel = ecs_new_id(world);
    test_assert(rel != 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, rel);
    ecs_entity_t child = ecs_new_w_pair(world, rel, target);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(ecs_is_alive(world, target));
    test_assert(!ecs_is_alive(world, rel));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_pair(world, child, rel, target));

    ecs_fini(world); 
}

void Commands_defer_add_to_deleted_object(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_TAG(world, Rel);

    ecs_entity_t target = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, target);
    ecs_entity_t child = ecs_new_w_pair(world, Rel, target);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, target));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_pair(world, child, Rel, target));

    ecs_fini(world); 
}

void Commands_defer_add_to_deleted_object_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t parent = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_delete(world, parent);
    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_add(world, child, Velocity);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, parent));
    test_assert(!ecs_is_alive(world, child));

    ecs_fini(world); 
}

void Commands_defer_delete_added_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t id = ecs_new_id(world);
    test_assert(id != 0);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_id(world, id);
    ecs_add(world, child, Velocity);
    ecs_delete(world, id);

    ecs_defer_end(world);  

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, id));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_id(world, child, id));

    ecs_fini(world); 
}

void Commands_defer_delete_added_id_w_role(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t id = ecs_new_id(world);
    test_assert(id != 0);
    
    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_id(world, ECS_TOGGLE | id);
    ecs_add(world, child, Velocity);
    ecs_delete(world, id);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, id));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_id(world, child, ECS_TOGGLE | id));

    ecs_fini(world);
}

void Commands_defer_delete_added_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t target = ecs_new_id(world);
    test_assert(target != 0);

    ecs_entity_t rel = ecs_new_id(world);
    test_assert(rel != 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, rel, target);
    ecs_add(world, child, Velocity);
    ecs_delete(world, rel);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(ecs_is_alive(world, target));
    test_assert(!ecs_is_alive(world, rel));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_pair(world, child, rel, target));

    ecs_fini(world); 
}

void Commands_defer_delete_added_object(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ECS_TAG(world, Rel);

    ecs_entity_t target = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, Rel, target);
    ecs_add(world, child, Velocity);
    ecs_delete(world, target);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, target));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has_pair(world, child, Rel, target));

    ecs_fini(world);
}

void Commands_defer_delete_added_object_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t parent = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_add(world, child, Velocity);
    ecs_delete(world, parent);

    ecs_defer_end(world);  

    ecs_frame_end(world);    

    test_assert(!ecs_is_alive(world, parent));
    test_assert(!ecs_is_alive(world, child));

    ecs_fini(world); 
}

void Commands_discard_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));
    
    ecs_add(world, e, Position);

    test_assert(ecs_is_alive(world, e));
    test_assert(!ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e));
    test_assert(!ecs_has(world, e, Position));    

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_discard_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));
    
    ecs_remove(world, e, Position);

    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_has(world, e, Position));

    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_has(world, e, Position));    

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_discard_add_two(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new_id(world);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));
    
    ecs_add(world, e, Position);
    ecs_add(world, e, Velocity);

    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));    
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_discard_remove_two(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, Position);
    ecs_add(world, e, Velocity);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);

    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));
    
    ecs_remove(world, e, Position);
    ecs_remove(world, e, Velocity);

    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_has(world, e, Position));    
    test_assert(ecs_has(world, e, Velocity));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_discard_child(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    test_assert(!ecs_has(world, e, Position));
    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));    

    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, e);
    test_assert(child != 0);
    test_assert(!ecs_has_pair(world, child, EcsChildOf, e));

    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_is_alive(world, child));
    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has_pair(world, child, EcsChildOf, e));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));
    test_assert(!ecs_is_alive(world, child));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));
    test_assert(!ecs_is_alive(world, child));

    ecs_fini(world);
}

void Commands_discard_child_w_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_frame_begin(world, 1);

    ecs_defer_begin(world);

    ecs_defer_begin(world);
    
    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    test_assert(!ecs_has(world, e, Position));
    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));    

    ecs_entity_t child = ecs_new(world, 0);
    test_assert(child != 0);

    ecs_add_pair(world, child, EcsChildOf, e);
    test_assert(!ecs_has_pair(world, child, EcsChildOf, e));

    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_is_alive(world, child));

    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));
    test_assert(!ecs_is_alive(world, child));

    ecs_frame_end(world);

    test_assert(!ecs_is_alive(world, e));
    test_assert(!ecs_is_alive(world, child));

    ecs_fini(world);
}

void Commands_defer_return_value(void) {
    ecs_world_t *world = ecs_mini();

    test_bool(ecs_defer_begin(world), true);

    test_bool(ecs_defer_begin(world), false);

    test_bool(ecs_defer_end(world), false);

    test_bool(ecs_defer_end(world), true);

    ecs_fini(world);
}

void Commands_defer_get_mut_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Pair);

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);

    Position *p = ecs_get_mut_pair(world, e, Position, Pair);
    test_assert(p != NULL);
    p->x = 10;
    p->y = 20;

    ecs_defer_end(world);

    const Position *pc = ecs_get_pair(world, e, Position, Pair);
    test_assert(pc != NULL);
    test_int(pc->x, 10);
    test_int(pc->y, 20);

    ecs_fini(world);
}

void Commands_async_stage_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new_id(world);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_add(async, e, Position);
    test_assert(!ecs_has(world, e, Position));
    ecs_merge(async);
    test_assert(ecs_has(world, e, Position));

    ecs_async_stage_free(async);

    ecs_fini(world);
}

void Commands_async_stage_add_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new_id(world);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_add(async, e, Position);
    test_assert(!ecs_has(world, e, Position));
    ecs_merge(async);
    test_assert(ecs_has(world, e, Position));

    ecs_add(async, e, Velocity);
    test_assert(!ecs_has(world, e, Velocity));
    ecs_merge(async);
    test_assert(ecs_has(world, e, Velocity));

    ecs_async_stage_free(async);

    ecs_fini(world);
}


void Commands_async_stage_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_remove(async, e, Position);
    test_assert(ecs_has(world, e, Position));
    ecs_merge(async);
    test_assert(!ecs_has(world, e, Position));

    ecs_async_stage_free(async);

    ecs_fini(world);
}

void Commands_async_stage_clear(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);
    ecs_add(world, e, Velocity);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_clear(async, e);
    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));
    ecs_merge(async);
    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_async_stage_free(async);

    ecs_fini(world);
}

void Commands_async_stage_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);
    ecs_add(world, e, Velocity);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_delete(async, e);
    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));
    test_assert(ecs_is_alive(world, e));
    ecs_merge(async);
    test_assert(!ecs_is_alive(world, e));

    ecs_async_stage_free(async);

    ecs_fini(world);
}

void Commands_async_stage_new(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_entity_t e = ecs_new(async, 0);
    ecs_add(async, e, Position);
    ecs_add(async, e, Velocity);
    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    ecs_merge(async);
    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity)); 
    test_assert(ecs_is_alive(world, e));

    ecs_async_stage_free(async);

    ecs_fini(world);
}

void Commands_async_stage_no_get(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);

    ecs_world_t *async = ecs_async_stage_new(world);
    
    test_expect_abort();
    ecs_get(async, e, Position);
}

void Commands_async_stage_readonly(void) {
    ecs_world_t *world = ecs_mini();

    ecs_world_t *async = ecs_async_stage_new(world);
    test_assert(!ecs_stage_is_readonly(async));

    ecs_async_stage_free(async);
    ecs_fini(world);
}

void Commands_async_stage_is_async(void) {
    ecs_world_t *world = ecs_mini();

    ecs_world_t *async = ecs_async_stage_new(world);
    test_assert(ecs_stage_is_async(async));

    ecs_async_stage_free(async);
    ecs_fini(world);
}

static
void RegisterComponent(ecs_iter_t *it) {
    ecs_entity_t ecs_id(Position) = ecs_component_init(it->world, 
        &(ecs_component_desc_t){
            .entity = ecs_entity(it->world, {.name = "Position"}),
            .type.size = ECS_SIZEOF(Position),
            .type.alignment = ECS_ALIGNOF(Position)
        });    

    test_assert(ecs_id(Position) != 0);
}

void Commands_register_component_while_in_progress(void) {
    ecs_world_t *world = ecs_init();

    ECS_SYSTEM(world, RegisterComponent, EcsOnUpdate, 0);

    ecs_progress(world, 0);

    ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position");
    test_assert(ecs_id(Position) != 0);
    test_assert(ecs_has(world, ecs_id(Position), EcsComponent));

    ecs_entity_t e = ecs_new_id(world);
    ecs_set(world, e, Position, {10, 20});
    test_assert(ecs_has_id(world, e, ecs_id(Position)));

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    ecs_fini(world);
}

void Commands_register_component_while_staged(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t canary = ecs_new_id(world);

    ecs_readonly_begin(world);

    ecs_world_t *stage = ecs_get_stage(world, 0);

    ecs_add_id(stage, canary, Tag);

    ecs_entity_t ecs_id(Position) = ecs_component_init(stage, 
        &(ecs_component_desc_t){
            .entity = ecs_entity(stage, {.name = "Position"}),
            .type.size = ECS_SIZEOF(Position),
            .type.alignment = ECS_ALIGNOF(Position)
        });

    test_assert(ecs_id(Position) != 0);

    ecs_set(stage, e, Position, {10, 20});
    test_assert(!ecs_has_id(world, e, ecs_id(Position)));
    test_assert(!ecs_has_id(world, canary, Tag));

    ecs_readonly_end(world);

    test_assert(ecs_has_id(world, e, ecs_id(Position)));

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    ecs_fini(world);
}

void Commands_register_component_while_deferred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t canary = ecs_new_id(world);

    ecs_defer_begin(world);

    ecs_add_id(world, canary, Tag);

    ecs_entity_t ecs_id(Position) = ecs_component_init(world, 
        &(ecs_component_desc_t){
            .entity = ecs_entity(world, {.name = "Position"}),
            .type.size = ECS_SIZEOF(Position),
            .type.alignment = ECS_ALIGNOF(Position)
        });

    test_assert(ecs_id(Position) != 0);

    ecs_set(world, e, Position, {10, 20});
    test_assert(!ecs_has_id(world, e, ecs_id(Position)));
    test_assert(!ecs_has_id(world, canary, Tag));

    ecs_defer_end(world);

    ecs_fini(world);
}

void Commands_defer_enable(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    ecs_enable(world, e, false);
    test_assert(!ecs_has_id(world, e, EcsDisabled));
    ecs_defer_end(world);

    test_assert(ecs_has_id(world, e, EcsDisabled));

    ecs_fini(world);
}

void Commands_defer_disable(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_w_id(world, EcsDisabled);

    ecs_defer_begin(world);
    ecs_enable(world, e, true);
    test_assert(ecs_has_id(world, e, EcsDisabled));
    ecs_defer_end(world);

    test_assert(!ecs_has_id(world, e, EcsDisabled));

    ecs_fini(world);
}

void Commands_defer_delete_with(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e_1 = ecs_new(world, TagA);
    ecs_entity_t e_2 = ecs_new(world, TagA);
    ecs_entity_t e_3 = ecs_new(world, TagA);
    ecs_add(world, e_1, TagB);

    ecs_defer_begin(world);
    ecs_delete_with(world, TagA);
    test_assert(ecs_is_alive(world, e_1));
    test_assert(ecs_is_alive(world, e_2));
    test_assert(ecs_is_alive(world, e_3));
    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e_1));
    test_assert(!ecs_is_alive(world, e_2));
    test_assert(!ecs_is_alive(world, e_3));

    ecs_fini(world);
}

void Commands_defer_remove_all(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e_1 = ecs_new(world, TagA);
    ecs_entity_t e_2 = ecs_new(world, TagA);
    ecs_entity_t e_3 = ecs_new(world, TagA);
    ecs_add(world, e_1, TagB);

    ecs_defer_begin(world);
    ecs_remove_all(world, TagA);
    test_assert(ecs_is_alive(world, e_1));
    test_assert(ecs_is_alive(world, e_2));
    test_assert(ecs_is_alive(world, e_3));
    ecs_defer_end(world);

    test_assert(ecs_is_alive(world, e_1));
    test_assert(ecs_is_alive(world, e_2));
    test_assert(ecs_is_alive(world, e_3));

    test_assert(!ecs_has_id(world, e_1, TagA));
    test_assert(!ecs_has_id(world, e_2, TagA));
    test_assert(!ecs_has_id(world, e_3, TagA));
    test_assert(ecs_has_id(world, e_1, TagB));

    ecs_fini(world);
}

void Commands_deferred_modified_after_remove(void) {
    ecs_world_t *world = ecs_mini();
    
    ECS_COMPONENT(world, Position);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = { EcsOnSet },
        .callback = OnSetTestInvoked
    });

    ecs_entity_t e = ecs_new(world, Position);

    ecs_modified(world, e, Position);

    test_int(on_set_invoked, 1);
    on_set_invoked = 0;

    ecs_defer_begin(world);

    ecs_remove(world, e, Position);
    test_assert(ecs_has(world, e, Position));

    ecs_modified(world, e, Position);
    ecs_defer_end(world);

    test_int(on_set_invoked, 0);

    ecs_fini(world);
}

typedef struct {
    int value;
} Counter;

ECS_COMPONENT_DECLARE(Counter);

static int update_counter_invoked = 0;
static int remove_counter_invoked = 0;

static void update_counter(ecs_iter_t *it) {
    ecs_world_t *world = it->world;

    for (int i = 0; i < it->count; i ++) {
        ecs_entity_t e = it->entities[i];
        ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0);
        test_assert(parent != 0);

        Counter *ptr = ecs_get_mut(world, parent, Counter);
        test_assert(ptr != NULL);

        if (it->event == EcsOnAdd) {
            ptr->value ++;
        } else if (it->event == EcsOnRemove) {
            ptr->value --;
        }

        update_counter_invoked ++;
    }
}

static void remove_counter(ecs_iter_t *it) {
    Counter *ptr = ecs_field(it, Counter, 1);

    for (int i = 0; i < it->count; i ++) {
        test_int(ptr[i].value, 0);
        remove_counter_invoked ++;
    }
}

void Commands_merge_cleanup_ops_before_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT_DEFINE(world, Counter);
    ECS_TAG(world, Tag);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .events = {EcsOnAdd, EcsOnRemove},
        .callback = update_counter
    });

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Counter),
            .src.flags = EcsSelf
        },
        .events = {EcsOnRemove},
        .callback = remove_counter
    });

    ecs_entity_t parent = ecs_new_id(world);
    ecs_set(world, parent, Counter, {0});

    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_add(world, child, Tag);

    test_int(update_counter_invoked, 1);

    const Counter *ptr = ecs_get(world, parent, Counter);
    test_assert(ptr != NULL);
    test_assert(ptr->value == 1);

    ecs_delete(world, parent);

    test_int(update_counter_invoked, 2);
    test_int(remove_counter_invoked, 1);

    ecs_fini(world);
}

void Commands_merge_nested_cleanup_ops_before_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT_DEFINE(world, Counter);
    ECS_TAG(world, Tag);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = Tag,
            .src.flags = EcsSelf
        },
        .events = {EcsOnAdd, EcsOnRemove},
        .callback = update_counter
    });

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Counter),
            .src.flags = EcsSelf
        },
        .events = {EcsOnRemove},
        .callback = remove_counter
    });

    ecs_entity_t parent = ecs_new_id(world);
    ecs_set(world, parent, Counter, {0});

    ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_add(world, child, Tag);
    test_int(update_counter_invoked, 1);
    ecs_set(world, child, Counter, {0});

    ecs_entity_t grand_child = ecs_new_w_pair(world, EcsChildOf, child);
    ecs_add(world, grand_child, Tag);
    test_int(update_counter_invoked, 2);

    const Counter *ptr = ecs_get(world, parent, Counter);
    test_assert(ptr != NULL);
    test_assert(ptr->value == 1);

    ptr = ecs_get(world, child, Counter);
    test_assert(ptr != NULL);
    test_assert(ptr->value == 1);

    ecs_delete(world, parent);

    test_int(update_counter_invoked, 4);
    test_int(remove_counter_invoked, 2);

    ecs_fini(world);
}

void Commands_defer_suspend_resume(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e = ecs_new_id(world);
    
    ecs_defer_begin(world);
    ecs_add(world, e, TagA);
    test_assert(!ecs_has(world, e, TagA));

    ecs_defer_suspend(world);
    ecs_add(world, e, TagB);
    test_assert(!ecs_has(world, e, TagA));
    test_assert(ecs_has(world, e, TagB));
    ecs_defer_resume(world);

    test_assert(!ecs_has(world, e, TagA));
    test_assert(ecs_has(world, e, TagB));

    ecs_defer_end(world);

    test_assert(ecs_has(world, e, TagA));
    test_assert(ecs_has(world, e, TagB));

    ecs_fini(world);
}

static
void System(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);
}

static int system_2_invoked = 0;

static
void System2(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);
    system_2_invoked ++;
}

void Commands_create_observer_while_deferred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    ecs_defer_begin(world);
    Probe ctx = {0};
    ecs_entity_t observer = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms = {{ .id = TagA }},
        .events = {EcsOnAdd},
        .callback = System,
        .ctx = &ctx
    });
    ecs_defer_end(world);
    test_assert(observer != 0);
    test_int(ctx.invoked, 0);

    ecs_new(world, TagA);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Commands_create_query_while_deferred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    ecs_defer_begin(world);
    ecs_query_t *query = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.terms = {{ .id = TagA }}
    });
    ecs_defer_end(world);
    test_assert(query != 0);

    ecs_entity_t e = ecs_new(world, TagA);

    ecs_iter_t it = ecs_query_iter(world, query);
    test_bool(true, ecs_query_next(&it));
    test_int(1, it.count);
    test_uint(e, it.entities[0]);
    test_bool(false, ecs_query_next(&it));

    ecs_fini(world);
}

void Commands_update_observer_while_deferred(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    ecs_defer_begin(world);
    Probe ctx = {0};
    ecs_entity_t observer = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms = {{ .id = TagA }},
        .events = {EcsOnAdd},
        .callback = System,
        .ctx = &ctx
    });
    ecs_defer_end(world);
    test_assert(observer != 0);
    test_int(ctx.invoked, 0);

    ecs_new(world, TagA);
    test_int(ctx.invoked, 1);

    test_int(system_2_invoked, 0);

    ecs_defer_begin(world);
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .entity = observer,
        .callback = System2
    });
    ecs_defer_end(world);

    ecs_new(world, TagA);
    test_int(ctx.invoked, 2);
    test_int(system_2_invoked, 1);

    ecs_fini(world);
}

typedef struct {
    int16_t arr[8096];
} LargeComponent;

void Commands_defer_set_large_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, LargeComponent);

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    LargeComponent *ptr = ecs_get_mut(world, e, LargeComponent);
    test_assert(ptr != NULL);
    for (int i = 0; i < 8096; i ++) {
        ptr->arr[i] = i;
    }
    test_assert(!ecs_has(world, e, LargeComponent));
    ecs_defer_end(world);

    ptr = ecs_get_mut(world, e, LargeComponent);
    test_assert(ptr != NULL);
    for (int i = 0; i < 8096; i ++) {
        test_int(ptr->arr[i], i);
    }

    ecs_fini(world);
}

static ECS_COMPONENT_DECLARE(Position);

static void CreatePosition(ecs_iter_t *it) {
    ecs_entity_t e = ecs_new_id(it->world);
    ecs_set(it->world, e, Position, {999, 1000});
}

void Commands_defer_while_suspend_readonly(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT_DEFINE(world, Position);

    /* Create observer on EcsComponent which will defer a command while readonly
     * mode is suspended */
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(EcsComponent),
        .events = { EcsOnAdd },
        .callback = CreatePosition
    });

    ecs_world_t *s = ecs_get_stage(world, 0);

    ecs_readonly_begin(world);

    /* Component creation suspends readonly mode so that they can be used right
     * after they have been registered */
    ECS_COMPONENT(s, Velocity);

    ecs_entity_t e = 
    ecs_set(s, 0, Position, {10, 20});
    ecs_set(s, e, Velocity, {1, 2});
    test_assert(!ecs_has(s, e, Position));
    test_assert(!ecs_has(s, e, Velocity));

    ecs_readonly_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    const Velocity *v = ecs_get(world, e, Velocity);
    test_assert(v != NULL);
    test_int(v->x, 1);
    test_int(v->y, 2);

    test_int(2, ecs_count(world, Position));

    ecs_fini(world);
}

void Commands_defer_while_suspend_readonly_w_existing_commands(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT_DEFINE(world, Position);

    /* Create observer on EcsComponent which will defer a command while readonly
     * mode is suspended */
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(EcsComponent),
        .events = { EcsOnAdd },
        .callback = CreatePosition
    });

    ecs_world_t *s = ecs_get_stage(world, 0);

    ecs_readonly_begin(world);

    ecs_entity_t e1 = ecs_set(s, 0, Position, {10, 20});
    test_assert(!ecs_has(s, e1, Position));

    /* Component creation suspends readonly mode so that they can be used right
     * after they have been registered */
    ECS_COMPONENT(s, Velocity);

    ecs_entity_t e2 = 
    ecs_set(s, 0, Position, {20, 30});
    ecs_set(s, e2, Velocity, {1, 2});
    test_assert(!ecs_has(s, e2, Position));
    test_assert(!ecs_has(s, e2, Velocity));

    ecs_readonly_end(world);

    test_assert(ecs_has(world, e1, Position));
    test_assert(ecs_has(world, e2, Position));
    test_assert(ecs_has(world, e2, Velocity));

    const Position *p = ecs_get(world, e1, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    p = ecs_get(world, e2, Position);
    test_assert(p != NULL);
    test_int(p->x, 20);
    test_int(p->y, 30);

    const Velocity *v = ecs_get(world, e2, Velocity);
    test_assert(v != NULL);
    test_int(v->x, 1);
    test_int(v->y, 2);

    test_int(3, ecs_count(world, Position));

    ecs_fini(world);
}

void Commands_defer_add_union_relationship(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Union);
    ECS_TAG(world, Tgt);

    ecs_entity_t e = ecs_new_id(world);
    ecs_defer_begin(world);
    ecs_add_pair(world, e, Rel, Tgt);
    test_assert(!ecs_has_pair(world, e, Rel, Tgt));
    ecs_defer_end(world);
    test_assert(ecs_has_pair(world, e, Rel, Tgt));

    ecs_fini(world);
}

void Commands_defer_add_existing_union_relationship(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Union);
    ECS_TAG(world, TgtA);
    ECS_TAG(world, TgtB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, Rel, TgtA);
    test_assert(ecs_has_pair(world, e, Rel, TgtA));

    ecs_defer_begin(world);
    ecs_add_pair(world, e, Rel, TgtB);
    test_assert(ecs_has_pair(world, e, Rel, TgtA));
    test_assert(!ecs_has_pair(world, e, Rel, TgtB));
    ecs_defer_end(world);
    test_assert(!ecs_has_pair(world, e, Rel, TgtA));
    test_assert(ecs_has_pair(world, e, Rel, TgtB));

    ecs_fini(world);
}

void Commands_defer_add_union_relationship_2_ops(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Union);
    ECS_TAG(world, Tgt);
    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_defer_begin(world);
    ecs_add_pair(world, e, Rel, Tgt);
    ecs_add(world, e, Tag);
    test_assert(!ecs_has_pair(world, e, Rel, Tgt));
    test_assert(!ecs_has(world, e, Tag));
    ecs_defer_end(world);
    test_assert(ecs_has_pair(world, e, Rel, Tgt));
    test_assert(ecs_has(world, e, Tag));

    ecs_fini(world);
}

void Commands_defer_add_existing_union_relationship_2_ops(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Union);
    ECS_TAG(world, TgtA);
    ECS_TAG(world, TgtB);
    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, Rel, TgtA);
    test_assert(ecs_has_pair(world, e, Rel, TgtA));

    ecs_defer_begin(world);
    ecs_add_pair(world, e, Rel, TgtB);
    ecs_add(world, e, Tag);
    test_assert(ecs_has_pair(world, e, Rel, TgtA));
    test_assert(!ecs_has_pair(world, e, Rel, TgtB));
    test_assert(!ecs_has(world, e, Tag));
    ecs_defer_end(world);
    test_assert(!ecs_has_pair(world, e, Rel, TgtA));
    test_assert(ecs_has_pair(world, e, Rel, TgtB));
    test_assert(ecs_has(world, e, Tag));

    ecs_fini(world);
}

void Commands_defer_remove_after_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {10, 20});
    test_assert(!ecs_has(world, e, Position));
    ecs_remove(world, e, Position);
    test_assert(!ecs_has(world, e, Position));
    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));

    ecs_fini(world);
}

static int position_observer_invoked = 0;

static
void PositionObserver(ecs_iter_t *it) {
    position_observer_invoked ++;
}

void Commands_defer_remove_after_set_w_observer(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_OBSERVER(world, PositionObserver, EcsOnSet, Position);

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {10, 20});
    test_assert(!ecs_has(world, e, Position));
    ecs_remove(world, e, Position);
    test_assert(!ecs_has(world, e, Position));
    ecs_defer_end(world);

    test_int(position_observer_invoked, 0);
    test_assert(!ecs_has(world, e, Position));

    ecs_fini(world);
}

void Commands_defer_override_after_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    ecs_set(world, inst, Position, {20, 30});

    ecs_defer_begin(world);
    ecs_remove(world, inst, Position);
    ecs_add(world, inst, Position);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, inst, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    test_assert(p != ecs_get(world, base, Position));

    ecs_fini(world);
}

void Commands_defer_override_after_remove_3_ops(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    ecs_set(world, inst, Position, {20, 30});

    ecs_defer_begin(world);
    ecs_remove(world, inst, Position);
    ecs_add(world, inst, Position);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, inst, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    test_assert(p != ecs_get(world, base, Position));

    ecs_fini(world);
}

void Commands_flush_stage_to_deferred_world(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);

    ecs_world_t *async = ecs_async_stage_new(world);
    ecs_add(async, e, Tag);
    test_assert(!ecs_has(world, e, Tag));

    ecs_defer_begin(world);
    ecs_merge(async);
    test_assert(!ecs_has(world, e, Tag));
    ecs_defer_end(world);
    test_assert(ecs_has(world, e, Tag));

    ecs_async_stage_free(async);

    ecs_fini(world);
}

static
void AddPosition(ecs_iter_t *it) {
    test_int(it->count, 1);
    ecs_set(it->world, it->entities[0], Position, {10, 20});
}

void Commands_add_in_observer_during_merge(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT_DEFINE(world, Position);
    ECS_TAG(world, TagA);

    ECS_OBSERVER(world, AddPosition, EcsOnAdd, TagA);

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    ecs_add(world, e, TagA);
    test_assert(!ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, Position));
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, TagA));
    test_assert(ecs_has(world, e, Position));

    const Position *ptr = ecs_get(world, e, Position);
    test_assert(ptr != NULL);
    test_int(ptr->x, 10);
    test_int(ptr->y, 20);

    ecs_fini(world);
}

void Commands_add_in_observer_during_merge_2_commands(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT_DEFINE(world, Position);
    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ECS_OBSERVER(world, AddPosition, EcsOnAdd, TagB);

    ecs_entity_t e = ecs_new_id(world);

    ecs_log_set_level(0);

    ecs_defer_begin(world);
    ecs_add(world, e, TagA);
    ecs_add(world, e, TagB);
    test_assert(!ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, TagB));
    test_assert(!ecs_has(world, e, Position));
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, TagA));
    test_assert(ecs_has(world, e, TagB));
    test_assert(ecs_has(world, e, Position));

    const Position *ptr = ecs_get(world, e, Position);
    test_assert(ptr != NULL);
    test_int(ptr->x, 10);
    test_int(ptr->y, 20);

    ecs_log_set_level(-1);

    ecs_fini(world);
}

static ECS_DECLARE(TagB);
static ECS_DECLARE(TagC);

static
void AddTwoTags(ecs_iter_t *it) {
    test_int(it->count, 1);
    ecs_add(it->world, it->entities[0], TagB);
    ecs_add(it->world, it->entities[0], TagC);
}

void Commands_add_2_in_observer_while_on_remove_for_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG_DEFINE(world, TagB);
    ECS_TAG_DEFINE(world, TagC);

    ECS_OBSERVER(world, AddTwoTags, EcsOnRemove, TagA);

    ecs_entity_t e = ecs_new(world, TagA);
    ecs_delete(world, e);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_add_2_in_observer_while_on_remove_for_delete_child(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG_DEFINE(world, TagB);
    ECS_TAG_DEFINE(world, TagC);

    ECS_OBSERVER(world, AddTwoTags, EcsOnRemove, TagA);

    ecs_entity_t parent = ecs_new_id(world);
    ecs_entity_t e = ecs_new(world, TagA);
    ecs_add_pair(world, e, EcsChildOf, parent);
    ecs_delete(world, e);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_add_2_in_observer_while_on_remove_for_delete_recycled_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG_DEFINE(world, TagB);
    ECS_TAG_DEFINE(world, TagC);

    ECS_OBSERVER(world, AddTwoTags, EcsOnRemove, TagA);

    ecs_delete(world, ecs_new_id(world));

    ecs_entity_t e = ecs_new(world, TagA);
    ecs_delete(world, e);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_add_2_in_observer_while_on_remove_for_deferred_delete_recycled_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG_DEFINE(world, TagB);
    ECS_TAG_DEFINE(world, TagC);

    ECS_OBSERVER(world, AddTwoTags, EcsOnRemove, TagA);

    ecs_delete(world, ecs_new_id(world));

    ecs_entity_t e = ecs_new(world, TagA);
    ecs_defer_begin(world);
    ecs_delete(world, e);
    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Commands_defer_add_after_clear(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    ecs_clear(world, e);
    ecs_add(world, e, Position);
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));

    ecs_fini(world);
}

void Commands_defer_cmd_after_modified(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new_id(world);
    ecs_entity_t e2 = ecs_new_id(world);
    
    ecs_defer_begin(world);
    ecs_set(world, e1, Position, {10, 20});
    ecs_modified(world, e2, Position);
    ecs_set(world, e2, Position, {20, 30});

    test_assert(!ecs_has(world, e1, Position));
    test_assert(!ecs_has(world, e2, Position));
    ecs_defer_end(world);
    test_assert(ecs_has(world, e1, Position));
    test_assert(ecs_has(world, e2, Position));

    const Position *p = ecs_get(world, e1, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    p = ecs_get(world, e2, Position);
    test_assert(p != NULL);
    test_int(p->x, 20);
    test_int(p->y, 30);

    ecs_fini(world);
}

void Commands_defer_remove_after_emplace_different_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, Tag);

    ecs_defer_begin(world);
    Position *p = ecs_emplace(world, e, Position);
    p->x = 10;
    p->y = 20;
    ecs_remove(world, e, Tag);
    test_assert(!ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Tag));
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Tag));

    const Position *ptr = ecs_get(world, e, Position);
    test_assert(ptr != NULL);
    test_int(ptr->x, 10);
    test_int(ptr->y, 20);

    ecs_fini(world);
}

void Commands_defer_remove_after_set_and_emplace_different_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, Tag);

    ecs_defer_begin(world);
    Position *p = ecs_emplace(world, e, Position);
    p->x = 10;
    p->y = 20;
    ecs_set(world, e, Velocity, {1, 2});
    ecs_remove(world, e, Tag);
    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));
    test_assert(ecs_has(world, e, Tag));
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));
    test_assert(!ecs_has(world, e, Tag));

    const Position *ptr = ecs_get(world, e, Position);
    test_assert(ptr != NULL);
    test_int(ptr->x, 10);
    test_int(ptr->y, 20);

    const Velocity *v = ecs_get(world, e, Velocity);
    test_assert(v != NULL);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

void Commands_clear_after_add_to_nonempty(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e = ecs_new(world, TagA);

    ecs_defer_begin(world);
    ecs_add(world, e, TagB);
    ecs_clear(world, e);

    test_assert(ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, TagB));
    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, TagB));

    ecs_fini(world);
}

void Commands_remove_after_add_to_nonempty(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e = ecs_new(world, TagA);

    ecs_defer_begin(world);
    ecs_add(world, e, TagB);
    ecs_remove(world, e, TagA);
    ecs_remove(world, e, TagB);

    test_assert(ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, TagB));
    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, TagB));

    ecs_fini(world);
}

void Commands_register_while_deferred_with_n_stages(void) {
    ecs_world_t *world = ecs_mini();

    ecs_set_stage_count(world, 2);

    ecs_defer_begin(world);
    ECS_COMPONENT(world, Position);
    ecs_defer_end(world);

    test_assert(ecs_id(Position));

    ecs_fini(world);
}

static int position_velocity_observer_invoked = 0;

static
void PositionVelocityObserver(ecs_iter_t *it) {
    position_velocity_observer_invoked ++;
    test_int(it->count, 1);
    
    Position *p = ecs_field(it, Position, 1);
    Velocity *v = ecs_field(it, Velocity, 2);

    test_assert(p != NULL);
    test_assert(v != NULL);

    test_int(p->x, 10);
    test_int(p->y, 20);

    test_int(v->x, 1);
    test_int(v->y, 2);
}

void Commands_defer_2_sets_w_multi_observer(void) {
    ecs_world_t *world = ecs_mini();
    
    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_observer(world, {
        .filter.terms = {{ ecs_id(Position) }, { ecs_id(Velocity) }},
        .callback = PositionVelocityObserver,
        .events = { EcsOnSet }
    });

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {10, 20});
    ecs_set(world, e, Velocity, {1, 2});
    ecs_defer_end(world);

    test_int(position_velocity_observer_invoked, 2);

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 10);
    test_int(p->y, 20);
    const Velocity *v = ecs_get(world, e, Velocity);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

void Commands_defer_2_get_muts_w_multi_observer(void) {
    ecs_world_t *world = ecs_mini();
    
    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_observer(world, {
        .filter.terms = {{ ecs_id(Position) }, { ecs_id(Velocity) }},
        .callback = PositionVelocityObserver,
        .events = { EcsOnSet }
    });

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        p->x = 10;
        p->y = 20;
        ecs_modified(world, e, Position);
        Velocity *v = ecs_get_mut(world, e, Velocity);
        v->x = 1;
        v->y = 2;
        ecs_modified(world, e, Velocity);
    }
    ecs_defer_end(world);

    test_int(position_velocity_observer_invoked, 2);

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 10);
    test_int(p->y, 20);
    const Velocity *v = ecs_get(world, e, Velocity);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

void Commands_defer_2_get_muts_no_modified_w_multi_observer(void) {
    ecs_world_t *world = ecs_mini();
    
    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_observer(world, {
        .filter.terms = {{ ecs_id(Position) }, { ecs_id(Velocity) }},
        .callback = PositionVelocityObserver,
        .events = { EcsOnSet }
    });

    ecs_entity_t e = ecs_new_id(world);

    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        p->x = 10;
        p->y = 20;
        Velocity *v = ecs_get_mut(world, e, Velocity);
        v->x = 1;
        v->y = 2;
    }
    ecs_defer_end(world);

    test_int(position_velocity_observer_invoked, 0);

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 10);
    test_int(p->y, 20);
    const Velocity *v = ecs_get(world, e, Velocity);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

void Commands_exists_remove_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_set(world, e, Position, {1, 2});

    ecs_defer_begin(world);
    ecs_remove(world, e, Position);
    ecs_set(world, e, Position, {5, 6});
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 5);
    test_int(p->y, 6);

    ecs_fini(world);
}

void Commands_absent_remove_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_defer_begin(world);
    ecs_remove(world, e, Position);
    ecs_set(world, e, Position, {5, 6});
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 5);
    test_int(p->y, 6);

    ecs_fini(world);
}

void Commands_exists_set_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_set(world, e, Position, {1, 2});

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {5, 6});
    ecs_remove(world, e, Position);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p == NULL);

    ecs_fini(world);
}

void Commands_absent_set_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {5, 6});
    ecs_remove(world, e, Position);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p == NULL);

    ecs_fini(world);
}

void Commands_exists_set_w_get_mut(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_set(world, e, Position, {1, 2});

    ecs_defer_begin(world);
    {
        ecs_set(world, e, Position, {5, 6});
        Position *p = ecs_get_mut(world, e, Position);
        p->x = 11;
    }
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 11);
    test_int(p->y, 6);

    ecs_fini(world);
}

void Commands_exists_remove_get_mut(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_set(world, e, Position, {1, 2});

    ecs_defer_begin(world);
    ecs_remove(world, e, Position);
    {
        Position *p = ecs_get_mut(world, e, Position);
        test_assert(p != NULL);
        p->x = 5;
        p->y = 6;
    }
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 5);
    test_int(p->y, 6);

    ecs_fini(world);
}

void Commands_absent_remove_get_mut(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_defer_begin(world);
    ecs_remove(world, e, Position);
    {
        Position *p = ecs_get_mut(world, e, Position);
        test_assert(p != NULL);
        p->x = 5;
        p->y = 6;
    }
    test_assert(!ecs_has(world, e, Position));
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 5);
    test_int(p->y, 6);

    ecs_fini(world);
}

void Commands_exists_get_mut_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_set(world, e, Position, {1, 2});

    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        test_assert(p != NULL);
        p->x = 5;
        p->y = 6;
    }
    ecs_remove(world, e, Position);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p == NULL);

    ecs_fini(world);
}

void Commands_absent_get_mut_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        test_assert(p != NULL);
        p->x = 5;
        p->y = 6;
    }
    test_assert(!ecs_has(world, e, Position));
    ecs_remove(world, e, Position);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p == NULL);

    ecs_fini(world);
}

void Commands_absent_set_invoke_on_set(void) {
    ecs_world_t *world = ecs_mini();
    on_set_invoked = 0;

    ECS_COMPONENT(world, Position);
    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Position);

    ecs_entity_t e = ecs_new(world, 0);

    /* create the component */
    ecs_defer_begin(world);
    ecs_set(world, e, Position, {1, 2});

    /* OnSet should not be run until defer has completed */
    test_int(on_set_invoked, 0);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_int(on_set_invoked, 1);
    test_int(p->x, 1);
    test_int(p->y, 2);

    ecs_fini(world);
}

void Commands_exists_set_invoke_on_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Position);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_set(world, e, Position, {1, 2});
    on_set_invoked = 0;

    /* create the component */
    ecs_defer_begin(world);
    ecs_set(world, e, Position, {5, 6});

    /* OnSet should not be run until defer has completed */
    test_int(on_set_invoked, 0);
    ecs_defer_end(world);

    const Position *p = ecs_get(world, e, Position);
    test_int(on_set_invoked, 1);
    test_int(p->x, 5);
    test_int(p->y, 6);

    ecs_fini(world);
}

void Commands_defer_get_mut_no_on_set(void) {
    ecs_world_t *world = ecs_mini();
    on_set_invoked = 0;

    ECS_COMPONENT(world, Position);
    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Position);

    ecs_entity_t e = ecs_new(world, 0);

    /* create the component */
    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        p->x = 1;
        p->y = 2;
    }
    test_assert(!ecs_has(world, e, Position));
    test_int(on_set_invoked, 0);
    ecs_defer_end(world);
    test_int(on_set_invoked, 0);

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 1);
    test_int(p->y, 2);

    ecs_fini(world);
}

void Commands_defer_existing_get_mut_no_on_set(void) {
    ecs_world_t *world = ecs_mini();
    on_set_invoked = 0;

    ECS_COMPONENT(world, Position);
    ECS_OBSERVER(world, OnSetTestInvoked, EcsOnSet, Position);

    ecs_entity_t e = ecs_new(world, Position);

    /* create the component */
    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        p->x = 1;
        p->y = 2;
    }
    test_assert(ecs_has(world, e, Position));
    test_int(on_set_invoked, 0);
    ecs_defer_end(world);
    test_int(on_set_invoked, 0);

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 1);
    test_int(p->y, 2);

    ecs_fini(world);
}

void Commands_get_mut_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_set(world, 0, Position, {1, 2});
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        test_assert(p != NULL);
        test_int(p->x, 1);
        test_int(p->y, 2);
        p->x = 5;
        p->y = 6;
    }

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_owns(world, e, Position));
    ecs_defer_end(world);
    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_owns(world, e, Position));

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 5);
    test_int(p->y, 6);

    const Position *bp = ecs_get(world, base, Position);
    test_assert(p != bp);
    test_int(bp->x, 1);
    test_int(bp->y, 2);

    ecs_fini(world);
}

void Commands_set_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_set(world, 0, Position, {1, 2});
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    ecs_defer_begin(world);
    {
        ecs_set(world, e, Position, {5, 6});
    }

    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_owns(world, e, Position));
    ecs_defer_end(world);
    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_owns(world, e, Position));

    const Position *p = ecs_get(world, e, Position);
    test_int(p->x, 5);
    test_int(p->y, 6);

    const Position *bp = ecs_get(world, base, Position);
    test_assert(p != bp);
    test_int(bp->x, 1);
    test_int(bp->y, 2);

    ecs_fini(world);
}

void Commands_absent_get_mut_for_entity_w_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);
    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Tag);
    test_assert(e != 0);
    
    ecs_defer_begin(world);
    {
        Position *p = ecs_get_mut(world, e, Position);
        test_assert(p != NULL);
        p->x = 10;
        p->y = 20;
    }
    ecs_defer_end(world);

    {
        const Position *p = ecs_get(world, e, Position);
        test_assert(p != NULL);
        test_int(p->x, 10);
        test_int(p->y, 20);
    }

    ecs_fini(world);
}

static int set_position_invoked = 0;
static int set_velocity_invoked = 0;
static int add_tag_invoked = 0;

static void set_position_hook(ecs_iter_t *it) {
    set_position_invoked ++;
}

static void set_velocity_hook(ecs_iter_t *it) {
    set_velocity_invoked ++;
}

static void add_tag(ecs_iter_t *it) {
    test_int(set_position_invoked, 1);
    add_tag_invoked ++;
}

void Commands_on_set_hook_before_on_add_for_existing_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_set_hooks(world, Position, {
        .on_set = set_position_hook
    });

    ecs_observer(world, {
        .filter.terms[0].id = TagA,
        .events = { EcsOnAdd },
        .callback = add_tag
    });

    ecs_entity_t e = ecs_new(world, Position);

    ecs_defer_begin(world);
    ecs_add(world, e, TagB); /* 2 add commands, to trigger batching */
    ecs_modified(world, e, Position);
    ecs_add(world, e, TagA);

    test_int(set_position_invoked, 0);
    test_int(add_tag_invoked, 0);
    ecs_defer_end(world);

    test_assert(set_position_invoked != 0);
    test_int(add_tag_invoked, 1);

    ecs_fini(world);
}

void Commands_defer_2_sets_w_observer_same_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_observer(world, {
        .filter.terms[0].id = ecs_id(Position),
        .events = { EcsOnSet },
        .callback = set_position_hook
    });

    ecs_entity_t e = ecs_new(world, Position);

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {10, 20});
    ecs_set(world, e, Position, {20, 30});
    ecs_defer_end(world);

    test_assert(set_position_invoked != 0);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 20);
    test_int(p->y, 30);

    ecs_fini(world);
}

void Commands_defer_2_sets_w_observer_other_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_observer(world, {
        .filter.terms[0].id = ecs_id(Position),
        .events = { EcsOnSet },
        .callback = set_position_hook
    });

    ecs_observer(world, {
        .filter.terms[0].id = ecs_id(Velocity),
        .events = { EcsOnSet },
        .callback = set_velocity_hook
    });

    ecs_entity_t e = ecs_new(world, Position);

    ecs_defer_begin(world);
    ecs_set(world, e, Position, {10, 20});
    ecs_set(world, e, Velocity, {1, 2});
    ecs_defer_end(world);

    test_assert(set_position_invoked != 0);
    test_assert(set_velocity_invoked != 0);

    const Position *p = ecs_get(world, e, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    const Velocity *v = ecs_get(world, e, Velocity);
    test_assert(v != NULL);
    test_int(v->x, 1);
    test_int(v->y, 2);

    ecs_fini(world);
}

static int remove_tag_invoked = 0;
static void remove_tag(ecs_iter_t *it) {
    remove_tag_invoked ++;
}

void Commands_on_remove_after_deferred_clear_and_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_TAG(world, TagC);

    ecs_observer(world, {
        .filter.terms[0].id = TagA,
        .events = { EcsOnRemove },
        .callback = remove_tag
    });

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, TagA);
    ecs_add(world, e, TagB);

    ecs_defer_begin(world);
    ecs_clear(world, e);
    ecs_add(world, e, TagC);
    test_int(remove_tag_invoked, 0);
    ecs_defer_end(world);
    test_int(remove_tag_invoked, 1);

    test_assert(!ecs_has(world, e, TagA));
    test_assert(!ecs_has(world, e, TagB));
    test_assert(ecs_has(world, e, TagC));

    ecs_fini(world);
}

static void Recycle(ecs_iter_t *it) {
    ecs_new_id(it->world);
    *(ecs_entity_t*)it->ctx = ecs_new_id(it->world);
}

void Commands_defer_delete_recycle_same_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Foo);
    ECS_TAG(world, Bar);

    ecs_entity_t parent = ecs_new_id(world);
    ecs_entity_t e1 = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_entity_t e2 = ecs_new_id(world);

    ecs_observer(world, {
        .filter.terms = {
            { .id = ecs_id(Foo) }
        },
        .events = { EcsOnAdd },
        .callback = Recycle,
        .ctx = &e1
    });

    ecs_defer_begin(world);
    ecs_delete(world, parent);
    ecs_add(world, e2, Foo);
    ecs_add(world, e1, Bar);
    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, parent));
    test_assert(ecs_is_alive(world, e2));
    test_assert(ecs_is_alive(world, e1));
    test_assert((uint32_t)e1 != e1);
    test_assert(ecs_has(world, e2, Foo));

    ecs_fini(world);
}

static ECS_COMPONENT_DECLARE(Velocity);

static
void AddWhileSuspended(ecs_iter_t *it) {
    for (int i = 0; i < it->count; i ++) {
        ecs_add(it->world, it->entities[i], Velocity);
    }
}

void Commands_observer_while_defer_suspended(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT_DEFINE(world, Velocity);

    ecs_observer(world, {
        .filter.terms = {
            { .id = ecs_id(Position) }
        },
        .events = { EcsOnAdd },
        .callback = AddWhileSuspended,
    });

    ecs_defer_begin(world);
    ecs_defer_suspend(world);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_resume(world);
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}

void Commands_on_add_hook_while_defer_suspended(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT_DEFINE(world, Velocity);

    ecs_set_hooks(world, Position, {
        .on_add = AddWhileSuspended
    });

    ecs_defer_begin(world);
    ecs_defer_suspend(world);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_resume(world);
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}

void Commands_on_set_hook_while_defer_suspended(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT_DEFINE(world, Velocity);

    ecs_set_hooks(world, Position, {
        .on_set = AddWhileSuspended
    });

    ecs_defer_begin(world);
    ecs_defer_suspend(world);

    ecs_entity_t e = ecs_set(world, 0, Position, {10, 20});
    test_assert(e != 0);
    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_resume(world);
    ecs_defer_end(world);

    test_assert(ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}

void Commands_on_remove_hook_while_defer_suspended(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT_DEFINE(world, Velocity);

    ecs_set_hooks(world, Position, {
        .on_remove = AddWhileSuspended
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_begin(world);
    ecs_defer_suspend(world);

    test_assert(ecs_has(world, e, Position));
    ecs_remove(world, e, Position);
    test_assert(!ecs_has(world, e, Position));
    test_assert(!ecs_has(world, e, Velocity));

    ecs_defer_resume(world);
    ecs_defer_end(world);

    test_assert(!ecs_has(world, e, Position));
    test_assert(ecs_has(world, e, Velocity));

    ecs_fini(world);
}
